sandbox: one report_data binding for a run, a relying-party challenge, and evidence a third party can verify #392

Merged
dcharlot merged 1 commit from feat/sandbox-run-binding into main 2026-10-21 10:20:27 -04:00
Owner

Sweep 2026-10-19 item #5, software side. Each claim was checked against origin/main before building:

  • The report bound too little. A confidential run's TEE report bound BLAKE3(unit) ‖ BLAKE3(receipt), with no domain tag.
  • The outcome sat outside it, under the Supervisor's signature only.
  • The only freshness input was host-chosen: a nonce that every call site passed as [0; 8].
  • No one else could check the report. It was dropped once the host had checked it.
  • verify_eat compared no nonce, so any token the Supervisor key signed verified, whichever run it was for.

The binding (SPEC §7.6)

report_data = BLAKE3-XOF-64(derive_key = "sandbox/1 run report_data v1",
                len‖unit_id ‖ len‖receipt_id ‖ len‖JCS(outcome) ‖ challenge_tag)
challenge_tag = 0x01 ‖ challenge[32]  |  0x00

The receipt binds the metered energy, so the report vouches for the joules through it.

  • TierRequest.challenge carries the relying party's 32 bytes into run_and_seal. seal_run and its callers are unchanged.

  • The sealed TeeEvidence carries report_hex (the report itself) and challenge_hex. Both are omitted when absent, so evidence sealed before them keeps its bytes.

  • verify_run_evidence lets a party other than the host check:

    • the report's signature and policy;
    • that report_data binds the attestation's unit, receipt and outcome, and the verifier's own challenge, never the recorded one;
    • that the summary is the report's.

    Evidence without its report is refused.

  • verify_eat(…, expect_nonce) refuses a token for another run. The CLI, MCP and build paths now pass the run's nonce. The MCP tool takes an optional expected_nonce_hex and reports nonce_checked.

SPEC: §7.6 gives the binding, the challenge and the third-party check; §7.8, spec/eat.md and spec/attestation.md are updated to match.

Also: an orphaned doc comment in sandbox-conformance that clippy refused. It predates this change.

Verification (build host)

  • sandbox-rs: 421 tests (--locked); every conformance class passes.
  • Clippy -D warnings is clean on the seven touched crates.
  • Mutation testing: 12 mutants, all killed. They cover:
    • the challenge, its presence tag, the outcome, the receipt and the length prefixes;
    • the verifier trusting the recorded challenge, the summary check, and the report and challenge being carried;
    • the seal and run_and_seal dropping the challenge;
    • the EAT nonce check.

Not in this PR

  • Running it on SEV-SNP hardware. The available cell is a billable cloud instance, so this waits for an explicit go-ahead.
  • A challenge slot in jcp-wire requests and responses: a follow-up.
  • TDX on the run path, which still verifies SEV-SNP only.

🤖 Generated with Claude Code

