open-standards/sandbox/spec/conformance.md
Transaction Science 96cd5e98a0
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 48s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m41s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-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 2m18s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m38s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 2m43s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 5m23s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 13m57s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 4m28s
sandbox: flow across a run's checkpoints — an untrusted read closes the sinks that follow
Sweep 2026-10-19 item #7, second half, by the owner's decision. Each
checkpoint was decided alone: a unit could read data an attacker writes and
later reach an endpoint it is granted, each call passing its own check, and
nothing connected the read to the egress. The Enforcer is stateless by
design (C2), so the connection had to be state the run carries explicitly.

sandbox-capability (SPEC §5.8):
- Grants.flow: FlowGrant { untrusted: [FlowSource { kind: Path|Kv|Program,
  pattern, origin }], tainted_may: [FlowSink { kind: Egress|Exec, target,
  accepts }] }. Omitted when empty, so a grant set without it keeps its bytes.
- RunTaint: the origins of untrusted data a run has read; only grows.
- Two pure Enforcer functions: taints(source), the origins a read adds; and
  may_reach(sink, &taint), where a tainted run reaches a sink only where
  tainted_may opens it to EVERY origin it carries (entries combine; `*`
  opens to any target or origin; an unopened sink is closed). Additional to
  the sink's capability, never instead. A decision stays a function of its
  inputs, the run's taint among them, as I/O counters already are.

sandbox-exec-wasm (S2, where the live checkpoints are): HostState carries
the taint. A storage or kv read that returns data, and a program's output,
taint the run from a source the grant marks; egress and exec are refused
with TAINTED (-6, distinct from DENIED) when the grant has not opened them
to the run's origins. sandbox-exec-os is untouched: it refuses network and
process grants outright, so no sink is reachable there.

sandbox-runtime: grants_content_id binds flow_untrusted and
flow_tainted_may when present, so the flow grant is part of the capability
surface (SPEC §1.2); a grant without one keeps its id.

Conformance class C13: sequence vectors (a run's calls in order; reads
check the origins after, sinks whether reachable), in code and in the new
conformance/v1/flow.json. The pack count goes to 13.

