sandbox: injected egress credentials + sandbox-jcp cross-workspace fix (re-land of #33) #36

Merged
dcharlot merged 2 commits from sandbox/cred-injection-2 into main 2026-07-21 08:19:55 -04:00
Owner

Re-land of #33. Its merge produced commit 9708f203 but main's ref never advanced to it — a concurrent ref-update race on an actively-merged branch — so the work was never on main while the PR still reports merged=true. Confirmed against the forge API, not a fetch. Same two commits, cherry-picked clean onto current main (88b9d91a, post-#34) and re-verified there.

(This is the documented main-clobber behaviour: a MERGED PR is not proof the work is on main. Verify the content, not the status.)


1. Injected egress credentials — the secret never enters the unit

Ingested from the pattern where a hosted agent runtime injects credentials at the network boundary so the sandboxed unit never holds them. NetworkGrant gains credentials: Vec<EgressCredential>, each binding an endpoint to a secret_ref handle — a name the boundary resolves in its own keystore, never key material.

A compromised or hallucinating agent makes authenticated calls without ever holding the key, so it cannot read, log, or exfiltrate what it was never given. The grant — and any receipt over it — carries no secret.

Two invariants, both enforced and tested:

  • injection ⊆ egress. credential_for() gates on may_egress() first, so even a malformed grant (a credential for a non-allowlisted endpoint) cannot cause an out-of-policy injection. injection_within_egress() catches it at mint time too — defence in depth, tested from both directions.
  • reference, never value. Serialization asserts the handle is present and no key material is.

commitment() content-addresses the (endpoint, secret_ref, mode) decision so an attestation can record which credential was injected — auditable secret usage without exposing the secret.

Backward compatible: empty credentials is skip_serializing_if-omitted, so grants encode byte-identically and the committed capability vectors are untouched (test asserts the exact JSON).

The TX wedge: the incumbent has this isolation without an energy meter or attestation. Here it composes with S5 + RAPL + a signed joule receipt — isolation and accounting.

2. sandbox-jcp: a latent cross-workspace break, repaired

sandbox-jcp path-depends on jcp-schema but lives in a different cargo workspace, so when budget_funds (dual ceilings) and caveats (§4.9) became required Grant fields, its literal broke — and neither jcp-workspace CI nor a cached local build caught it. A clean checkout would not compile. Found only because this branch forced a cold build.

36 suites green, clippy clean, full sandbox workspace compiles on current main.

Re-land of #33. Its merge produced commit `9708f203` but **main's ref never advanced to it** — a concurrent ref-update race on an actively-merged branch — so the work was never on main while the PR still reports `merged=true`. Confirmed against the forge API, not a fetch. Same two commits, cherry-picked clean onto current main (`88b9d91a`, post-#34) and re-verified there. *(This is the documented main-clobber behaviour: a MERGED PR is not proof the work is on main. Verify the content, not the status.)* --- ### 1. Injected egress credentials — the secret never enters the unit Ingested from the pattern where a hosted agent runtime injects credentials **at the network boundary** so the sandboxed unit never holds them. `NetworkGrant` gains `credentials: Vec<EgressCredential>`, each binding an endpoint to a **`secret_ref` handle** — a name the boundary resolves in its own keystore, never key material. A compromised or hallucinating agent makes authenticated calls **without ever holding the key**, so it cannot read, log, or exfiltrate what it was never given. The grant — and any receipt over it — carries no secret. **Two invariants, both enforced and tested:** - **injection ⊆ egress.** `credential_for()` gates on `may_egress()` first, so even a *malformed* grant (a credential for a non-allowlisted endpoint) cannot cause an out-of-policy injection. `injection_within_egress()` catches it at mint time too — defence in depth, tested from both directions. - **reference, never value.** Serialization asserts the handle is present and no key material is. `commitment()` content-addresses the `(endpoint, secret_ref, mode)` decision so an attestation can record **which** credential was injected — auditable secret *usage* without exposing the secret. **Backward compatible:** empty `credentials` is `skip_serializing_if`-omitted, so grants encode byte-identically and the committed capability vectors are untouched (test asserts the exact JSON). **The TX wedge:** the incumbent has this isolation *without* an energy meter or attestation. Here it composes with S5 + RAPL + a signed joule receipt — isolation *and* accounting. ### 2. `sandbox-jcp`: a latent cross-workspace break, repaired `sandbox-jcp` path-depends on `jcp-schema` but lives in a **different cargo workspace**, so when `budget_funds` (dual ceilings) and `caveats` (§4.9) became required `Grant` fields, its literal broke — and neither jcp-workspace CI nor a cached local build caught it. A clean checkout would not compile. Found only because this branch forced a cold build. 36 suites green, clippy clean, full sandbox workspace compiles on current main.
sandbox-jcp path-depends on jcp-schema across a workspace boundary
(../../../../jcp/jcp-rs/crates/jcp-schema), and its Grant literal was missing
`budget_funds` and `caveats` — the two fields added to Grant by the dual-ceilings
and caveats work in the JCP workspace. Those changes swept every Grant
construction site inside the jcp workspace (the compiler forced it), but
sandbox-jcp lives in the SEPARATE sandbox workspace, so its literal went
unswept and a clean build of the sandbox workspace has been broken since.

It went unnoticed because the two workspaces are tested independently and a warm
target cache masked it in the sandbox tree. A fresh checkout surfaces it
immediately. Adds the two fields (both default: no money ceiling, no caveats).

Lesson: a required-field addition to a shared schema must be verified against
EVERY workspace that path-depends on it, not only the schema's own.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Ingests the credential-injection pattern the microVM-sandbox field converged on
(Notion's Workers run on Vercel Firecracker microVMs with network-level
credential injection), and adds the two things that pattern ships without: the
secret held only by REFERENCE, and an audit commitment — both bound into the
Sandbox standard's existing capability model.

A NetworkGrant may now carry `credentials: Vec<EgressCredential>`. Each names an
authorized egress `endpoint`, a `secret_ref` HANDLE the enforcement boundary
resolves in its own keystore OUTSIDE the sandbox, and an `Injection` mode
(Bearer / Header / Query / BasicAuth) describing how the resolved secret is
applied to the request. The unit makes authenticated calls without ever holding
the key: a compromised or hallucinating agent inside the sandbox cannot read,
log, or exfiltrate a credential it was never given. The grant — and any receipt
over it — carries only the reference, never the material.

Two invariants:
- injection ⊆ egress. `Enforcer::credential_for(endpoint)` returns a credential
  only when the endpoint is BOTH on the egress allowlist and has a declared
  credential, so injection can never reach further than egress even if the grant
  is malformed. `NetworkGrant::injection_within_egress()` flags such a grant at
  mint time; the enforcer is defensive regardless.
- reference, not value. `EgressCredential::commitment()` content-addresses the
  (endpoint, secret_ref, mode) decision, so an attestation can record WHICH
  credential was injected for WHICH call without exposing the secret — the audit
  primitive Notion/Vercel lack.

Backward compatible: `credentials` skips serialization when empty, so a grant
with none is byte-identical to one minted before the field existed. The
published sandbox conformance vectors are unchanged and still verify (empty
credential set omitted; asserted by test).

Five tests: injection only for authorized egress; injection can never exceed
egress even for a malformed grant; the reference is recorded while no secret
material ever appears in the encoding; commitments distinguish credentials
without the secret; empty sets are omitted. sandbox-capability clippy-clean; full
sandbox workspace green (one pre-existing warning in sandbox-conformance/vectors.rs,
unrelated and untouched).

Co-Authored-By: Claude Opus 4.8 <[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!36
No description provided.