sandbox: an S1 executor — a native program confined by the operating system, earning the rung by probe #281

Merged
dcharlot merged 1 commit from feat/sandbox-exec-os into main 2026-10-17 04:02:09 -04:00
Owner

What was missing

The isolation ladder had no executor between a bare child process (S0) and a managed runtime (S2). As a result, sandbox-build's command steps ran either unconfined or in a microVM, with nothing in between.

What this adds

sandbox-exec-os runs a native program under the operating system's sandbox. The policy is derived from the unit's grants and denies by default:

  • the program may read and write exactly the granted storage paths;
  • it has no network;
  • it cannot spawn a process;
  • it reaches no device beyond the host CPU.

Why S1, and no higher. S1's guarantee (spec/tiers.md) is that no resource is reachable without a capability, and that holds for every resource grants govern. A native process keeps a residue, which the crate documents:

  • the system libraries, read-only;
  • the root directory's listing;
  • /dev/null;
  • the clock and randomness.

The clock and randomness cannot be withheld from a native process, so a grant that withholds either one is refused, never run with a capability it denies. Also refused:

  • network grants, because egress cannot be limited to the named endpoints;
  • everything that needs S2: subprocesses, a browser, a display, accelerators, the key-value store.

S2 itself needs fuel metering and memory caps, which a native process cannot have.

The rung is earned on each host. A probe, run once per process, runs a program under a policy that grants nothing. The control run must succeed, and the program must be denied:

  • a file outside the policy;
  • a write;
  • a subprocess;
  • a loopback connection to a listener the probe opened.

Only when all of that holds is the tier S1, sealed with the SPEC §7.9 mechanism: seatbelt/<macOS version> plus a commitment to the exact policy applied. Anywhere else, the program runs unconfined at S0 with no mechanism.

Linux is S0 for now. A namespace sandbox there would also need a system-call filter to enforce "no subprocess", and the fleet's Linux hosts refuse the network namespace (bubblewrap fails to set up loopback under the AppArmor user-namespace restriction), so it could not be verified.

Energy is the child's share of the host's CPU reading (sandbox_meter::run_attributed). It is ModelBased where that reading comes from hardware, and Estimator otherwise.

sandbox-build: a command step runs confined, and seals S1 with its mechanism, when the confinement holds here and can enforce the step's grants. Every other step falls back to the existing unconfined child process. Today's steps withhold the clock, so they are unchanged.

CI: the macOS energy job gains a step that sets SANDBOX_EXEC_OS_EXPECT_S1=1. With that set, the tests fail if the probe does not hold, and the step requires the confinement line and a denied read in the output. Every other runner takes the S0 branch, which the same tests also assert. check-tests-in-ci.py passes.

Checked

On the build host, macOS 27.0.1:

  • Probe: seatbelt/27.0.1 probes S1.
  • Confinement: a granted copy succeeds and a read outside the grants is denied.
  • Sealing: Host::run_and_seal seals S1 with the mechanism.
  • Refusal: a unit that withholds the clock is refused before it runs.
  • Suites: the sandbox-rs workspace passes 407 tests, conformance passes all classes, and clippy is clean on both crates.

Each of these mutations fails a test:

  • the policy allows the network;
  • a withheld clock is accepted;
  • the mechanism commits to another policy;
  • sandbox-build never confines;
  • the policy reads everything. The probe catches this one: it drops to S0, and SANDBOX_EXEC_OS_EXPECT_S1 makes that a failure.

🤖 Generated with Claude Code

