Skip to content

Bound every sandbox Docker invocation and terminate timed-out execs #394

Closed

Description

@SUaDtL

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.

Activity

  1. SUaDtL commented on Jul 25, 2026

    @SUaDtL
    CollaboratorAuthor

    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.

  2. added 3 commits that reference this issue on Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    sev:highTribunal/triage: high severity

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions