Skip to content

feat(sandbox): headless gate, credential proxy, and adapter binary mapping - #42

Merged
awtprod merged 2 commits into
mainfrom
impl/sandbox-server
Aug 17, 2026
Merged

feat(sandbox): headless gate, credential proxy, and adapter binary mapping#42
awtprod merged 2 commits into
mainfrom
impl/sandbox-server

Conversation

@awtprod

@awtprod awtprod commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Part 2 of 3 for per-thread sandbox isolation (headless-first). Server-side changes that make sandboxed threads actually usable without desktop/WebRTC streaming, and let provider CLIs authenticate inside the container without ever handing it a real credential.

  • Headless gate (T3_SANDBOX_DESKTOP=disabled): provisioning and reconciliation skip desktop start/recovery entirely; only the preview sidecar starts.
  • SandboxCredentialProxy: real provider credentials are pushed to a per-thread sidecar over podman exec -i (never in run --env, never mounted into the workspace container); the workspace gets a short-lived per-thread token and a proxy base URL instead.
  • SandboxProviderProcess: env allowlist plus a deny path that strips every persistent credential key whenever a proxy binding is active, so the real token can't leak through even if it were requested.
  • Runtime resolver: sandboxConfig.runtime now falls back to a deployment default (T3_SANDBOX_RUNTIME) instead of hardcoding docker, since nothing populates the per-thread field.
  • Storage-opt gate for rootless podman (no --storage-opt size= support over podman-remote) and git identity applied inside the container after the thread-branch switch.

Merges independently of the bridge and host PRs (no file overlap), but the feature only comes alive once all three are in, images are built, and the host is bootstrapped.

Test plan

  • Focused vitest on apps/server/src/sandbox/ — 112/112 passing across 7 files.
  • Mutation-tested the credential containment path directly: confirmed a real host token could reach the workspace container on the unbound path before the fix, and cannot after.
  • Targeted typecheck for the changed scope.

claude-opus-5 via Claude Code

awtprod-agent and others added 2 commits August 17, 2026 03:05
PR #35 landed container sandbox isolation but it could not be turned on
here: the desktop stack is mandatory, `--storage-opt size=` is rejected by
`podman --remote`, in-sandbox commits have no git identity, provider
credentials are hard-rejected with no proxy to route them through, and the
runtime binary is pinned to `docker` with no way to select podman.

- `T3_SANDBOX_DESKTOP=disabled` provisions headless: no desktop start, no
  automation target, no desktop recovery. The preview sidecar still runs.
- `T3_SANDBOX_CONTAINER_STORAGE_QUOTA=disabled` omits the storage-opt pair.
- `T3_SANDBOX_GIT_USER_NAME`/`_EMAIL` configure the clone's local identity.
- New thread-scoped credential proxy sidecar holds the real secret on the
  internal network; the workspace container only ever sees the proxy URL and
  an opaque per-thread token. The fail-closed throw is unchanged.
- Host provider binary paths map to the in-image command name on sandbox exec.
- `T3_SANDBOX_RUNTIME` selects the deployment's container runtime.

Every new variable defaults to existing behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit added ANTHROPIC_AUTH_TOKEN to the provider env allowlist
but not to the persistent-credential deny set, so with no proxy bound a host
copy passed the allowlist, met no deletion step, and failed to trip the
fail-closed guard. That key is the bearer credential Claude Code reads and
exactly what `claude setup-token` mints, making it the variable most likely
to hold a real long-lived secret on a host configured for this feature.

Adding it to the regex alone is not enough: the guard ran on the merged
environment, so the proxy's own injected token would trip it. The guard now
runs on host-derived env before proxy values are merged, which makes the
opaque per-thread token exempt by construction rather than by omission from
a deny list. Persistent keys are now dropped unconditionally when a proxy is
bound, so an openai-only binding cannot pass through a host Anthropic token.

The test that asserted the forwarded token encoded the hole as correct; it
now asserts the unbound case throws. Added coverage for the unbound and
bound-but-openai-only paths. Verified by mutation: reverting the regex fails
exactly the three new deny tests, and moving the guard back after the merge
fails the two injection tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL labels Aug 17, 2026
@awtprod

awtprod commented Aug 17, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@awtprod

awtprod commented Aug 17, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 7.3 KiB
Codex Live turn WebSocket wire 6.7 KiB 7.8 KiB
Codex Live turn WebSocket decoded 54.2 KiB 66.4 KiB
Codex Live turn messages 17 21
Claude Total thread wire 13.7 KiB 15.1 KiB
Claude Thread snapshot wire 7.0 KiB 7.3 KiB
Claude Live turn WebSocket wire 6.7 KiB 7.8 KiB
Claude Live turn WebSocket decoded 55.0 KiB 66.4 KiB
Claude Live turn messages 17 21

Baseline: unavailable · PR result: 5bd7886 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 107.9 KiB
  • Claude decoded thread snapshot: 108.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@awtprod
awtprod merged commit 55c0b6a into main Aug 17, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant