Skip to content

[codex] Add OCI sandbox execution backend - #155

Open
camerondurham wants to merge 6 commits into
mainfrom
codex/oci-sandbox-runtime
Open

[codex] Add OCI sandbox execution backend#155
camerondurham wants to merge 6 commits into
mainfrom
codex/oci-sandbox-runtime

Conversation

@camerondurham

Copy link
Copy Markdown
Owner

Summary

Adds a Docker/OCI sandbox backend for code execution behind CODECANVAS_EXECUTION_BACKEND=docker while keeping the legacy process backend available by default.

Key changes:

  • Adds engine/sandbox and engine/sandbox/docker with per-request disposable container execution.
  • Applies hardened Docker flags: no network, read-only root, non-root user, dropped capabilities, no-new-privileges, cgroup CPU/memory/pids limits, nofile ulimit, private IPC, init process, and bounded stdout/stderr capture.
  • Supports stronger OCI runtimes such as gVisor via CODECANVAS_SANDBOX_RUNTIME=runsc.
  • Adds startup preflight for Docker daemon access, requested runtime availability, and local sandbox image availability.
  • Adds a local all-tools sandbox image at docker/sandbox/Dockerfile plus make dkr-build-sandbox.
  • Documents rollout, config, validation gates, and Firecracker as a later backend in docs/oci-sandbox-runtime.md.
  • Hardens the legacy path by running compile steps under the runner UID/GID and using cmd.Dir instead of process-wide chdir.

Validation

  • go test ./...
  • docker build -t codecanvas-sandbox:local -f docker/sandbox/Dockerfile .
  • CODECANVAS_DOCKER_INTEGRATION=1 CODECANVAS_SANDBOX_IMAGE=codecanvas-sandbox:local go test ./engine/sandbox/docker ./engine/coderunner/v2 -run Integration -count=1 -v

The gated Docker tests prove sandbox execution, CodeRunner integration, startup preflight, non-root UID, and blocked outbound network.

Notes

The host currently has Docker runtimes runc, io.containerd.runc.v2, and nvidia; runsc is not registered yet. This PR supports and preflights CODECANVAS_SANDBOX_RUNTIME=runsc, but enabling gVisor still requires installing/registering runsc on the host.

@camerondurham
camerondurham marked this pull request as ready for review June 11, 2026 02:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b7b197ea1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread engine/sandbox/docker/runner.go Outdated
"--security-opt", "no-new-privileges",
"--tmpfs", "/tmp:rw,nosuid,nodev,size=64m",
"--workdir", containerWorkDir,
"--mount", fmt.Sprintf("type=bind,source=%s,target=%s", hostWorkdir, containerWorkDir),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bound writes to the sandbox workspace

When the Docker backend is enabled, this bind mount is writable by default, and Docker's --read-only only protects the container root filesystem while allowing writes to mounted volumes/binds. A submitted program can therefore write unbounded data under /work (for example yes > bigfile) and consume the host filesystem despite the memory, pid, timeout, and /tmp limits; the legacy path applied file-size limits, but this sandbox path has no equivalent quota for the host workspace.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex

Review this again

@camerondurham
camerondurham marked this pull request as draft June 14, 2026 18:29
@camerondurham
camerondurham marked this pull request as ready for review June 14, 2026 18:29

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb5753e2b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

if runprops.PreRunProps != nil {
preRunProps = runPropsWithRuntimeIdentity(preRunProps, agent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep compiler UID aligned with runner workdirs

In the default server Docker image using the legacy backend, this now runs compile steps as the controller's agent UID (1, 2, ...), but docker/server-debian/entrypoint.sh creates /tmp/runnerN owned by UIDs starting at 1001. For compiled languages, g++/go build/rustc therefore run as a user that can read the source file but cannot create a.out or run in the 0755 workdir, so C++/Go/Rust requests through make dkr-server fail before execution; interpreters are unaffected because they do not write build artifacts.

Useful? React with 👍 / 👎.

Comment on lines +22 to +26
return CodeRunner{
sandboxRunner: config.Runner,
sandboxPolicy: config.Policy,
sandboxImages: config.Images,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cap Docker backend concurrency

When CODECANVAS_EXECUTION_BACKEND=docker is enabled, this sandbox runner bypasses the legacy NUM_RUNNERS/NewAsyncController reservation path and has no replacement semaphore. Every simultaneous /api/v1/run request therefore starts a docker run immediately, so a request flood can create unbounded containers and exhaust aggregate host CPU, memory, or Docker capacity despite each individual container having limits.

Useful? React with 👍 / 👎.

Comment thread engine/coderunner/v2/runner.go Outdated
}
steps = append(steps, sandbox.Command{Args: plan.runCmd})

out, err := cr.sandboxRunner.Run(context.Background(), sandbox.Job{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate request cancellation into sandbox runs

When the Docker backend is configured with CODECANVAS_SANDBOX_TIMEOUT above the server's 10-second middleware timeout, this context.Background() prevents HTTP cancellation from reaching the sandbox. Timed-out or disconnected requests can keep the handler and Docker container running until the sandbox timeout instead of stopping when the request context is canceled, tying up Docker resources after the client is gone.

Useful? React with 👍 / 👎.

Comment thread engine/sandbox/docker/runner.go Outdated
if len(image) == 0 {
continue
}
if output, err := exec.CommandContext(ctx, docker, "image", "inspect", image).CombinedOutput(); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor pull policy during preflight

When CODECANVAS_SANDBOX_PULL is set to missing or always, Docker's --pull policy is supposed to pull absent or refreshed images during docker run, but the default startup preflight still fails first unless every image is already in the local cache. Operators following the documented pull-policy setting on a fresh host therefore get a startup panic instead of an image pull, making those new policy values unusable unless they also disable preflight.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41e1d1da03

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +145 to +146
"--label", "codecanvas.sandbox=true",
"--pull", policy.PullPolicy,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disable Docker logging for sandbox output floods

When the Docker backend runs an output-heavy snippet such as yes, cappedBuffer only truncates the API response while continuing to drain the Docker CLI stream, and this docker run leaves the daemon's logging driver at its default. Docker documents --log-driver for docker run and explicitly notes --log-driver=none disables container logging, so without that or a bounded max-size log option, a request can still force the host Docker daemon to write unbounded stdout/stderr logs during the sandbox timeout despite CODECANVAS_SANDBOX_OUTPUT_BYTES.

Useful? React with 👍 / 👎.

DefaultContainerWorkDir = "/work"
DefaultTimeoutSec = 2
DefaultCPUs = "1"
DefaultMemory = "128m"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Increase the Docker timeout for cold Go builds

With the Docker backend's default policy, each Go request starts a fresh container and sends GOCACHE=/tmp/go-cache, so common submissions such as a fmt.Println program have to populate the build cache inside that container before running. This 2-second wall timeout covers the go build run.go compile step and execution together, so default Docker deployments can time out supported Go submissions unless operators know to raise CODECANVAS_SANDBOX_TIMEOUT.

Useful? React with 👍 / 👎.

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.

1 participant