Skip to content

fix(sandbox): require a per-sandbox secret on the egress broker - #220

Merged
quickbeard merged 1 commit into
mainfrom
fix/broker-require-token
Aug 13, 2026
Merged

quickbeard merged 1 commit into
mainfrom
fix/broker-require-token

Conversation

@quickbeard

@quickbeard quickbeard commented Aug 13, 2026 •

Copy link
Copy Markdown
Owner

Result of a security review of the sandbox surface. One finding worth fixing before this ships to a fleet; the rest of the review is summarized below.

Finding: the broker was an unauthenticated proxy to the gateway

The WSL backend cannot use a unix socket — the guest reaches the host across NAT — so its broker binds TCP 0.0.0.0. It had no authentication, and the broker attaches the real gateway credential host-side. So any other process that could reach that port (any local process on the Windows host, plus whatever the firewall admits from the LAN) could spend the corporate gateway token at will, with nothing tying the usage back to a sandbox. The vz/bwrap unix sockets were narrower — same-user local processes only — but equally unauthenticated.

Fix

  • createBrokerServer takes an optional shared secret. It is checked before the route table is consulted, so an unauthorized caller cannot probe which upstreams exist (a wrong token gets 401 on /not-a-route, not 403).
  • The desktop generates a fresh random token per sandbox start and injects it as the guest provider's apiKey — replacing the previous fixed "sandbox-broker" placeholder. The real gateway token still never enters the guest.

Consequence for named egress rules (#212): tools using those routes must send the token too. It is readable inside the guest at /codev-config/codev.json.

Verification (against a running sandbox, over its unix socket)

no token        → 401 {"error":"sandbox egress unauthorized"}
injected token  → 200, real completion (193 tokens)

Suites: sandbox 93 pass / 2 skip, desktop 76 pass.

Rest of the review (no changes needed)

  • Path traversal / SSRF through route prefixes: safe. new URL() normalization defeats .. and %2e%2e (both resolve away from the prefix and get denied), and the upstream host comes from the route table, never from the request — an absolute-form request line cannot redirect the forward.
  • Credential exposure to the guest: the guest only ever holds the per-start placeholder; the gateway token is attached host-side and rotates every start.
  • Image supply chain: assets are sha256-verified before use and staged content-addressed; a partial or tampered download cannot be promoted.
  • Guest permission auto-allow (feat(sandbox): auto-approve permissions inside sandboxes #210): by design, and scoped to the sandbox's own config.
  • Noted, not fixed: the guest server still runs as root until the image respin (feat(sandbox): run the guest server as an unprivileged user #214), and boot.json holds the sandbox's server password inside the app data dir (protected by the user's home permissions).

🤖 Generated with Claude Code

@quickbeard quickbeard changed the title fix(sandbox): require a per-sandbox secret on the egress broker fix(sandbox): secure and correct the egress broker's credential path Aug 13, 2026
@quickbeard
quickbeard force-pushed the fix/broker-require-token branch from 15757c4 to 3d87144 Compare August 13, 2026 07:34
@quickbeard quickbeard changed the title fix(sandbox): secure and correct the egress broker's credential path fix(sandbox): require a per-sandbox secret on the egress broker Aug 13, 2026
Found reviewing the sandbox surface: the WSL backend must bind the
broker on TCP for the guest to reach the host across NAT, and the broker
had no authentication. Anything else able to reach that port — any local
process, and whatever the firewall lets onto the LAN — could use it as
an open proxy to the model gateway, spending the real token the broker
attaches host-side. The unix-socket backends were narrower but equally
unauthenticated.

createBrokerServer now takes an optional shared secret, checked before
the route table is consulted so an unauthorized caller cannot even probe
which upstreams exist, and the desktop generates a fresh one per sandbox
start and injects it as the guest provider's apiKey.

Verified against a running sandbox: an unauthenticated request gets
{"error":"sandbox egress unauthorized"}, while the guest's injected
token is accepted and forwarded upstream with the real credential
swapped in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quickbeard
quickbeard force-pushed the fix/broker-require-token branch from 3d87144 to 9eb6a77 Compare August 13, 2026 07:40
@quickbeard
quickbeard merged commit 532eb17 into main Aug 13, 2026
0 of 5 checks passed
@quickbeard
quickbeard deleted the fix/broker-require-token branch August 13, 2026 07:41
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.

1 participant