security: require explicit trust roots across delegation verification - #132
Conversation
zohebk8s
left a comment
There was a problem hiding this comment.
Approving. Requiring an explicit trust set and failing closed on an empty one is the right shape, and the core property holds.
One thing worth changing: the root check now runs after the full signature and structural loop rather than before it. I ran the same three chains through verify_chain on current main and on this branch, each time with a trust set that does not contain the chain root:
main this branch
well formed chain UntrustedDelegationRoot UntrustedDelegationRoot
scope escalation at hop 1 UntrustedDelegationRoot InvalidCredential
forged signature at hop 1 UntrustedDelegationRoot InvalidCredential
An untrusted caller can now tell "my forged chain is malformed" apart from "my chain is fine but you do not trust my root", which makes the verifier a debugging oracle for chains it will never accept. Counting verify_signature calls before rejection, for an untrusted root:
hops main this branch
1 0 1
4 0 4
8 0 8
Callers who benefit from the precise diagnostics already hold a trusted root, so moving the check back to the top costs them nothing. The only behaviour it changes is for an untrusted root.
Three smaller notes. verify-dag declares --trusted-root-issuer with default=[] while its help says it is required with --chain, and nothing enforces it: verify-chain without the flag is rejected by argparse, but verify-dag --dag ... --chain ... parses and then returns UNTRUSTED_DELEGATION_ROOT, reporting a trust decision for what is a missing argument. And the conformance profile passes trusted_root_issuers={chain[0].issuer}, so it never asserts that an untrusted root is refused, which seems worth pinning there given why this PR exists.
And AgentManifestBinding.verification_scope is only ever set to its default and only read by an assertion in test_agent_manifest_cose.py, so it records the signature-and-identity limit without enforcing it. Is something downstream meant to branch on it?
Suite is green on my checkout, 526 passed and 2 skipped.
Needs a rebase, GitHub reports merge conflicts against main. Also worth noting this cannot install until agent-manifest 0.11.1 is on PyPI, so the release is gated on agentrust-io/agent-manifest#331.
# Conflicts: # CHANGELOG.md
Signed-off-by: Imran Siddique <45405841+imran-siddique@users.noreply.github.com>
65bddb6 to
6bd0319
Compare
…top it dying on cp1252 (#140) 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. Claude-Session: https://claude.ai/code/session_01P1p7BCjLPmLCw4VcBpcF8J Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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>
Summary
Fixes the trust-root verification gap disclosed in GHSA-95qh-qjph-8622.
Evidence
Security
This PR is intentionally public only after publication of GHSA-95qh-qjph-8622. The advisory contains impact and affected-version details.