mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 13:38:55 +08:00
fix: close remaining launcher key injection paths
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -124,7 +124,7 @@ Access the UI at [http://localhost:8000](http://localhost:8000) for the npm/sour
|
||||
|
||||
Local (`npx` / `npm run dev`) listeners bind **loopback only** (`127.0.0.1`) so the auto-injected session key is not reachable from other machines on the network. To listen on all interfaces, pass `--host 0.0.0.0` (or set `OH_BIND_HOST`); the session key is then **not** injected and the UI uses the same API-key entry screen as `--public`.
|
||||
|
||||
Docker listens on all container interfaces so port publishing works, but does not inject its session key into HTML by default. The quickstart above explicitly enables injection while publishing the host port on `127.0.0.1` only. If you publish Docker on a LAN or public interface, omit `AGENT_CANVAS_ALLOW_LAN_SESSION_KEY`, set `LOCAL_BACKEND_API_KEY` to a strong value, and enter that value in the UI. For internet-facing installs, follow [self-hosting](./docs/SELF_HOSTING.md).
|
||||
Docker listens on all container interfaces so port publishing works, but does not inject its session key into HTML by default. The quickstart above explicitly enables injection while publishing the host port on `127.0.0.1` only. If you publish Docker on a LAN or public interface, omit `AGENT_CANVAS_ALLOW_LAN_SESSION_KEY` and enter the API key in the UI. Set `LOCAL_BACKEND_API_KEY` to a strong value, or retrieve the generated value with `docker exec <container> sh -c 'cat "$STATE_DIR/api-key.txt"'`. For internet-facing installs, follow [self-hosting](./docs/SELF_HOSTING.md).
|
||||
|
||||
# Architecture
|
||||
|
||||
@@ -145,16 +145,15 @@ The Agent Server is often paired with an [Automation Server](https://github.com/
|
||||
|
||||
Agent Canvas is part of a multi-repository OpenHands system. Changes should go to the repository that owns the behavior:
|
||||
|
||||
| Repository | Responsibility |
|
||||
|---|---|
|
||||
| [`OpenHands/OpenHands`](https://github.com/OpenHands/OpenHands) | Agent Canvas frontend, user-facing control center, backend selection, and local-stack orchestration. |
|
||||
| Repository | Responsibility |
|
||||
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| [`OpenHands/OpenHands`](https://github.com/OpenHands/OpenHands) | Agent Canvas frontend, user-facing control center, backend selection, and local-stack orchestration. |
|
||||
| [`OpenHands/software-agent-sdk`](https://github.com/OpenHands/software-agent-sdk) | Python SDK, Agent Server, agents, tools, conversations, workspaces, events, and the canonical server API. |
|
||||
| [`OpenHands/typescript-client`](https://github.com/OpenHands/typescript-client) | Browser-compatible TypeScript client for the Agent Server API. |
|
||||
| [`OpenHands/automation`](https://github.com/OpenHands/automation) | Automation definitions, scheduling, webhooks, run history, and dispatching. |
|
||||
| [`OpenHands/typescript-client`](https://github.com/OpenHands/typescript-client) | Browser-compatible TypeScript client for the Agent Server API. |
|
||||
| [`OpenHands/automation`](https://github.com/OpenHands/automation) | Automation definitions, scheduling, webhooks, run history, and dispatching. |
|
||||
|
||||
The Agent Server API is implemented by the SDK and consumed through the TypeScript client by Agent Canvas. The automation service decides when work runs and dispatches conversations to the Agent Server/SDK, which decides what runs. See [`AGENTS.md`](./AGENTS.md) for contributor-specific boundaries and the required custom code-review guide.
|
||||
|
||||
|
||||
## More documentation
|
||||
|
||||
- [Documentation index](./docs/README.md)
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
formatMissingUvxGuidance,
|
||||
formatMissingFrontendDependenciesGuidance,
|
||||
getMissingFrontendDependencyBins,
|
||||
getViteSessionApiKey,
|
||||
validateFrontendDependencies,
|
||||
validateLocalAgentServerPath,
|
||||
findFreePort,
|
||||
@@ -690,6 +691,20 @@ describe("validateLocalAgentServerPath", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getViteSessionApiKey", () => {
|
||||
it("only injects the key on loopback listeners", () => {
|
||||
const config = { sessionApiKey: "local-secret" };
|
||||
|
||||
expect(getViteSessionApiKey(config, {})).toBe("local-secret");
|
||||
expect(getViteSessionApiKey(config, { VITE_BIND_HOST: "127.0.0.1" })).toBe(
|
||||
"local-secret",
|
||||
);
|
||||
expect(getViteSessionApiKey(config, { VITE_BIND_HOST: "0.0.0.0" })).toBe(
|
||||
"",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSafeDevConfig", () => {
|
||||
let keyTmp: string | null = null;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
buildLocalServiceRouteArgs,
|
||||
parseArgs,
|
||||
} from "../../scripts/dev-static.mjs";
|
||||
import { buildFrontendEnv } from "../../scripts/static-build.mjs";
|
||||
|
||||
describe("dev-static CLI", () => {
|
||||
it.each([
|
||||
@@ -38,6 +39,16 @@ describe("dev-static", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps reusable frontend builds free of session credentials", () => {
|
||||
const env = buildFrontendEnv(
|
||||
{ viteWorkingDir: "/tmp/workspace" },
|
||||
{ VITE_SESSION_API_KEY: "inherited-secret" },
|
||||
);
|
||||
|
||||
expect(env.VITE_SESSION_API_KEY).toBe("");
|
||||
expect(env.VITE_WORKING_DIR).toBe("/tmp/workspace");
|
||||
});
|
||||
|
||||
it("points every local proxy route at the IPv4 loopback", () => {
|
||||
// Both backends bind to `0.0.0.0`, which only accepts IPv4, so a
|
||||
// `localhost` target strands the proxy on ::1 on Windows.
|
||||
|
||||
+11
-2
@@ -16,6 +16,7 @@ import process from "node:process";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
import { applySessionKeyPolicy } from "./bind-host.mjs";
|
||||
import {
|
||||
getProcessTreeSpawnOptions,
|
||||
isProcessRunning,
|
||||
@@ -576,6 +577,15 @@ export function buildSafeDevConfig(cwd = process.cwd(), env = process.env) {
|
||||
return buildConfigFromPorts({ backendPort, vscodePort }, cwd, env);
|
||||
}
|
||||
|
||||
export function getViteSessionApiKey(config, env = process.env) {
|
||||
return (
|
||||
applySessionKeyPolicy({
|
||||
host: env.VITE_BIND_HOST || "127.0.0.1",
|
||||
sessionApiKey: config.sessionApiKey,
|
||||
}).sessionApiKey || ""
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build safe dev configuration with dynamic port allocation.
|
||||
*
|
||||
@@ -1088,8 +1098,7 @@ async function main() {
|
||||
VITE_BACKEND_HOST: config.backendHost,
|
||||
VITE_BACKEND_BASE_URL: config.backendBaseUrl,
|
||||
VITE_WORKING_DIR: config.workingDir,
|
||||
// Pass session API key so frontend can authenticate with agent-server
|
||||
VITE_SESSION_API_KEY: config.sessionApiKey,
|
||||
VITE_SESSION_API_KEY: getViteSessionApiKey(config),
|
||||
// This mode has no static server or ingress in front of Vite, so Vite's
|
||||
// own proxy is the only thing that can serve the editor prefix on the
|
||||
// frontend origin. The editor is a separate process on a port of its
|
||||
|
||||
+13
-12
@@ -11,6 +11,16 @@ import {
|
||||
} from "./dev-with-automation.mjs";
|
||||
import { buildNpmScriptCommand } from "./dev-safe.mjs";
|
||||
|
||||
export function buildFrontendEnv(config, env = process.env) {
|
||||
return {
|
||||
...env,
|
||||
VITE_SESSION_API_KEY: "",
|
||||
...(config.viteWorkingDir
|
||||
? { VITE_WORKING_DIR: config.viteWorkingDir }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildFrontend(config, args = {}) {
|
||||
const buildDir = join(config.canvasPath, "build");
|
||||
|
||||
@@ -39,18 +49,9 @@ export function buildFrontend(config, args = {}) {
|
||||
);
|
||||
|
||||
const cmd = buildNpmScriptCommand("build:app");
|
||||
const buildEnv = {
|
||||
...process.env,
|
||||
// Bake the session API key — used by the frontend for both agent-server
|
||||
// and automation auth via the `X-Session-API-Key` header.
|
||||
VITE_SESSION_API_KEY: config.sessionApiKey,
|
||||
// Intentionally do NOT set VITE_BACKEND_BASE_URL: leaving it unset makes
|
||||
// the runtime fall back to window.location.origin, which keeps the build
|
||||
// portable across localhost, LAN hosts, and tunnels such as ngrok.
|
||||
};
|
||||
if (config.viteWorkingDir) {
|
||||
buildEnv.VITE_WORKING_DIR = config.viteWorkingDir;
|
||||
}
|
||||
// The static server injects runtime configuration after applying the
|
||||
// bind-host policy, so the reusable build itself must remain key-free.
|
||||
const buildEnv = buildFrontendEnv(config);
|
||||
|
||||
const result = spawnSync(cmd.command, cmd.args, {
|
||||
cwd: config.canvasPath,
|
||||
|
||||
Reference in New Issue
Block a user