SPEC §5.8, spec/capabilities.md (the enforcer contract: pure, with the
run's history as an input), spec/conformance.md, conformance/v1/README.md;
SPEC §10's class list was missing C11 and C12 and now lists C11–C13.

Residuals, stated in §5.8: an unmarked source is read as the principal's
data, and a tainted run's writes are not marked, so data can lose its taint
between runs.

Note: running gen_vectors also rewrites attestation.json and negative.json
with "mechanism": null, a drift that predates this change; left out of it.

Tests:
- capability: three unit tests (a read carries every matching source's
  origin, kinds do not cross, `*` is any program; a tainted run reaches
  only what is opened to all it carries, entries combine; the taint only
  grows and an empty flow grant is not encoded).
- exec-wasm: three end-to-end through real guests (an untrusted storage,
  kv or program read closes the egress that follows, a clean read leaves it
  open; an untrusted read closes the launch that follows; a sink opens only
  to the origins named, and no flow grant is untouched).
- runtime: the flow grant is bound into the grant id, every part of it.
- C13.
Mutation testing: 17 mutants; 16 killed, 1 equivalent (the clean-run early
return in may_reach: an empty taint already satisfies "every origin").

Verified on the build host: sandbox-rs 428 tests (--locked); every
conformance class passes, C13 in code and from flow.json; clippy -D
warnings clean on sandbox-capability, -exec-wasm, -runtime, -conformance.
Repo checks pass.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 10:49:52 -04:00

6.9 KiB
Raw Permalink Blame History

Conformance

Normative companion to SPEC.md §10. RFC 2119 keywords apply.

A host is conformant if, for every isolation tier it advertises, it satisfies every REQUIRED clause of SPEC.md §1–§7 and round-trips the conformance vectors in ../conformance/. A conforming host MUST pass the full pack with no skips for its advertised tiers. The vectors — not any brand — define what is correct.

Vector classes

C1 — Canonicality

The content addressing (b3: + base64url over BLAKE3-256) and the canonical forms (det-CBOR for the receipt core, JCS for the attestation core) are reproduced byte-for-byte. A unit descriptor, a grant set, a receipt core, and an attestation core each address to the fixed ContentId in the vector. No float appears in any canonical form.

C2 — Capability enforcement (deny-by-default)

Given the canonical grant set, the Enforcer returns the fixed allow/deny decision for each query in the vector, including the negatives: a dispatch to an ungranted device class is denied; a write to a read-only path is denied; an egress to an unlisted endpoint is denied; a kv access outside granted prefixes is denied; a host service that was not granted is denied. The empty grant set denies everything.

C3 — Receipt integrity and per-device provenance

A receipt round-trips through its JCR-1 envelope to byte-identical bytes and verifies under the steward key. The per-device records sum to total (field-wise), total.measured_uj is present iff every used class carries one, and the (provenance, measured_uj) coupling holds for every record (HwShunt ⇒ present, Estimator ⇒ absent). The pack includes adversarial negatives: an accelerator run that folds GPU energy into the CPU record (rejected), a HwShunt record with no measured_uj (rejected), an Estimator record carrying a measured_uj (rejected), a total that does not equal the device sum (rejected).

C4 — Attestation signing and verification

An attestation core canonicalizes to the fixed JCS bytes and produces the fixed Ed25519 signature under the known signing key; verification accepts it and rejects a single-bit mutation of any field. The attestation's devices classes match the bound receipt's devices classes. A journal_root present in the attestation replays Consistent against the vector's journal; a tampered journal replays Diverged.

C5 — Tier minimization

Given a grant set, the host selects the lowest tier whose guarantees satisfy it (the maximum of the per-grant minimums in tiers.md). The pack fixes the required tier for a set of grant sets: CPU-only trusted → S0/S1; untrusted code → S2; accelerator via brokered interface → S2; multi-tenant → S3; accelerator via direct assignment → S5; confidential → S6. A host that runs above or below the required tier fails C5.

C6 — Durable run records (§13)

A run record chains execute → suspend → resume segments; the head advances and recomputes from genesis. Resuming a snapshot that differs from the one recorded at suspension is refused, as is an execution while parked or an append after close. Idle energy accumulates across parks; a tampered segment (via the wire form) fails verification.

C7 — Environment manifest (§14)

A devcontainer.json ingests to a canonical, content-addressed manifest whose address is invariant to key order and whitespace. Fields that execute commands or widen the sandbox are refused, and unknown fields are refused rather than silently dropped. A resume offering a drifted manifest address is refused.

C8 — Content-addressed process control (§5.5)

Process control decides on the BLAKE3 of the executable's bytes. A denylisted hash is refused regardless of the binary's name (survives rename / move / wrapper) and overrides every allow rule; when a hash allowlist is set it is authoritative over names; exec is still required.

C9 — Warm pools (§15)

A claim attributes the slot's accrued idle energy to the run and is refused unless the slot's environment address matches. An evicted unclaimed slot records its idle energy as overhead, never zero. The pool's total idle energy equals claimed + overhead + pending — nothing hidden.

C10 — Fleet records (§16)

Every item in a fleet proves against the merkle aggregate root with a logarithmic proof, across odd and even leaf counts. A foreign item on a real proof path fails to verify, and any change to a leaf changes the root.

C11 — Enforcement mechanism (§7.9)

An attestation without a mechanism canonicalizes to byte-identical JSON under the /1 profile, so historical signatures and published vectors still verify; one carrying a mechanism is /2, and the commitment is inside the signed bytes. The commitment is injective across field boundaries — moving bytes between the runtime name and the profile changes the digest. A mechanism at S0 is refused, and the relying-party requirement rejects a bare S1–S5 tier label while an evidenced one satisfies it.

C13 — Flow across a run's checkpoints (§5.8)

A run's calls, in order: a read from an untrusted source adds its origins to the run's taint, and a sink is decided against everything read before it. A clean run reaches every sink its capabilities allow. An untrusted read closes the egress and launch that follow it, except where the flow grant opens a sink to every origin the run carries. The taint only grows, so a later clean read restores nothing, and a second origin closes a sink opened to the first. A grant set without a flow grant is never tainted. The order is part of the vector, since no single query can show what a run may do after what it did.

Adversarial obligation

The most valuable vectors are negatives — runs a conformant host must reject. A standard whose vectors only assert the happy path certifies nothing. The committed pack (../conformance/v1/negative.json) carries a C-neg class the harness asserts is rejected:

  • a HwShunt reading with no measured figure, an Estimator carrying one, a total that does not equal the device sum, and a measured-coverage mismatch (SPEC §6);
  • an attestation that omits an accelerator the receipt metered (SPEC §7.2), and an S6 attestation without TEE evidence (SPEC §7.6);
  • a replay journal that diverges and one that is truncated — neither may compare Consistent (SPEC §8);
  • a mid-run grant-set mutation, whose before/after content addresses MUST differ so a host can detect it (SPEC §1.2).

Growing this set (tier-minimization violations, anti-evasion probing) is the highest-value contribution to this directory (see ../CONTRIBUTING.md).

Running the pack

The reference harness (sandbox-conformance) loads the vectors and checks each class; the sandbox CLI exposes it as sandbox conformance. A run that skips a class for an advertised tier is a fail, not a pass: silent truncation reads as "covered" when it is not.