15 Commits

Author SHA1 Message Date
epha fb37444db3 feat(execd): server-path hardening e2e coverage (OSEP-0018 R-i) (#1554)
* 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)
2026-08-18 12:18:51 +08:00
epha 43ab6bbb68 feat(execd): execd as sandbox init with hardening floor (OSEP-0018) (#1474)
* 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>
2026-08-17 18:59:16 +08:00
Wenxiang Jin 28aa332654 Add credential vault SDKs and e2e coverage (#1023)
* add credential vault SDKs and e2e coverage

* format Kotlin credential vault SDK

* document credential vault SDK usage

* mention credential vault in root features

* simplify credential vault feature copy

* Address credential vault SDK review feedback

* Address credential vault review feedback

* Remove JS credential vault proxy fail-fast

* Keep Kotlin egress policy interface compatible

* Add C# credential vault e2e coverage

* Potential fix for pull request finding 'Poor error handling: empty catch block'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-06-12 10:24:30 +08:00
Wenxiang Jin 0ecfb731f0 Add credential vault support (#1009)
* Add credential vault support

* Add credential vault auth type e2e

* Allow targeted credential vault e2e dispatch

* Handle missing HOME in credential vault e2e

* fix(egress): harden credential vault proxy access

Use a mitmproxy-only internal token for active vault reads, strip that header from public proxies, and add regression coverage for execd/server proxy access.

Apply vault write guards to delete, validate active vault coverage on policy resets, align credential header patterns with Go/RFC tokens, and keep response redaction to logs and headers by default.

* fix(server): gate credential proxy mitm opt-in

Add credentialProxy.enabled to sandbox create requests and only inject transparent MITM env, CA bundle mounts, and runtime trust when the option is explicitly enabled.

Keep plain networkPolicy sidecars free of transparent MITM, update SDK request models, and cover Docker/Kubernetes/server/schema/E2E regressions.

* fix(sdk): revert non-python credential proxy changes

* fix(egress): use uds for active credential vault

* Document credential vault and simplify auth types

* Potential fix for pull request finding 'Empty except'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>

* Address credential vault review feedback

* Address credential vault package review

* Fix credential vault review regressions

* Clean Docker cache before self-hosted CI

* Bound Docker cleanup time in CI

* Address follow-up credential proxy reviews

* Align credential proxy MITM trust handling

* Run all real e2e jobs on manual dispatch

* Update credential vault OSEP and mutation e2e

* chore: rerun pr checks

* fix(server): share MITM CA for Docker credential proxy

* Scope real e2e cleanup

* Avoid proxy egress token injection

---------

Co-authored-by: Sky <yutian.taoyt@alibaba-inc.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
2026-06-11 17:35:19 +08:00
ninan.nn 1800c41597 test(tests): run code-interpreter e2e manually 2026-05-11 20:07:52 +08:00
epha 4876b6eb43 fix(server): Sandbox escape via arbitrary host path mounts in the default configuration (#751)
* 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
2026-04-23 21:30:42 +08:00
ninan-nn 4985a03135 chore: refactor server structure (#558) 2026-03-24 21:02:34 +08:00
Wenxiang Jin 58ef919c0a Merge pull request #233 from hittyt/feat/docker-named-volume
feat: add Docker named volume support with subPath for PVC backend
2026-02-24 17:23:04 +08:00
epha ebc9dde919 chore(execd): replace logger with internal package (#237) 2026-02-16 09:41:39 +08:00
yutian.taoyt 10e8369e98 feat: add Docker named volume support with subPath for PVC backend
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>
2026-02-13 14:18:04 +08:00
高然 7e27245e4e chore(e2e): sync execd's log to hostpath and upload artifact 2026-02-10 10:57:51 +08:00
高然 ca8c826a69 fix(tests): use local tag execd's image built by source 2026-01-19 13:32:04 +08:00
高然 6aa6f77bca feat(e2e): build execd image by source within e2e workflow 2026-01-13 13:57:10 +08:00
高然 faf34c8356 feat(workflow): add real e2e workflow for docker bridge network 2025-12-19 16:05:47 +08:00
高然 ea1d7c6f47 initial commit
Co-authored-by: Wenxiang Jin <wenxiang.jin@alibaba-inc.com>
Co-authored-by: Peipei Shi <anchen.spp@alibaba-inc.com>
Co-authored-by: Yutian Tao <yutian.taoyt@alibaba-inc.com>
Co-authored-by: Nan Ni <ninan.nn@alibaba-inc.com>
2025-12-17 17:42:22 +08:00