## What was missing The isolation ladder had no executor between a bare child process (S0) and a managed runtime (S2). As a result, sandbox-build's command steps ran either unconfined or in a microVM, with nothing in between. ## What this adds **`sandbox-exec-os`** runs a native program under the operating system's sandbox. The policy is derived from the unit's grants and denies by default: - the program may read and write exactly the granted storage paths; - it has no network; - it cannot spawn a process; - it reaches no device beyond the host CPU. **Why S1, and no higher.** S1's guarantee (`spec/tiers.md`) is that no resource is reachable without a capability, and that holds for every resource grants govern. A native process keeps a residue, which the crate documents: - the system libraries, read-only; - the root directory's listing; - `/dev/null`; - the clock and randomness. The clock and randomness cannot be withheld from a native process, so a grant that withholds either one is **refused**, never run with a capability it denies. Also refused: - **network grants**, because egress cannot be limited to the named endpoints; - **everything that needs S2:** subprocesses, a browser, a display, accelerators, the key-value store. S2 itself needs fuel metering and memory caps, which a native process cannot have. **The rung is earned on each host.** A probe, run once per process, runs a program under a policy that grants nothing. The control run must succeed, and the program must be denied: - a file outside the policy; - a write; - a subprocess; - a loopback connection to a listener the probe opened. Only when all of that holds is the tier S1, sealed with the SPEC §7.9 mechanism: `seatbelt/<macOS version>` plus a commitment to the exact policy applied. Anywhere else, the program runs unconfined at S0 with no mechanism. Linux is S0 for now. A namespace sandbox there would also need a system-call filter to enforce "no subprocess", and the fleet's Linux hosts refuse the network namespace (bubblewrap fails to set up loopback under the AppArmor user-namespace restriction), so it could not be verified. **Energy** is the child's share of the host's CPU reading (`sandbox_meter::run_attributed`). It is `ModelBased` where that reading comes from hardware, and `Estimator` otherwise. **sandbox-build:** a command step runs confined, and seals S1 with its mechanism, when the confinement holds here and can enforce the step's grants. Every other step falls back to the existing unconfined child process. Today's steps withhold the clock, so they are unchanged. **CI:** the macOS energy job gains a step that sets `SANDBOX_EXEC_OS_EXPECT_S1=1`. With that set, the tests fail if the probe does not hold, and the step requires the confinement line and a denied read in the output. Every other runner takes the S0 branch, which the same tests also assert. `check-tests-in-ci.py` passes. ## Checked On the build host, macOS 27.0.1: - **Probe:** `seatbelt/27.0.1` probes S1. - **Confinement:** a granted copy succeeds and a read outside the grants is denied. - **Sealing:** `Host::run_and_seal` seals S1 with the mechanism. - **Refusal:** a unit that withholds the clock is refused before it runs. - **Suites:** the sandbox-rs workspace passes 407 tests, conformance passes all classes, and clippy is clean on both crates. Each of these mutations fails a test: - the policy allows the network; - a withheld clock is accepted; - the mechanism commits to another policy; - sandbox-build never confines; - the policy reads everything. The probe catches this one: it drops to S0, and `SANDBOX_EXEC_OS_EXPECT_S1` makes that a failure. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sandbox: an S1 executor — a native program confined by the operating system, earning the rung by probe
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 4s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 58s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m54s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 57s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m1s
standards workspaces build + test / aac-rs (aac-conformance, aac/aac-rs, aac-rs, linux-arm64) (pull_request) Successful in 3m15s
standards workspaces build + test / map-rs (map-conformance, map/map-rs, map-rs, linux-arm64) (pull_request) Successful in 1m1s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 57s
standards workspaces build + test / openpay (openpay, openpay, linux-arm64) (pull_request) Successful in 3m28s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m25s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 52s
standards workspaces build + test / arl-rs (arl/arl-rs, arl-rs, linux) (pull_request) Successful in 1m13s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m46s
standards workspaces build + test / eoc-rs (eoc/eoc-rs, -p eoc-meter --features cuda, eoc-rs, linux) (pull_request) Successful in 2m7s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m10s
standards workspaces build + test / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m32s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m23s
standards workspaces build + test / sense-rs (sense/sense-rs, sense-rs, linux) (pull_request) Successful in 1m10s
standards workspaces build + test / smart-byte-rs (smart-byte/smart-byte-rs, smart-byte-rs, linux) (pull_request) Successful in 4m12s
cdd809ca7b
The ladder had no executor between a bare child process (S0) and a managed
runtime (S2). `sandbox-exec-os` runs a native program under the operating
system's sandbox, with a deny-by-default policy derived from the unit's
grants:
- the program may read and write exactly the granted storage paths;
- it has no network;
- it cannot spawn a process;
- it reaches no device beyond the host CPU.

Why S1 and no more. S1's guarantee is that no resource is reachable without
a capability, and that holds for every resource grants govern. A native
process holds a stated residue: the system libraries read-only, the root
directory's listing, /dev/null, the clock and randomness. Because the clock
and randomness cannot be withheld, a grant that withholds either is refused,
never run with a capability it denies. So are network grants (egress cannot
be limited to named endpoints) and everything that needs S2: subprocesses,
browser, display, accelerators, key-value store. S2 itself needs fuel and
memory caps, which a native process cannot have.

The rung is earned per host. A probe, run once per process, runs a program
under a policy that grants nothing. It must succeed at a control run, and be
denied a file outside the policy, a write, a subprocess and a loopback
connection to a listener the probe opened. Only then is the tier S1, with
SPEC §7.9's mechanism: `seatbelt/<macOS version>` and a commitment to the
exact policy applied. Anywhere else the program runs unconfined at S0 with
no mechanism. Linux is S0 for now: a namespace sandbox there also needs a
system-call filter to enforce "no subprocess", and the fleet's Linux hosts
refuse the network namespace, so it could not be verified.

Energy is the child's share of the host's CPU reading (`run_attributed`):
ModelBased where that reading is a hardware one, Estimator otherwise.

sandbox-build: a command step runs confined at S1 where the confinement
holds and can enforce the step's grants, sealed with its mechanism. Any
other step falls back to the existing unconfined child process (S0), so
today's steps, whose grants withhold the clock, are unchanged.

CI: the macOS energy job runs sandbox-exec-os and fails unless the S1
branch ran (the probe's confinement line and a denied read). Every other
runner takes the S0 branch, which the same tests assert.

Checked on the build host (macOS 27.0.1): `seatbelt/27.0.1` probes S1. A
granted copy succeeds, a read outside the grants is denied, run_and_seal
seals S1 with the mechanism, and a unit withholding the clock is refused
before it runs. The sandbox-rs workspace passes 407 tests, conformance
passes all classes, and clippy is clean on both crates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!281
No description provided.