open-standards/sandbox/spec/attestation.md
David Charlot 41dcdb9484
All checks were successful
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
sandbox: let an S1-S5 attestation say how it confined the run (SPEC §7.9)
The previous commit made the sealed tier true. It did not make it checkable.
Below S6 the standard requires no evidence at all — §7.5 says so outright, and
`validate_structure` enforces evidence only at S6 (TEE) and S7 (locality) — so
`tier = "S2"` is the host's word with a signature around it, and the honest
`S0` the last commit produced is equally just the host's word.

§7.9 adds an optional `mechanism`: the enforcement runtime (name AND version)
plus a commitment to the policy profile applied and the egress allowlist
installed. An attestation carrying one is `sandbox.attestation/2`; without one
it stays `/1` and canonicalizes to byte-identical JSON, so every attestation
sealed before this section — and every published vector — verifies unchanged.
That is the same add-field/bump-profile/dispatch shape used for `payload_hash`.

What this is careful not to claim: a mechanism is NOT proof of isolation, and
both the SPEC text and the type docs say so. A host willing to misstate its
tier can misstate its profile; a commitment made by the party under scrutiny
cannot bootstrap its own trustworthiness. What changes is the shape of the
claim — an unadorned tier label is unfalsifiable, while a profile_id is
checkable against a known-good profile a relying party already trusts, and a
false one is a specific, attributable lie about a named artifact. A party that
needs proof rather than assertion still requires S6+.

Design notes:

- The commitment length-prefixes each field rather than concatenating them.
  Bare concatenation is not injective: "seatbelt/14" + "5;deny-net" and
  "seatbelt/145" + ";deny-net" would collide. C11 asserts they no longer do.
- Requiring a mechanism is a *relying-party* check, not a producer obligation.
  Making it mandatory would retroactively invalidate every honest attestation
  sealed before §7.9, and a standard does not get to make historical records
  non-conformant. It lives in `require_mechanism_evidence()`, the same posture
  §7.5 already takes for isolation proof.
- A mechanism at S0 is rejected, not ignored. S0 is unconfined; describing a
  boundary that does not exist is incoherent rather than merely redundant.
- `Executor::mechanism()` defaults to `None` — as with `attained_tier()`, the
  component that did the work is the one that gets to describe it, and silence
  beats invention. `run_and_seal` takes it from the executor; the bare
  `seal_run` primitive keeps its old signature and passes `None`.
- `WasmExecutor` is a real producer, so the field is not decorative: it commits
  to fuel metering, the fuel budget and the memory cap — deliberately NOT
  `uj_per_million_fuel`, an accounting coefficient that confines nothing and
  would make two identically-confined runs disagree when someone retunes it.
- wasmtime exposes no version constant and Cargo gives no compile-time access
  to a dependency's resolved version, so `WASMTIME_VERSION` is hand-maintained
  and a test fails the build if it drifts from Cargo.lock. A stale value would
  attest confinement to a runtime that did not run.

Verified: 194 workspace tests pass, conformance passes with no skips across all
11 classes. C4 attestation.json — a published vector — still passes untouched,
which is the backward-compatibility claim holding in practice rather than in
argument.
2026-08-24 23:58:46 -04:00

89 lines
7.4 KiB
Markdown

