* feat(execd): server-path hardening e2e coverage (OSEP-0018 R-i)
- tests/python/tests/test_execd_hardening_e2e.py: SDK-level assertions
for the [hardening]/[landlock] floor (entrypoint + /command reduced
caps/seccomp/NNP, config-env strip, Landlock FS enforcement incl. the
bind-mounted workspace, capabilities endpoint states) plus the
missing-CAP_SETPCAP degradation class
- scripts/python-execd-hardening-e2e.sh: two-phase runner (floor, then
SETPCAP-dropped degradation) injecting the hardened isolation TOML via
config-level bind mount + EXECD_ISOLATION_CONFIG
- components/execd/configs/isolation.hardened.toml: hardening + landlock
e2e config
- .github/workflows/real-e2e.yml: python-execd-hardening-e2e job;
detect-changes.yml wires the new script to the real-e2e area
- oseps/0018: R-i marked implemented (docker bridge), authors unified to
@Pangjiping; oseps/0013: author handle fix
* docs(osep-0018): reference the actual R-i PR number (#1554)
* fix(execd): hardening e2e CI failures (OSEP-0018 R-i)
- entrypoint status dump: read /proc/self/status with the shell's own loop
instead of cat — under Landlock a forked descendant gets EACCES on its
own procfs (documented OSEP-0018 limitation), which failed
test_entrypoint_is_reduced_and_env_stripped on Landlock-capable runners
- scripts/python-e2e.sh: ignore test_execd_hardening_e2e.py in the default
(non-hardened) python e2e job, matching test_execd_init_e2e.py
- isolation.hardened.toml: add the Apache license header (verify-license)
* fix(execd): hardening e2e — exclude from make test target, unconfined container baseline (OSEP-0018 R-i)
- tests/python/Makefile: ignore test_execd_hardening_e2e.py in the
code-interpreter dispatch branch, matching test_execd_init_e2e.py
- hardening e2e server config: no_new_privileges=false +
seccomp_profile=unconfined so NoNewPrivs=1/Seccomp=2 in the workload
can only come from the opensandbox-launcher, not from the container
baseline (verified locally: both phases pass without the mask)
* docs(osep-0018): sync implementation status with the R-i hardening e2e landing (#1554)
* 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>
* fix(server): make empty allowed_host_paths deny host mounts by default
* fix(server): require explicit startup confirmation when api_key is unset
* fix(server): add api-key for kubernetes e2e test
* fix(server): enforce api_key confirmation at startup with hard process exit
This commit implements Docker named volume support for the PVC backend,
with full subPath support to enable fine-grained path isolation within
named volumes, consistent with Kubernetes PVC subPath behavior.
- `server/src/services/docker.py`:
- `_validate_pvc_volume`: Now validates named volume existence via
`docker volume inspect`, validates driver is "local" when subPath
is used, checks Mountpoint exists, validates resolved subPath
exists, and adds path traversal protection
- `_build_volume_binds`: Generates appropriate bind strings:
- Without subPath: `volume-name:/container/path:ro|rw`
- With subPath: `/var/lib/docker/volumes/.../subdir:/container/path:ro|rw`
- `server/src/services/constants.py`: Add error codes:
- `PVC_VOLUME_NOT_FOUND`: Named volume does not exist
- `PVC_VOLUME_INSPECT_FAILED`: Docker API call failed
- `PVC_SUBPATH_UNSUPPORTED_DRIVER`: subPath requires "local" driver
- `PVC_SUBPATH_NOT_FOUND`: subPath directory does not exist
- `server/src/api/schema.py`: Update PVC documentation to reflect
runtime-neutral behavior (Docker named volumes + Kubernetes PVCs)
- `server/tests/test_docker_service.py`:
- Convert `_build_volume_binds` tests to instance methods
- Add 5 new tests for PVC volume handling:
- `test_single_pvc_volume_rw/ro`
- `test_pvc_volume_with_subpath`
- `test_pvc_volume_with_subpath_readonly`
- `test_mixed_host_and_pvc_volumes`
- Add 5 new validation tests:
- `test_pvc_volume_not_found_rejected`
- `test_pvc_volume_inspect_failure_returns_500`
- `test_pvc_volume_binds_passed_to_docker`
- `test_pvc_subpath_non_local_driver_rejected`
- `test_pvc_subpath_not_found_rejected`
- `test_pvc_subpath_binds_resolved_to_mountpoint`
- All language SDKs now test:
- Read-write named volume mount
- Read-only named volume mount
- SubPath mount with isolation verification
- Test scripts (`scripts/*-e2e.sh`) now seed named volume with
subPath test data (`datasets/train/marker.txt`)
- `oseps/0003-volume-and-volumebinding-support.md`: Update to reflect
cross-runtime PVC behavior and subPath support in Docker
- `specs/sandbox-lifecycle.yml`: Update PVC component documentation
- `examples/docker-pvc-volume-mount/`: New example demonstrating:
- Read-write/read-only named volume mounts
- Cross-sandbox data sharing
- SubPath isolation
The `pvc` backend is now a runtime-neutral abstraction:
- **Docker**: Maps to named volumes created via `docker volume create`
- **Kubernetes**: Maps to PersistentVolumeClaims
When `subPath` is specified:
- Volume must use `local` driver (for accessible Mountpoint)
- Resolved path is `Mountpoint + subPath` (bind mount)
- Path traversal protection ensures subPath stays within volume
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>