Sweep 2026-10-19 item #5, software side. Each claim was checked against origin/main before building: - **The report bound too little.** A confidential run's TEE report bound `BLAKE3(unit) ‖ BLAKE3(receipt)`, with no domain tag. - **The outcome sat outside it,** under the Supervisor's signature only. - **The only freshness input was host-chosen:** a nonce that every call site passed as `[0; 8]`. - **No one else could check the report.** It was dropped once the host had checked it. - **`verify_eat` compared no nonce,** so any token the Supervisor key signed verified, whichever run it was for. ## The binding (SPEC §7.6) ```text report_data = BLAKE3-XOF-64(derive_key = "sandbox/1 run report_data v1", len‖unit_id ‖ len‖receipt_id ‖ len‖JCS(outcome) ‖ challenge_tag) challenge_tag = 0x01 ‖ challenge[32] | 0x00 ``` The receipt binds the metered energy, so the report vouches for the joules through it. - **`TierRequest.challenge`** carries the relying party's 32 bytes into `run_and_seal`. `seal_run` and its callers are unchanged. - **The sealed `TeeEvidence` carries** `report_hex` (the report itself) and `challenge_hex`. Both are omitted when absent, so evidence sealed before them keeps its bytes. - **`verify_run_evidence`** lets a party other than the host check: - the report's signature and policy; - that `report_data` binds the attestation's unit, receipt and outcome, and the verifier's **own** challenge, never the recorded one; - that the summary is the report's. Evidence without its report is refused. - **`verify_eat(…, expect_nonce)`** refuses a token for another run. The CLI, MCP and build paths now pass the run's nonce. The MCP tool takes an optional `expected_nonce_hex` and reports `nonce_checked`. **SPEC:** §7.6 gives the binding, the challenge and the third-party check; §7.8, `spec/eat.md` and `spec/attestation.md` are updated to match. **Also:** an orphaned doc comment in sandbox-conformance that clippy refused. It predates this change. ## Verification (build host) - **sandbox-rs:** 421 tests (`--locked`); every conformance class passes. - **Clippy** `-D warnings` is clean on the seven touched crates. - **Mutation testing:** 12 mutants, all killed. They cover: - the challenge, its presence tag, the outcome, the receipt and the length prefixes; - the verifier trusting the recorded challenge, the summary check, and the report and challenge being carried; - the seal and `run_and_seal` dropping the challenge; - the EAT nonce check. ## Not in this PR - **Running it on SEV-SNP hardware.** The available cell is a billable cloud instance, so this waits for an explicit go-ahead. - **A challenge slot in jcp-wire** requests and responses: a follow-up. - **TDX on the run path,** which still verifies SEV-SNP only. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sandbox: one report_data binding for a run, a relying-party challenge, and evidence a third party can verify
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 50s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m53s
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 / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m37s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m36s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 1m16s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m32s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m13s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m33s
df918bf256
A confidential run's TEE report bound BLAKE3(unit) ‖ BLAKE3(receipt), with
no domain tag. The outcome sat only under the Supervisor's signature, the
only freshness input was a host-chosen nonce (every call site passed
[0; 8]), and the report was dropped once the host had checked it, so no
one else could. verify_eat compared no nonce, so any token the Supervisor
key signed verified, whichever run it was for.

sandbox-runtime (SPEC §7.6):
- run_report_data is one preimage: BLAKE3-XOF-64 in derive-key mode
  ("sandbox/1 run report_data v1") over length-prefixed unit_id,
  receipt_id and JCS(outcome), then 0x01 ‖ challenge, or 0x00 when there
  is none. The receipt binds the metered energy, so the report vouches for
  the joules through it. It replaces tee_report_data.
- TierRequest.challenge carries the relying party's 32 bytes into
  run_and_seal. seal_run_with_mechanism takes a challenge; seal_run and its
  callers are unchanged.
- The sealed TeeEvidence carries report_hex (the report itself) and
  challenge_hex.
- verify_run_evidence lets a party other than the host check:
  - the report's signature and policy;
  - that its report_data binds the attestation's unit, receipt and
    outcome and the verifier's OWN challenge, never the recorded one;
  - that the summary is the report's.
  Evidence without its report is refused.

sandbox-attest:
- Outcome::core is shared by the attestation core and the preimage.
- TeeEvidence.report_hex and challenge_hex are omitted when absent, so
  evidence sealed before them keeps its bytes.
- run_nonce is exposed.
- verify_eat takes expect_nonce and refuses a token whose nonce is not
  that run's. The CLI, MCP and build paths now pass the run's nonce. The
  MCP verify_eat tool takes an optional expected_nonce_hex and reports
  nonce_checked.

SPEC §7.6 gives the binding, the challenge and the third-party check;
§7.8, spec/eat.md and spec/attestation.md are updated to match.

Also: an orphaned doc comment in sandbox-conformance's vectors.rs that
clippy refused (it predates this change).

Tests:
- Binding and challenge.
- An unchallenged report is never taken for a challenged one.
- The preimage separates its inputs, including a length-delimitation
  collision.
- A requested challenge reaches the report through run_and_seal.
- An EAT is checked against the run the verifier holds.
Mutation testing: 12 mutants, all killed. They cover:
- the challenge, its presence tag, the outcome, the receipt and the
  length prefixes;
- the verifier trusting the recorded challenge, the summary check, and the
  report and challenge being carried;
- the seal and run_and_seal dropping the challenge;
- the EAT nonce check.

Verified on the build host:
- sandbox-rs: 421 tests (--locked); every conformance class passes.
- Clippy -D warnings clean on sandbox-runtime, -attest, -tee, -build,
  -mcp, -cli and -conformance.

Not in this change: running it on SEV-SNP hardware (a billable cloud cell),
a challenge slot in jcp-wire, and TDX on the run path, which still
verifies SEV-SNP only.

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!392
No description provided.