Skip to content

feat(adapters): TraceSandboxAdapter for sandboxed agent runtimes - #130

Merged
imran-siddique merged 1 commit into
mainfrom
feat/sandbox-runtime-adapter
Aug 8, 2026
Merged

feat(adapters): TraceSandboxAdapter for sandboxed agent runtimes#130
imran-siddique merged 1 commit into
mainfrom
feat/sandbox-runtime-adapter

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

What this changes

Adds TraceSandboxAdapter, a first-party adapter that builds Trust Records from a sandboxed agent runtime: the class of runtime that confines one agent on one machine with kernel isolation (Landlock, seccomp), an egress policy, and injected credentials.

That confinement answers what a single agent may touch. It does not answer, on its own:

  • across the estate — which agent, on which of two hundred machines, took an action, under whose authority
  • from a regulator — not what the policy said, but what actually ran, evidenced
  • from a sovereign or on-prem buyer — the same answer on a TPM host, a confidential VM, or a machine with no secure hardware

The adapter answers all three from what the runtime already has at session close. No change to the runtime is required, and no schema change was needed.

Design notes

One code path spans Level 0 and Level 1. TraceAGTAdapter hardcodes software-only. This adapter takes an optional SandboxAttestation; supplying it moves the record to the attested platform and nothing else about the call changes. A sandbox runs wherever the customer runs it, and the deployments that most need evidence often have the least hardware. An adapter that could only emit Level 0 would force a second code path for exactly those.

A caller cannot claim hardware it does not have. platform is only ever set from a supplied attestation; an attestation may not name software-only; the platform is checked against get_args(RuntimeInfo.model_fields["platform"].annotation) rather than a hand-maintained copy, with a test guarding the drift; and the measurement must be a sha256:/sha384: digest. A record reading tpm2 therefore carries a measurement something other than this process produced.

Two defaults differ from the AGT adapter, deliberately.

  • appraisal.status defaults to "none", not "affirming". Building a record does not appraise it, and affirming would put a verdict in the field a consumer reads to find out whether anybody checked.
  • transparency defaults to None and is omitted. That is what an unanchored record should say below Level 2.

tool_transcript.hash uses RFC 8785, not json.dumps(sort_keys=True). The two agree on ASCII and diverge on non-ASCII strings and number formatting. A decision log carries paths and hostnames, and sign.py already canonicalises the signature pre-image with JCS for the reason its docstring gives. A test asserts the divergence on non-ASCII input so the choice is not silently reverted.

Sandbox identity and image ride the existing subject and build_provenance.digest. A dedicated sandbox object belongs in a later profile.

Type of change

  • Editorial (typo, link fix, clarification — no normative effect)
  • Non-breaking spec change (new optional field, new platform profile, informative addition)
  • Breaking spec change (requires 14-day comment period and Project Lead sign-off)
  • Schema change
  • Example addition

Additive only. No normative text, no schema field, no existing behaviour altered.

Spec section

None modified. docs/integration/sandbox-runtime.md is a new informative integration guide alongside agt.md and cmcp.md.

Testing

43 new tests, 148 passing overall. Coverage includes: structural validity and sign/verify round trip; a tampered record failing verification; the Level 0 measurement being reproducible from its two named inputs and moving when either changes; attestation fields reaching the record verbatim; every rejection path for a dishonest platform or measurement claim; the platform-enum drift guard; JCS vs naive-sorted-JSON divergence on non-ASCII; key-order stability; determinism.

Two pre-existing issues surfaced, not introduced

  1. transparency model/schema divergence. 0.5.1 made transparency optional on the model because an unanchored record has no receipt to name, but schema/trace-claim.json still lists it in required. An unanchored record therefore validates against the model and not against the published schema. test_unanchored_record_is_model_valid_but_the_json_schema_still_requires_transparency documents this and will fail loudly the day it is reconciled, rather than being rediscovered downstream. Happy to split the schema fix into its own PR.

  2. ruff check src reports UP038 in sign.py:356 on main, unrelated to this change. Left alone to keep this PR additive.

Checklist

  • DCO sign-off on all commits (git commit -s)
  • CHANGELOG.md updated
  • Breaking changes marked — n/a
  • Backward compatibility statement — n/a, additive only
  • examples/sandbox-runtime.json generated by the adapter and validated against schema/trace-claim.json
  • ruff and mypy clean on all changed files
  • mkdocs nav updated

🤖 Generated with Claude Code

A kernel sandbox confines one agent on one machine. It does not answer,
on its own, which agent on which of two hundred machines took an action,
what actually ran rather than what the policy said, or how to say either
on a host with no secure hardware.

TraceSandboxAdapter builds a Trust Record from what such a runtime
already has at session close: sandbox identity, image digest, the
effective policy bundle bytes, and the decision log. It requires no
change to the runtime.

One code path spans Level 0 and Level 1. Passing a SandboxAttestation
moves the record from software-only to the attested platform and nothing
else about the call changes, because a sandbox runs wherever the customer
runs it and the deployments that most need evidence often have the least
hardware.

A caller cannot claim hardware it does not have: platform is only ever
set from a supplied attestation, an attestation may not name
software-only, the platform is validated against the enum on RuntimeInfo
rather than a copy of it, and the measurement must be a digest.

Sandbox identity and image ride the existing subject and
build_provenance.digest, so no schema change was needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Imran Siddique <imran.siddique@opaque.co>
@imran-siddique
imran-siddique merged commit df64cfd into main Aug 8, 2026
8 checks passed
@imran-siddique
imran-siddique deleted the feat/sandbox-runtime-adapter branch August 8, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant