feat: execution sandbox — per-thread hard isolation for the agent workspace (opt-in)#289
Conversation
Provider-agnostic SandboxProvider contract + Docker reference; per-thread whole-workspace (fs+exec+network) isolation; create-or-reattach lifecycle (release keeps the volume, destroy removes it); allow+denylist egress; opt-in `sandbox` config key + new typed `config()` helper. Grounded against the real integration points (workspace capability backends, thread_id plumbing into prepareRouteExecution, DELETE/shutdown hooks, dawn check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
16 tasks across 6 phases (contract+config helper → fakeSandbox+conformance+ SandboxManager → runtime wiring → dawn check → dockerSandbox+gated lane → docs+changeset). Corrects spec type placement (contract types in @dawn-ai/ workspace, config() in @dawn-ai/core) after verifying the dep graph avoids a core↔sandbox / sdk↔core cycle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SandboxPolicy, SandboxHandle, SandboxProvider, and SandboxConfig interfaces to @dawn-ai/workspace, re-using FilesystemBackend/ExecBackend so the workspace capability can be redirected into a sandbox with no interface changes. Export all four types from the package index.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add runProviderConformance() shared suite that enforces the four SandboxProvider invariants (acquire idempotency, thread isolation, release-keeps/destroy-clears, numeric exit code) against fakeSandbox now and dockerSandbox in a later gated lane. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ead sandbox Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire a per-server SandboxManager into createRuntimeRequestListener: resolve it from config, start a 60 s idle-reaper interval (unref'd), pass it through buildRouteTable into every run handler so streamResolvedRoute/invokeResolvedRoute receive it alongside thread_id. DELETE /threads/:id calls destroyThread before 204; close() clears the reaper and calls releaseAll. All hooks are no-ops when no sandbox config is present. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keystone behavioral proof for the execution-sandbox feature. Drives streamResolvedRoute directly with the SandboxManager resolved from the fixture's dawn.config.ts (+ threadId) — exactly how the runtime HTTP server injects it — so no Docker / real model key is needed (aimock). Proves (green): an agent's workspace writeFile/readFile route into the thread's sandbox volume, persist across turns on the same thread, and leave the host fs untouched. The workspace capability activates here ONLY because the injected sandbox workspaceRoot is present (no host workspace/ dir), so writeFile being offered is itself proof. Verified real via a false-green check: forcing the local backend (disabling the sandboxBackends assignment) makes writeFile disappear and the test fail. Also surfaces a real DEFECT (tracked as `it.fails`): per-thread sandbox isolation is currently broken — @dawn-ai/langchain materializeAgent() caches the compiled agent per descriptor, freezing the first thread's sandbox-bound workspace tools, so a second thread reuses them and sees the first thread's files. Affects the real server for any agent served across more than one thread in a process. Exposes resolveSandboxManager + SandboxManager from @dawn-ai/cli/runtime so out-of-band drivers (and this test) can build the same manager. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thread tool isolation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ubagent dispatch Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude finished @blove's task in 4m 24s —— View job PR Review: feat/execution-sandbox
The overall architecture is solid — the Found 4 correctness/security issues and 1 minor concern: Security
Correctness
Feature gap
Minor
|
…elease shutdown, env key validation Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @blove's task in 4m 42s —— View job PR Review: feat/execution-sandbox
The architecture is solid and the core isolation contract is clean. Three of the five issues from the previous review are confirmed fixed:
Two prior issues remain open, and one new correctness bug was found: Still open from prior review
New issue
The sanitize collision minor ( |
…e — sandbox cleanup on production shutdown
|
Claude finished @blove's task in 2m 55s —— View job PR Review: feat/execution-sandbox
Three of the five issues from prior reviews are confirmed fixed:
Two issues remain open:
The |
…ating Resolves the check.ts import conflict: keeps both collectSandboxErrors (from #289) and the renamed collectToolScopeIssues (this branch). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
An opt-in execution sandbox: each Agent-Protocol conversation thread gets a hard-isolated workspace — filesystem, shell, and network — via a provider-agnostic
SandboxProvidercontract with a Docker reference implementation. Complements tool scoping (#261): scoping limits which tools the model may name; the sandbox bounds what an allowed tool can actually do.Spec:
docs/superpowers/specs/2026-06-25-execution-sandbox-design.md· Plan:docs/superpowers/plans/2026-06-25-execution-sandbox.md· Docs page:/docs/sandboxNo
sandboxkey → zero behavior change.How
SandboxProvider/SandboxHandle/SandboxPolicy/SandboxConfig) in@dawn-ai/workspace— the handle'sfilesystem/execimplement the existing backend interfaces, so the workspace capability consumes them unchanged.SandboxManager(cli, per-server singleton): per-thread acquire/reuse with concurrent-acquire dedup, idle reaper (release keeps the volume), threadDELETE→ destroy (volume too), shutdown → release-all.threadIdthreaded into route prep; when configured, the workspace capability + offload store get the thread's sandbox backends and in-sandboxworkspaceRoot. Subagents share the parent thread's sandbox via a dedicatedsandboxThreadIdkey (deliberately decoupled from LangGraph's checkpointthread_id).dockerSandbox(new@dawn-ai/sandboxpkg): persistent per-thread container + named volume (create-or-reattach → workspace survives turns, restarts, container crashes), clean env (host env never inherited),--memory/--cpuscaps,deny→--network none(exact).dawn checkvalidates the config + runsprovider.preflight()(e.g. Docker daemon reachable).sandboxThreadIddecoupling).Testing
validate:fakeSandbox(in-memory provider) + a provider conformance kit +SandboxManagerlifecycle units + a behavioral wiring e2e (writes route into the sandbox, persist across turns, host fs untouched, per-thread isolation — false-green-verified both ways).sandbox-dockerCI job (new, independent lane): the conformance kit against real Docker + deny-network egress block + host-fs-untouched + release→reacquire volume durability. This PR's run is the authoritative real-Docker proof (local daemon here can't pull images).pnpm test976 passing (the one failure found during verification was bisected and fixed on this branch); check-docs green; Verdaccio publish harness green including the new package.Honest scope (also in the docs page)
Docker's boundary, not a microVM — the seam is provider-agnostic so a gVisor/Kata/cloud-microVM provider is the stronger drop-in for hostile-grade multi-tenant.
allow-mode denylist is best-effort in the Docker reference (denyis exact). The sandbox protects the host and other tenants; it does not prevent an agent exfiltrating its own sandbox's data underallowmode.Release notes
@dawn-ai/sandboxis a first publish → needs the one-time manual OIDC bootstrap + trusted-publishing config at release time (same as@dawn-ai/memoryat 0.8.3).🤖 Generated with Claude Code