# The attestation (signed claim + replay journal)
Normative companion to [`SPEC.md`](../SPEC.md) §7 and §8. RFC 2119 keywords apply.
An **attestation** is the Supervisor's signed statement of what a run was: which image ran, at which tier, under which grants, on which hardware, with which energy account, and — where recorded — replayable to a journal root. It is the artifact a third party verifies without trusting the host.
## Core
```
Attestation {
unit_id : ContentId,
image_id : ContentId,
grants_id : ContentId,
tier : Tier, // S0..S7
devices : [DeviceIdentity],
receipt_id : ContentId, // the energy receipt for this run (SPEC §6)
journal_root : Option<Hash>, // BLAKE3 root of the replay journal (SPEC §8)
outcome : Outcome, // Completed | Failed | Terminated{ breach }
tee : Option<TeeEvidence>, // present iff tier >= S6 (SPEC §7.6)
locality : Option<Locality>, // REQUIRED at S7 (SPEC §7.7)
audit_prev : Option<ContentId>, // REQUIRED at S7: audit-chain head before this run
mechanism : Option<Mechanism>, // how confinement was established; S1-S5 (SPEC §7.9)
host_id : String,
started_at_us : u64,
finished_at_us: u64,
}
DeviceIdentity { class : DeviceClass, model : String, driver : String }
Mechanism { runtime : String, profile_id : ContentId }
// runtime : enforcement runtime, name AND version ("wasmtime/43.0.2")
// profile_id : BLAKE3 over the length-prefixed encoding of
// (runtime, policy profile applied, egress allowlist installed)
Outcome = Completed
| Failed
| Terminated { breach : Breach } // EnergyBudget | CpuTime | Memory | Tamper | GrantMutation
TeeEvidence { kind : SevSnp | Tdx | ArmCca, measurement_hex : String, tcb : String }
Locality { region : String, jurisdiction : String }
```
## Confidential and sovereign binding (S6/S7)
At **S6**, the host obtains a TEE report from the platform's attestation interface, binds it to the run by placing a value derived from the run identity in the report's `report_data`, and **verifies** it — signature against the VCEK, and measurement / TCB / debug-state / report-data against policy — before sealing the `TeeEvidence` summary (SPEC §7.6). The reference `sandbox-tee` crate implements the full SEV-SNP verification path (parse, policy, ECDSA-P384/SHA-384); report *generation* is hardware-bound (`configfs-tsm` on real confidential hardware), with a tagged software-grounded provider for wiring.
An attestation carrying `mechanism` uses the profile tag `sandbox.attestation/2`; one without it remains `sandbox.attestation/1` and canonicalizes to byte-identical JSON, so attestations sealed before §7.9 verify unchanged. A `mechanism` **MUST NOT** be present at S0 (unconfined by definition), and is **not** proof of isolation — a host that will misstate its tier can misstate its profile. It makes the claim checkable against a known-good profile rather than unfalsifiable. A relying party needing proof rather than assertion still requires S6+ (SPEC §7.5, §7.9).
At **S7**, the host additionally binds `locality` and links the run into a hash-chained audit log: `audit_prev` is the chain head before this run, and after signing the host appends the attestation's content address, advancing the head (`head' = BLAKE3(head ‖ attestation_id)`). The chain is tamper-evident, so the ordered sequence of sovereign runs is itself attestable (SPEC §7.7).
## Device identity (REQUIRED — heterogeneity clause)
`devices` **MUST** name, for **each device class the unit used**, its `{ class, model, driver }`. A run that dispatched accelerator work but does not record that accelerator's identity is **not conformant** (SPEC §7.2). The classes in the attestation's `devices` **MUST** match the classes in the receipt's `devices` (SPEC §6.2): the same silicon that was metered is the silicon that is named.
## Binding, not duplication
The attestation does **not** restate the energy figures. It carries `receipt_id`, the content address of the receipt core, so the receipt is bound to the run by hash. A verifier checks the attestation signature, then checks that the receipt it holds addresses to `receipt_id`. The grant set is bound the same way through `grants_id`, and the executed artifact through `image_id`. None can be swapped without breaking the signature chain.
## Signing (Ed25519 over JCS)
The attestation signs **Ed25519** (RFC 8032) over the **JCS-canonicalized JSON** (RFC 8785) of its core, with SHA-256 backing the content addresses it references. This is the attestation primitive of the **ARL Sandbox**; a Sandbox attestation and an ARL-S session attestation verify under the same code path, so a measurement run (ARL-S) and a production run (Sandbox) speak one attestation language.
The reference `sandbox-attest` crate implements JCS canonicalization and the sign/verify pair, with vectors fixing the canonical bytes and a known-answer signature (vector class **C4**).
### Why two crypto forms in one standard
The **receipt** seals as JCR-1 (det-CBOR + COSE) because it is a member of the family's receipt class and must verify under shared JCR-1 tooling. The **attestation** signs Ed25519-over-JCS because it is the generalization of ARL-S and must verify under shared ARL-S tooling. They cross-reference by content address (§ "Binding, not duplication"). This is deliberate interoperability with two existing surfaces, not redundancy.
## Key custody
The Supervisor's signing key **MUST** be unreachable by the unit and by the host's in-boundary code — TPM, HSM, or secure enclave at production tiers (SPEC §7.4). The unit has no path to the telemetry stream or the signing operation. The Supervisor's read of physical telemetry occurs over a channel the unit cannot inspect or influence.
## Anti-evasion (recorded in the attestation)
The host **MUST** normalize the environment the unit observes — hostname, paths, process tree, time, timezone, locale — to deployment-realistic values, and **MUST** record probing in the attestation: hardware enumeration, root filesystem walks, attempts to read or defeat the energy interface, timing-channel probing. A detected VM-escape, privilege escalation, or out-of-allocation write **MUST** set `outcome = Terminated{ Tamper }` and invalidate the run. Any mutation to the content-addressed grant set, image, or harness configuration during the run **MUST** set `outcome = Terminated{ GrantMutation }` (SPEC §1.2, §7.5).
## Deterministic replay (SPEC §8)
Where a host records a **replay journal**, it logs the unit's nondeterministic inputs in order:
```
JournalEntry = HostCall { name : String, args_hash : Hash, result_hash : Hash }
| TimerFired { at_us : u64 }
| Random { draw_hash : Hash }
| Checkpoint { state_hash : Hash }
```
The journal's BLAKE3 root **MUST** appear as `journal_root`. Given the recorded inputs and seeds, the host **MUST** be able to reconstruct the run to the identical sequence of `Checkpoint` state hashes. Replay is for **audit**, not re-execution: re-running a stochastic unit produces a new sample; replay reproduces the run that happened. A journal that does not replay to its recorded root invalidates the attestation. The reference `sandbox-attest` crate computes the journal root and verifies a `Consistent | Diverged{at} | Truncated` replay outcome.