Summary
The Controller server exposes its CLI-facing HTTP routes with global CORS and no authentication. server/index.ts does app.use(cors()) and binds with no token, so any web page the user visits can reach the Controller port and drive these endpoints. They trust a client-supplied cwd to scope work to a worktree, but nothing stops an arbitrary origin from POSTing to them.
Surfaced in review of #262 (PR for #261): the new POST /api/terminal/command run action is a local command-execution sink — a malicious page could enumerate projects/worktrees/open terminals via the existing read APIs, then inject arbitrary text into an open terminal tab. The same exposure already applies to the other CLI surfaces:
POST /api/terminal/command (action: "run") — inject into a terminal — server/routes/terminal.ts
POST /api/browser/command — drive the preview browser — server/routes/browser.ts
POST /api/integrations/gateway/* — call connected third-party services
POST /api/projects/:id/sessions — start agent sessions
POST /api/projects/:id/schedules — schedule future agent runs
This is an app-wide architectural gap, not specific to the terminal route, which is why it's tracked separately rather than bolted onto #262.
Proposed direction
Add a single guard in front of the CLI-facing /api/* routes:
- The server already installs the CLI and publishes a runtime file (
controller-runtime.json) next to it. Publish an unguessable token there too and require it (e.g. X-Controller-Token) on the CLI routes; the CLI already reads that file to resolve the server URL, so it can attach the token transparently.
- And/or tighten CORS to a strict allowlist and reject cross-origin requests on these routes.
- Keep the renderer's existing access working (it runs same-origin / in-process).
Acceptance criteria
Refs
Summary
The Controller server exposes its CLI-facing HTTP routes with global CORS and no authentication.
server/index.tsdoesapp.use(cors())and binds with no token, so any web page the user visits can reach the Controller port and drive these endpoints. They trust a client-suppliedcwdto scope work to a worktree, but nothing stops an arbitrary origin from POSTing to them.Surfaced in review of #262 (PR for #261): the new
POST /api/terminal/commandrunaction is a local command-execution sink — a malicious page could enumerate projects/worktrees/open terminals via the existing read APIs, then inject arbitrary text into an open terminal tab. The same exposure already applies to the other CLI surfaces:POST /api/terminal/command(action: "run") — inject into a terminal —server/routes/terminal.tsPOST /api/browser/command— drive the preview browser —server/routes/browser.tsPOST /api/integrations/gateway/*— call connected third-party servicesPOST /api/projects/:id/sessions— start agent sessionsPOST /api/projects/:id/schedules— schedule future agent runsThis is an app-wide architectural gap, not specific to the terminal route, which is why it's tracked separately rather than bolted onto #262.
Proposed direction
Add a single guard in front of the CLI-facing
/api/*routes:controller-runtime.json) next to it. Publish an unguessable token there too and require it (e.g.X-Controller-Token) on the CLI routes; the CLI already reads that file to resolve the server URL, so it can attach the token transparently.Acceptance criteria
/api/*route without the token (or from a disallowed origin) is rejected.controllerCLI continues to work unchanged for the agent (token attached automatically).Refs