Skip to content

fix(serve): refuse to hand the session token to the network - #921

Merged
agentforce314 merged 1 commit into
mainfrom
fix/serve-refuses-non-loopback-bind
Sep 10, 2026
Merged

fix(serve): refuse to hand the session token to the network#921
agentforce314 merged 1 commit into
mainfrom
fix/serve-refuses-non-loopback-bind

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Found while correcting the trust-model comments after #920. It is pre-existing, not from the sidebar work.

The gap

GET / is unauthenticated by construction — it is the page that hands out window.__CLAWCODEX_SESSION_TOKEN__, so the desktop shell and the browser client can both adopt a running backend (server/web_assets.py). That is safe exactly as long as the port is reachable from this machine alone.

clawcodex web has always enforced it (web_cli.py:242): a non-loopback --host is refused unless --allow-remote says the caller has put their own auth in front. clawcodex serve had no check at allargs.host went straight to uvicorn.Config(host=...).

So clawcodex serve --host 0.0.0.0 published a page handing the session token to anyone who could reach the port, and that token opens the whole gateway: session.create, prompt.submit, tools — at Full Access by default, per the resolver comment in that same file. Same app, same route as the guarded command, one gate short.

Default binds are unaffected (127.0.0.1), and the desktop spawns serve with an explicit --host 127.0.0.1.

The fix

Same guard, same flag as its sibling. Three parts that are not cosmetic:

  • is_loopback moves down to serve_cli, the layer that owns the bind; web_cli imports it instead of keeping a second copy, so the two commands cannot drift.
  • web_cli forwards --allow-remote. Without that, web --host 0.0.0.0 --allow-remote would clear the parent's gate and then be refused by the child — breaking the documented remote path. _serve_argv did not forward it.
  • The refusal test stubs build_app to raise. Removing the guard otherwise makes the call reach uvicorn and block, so a regression would appear in CI as a timed-out job rather than a red test. That is how my first draft behaved; mutation-testing it is what surfaced that.

Also: three comments that asserted the loopback bind as a fact now state it as the condition it is — desktop_serve's docstring (the module that owns _token_ok), web_assets's note on the token page, and serve_cli's permission rationale.

Compatibility

This is a behaviour change to a shipped command. Anyone running clawcodex serve on a non-loopback host today must add --allow-remote; the refusal names the flag and says why.

959 tests across tests/entrypoints, tests/server and tests/nano pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WAB22BuCSRhjbpR3a8kc5v

`GET /` is unauthenticated by construction — it is the page that *hands out*
`window.__CLAWCODEX_SESSION_TOKEN__`, so the desktop shell and the browser
client can both adopt a running backend. That is safe exactly as long as the
port is reachable from this machine alone.

`clawcodex web` has always enforced it: a non-loopback `--host` is refused
unless `--allow-remote` says the caller has put their own authentication in
front of it. `clawcodex serve` — the command that actually opens the socket,
and the one the desktop spawns — took an arbitrary `--host` straight to uvicorn
with no check. So `clawcodex serve --host 0.0.0.0` published a page handing that
token to anyone who could reach the port, and the token opens the whole gateway:
sessions, prompts, tools. Same app, same route, one gate short.

It now carries the same guard and the same flag. Three details that are not
cosmetic:

- `is_loopback` moves down to `serve_cli`, which is the layer that owns the
  bind, and `web_cli` imports it rather than keeping a second copy — one
  predicate, so the two commands cannot drift.
- `web_cli` forwards `--allow-remote` to the child. Without that, a
  `web --host 0.0.0.0 --allow-remote` would clear the parent's gate and then be
  refused by the child, breaking the documented remote path.
- the refusal test stubs `build_app` to raise. Removing the guard otherwise
  makes it reach uvicorn and block, so a regression would surface in CI as a
  timed-out job rather than a red test — which is how the first draft of it
  behaved.

The three trust-model comments that asserted the loopback bind as a fact now
state it as the condition it is: `desktop_serve`'s module docstring (which owns
`_token_ok`), `web_assets`'s note on the token page, and `serve_cli`'s
permission-resolution rationale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WAB22BuCSRhjbpR3a8kc5v
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Test Results

     5 files   1 008 suites   29m 56s ⏱️
15 694 tests 15 668 ✅ 22 💤 4 ❌
31 516 runs  31 446 ✅ 66 💤 4 ❌

For more details on these failures, see this check.

Results for commit 24536aa.

@agentforce314
agentforce314 merged commit 97c0c28 into main Sep 10, 2026
6 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants