Skip to content

Refactor sandbox providers to SPI with capability-driven routing#77

Draft
geminixiang wants to merge 5 commits into
mainfrom
claude/sandbox-architecture-security-bk22oz
Draft

Refactor sandbox providers to SPI with capability-driven routing#77
geminixiang wants to merge 5 commits into
mainfrom
claude/sandbox-architecture-security-bk22oz

Conversation

@geminixiang

Copy link
Copy Markdown
Owner

Summary

Decouples sandbox implementations from the control plane by introducing a provider SPI (SandboxProvider) with declared capabilities. The control plane now routes based on capabilities (lifecycle, credential scope, env injection, file mounts) rather than type-switching. Moves Docker provisioning into the sandbox layer, eliminates vault-sandbox coupling, and implements secure secret injection with a single audit chokepoint.

Key Changes

Architecture

  • New SPI: src/sandbox/spi.ts defines SandboxProvider, SandboxInstance, SandboxCapabilities, and AcquireContext. Providers declare how they handle lifecycle, credentials, env injection, and file operations.
  • Provider Registry: src/sandbox/registry.ts replaces adapter pattern with a factory-based registry. Third-party providers can register via registerSandboxProvider().
  • Decoupled Control Plane: ActorExecutionResolver now consults provider capabilities instead of type-switching. Vault routing moved from vault/routing.ts into resolveActorScopeKey() in the sandbox layer.

Providers

  • Moved all concrete implementations to src/sandbox/providers/:
    • host.ts, docker/container.ts, docker/image.ts, firecracker.ts, cloudflare.ts
    • Docker provisioner (DockerContainerManager) moved to src/sandbox/providers/docker/provisioner.ts
  • Each provider implements parse(), validate(), getPathContext(), and acquire().
  • Image provider now manages container lifecycle via provisioner; container provider remains external.

Secret Injection

  • Cloudflare: Env now injected once per session via /env endpoint; /exec no longer carries secrets. Falls back to per-exec injection for legacy bridges.
  • Docker: Secrets passed via docker exec -e KEY (reads from parent process env) instead of tmpdir env files. Validates environment variable names.
  • File Push: New SandboxFs API (mkdir, writeFile) for pushing vault files. Cloudflare bridge implements /mkdir and /write-file endpoints.
  • Audit Chokepoint: auditSecretInjection() in src/sandbox/secret-injection.ts logs key names and counts (never values) for all injection paths.

Removed Coupling

  • Vault no longer imports Docker provisioner or switches on sandbox types.
  • Vault files no longer assume container paths; providers define mount targets via getPathContext().
  • image: config no longer requires special handling in execution-resolver.ts; image provider handles template→instance conversion.

Testing

  • Added tests for unsafe environment variable names in Docker exec.
  • Added tests for Cloudflare session env injection and fallback to legacy per-exec mode.
  • Added tests for file push via Cloudflare bridge fs API.
  • Added tests for provider capability declarations.
  • Existing vault and sandbox tests updated to use new resolveActorScopeKey() API.

Notes for Reviewers

  • Design: The SPI makes adding third-party sandboxes (E2B, gondolin, Docker Sandboxes) straightforward—just implement the interface and register. No control-plane changes needed.
  • Security: Secrets no longer touch disk in Docker mode; Cloudflare session env eliminates per-exec secret transmission. Audit logging provides observability without exposing values.
  • Backward Compatibility: Cloudflare bridge detects /env support and falls back to per-exec injection for older bridges.
  • Follow-up: Phase 3–4 of the RFC (advanced secret models, audit enrichment) can build on this foundation without further refactoring.

https://claude.ai/code/session_01PhGdhDZviXgebWYTXJ89PF

claude and others added 5 commits June 10, 2026 02:00
Implements Phase 1 of docs/rfc-sandbox-provider.md:

- Add SandboxProvider/SandboxInstance SPI with declared capabilities
  (lifecycle, credential scope, env injection, file mounts) so the
  control plane never switches on sandbox types.
- Move all five builtin backends into src/sandbox/providers/; the
  Docker provisioner becomes a private detail of the docker providers.
- Model image:* as a managed provider: per-conversation container
  naming, provisioning, and the ensureReady wiring live in
  providers/docker/image.ts instead of the execution resolver. Managed
  instances expose suspend()/destroy().
- Derive vault keys / scope keys from provider capabilities via
  resolveActorScopeKey(); delete vault/routing.ts and the cloudflare
  special-case in FileVaultManager.getSandboxConfig() (the provider
  derives per-scope sandbox ids at acquire time). The vault layer no
  longer imports anything from the sandbox layer.
- Add registerSandboxProvider() so third-party backends (E2B, gondolin,
  Docker Sandboxes) can plug in without touching the control plane.
- CLI sandbox strings, vault directories, container naming, and wire
  behaviour are unchanged; existing tests pass as-is plus new SPI
  coverage for capabilities, scope routing, and acquire semantics.

https://claude.ai/code/session_01PhGdhDZviXgebWYTXJ89PF
…, fs push

Implements Phase 2 of docs/rfc-sandbox-provider.md:

- Docker exec no longer writes vault env to a host tmpdir env file.
  Secrets ride the docker CLI's own process environment and are
  forwarded with bare '-e KEY' flags, so they never touch disk or the
  command line and cannot leak through crash residue. Env var names are
  validated before use.
- The cloudflare provider pushes vault env to the bridge once per
  instance via a new POST /env (setEnvVars) instead of resending the
  full secret set in every /exec payload. Legacy bridges without /env
  are detected (404/405) and fall back to per-exec injection.
- New SandboxFs surface on SandboxInstance plus a filePush capability:
  vault file credentials are now projected into cloudflare sandboxes
  through bridge /mkdir + /write-file (written 0600), closing the
  'cloudflare has no vault files' gap without bind mounts.
- All injection now flows through a single audited chokepoint
  (src/sandbox/secret-injection.ts) that logs key names, modes, and
  file counts — never values — per scope.
- Bridge example worker gains /env, /mkdir, /write-file endpoints;
  docs updated accordingly.

https://claude.ai/code/session_01PhGdhDZviXgebWYTXJ89PF
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