Add public sandbox tunnels and custom-domain ingress - #6
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — one error-path robustness concern worth a look, inline.
Reviewed changes — an initial review of the public-tunnel feature: lifecycle-bound HTTP/WebSocket exposure with exact-host routing, five Compose ingress topologies, and the v0.1.1 version bump.
- Controller tunnel endpoints —
create_tunnel,delete_tunnel, and an unauthenticatedauthorize_tunnel_domain(for Caddyon_demand_tls) incmd/sandboxd/src/controller.rs;materialize_tunnelsenforces DNS-label subdomains, blocksconfidential/restrictedsensitivity,authenticated=true, and non-HTTP protocols. - Model & config —
Tunnel/TunnelStateplus a strictvalidate_dns_label, and a thoroughly validatedTunnelConfig(base-domain shape, config-token charset, absoluteconfig_dir). - Docker tunnel manager —
crates/runtime/src/tunnel.rswrites per-tunnel Traefik file-provider routes and manages a per-sandbox--internalnetwork shared only with the edge container, includingnone-mode save/restore fordenysandboxes. - Storage & scheduler —
find_tunnel_by_hostname(memory + Postgres@>jsonb with a new GIN index) matching onlyActivetunnels;apply_tunnel_completionstate machine; scheduler rejects placement when exposures are requested but the node reports!supports_http_tunnels. - CLI & MCP —
sandbox tunnelsubcommands,--exposeon create, andsandbox_tunnel_create/sandbox_tunnel_deleteMCP tools that return operator-issued URLs and forbid client scheme rewrites.
The security model holds up well: subdomain and base-domain validation prevent Traefik route injection, the on-demand-TLS authorize endpoint is exact-match and length-capped, and the sensitivity/authentication/protocol gates are enforced consistently across controller, CLI, and MCP. The Docker image tags (traefik:v3.7.8, caddy:2.11.4-alpine, cloudflare/cloudflared:2026.7.2) were verified against upstream releases and are real.
One note on the CI validation: the "all five topologies render successfully" step runs docker compose ... config, which only validates YAML rendering — it does not prove the images are pullable. That's fine here since the tags are real, but the validation claim is weaker than it reads.
Claude Opus | 𝕏
| } | ||
| Ok(()) | ||
| } else { | ||
| Err(command_error("network rm", &output.stderr)) |
There was a problem hiding this comment.
network rm failure branch returns Err without restoring none mode, but the success branch above does restore it. By this point both endpoints have already been disconnected (lines 200-202) and expose earlier detached the sandbox from none. So if docker network rm fails transiently, a deny-mode sandbox is left attached to no network at all — silently, because expose's rollback calls discard the result (let _cleanup = self.teardown_network(...).await). This contradicts the contract documented in docs/tunnels.md ("Removing the final tunnel restores none").
Technical details
# `teardown_network` skips `none`-mode restore on `network rm` failure
## Affected sites
- `crates/runtime/src/tunnel.rs` `teardown_network` — success branch restores `none` (the `if self.uses_none_mode(...) { self.connect("none", ...) }` block), but the `else` branch returning `Err(command_error("network rm", ...))` does not.
- `crates/runtime/src/tunnel.rs` `expose` — rollback sites discard the teardown result: `let _cleanup = self.teardown_network(sandbox_id).await;` and `let _cleanup = self.unexpose(...)`. A teardown failure is therefore invisible to the caller.
- Only reachable for `NetworkMode::Deny` sandboxes (mapped to Docker `none`); non-deny sandboxes keep their original network regardless.
## Required outcome
- A failed `docker network rm` during teardown must not leave the sandbox container detached from every network. The `none`-mode restoration should run on the failure path too (or before the `rm` is attempted), so denied-egress sandboxes are always reattached to `none`.
## Suggested approach (optional)
- Perform the `uses_none_mode` check + `connect("none", ...)` restore before returning, regardless of the `network rm` result (e.g. compute the restore need up front, attempt `rm`, then reattach to `none` if needed, and only then propagate any `rm` error).
What changed
sandbox-mcpto 12 typed tools, including tunnel create/list/deleteValidation
cargo fmt --checkcargo check --workspaceSecurity model
Docker mode remains intended for dedicated or trusted worker hosts. Public exposure is explicit, policy-controlled, and lifecycle-bound. The external runtime driver remains the extension point for stronger isolation.