* feat(execd): execd as sandbox init (OSEP-0018 phase 1) Make execd the sandbox init (PID 1): it becomes the parent of the user entrypoint, reaps every child through a single reaper, forwards application signals, and owns the container lifecycle (entrypoint exit code is propagated to the runtime). - --init flag + EXECD_INIT bootstrap.sh exec branch (default off, classic background-and-wait topology unchanged) - single reaper (only wait4-family caller) with pre-reap WNOWAIT barrier; managedProcess abstraction replaces Cmd.Wait across all launch paths (command, bash session, PTY, isolated session) - signal forwarding (HUP/USR1/USR2/WINCH) and SIGTERM graceful shutdown; subreaper fallback when not PID 1; PR_SET_DUMPABLE(0) - hardening.init_mode/signal_shield reported on the capabilities endpoint (spec + Go/Python SDKs aligned) - unit tests for the reaper and lifecycle; bootstrap contract test * feat(execd): pre-exec hardening floor (OSEP-0018 phase 2) Route every user-code launch (entrypoint, /command, /code, PTY) through a native launcher that applies the privilege floor between fork and exec: execd credential env stripped, bounding set trimmed to keep_capabilities (default none), no_new_privs, identity drop to the image user, ambient caps raised, and the seccomp filter installed last. - native/launcher.c: static prelude helper, fail-closed on malformed policy, fail-open per step; built/installed by the Makefile and image - [hardening] enabled + keep_capabilities in the isolation TOML; the launcher's execve is reserved and rejected at config time (execveat stays valid); [seccomp] deny is reused as the floor filter - isolated sessions exempt: the bwrap workload is already reduced inside the namespace, and flooring the bwrap process would deny unshare and strip the caps it needs to build the namespace - hardening.cap_drop/seccomp/landlock/ebpf layer states on the capabilities endpoint (spec + Go/Python SDKs aligned) - all layers fail open: missing launcher or CAP_SETPCAP degrades with a reason instead of blocking startup * feat(server): runtime.execd_run_as_init config injects EXECD_INIT Single server-side switch (default false) that sets EXECD_INIT=1 in the sandbox environment across all paths (Docker, K8s Batch/Agent, Pool taskTemplate). bootstrap.sh then execs execd --init, so topology and the --init flag stay in lockstep by construction (OSEP-0018 open question 1). Execd init mode and hardening remain independently controllable; the switch is intended to be flipped on by default in a later release. * test(execd): real-container init-mode regression (OSEP-0018) Runs the execd image with EXECD_INIT=1 in Docker and verifies the init contract end to end: execd is PID 1 with the workload as its direct child, orphans are reaped (no zombie accumulation), in-namespace kill -9 1 is inert (signal shield), entrypoint exit codes are propagated, runtime SIGTERM is forwarded with the workload status preserved, and with [hardening] enabled the floor applies (CapEff=0, no_new_privs, seccomp filter, env strip) while the capabilities endpoint reports pid1 + active layers. Also covers the Pool-style backgrounded topology reporting subreaper mode. Wired into execd-test.yml (ubuntu smoke job); EXECD_TEST_IMAGE allows local iteration against a prebuilt image. * feat(execd): landlock confinement (OSEP-0018 phase 3) Add [landlock] enabled/extra_writable/extra_readable on top of the hardening floor. The launcher applies the filesystem allowlist between the identity drop and seccomp: system paths and /proc/self read+exec, writable device files and the controlling tty, /tmp, /run, and allowed_writable — everything else denied. A root EXECUTE-only rule covers traversal and execve of any binary without granting reads, and all of /proc is never granted (would re-expose /proc/1). The kernel ABI is probed (v1-v4) and the launcher trims access bits (REFER/TRUNCATE) accordingly; ABI < 1 reports unsupported and skips. The container regression now enables landlock and asserts that /proc/1/environ is unreadable by the workload when the layer is active. * feat(execd): eBPF observation variant (OSEP-0018 phase 4) Opt-in exec/connect/privilege audit, scoped to the sandbox cgroup and written as rotating JSONL (stable envelope: ts/event/sandbox_id/pid/comm plus per-kind fields). Ships as the execd-ebpf build variant (CGO + cilium/ebpf); the default image is unchanged and reports disabled. - BPF programs (CO-RE): sched_process_exec (filename+argv via __data_loc), inet_sock_set_state (dst ip:port), commit_creds kprobe (uid/gid deltas, cap_added) - kernel compatibility from 5.10: the exec trace event layout change in 5.16 is handled with a bpf_core_field_exists discriminator, the kernel_cap_t shape change in 6.3 with a layout-agnostic 8-byte read; Landlock (phase 3) still requires >= 5.13 and degrades to unsupported - [ebpf] enabled/observe/audit_file config; capabilities endpoint layer state (active/unsupported/degraded/disabled), all fail-open - Dockerfile ebpf target + make build-ebpf; generated bytecode committed; unit tests for event decoding; CI runs the ebpf-tagged tests * docs(spec): drop phase markers from hardening layer descriptions * feat(sdks): surface hardening status across all execd-capabilities SDKs Regenerate the JS execd client from the spec and extend the handwritten adapters so every SDK exposes the hardening object (init_mode, signal_shield, cap_drop/seccomp/landlock/ebpf layer states): - javascript: generated execd.ts gains hardening + HardeningLayerState - kotlin: IsolatedCapabilities.hardening domain model + adapter mapping (with a mock-server test) — the generated sandbox-api client picks the spec up at build time - csharp: IsolatedCapabilities gains Hardening/HardeningStatus/ HardeningLayerState records Go and Python already carried the field; MCP and code-interpreter SDKs do not consume the capabilities endpoint. * feat(server,k8s): pool tasks run execd as the task root (OSEP-0018 phase 5) With runtime.execd_run_as_init enabled, the pool taskTemplate no longer backgrounds bootstrap: the task-executor shim's shell execs bootstrap.sh, which execs `execd --init` (EXECD_INIT=1), so execd becomes the root of the task process tree — orphaned task children are reaped (subreaper) and the entrypoint exit code propagates to the task status. Classic background-and-wait topology is preserved when the switch is off. The K8s Restart recycle contract (kill 1 via pod exec) is confirmed compatible with init-mode execd under the current SIGTERM-forward semantics: execd forwards the signal and exits with the workload status, so the kubelet restarts the container. A note marks the reconciliation required once a trusted out-of-band stop channel replaces signal-driven stop (OSEP-0018 §3). Docs: Pool pod template guidance for running execd as the pod's PID 1. * docs(oseps): mark 0018 as implementing with granular phase status Record the phased implementation state (phases 1-5 + server switch done), the resolutions of open questions 1-5, and the remaining work (trusted stop channel, Pool pod-level PID 1, 5.10 validation, e2e). * fix: address Codex/human review feedback across five rounds + CI repairs Squashed review-fix series: - CI: Dockerfile stage order (ebpf variant was the default image), license headers on bpf2go output, verify-license skip for generated files, golangci (gci/predeclared/unused), kotlin ktlint - landlock: required-vs-best-effort rule semantics, mount-expanded rules (bind-mounted workspaces), dynamic rule counts, preflight degradation for operator-explicit grants, per-launch fail-closed ruleset - hardening: trusted launcher path first, MFD_CLOEXEC policy memfd, per-request uid/gid folded into the policy, entrypoint keeps bootstrap env but never EXECD_ACCESS_TOKEN, bash-session env snapshot scrub - init: reaper drops reaped pids (bounded map, no stale pgid signalling), SIGTERM/SIGKILL sent under the reaper lock, synchronous signal.Notify before the entrypoint starts - eBPF: sandbox_id in records, cgroup-id under the cgroup v2 mount, requested-hook attach failures degrade, IPv4-mapped event format, cap-only privilege events, argv dropped from exec events - distribution: launcher shipped on docker/k8s paths, pool taskTemplate exec + needs_task_template fix, ebpf image keeps the default layout and builds static - container regression: workdir world-writable (no CAP_DAC_OVERRIDE under hardening), /proc/self read via the entrypoint process (Landlock descendant limitation) * fix: address Codex round 6 (credential identity, audit sink, /opt, sandbox_id) - hardening: per-request credentials now force the launcher's UID_DROP even when execd is not root (a /command uid/gid request previously ran as the image user), and supplementary groups are serialized into the policy so the launcher applies setgroups(groups) instead of clearing them - audit: loaded eBPF objects are retained on the Observer (GC could close the fd and detach the programs) and closed on Close(); ringbuf reader failure now also closes already-attached links; audit write errors are logged instead of dropped - landlock: /opt joins the default read+exec set (bundled code-interpreter entrypoints and runtimes live under /opt) - server: OPENSANDBOX_ID is injected on the Docker, K8s Batch/Agent and pool taskTemplate env paths so eBPF audit records carry the sandbox id * test(e2e): dedicated execd-as-init real-e2e for python, plus nightly job Add tests/python/tests/test_execd_init_e2e.py (sync SDK): PID 1 is execd, the workload is its direct child, orphans are reaped, in-namespace kill -9 1 is inert, and /v1/isolated/capabilities reports hardening.init_mode=pid1. Runs against a server with runtime.execd_run_as_init=true. - scripts/python-execd-init-e2e.sh: docker-bridge runner for the new suite; wired as a dedicated real-e2e job (python-execd-init-e2e) - scripts/python-k8s-execd-init-e2e.sh + E2E_EXECD_RUN_AS_INIT in k8s_e2e_write_server_helm_values: Kind/Kubernetes variant; wired as a dedicated kubernetes-nightly-build job (execd-init-e2e) * ci(e2e): fix execd-init e2e failures in python and k8s nightly jobs * feat(execd): ship execd-ebpf in default image; docs and e2e sync (OSEP-0018) * fix(execd): address Codex round 7 (landlock jupyter log, init-mode report, launcher identity, ebpf build tags) * docs(oseps): fold remaining-work tracking into OSEP-0018 status section * fix(execd,server): resolve codex review on hardening report and pool sandbox id attribution - ReportHardening keys the non-init topology correction off hardening being enabled instead of cap_drop's state, and only degrades layers that are actually in effect (previously a degraded cap_drop left seccomp/landlock claiming active, and a disabled landlock could be marked degraded) - eBPF Init reports unsupported when OPENSANDBOX_ID is missing, so pool fast-path allocations without a task template cannot silently claim active attribution - batchsandbox_provider logs the pool fast-path limitation (no per-allocation env injection without a task template) - docs/components/execd.md documents the pool path attribution behavior - tests: cover non-init degradation, landlock enabled case, and pool fast path * fix(execd): report configured non-Linux layers as unsupported; fix golint goconst - non-Linux: InitHardening/SetEbpfState now record requested layers so the capabilities endpoint reports configured hardening/landlock/ebpf as unsupported instead of disabled (codex review) - hardening_linux.go: hoist repeated "disabled" state string into a const (golangci-lint goconst) - docs: describe the dual-binary default image (execd + execd-ebpf) and the deferred server-side selection; no Dockerfile 'ebpf' target exists --------- Co-authored-by: Sky <yutian.taoyt@alibaba-inc.com>
OpenSandbox Code Interpreter Environment
English | 中文
This directory contains the Docker build files for the Code Interpreter sandbox. The image is based on Ubuntu 24.04
and comes pre-installed with multiple mainstream programming languages and their multi-version environments, designed to
provide an out-of-the-box multi-language code execution environment.
Features
- Multi-Language Support: Pre-installed Python, Java, Node.js, and Go with multiple versions
- Version Switching: Easy runtime version switching without rebuilding
- Jupyter Integration: Built-in Jupyter Notebook with multi-language kernels
- Multi-Architecture: Supports both amd64 and arm64 architectures
- clone3-workaround (amd64): The image installs AkihiroSuda/clone3-workaround v1.0.0 as
/usr/local/bin/clone3-workaroundon linux/amd64 only (upstream ships no arm64 binary), pluslibseccomp2because the upstream binary is dynamically linked tolibseccomp. Use it to wrap commands on very old Docker/containerd hosts, e.g.clone3-workaround apt-get update. - Production Ready: Optimized for containerized execution environments
Supported Languages & Versions
The image comes pre-installed with the following languages and versions:
| Language | Supported Versions | Installation Path | Notes |
|---|---|---|---|
| Python | 3.10, 3.11, 3.12, 3.13, 3.14* | /opt/python/versions |
Installed via uv; 3.14 is experimental |
| Java | 8, 11, 17, 21 | /usr/lib/jvm |
OpenJDK; includes Maven 3.9.2 |
| Node.js | v18, v20, v22 | /opt/node |
Official Linux binaries |
| Go | 1.23, 1.24, 1.25 | /opt/go |
Official Linux binaries |
> Note: Version numbers may be updated to the latest patch versions at build time.
Quick Start
1. Build the Image
Since multi-architecture (amd64/arm64) is supported, it's recommended to use Docker Buildx:
# Navigate to the directory
cd sandboxes/code-interpreter
# Build local image
docker build -t opensandbox/code-interpreter:latest .
# For multi-architecture builds (requires Docker Buildx)
docker buildx build --platform linux/amd64,linux/arm64 \
-t opensandbox/code-interpreter:latest .
2. Run the Container
With Custom Version Selection:
docker run -it --rm \
-e PYTHON_VERSION=3.11 \
-e JAVA_VERSION=17 \
-e NODE_VERSION=20 \
-e GO_VERSION=1.24 \
opensandbox/code-interpreter:latest
EXECD_CLONE3_COMPAT (clone3-workaround)
If you set EXECD_CLONE3_COMPAT to 1, true, yes, on, or reexec (same semantics as execd), the entrypoint script re-executes itself under /usr/local/bin/clone3-workaround before Jupyter and kernel setup. That binary is included on linux/amd64 only; on arm64 builds the script prints a warning and continues without wrapping. After a successful wrap, the script unsets EXECD_CLONE3_COMPAT in the running process tree. Use 0, false, off, no, or leave unset to disable.
Version Switching
The image includes a built-in version switching script /opt/code-interpreter/code-interpreter-env.sh. You need to use the
source command to load it to modify the current shell's environment variables.
Basic Usage
source /opt/code-interpreter/code-interpreter-env.sh <language> <version>
Examples
Switch Python Version:
# Switch to Python 3.11
source /opt/code-interpreter/code-interpreter-env.sh python 3.11
python3 --version
# Output: Python 3.11.x
Switch Java Version:
# Switch to Java 8
source /opt/code-interpreter/code-interpreter-env.sh java 8
java -version
Switch Node.js Version:
# Switch to Node 22
source /opt/code-interpreter/code-interpreter-env.sh node 22
node -v
Switch Go Version:
# Switch to Go 1.25
source /opt/code-interpreter/code-interpreter-env.sh go 1.25
go version
List Available Versions
If you don't specify a version number, the script will list all available versions installed in the current image:
# List all Python versions
source /opt/code-interpreter/code-interpreter-env.sh python
# List all Java versions
source /opt/code-interpreter/code-interpreter-env.sh java
# List all Node.js versions
source /opt/code-interpreter/code-interpreter-env.sh node
# List all Go versions
source /opt/code-interpreter/code-interpreter-env.sh go
Default Versions
The default version configuration when the container starts:
- Python: 3.14
- Java: 21
- Node.js: 22
- Go: 1.25
To permanently modify the default version at the Dockerfile level, adjust the ENV PATH settings at the bottom of the
Dockerfile.
Jupyter Notebook Integration
Available Kernels
The image comes with pre-configured Jupyter kernels for all supported languages:
- Python: ipykernel for all Python versions
- Java: IJava kernel
- TypeScript/JavaScript: tslab kernel
- Go: gonb kernel
- Bash: bash_kernel
Starting Jupyter
/opt/code-interpreter/code-interpreter.sh
Environment Variables
JUPYTER_HOST: Jupyter server host (default:http://127.0.0.1:44771)JUPYTER_PORT: Jupyter server port (default:44771)JUPYTER_TOKEN: Access token (default:opensandboxcodeinterpreterjupyter)
Advanced Usage
Persistent Workspace
Mount a local directory to persist your work:
docker run -it --rm \
-v $(pwd)/workspace:/workspace \
opensandbox/code-interpreter:latest
Custom Configuration
Override Jupyter configuration:
docker run -it --rm \
-v $(pwd)/jupyter_config.py:/root/.jupyter/jupyter_notebook_config.py \
opensandbox/code-interpreter:latest
Install Additional Packages
Python:
python3 -m pip install pandas numpy --break-system-packages
Node.js:
npm install -g typescript
Go:
go install github.com/user/package@latest
Java:
mvn install dependency:copy-dependencies
Architecture
code-interpreter/
├── Dockerfile # Main build file
├── Dockerfile_base # Base build file
├── README.md # This file
├── README_zh.md # Chinese README
└── scripts/
├── code-interpreter-env.sh # Version switching script
├── code-interpreter.sh # Jupyter startup script
└── jupyter_notebook_config.py # Jupyter configuration
Troubleshooting
If a specific version is not found, list available versions:
source /opt/code-interpreter/code-interpreter-env.sh <language>
License
This project is part of the OpenSandbox suite. See the main LICENSE file for details.
Support
For issues and questions:
- GitHub Issues: OpenSandbox Issues
Related Projects
- OpenSandbox - Main project
- Server - Server implementation
- Execd - Runtime execution engine