IMPL-0010: Sandbox Backends and Execution Budgets¶
Status¶
Accepted. Implements ADR-0021, and the execution limits of ADR-0013 P1, ADR-0015 ยง1 and ADR-0017 Pillar 4, which ADR-0021 and the budgets below supersede. Recorded 2026-10-03 from the 2.6.0rc1 code. The user guide is docs/SANDBOX.md.
Decision (as implemented)¶
- Session layout.
- There is one sandbox session per
SessionEngine(and per grader call), at<tempdir>/pdlt-sandboxes/<sid>/, withowner.jsonandwork/. - It is recorded as a
SANDBOX_SESSIONevent, and released byclose()on every REPL exit path. - Roots whose owner process is dead are swept when the next session is built.
- Per-run layout.
- Each program gets a fresh
work/run-NNNN-*, withTMP/TEMP/TMPDIRpointed at itstmp/. work/is emptied after each run.- The interpreter is always
sys._base_executable. - Backends:
| Mode | Backend | Mechanism |
|---|---|---|
native on Linux |
Landlock | Raw syscalls 444/445/446, PR_SET_NO_NEW_PRIVS; one ruleset per session; ABI 4+ denies TCP bind and connect; ABI 6+ scopes abstract UNIX sockets and signals. |
native on macOS |
Seatbelt | /usr/bin/sandbox-exec, deny-by-default profile, realpath'd -D parameters. |
native on Windows |
AppContainer | Per-session profile with modify rights on work/ only; a ctypes CreateProcessW launcher with no capabilities, inside the run's Job Object; no child processes. |
container (opt-in) |
docker / podman | One container per session: no network, read-only root, no capabilities, no privilege gain, a process cap, only work/ mounted. |
audit-only (opt-out) |
none | Audit hook and resource limits only. |
The mode is chosen by --sandbox {auto,native,container,audit-only} (REPL and catalogue runner), defaulting to PDLT_SANDBOX; auto means native.
- Fail closed. run_code returns sandbox_unavailable:<reason>, the engine records SANDBOX_UNAVAILABLE, and no repair is spent on it.
- Audit hook (defense in depth):
- denies ctypes, _ctypes, cffi, _cffi_backend and sqlite extension loading;
- confines paths by realpath and commonpath;
- denies signals, network and process creation.
- Execution budgets (verification/sandbox.py:402-406). Steps are executed bytecode instructions of the script's own code; the wall clock is a safety net.
| Tier | Steps | Wall clock | Memory | Repairs |
|---|---|---|---|---|
MINIMAL |
100,000 | 30 s | 256 MB | 1 |
STANDARD |
10,000,000 | 30 s | 256 MB | 1 |
HEAVY_COMPUTE |
100,000,000 | 120 s | 512 MB | 2 |
System 1's execution_profile recipe chooses the tier; --max-repairs overrides repairs. The engine's sandbox object is built with a 15 s default (session_engine.py:361); each run uses its tier's limit.
- Model-facing text states capabilities only (AVAILABLE_EXECUTION_TOOLS): directory, file, network and process limits, the step budget and how it counts.
Superseded limits¶
- ADR-0013 P1 and ADR-0015: Job Objects or rlimits with a 5.0 s timeout and 256 MB.
- ADR-0017 Pillar 4: 15 s for verified-execution tasks.
- The filesystem clause of ADR-0013 P1, which no code enforced before ADR-0021.
Evidence and platform notes¶
- Linux: Landlock setup costs about 3 ms per session, and per-run medians are within noise of
audit-only. - Container mode: about 0.3โ2 s per session and 100โ300 ms per run.
- Coverage gaps:
- Landlock needs Linux 5.13+ and does not cover UDP, pathname UNIX sockets or
fork(the audit hook covers those); - macOS does not enforce
RLIMIT_AS. - Windows:
- a per-user Python install needs a one-time ALL APPLICATION PACKAGES grant, recorded with a marker under
~/.pdlt/; - the Microsoft Store Python fails closed;
- AppContainers cannot open
NUL. - REG-006 (ADR-0017): naive backtracking over 3,000,000+ states exceeded the old 5 s ceiling.
Verification¶
tests/test_confinement.py: an escape suite per backend, with the hook on and off.tests/test_sandbox.py.- The CI sandbox matrix: Linux, macOS and Windows, on 3.10โ3.14.