Skip to content

feat(sandbox): add bridge binaries and container image tooling - #41

Merged
awtprod merged 3 commits into
mainfrom
impl/sandbox-bridge
Aug 17, 2026
Merged

feat(sandbox): add bridge binaries and container image tooling#41
awtprod merged 3 commits into
mainfrom
impl/sandbox-bridge

Conversation

@awtprod

@awtprod awtprod commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

Part 1 of 3 for per-thread sandbox isolation (headless-first). The container backend for sandboxed threads (landed in #35) needs a preview bridge, an egress proxy, and container images to actually run — none of that existed. This adds them.

  • packages/sandbox-bridge/: three esbuild-bundled binaries (t3-preview-bridge, t3-egress-proxy, t3-credential-proxy) that speak the stdio/websocket-framed protocol the server already expects from ThreadPreviewProxy.ts and DesktopHttpRoutes.ts.
  • deploy/openclaw/sandbox-image/: Containerfiles for the workspace and sidecar images, plus a build script that resolves and prints the digest-pinned image refs the server config needs.
  • A stale-bundle guard: test now rebuilds (node build.ts && vp test run) before running, so tests can't pass against a bundle that no longer matches source.

Merges independently of the other two PRs (no file overlap), but sandboxing only becomes usable once all three plus the orphan-test PR are in and the host is bootstrapped.

Test plan

  • node build.ts && vp test run from packages/sandbox-bridge — 34/34 passing, including a proxyClient suite (CONNECT-tunnelling through HTTPS_PROXY, absolute-URI through HTTP_PROXY, 502 on unreachable proxy) that only passes against a rebuilt bundle, catching a real proxy-chaining bug during review.
  • Verified the stale-bundle guard empirically: deleted dist/ entirely, test rebuilt from nothing and passed 34/34.

claude-opus-5 via Claude Code

awtprod-agent and others added 3 commits August 17, 2026 03:10
PR #35 wires the server to three container binaries (t3-preview-bridge,
t3-egress-proxy) plus a credential proxy, but no source for them existed in
this repo or any registry, so the sandbox path could not run.

Adds packages/sandbox-bridge, which esbuild-bundles three dependency-free
.mjs executables for Node 22. The preview bridge's wire contract is derived
from its only caller (ThreadPreviewProxy/DesktopHttpRoutes) rather than
invented, including the zero-length frame it must emit when an upstream
WebSocket opens so the server relay's 10s handshake timer is cleared. The
egress proxy resolves, vets every resolved address, then dials the vetted
literal, closing the DNS-rebinding hole a resolve-then-redial-by-name would
leave. The credential proxy keeps provider secrets outside the workspace
container: it serves 503 until the server pushes a config, compares the
thread token over fixed-size digests, routes only on the first path segment,
and streams bodies so SSE turns are not buffered.

Adds deploy/openclaw/sandbox-image with a headless workspace Containerfile
(no desktop, no WebRTC), a sidecar Containerfile carrying all three
binaries, and a build script that emits the digest-pinned refs the server's
image regex requires.

Tests drive the bundled binaries as real child processes over stdio and
HTTP, waiting only on real events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…PROXY

The credential proxy dialed provider APIs directly with node:http/node:https.
Those core modules ignore HTTP_PROXY/HTTPS_PROXY entirely — only undici reads
them, via EnvHttpProxyAgent — so every upstream request would have failed
inside a real sandbox: the thread network is created `--internal`
(ContainerSandboxBackend.ts), leaving the egress sidecar as the only route
out. The existing tests missed it because they point baseUrl at a loopback
origin in the same netns, the one topology where the omission is invisible.

Adds src/proxyClient.ts, which selects a proxy per the usual environment
grammar (including NO_PROXY with wildcard, leading-dot and :port forms) and
then issues CONNECT-then-TLS for https upstreams or an absolute-URI request
for http upstreams — the two shapes t3-egress-proxy already serves. SNI is
omitted for IP literals per RFC 6066.

The new tests drive the real chain, credential proxy -> egress proxy ->
origin, over both http and https, plus NO_PROXY bypass and an unreachable
proxy. Verified by mutation: reverting to a direct dial fails exactly these
and no others. t3-egress-proxy now also logs forwarded requests, not just
denials, so a successful traversal is observable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…testing

build-sandbox-images.sh printed three of the four image variables the server
reads, leaving the operator to hand-copy the credential-proxy digest. It is
the same sidecar ref as the other two sidecar roles, so emit it in both the
env and export blocks; pasting three of four required variables is exactly
the silent misconfiguration this script exists to prevent.

Also builds the bundles before running the package's tests. The suite spawns
dist/*.mjs, which `vp test run` does not rebuild, so a source-only edit was
invisible to the tests and could report a false green.

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:XXL 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: 0fa5f83 · 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 88ea72a into main Aug 17, 2026
12 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 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