You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Bound every sandbox Docker invocation and terminate timed-out execs #394
Bound every sandbox Docker invocation and terminate timed-out execs
Severity: high | Confidence: 0.96 | Effort: L
Where:
plugins/ca-sandbox/tools/docker.ts:39-48
plugins/ca-sandbox/tools/exec.ts:68-70
plugins/ca-sandbox/tools/exec.ts:120-140
Evidence: The single Docker chokepoint calls spawnSync("docker", args, { encoding: "utf8", env: DOCKER_ENV, ...extra }) with no default timeout. execInSandbox widens only maxBuffer and then synchronously waits for the injected/default runner; an in-container command such as sleep infinity, a wedged daemon, or a stalled build has no deadline, cancellation path, or cleanup escalation.
Impact: Any sandbox lifecycle operation can block the host process up to external termination, and untrusted repository code can deterministically wedge sandbox exec. Because the call is synchronous, the CLI cannot service cancellation, emit progress, or run teardown while blocked.
Recommendation: Replace the unbounded synchronous execution boundary with a bounded cancellable child-process runner. Give lifecycle operations explicit per-operation deadlines; on timeout terminate the Docker client and, for docker exec, terminate or stop the targeted in-container process/container according to the command contract before returning a typed timeout failure.
Acceptance criteria:
Every production Docker invocation has a finite documented timeout and returns a typed timeout result.
A hanging sandbox exec fixture terminates within the configured deadline and leaves no targeted process running in the container.
Cancellation can interrupt an active Docker call and still executes bounded teardown.
Tests cover hung daemon/client behavior and an in-container command that ignores graceful termination on Windows, macOS, and Linux runners where Docker is available.
Partially delivered by #480 (merged). Staying open, because one acceptance criterion is genuinely unmet — not closing it on three-out-of-four.
Shipped:
AC-1 — every production Docker invocation carries a finite per-operation deadline and returns a typed timedOut result. The deadline is layered into the one runner, so a caller can still widen maxBuffer but cannot remove it.
AC-2 — a hanging sandbox exec terminates within its deadline and leaves no targeted process running, asserted against a real container in a new docker-gated deadline layer.
AC-4 — that hung-command case runs on every OS where Docker is available, through the existing docker-gate.
One finding worth recording: the escalation uses docker stop --time 0, not a grace window. Measured, a stop --time 5 under a 4s runner deadline never completes — so the "cleanup" itself times out and the container survives it. The graceful window is what the deadline already spent.
Not shipped — AC-3 ("cancellation can interrupt an active Docker call"): spawnSync is by construction uninterruptible. A deadline can be set on it, but nothing can interrupt the call in flight, because the calling thread is blocked inside the syscall. Honouring an AbortSignal requires converting the whole docker boundary to async, rippling through build.ts, claude-inside.ts, cli.ts, cp.ts, create.ts, destroy.ts, exec.ts, registry.ts and run.ts while preserving the CLI's synchronous exit-code contract.
That work is scoped in #479. This issue closes when it lands.
Activity
fix(sandbox): bound every docker invocation and stop an orphaned exec (…
Merge origin/main into fix/433-sandbox-teardown-diagnostics (#394's d…
SUaDtL commentedon Jul 25, 2026
Partially delivered by #480 (merged). Staying open, because one acceptance criterion is genuinely unmet — not closing it on three-out-of-four.
Shipped:
timedOutresult. The deadline is layered into the one runner, so a caller can still widenmaxBufferbut cannot remove it.sandbox execterminates within its deadline and leaves no targeted process running, asserted against a real container in a new docker-gateddeadlinelayer.docker-gate.One finding worth recording: the escalation uses
docker stop --time 0, not a grace window. Measured, astop --time 5under a 4s runner deadline never completes — so the "cleanup" itself times out and the container survives it. The graceful window is what the deadline already spent.Not shipped — AC-3 ("cancellation can interrupt an active Docker call"):
spawnSyncis by construction uninterruptible. A deadline can be set on it, but nothing can interrupt the call in flight, because the calling thread is blocked inside the syscall. Honouring anAbortSignalrequires converting the whole docker boundary to async, rippling throughbuild.ts,claude-inside.ts,cli.ts,cp.ts,create.ts,destroy.ts,exec.ts,registry.tsandrun.tswhile preserving the CLI's synchronous exit-code contract.That work is scoped in #479. This issue closes when it lands.
fix(sandbox): bound build.ts's own spawn, which #480 missed (#489)
feat(#479): Ctrl-C cancels the in-flight call and lets teardown finish
docs(#479): changelog entry for the cancellable docker boundary