feat(attestation): appraise SEV-SNP PLATFORM_INFO against an explicit policy - #139
Merged
Merged
Conversation
… policy
The four checks in verify_sev_snp_report establish that a report is authentic
and which workload it describes: signature algorithm, VCEK-to-ARK chain, report
signature, and measurement binding. None of them ask what kind of machine the
report came from, so a report from a host with SMT enabled and the firmware DRAM
alias check never completed verified exactly as cleanly as one from a host with
neither condition. LIMITATIONS.md said so and pointed at agent-manifest, which
has been able to appraise these fields since 2026-08-20. cA2A just never called
it.
SevSnpReport.platform_info decodes the bitfield at offset 0x40, re-using the
shared SNP_OFFSETS table rather than hardcoding it. verify_sev_snp_report takes
require_platform, forbid_platform and reject_unrecognized_platform_bits and
delegates to agent_manifest.appraise_platform_info, so the semantics live in one
place instead of two. A failure raises AttestationFailed carrying the raw
platform_info value, so a rejection says which host state caused it.
The direction lives in the argument name rather than the field name. That is
the whole point of the signature: forbid_platform={"smt_enabled"} demands SMT be
off and cannot be misread as demanding it be on, which is the failure mode
google/go-sev-guest#195 describes in the reference verifier's single-struct
"maximum of acceptable PLATFORM_INFO" policy that enforces four of its seven
fields as minimums.
Appraisal is opt in and appraises nothing when no policy is passed, so existing
callers are unaffected. Five tests cover decode, the no-policy default, a
missing required bit, a present forbidden bit, and a satisfied policy.
545 passed, 2 skipped. ruff and ruff format clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P1p7BCjLPmLCw4VcBpcF8J
imran-siddique
added a commit
that referenced
this pull request
Aug 25, 2026
* fix(examples): pin the trusted root in the cross-operator demo, and stop it dying on cp1252 Two bugs, both of which made `demo.py` fail before it reached its own point. **The trust root was only half-threaded through.** #132 made `verify_chain` fail closed when no trusted root issuer is supplied, which is right: a self-consistent chain minted by an attacker passes every signature and attenuation check, so the pinned anchor is what makes the call authorization rather than structure checking. That change updated the two CLI invocations at the end of this demo to pass `--trusted-root-issuer`, and missed all three in-process call sites. The demo died at step 4 with `UntrustedDelegationRoot`, which reads like the example is wrong about delegation when the example was simply not passing the root. `effective_scope` and both `enforce_peer_call` sites now pass `trusted_root_issuers=[chain[0].issuer]`, with a comment saying why the anchor is load-bearing rather than ceremony. **The demo crashed on a default Windows console.** It prints the set-intersection sign when reporting scope n policy, and cp1252 cannot encode it, so a run on stock Windows died with a UnicodeEncodeError partway through the checks. stdout and stderr are reconfigured to UTF-8 rather than downgrading the notation, since the intersection is the clearest way to write what that step does. 12/12 checks now pass on a default Windows console, including the sealed task, the binary-swap rejection, and the offline provenance-DAG verification. The regenerated chain.json and dag.json are deliberately not included: the demo mints fresh keys each run, so committing them is pure churn. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1p7BCjLPmLCw4VcBpcF8J * docs: record SEV-SNP and TDX collection on real silicon, and what the host's PLATFORM_INFO said The collection table in hardware-validation.md recorded both configfs-TSM collectors as never run on real silicon: exercised against a simulated configfs tree and synthetic reports, so code that should work rather than a validated capability. Both have now been run. Intel TDX on a GCP c3-standard-4 (kernel 6.17, tdx: Guest detected) produced an 8000-byte DCAP v4 quote whose REPORTDATA matched the derived binding, and verify_tdx_quote appraised it to the Intel SGX Root CA committed in the fixtures. AMD SEV-SNP on a GCP n2d-standard-4 pinned to Milan produced a 1184-byte report, with auxblob carrying the AMD certificate table: VCEK, SEV-Milan, and the self-signed ARK-Milan. verify_sev_snp_report appraised it to that ARK and failed closed on a flipped bit in the signed body. Both quotes were produced by this codebase's own providers and appraised by its own verifiers, which is the loop the "Not yet validated" entries asked for. The captures are not committed: a report's 64-byte CHIP_ID is a per-CPU identifier. The SNP capture was also the first chance to point the PLATFORM_INFO appraisal added in #139 at a real cloud host. It reported 0x25: SMT enabled, ECC enabled, DRAM alias check complete. So require_platform={"alias_check_complete"} is accepted on GCP, and forbid_platform={"smt_enabled"} is rejected there. Neither is a defect in cA2A or in GCP. The point is that before that appraisal existed this report verified exactly as cleanly as one from a host with SMT off, because a signature, a chain and a measurement say which workload ran and never what the host was doing while it ran. One reproduction detail worth writing down, because it cost a failed run: cert_chain.pem must carry ASK then ARK only. The test prepends the VCEK itself, so including it in the PEM duplicates the leaf and chain verification fails. 545 passed, 2 skipped. The 2 skips are the gated hardware tests, which pass when pointed at the captures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P1p7BCjLPmLCw4VcBpcF8J --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the
Platform state is not appraisedgap inLIMITATIONS.mdon the SEV-SNP path.SevSnpReport.platform_infodecodes the bitfield at offset0x40, re-using the sharedSNP_OFFSETStable rather than hardcoding the offset.verify_sev_snp_reportgainsrequire_platform,forbid_platformandreject_unrecognized_platform_bits, delegating toagent_manifest.appraise_platform_infoso the semantics live in one place instead of two.Why
The four existing checks establish that a report is authentic and which workload it describes. None of them ask what the host was doing while it ran. A report from a machine with SMT enabled and the firmware DRAM alias check never completed verified exactly as cleanly as one from a machine with neither condition.
agent-manifesthas been able to appraise these fields since 2026-08-20; cA2A simply never called it.The signature is the point
The direction lives in the argument name, never the field name:
forbid_platform={"smt_enabled"}demands SMT be off and cannot be misread as demanding it be on. That is the failure mode google/go-sev-guest#195 describes in the reference verifier, whose singleSnpPlatformInfostruct is documented as "the maximum of acceptable PLATFORM_INFO data" while enforcing four of its seven fields as minimums.A rejection raises
AttestationFailedcarrying the rawplatform_infovalue, so it says which host state caused it.Compatibility
Opt in, and appraises nothing by default. No existing caller changes behaviour. The attested-peer and sealed-channel paths deliberately do not set a policy: cA2A supplies the mechanism and the vocabulary, the deployment picks the policy.
LIMITATIONS.mdnow says exactly that rather than saying we cannot do it at all.Tests
Five new cases: decode from the report, the no-policy default still admitting an SMT-enabled host, a missing required bit rejected, a present forbidden bit rejected, and a satisfied policy passing.
The 2 skips are the hardware-capture tests (
CA2A_SNP_FIXTURE_DIR,CA2A_TDX_QUOTE), unchanged by this PR.