fix(conformance): make the gate actually gate - #14
Open
tomjwxf wants to merge 1 commit into
Open
Conversation
Addresses the first, third and fifth findings in #13, reported by @arian-gogani. Verified each against a clean clone before changing anything. run.sh had no exit after its loop, so its status was that of the last echo. CI has been green while every implementation was reported NON-CONFORMANT. It now tracks failures and returns them, and treats a run that verified nothing as a failure rather than a pass, since an empty run proves nothing. Check 2 never passed a key, so the verifier exited with no_public_key and this script reported it as "one or more signatures failed (exit 1 = tampered)". That conflates "this receipt was altered" with "I was not told what to trust". It now passes the published fixture key, overridable via CONFORMANCE_KEY. Check 3 computed the expected parent digest and discarded it, accepting any non-empty parent_receipt_hash, and expected/chain.jsonl and the fixtures' expected_decision were read by no code in the repository. An implementation could ignore the Cedar policy, emit four correctly signed receipts with arbitrary decisions, and be reported conformant. Check 3 now compares each receipt against expected/chain.jsonl on tool_name, decision and policy_id, compares the parent hash against the predecessor digest as the original comment intended, and cross-checks chain.jsonl against the fixtures' expected_decision so the two sources cannot drift apart unnoticed. It moves to conformance/check_chain.py so it can be read and run on its own. Demonstrated on a receipt set that allows the destructive Bash fixture the policy forbids: old check 3 exits 0, new check 3 exits 1. Same for a constant parent hash. run.sh now exits 1 on the current tree, where it exited 0. Adds a .gitignore for receipts/, which run.sh writes into and which can contain node compile caches. Not included: the receipt-shape decision from finding 4. The schema rejects this repository's own reference receipts, and which shape wins is a call for the maintainer rather than something to slip into a fix PR.
This was referenced Aug 29, 2026
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.
Addresses findings 1, 3 and 5 from #13, reported by @arian-gogani. He was right on every one I checked, and I verified each against a clean clone before changing anything.
Deliberately does not include the receipt-shape decision from finding 4. More on that below.
What was broken
The gate did not gate.
run.shhad noexitafter its loop, so the script's status was that of the lastecho. CI has been green this whole time while every implementation was reported NON-CONFORMANT. On the current tree it now exits 1:That is the real state of the suite, and it should have been visible months ago.
Check 2 could not tell a bad signature from a missing key. It never passed
--key, so the verifier exited withno_public_keyand this script reported "one or more signatures failed (exit 1 = tampered)". Those are completely different problems. It now passes the published fixture key, overridable viaCONFORMANCE_KEY.Check 3 checked almost nothing. It computed the expected parent digest and then discarded it, accepting any non-empty
parent_receipt_hash. Meanwhileexpected/chain.jsonland the fixtures'expected_decisionwere read by no code anywhere in the repository, despite the README promising the first.The consequence, which is the part that matters: an implementation could ignore the Cedar policy entirely, emit four correctly signed receipts with arbitrary decisions, and be reported conformant.
What check 3 does now
Compares each receipt against
expected/chain.jsonlontool_name,decisionandpolicy_id. Compares the parent hash against the predecessor digest, accepting a prefix so truncating implementations still pass, which is what the original comment intended before the computed value was dropped on the floor. And cross-checkschain.jsonlagainst the fixtures'expected_decision, so if those two sources ever disagree the suite says so instead of silently picking one.Moved to
conformance/check_chain.pyso it can be read and run on its own.Demonstrated, not asserted
On a receipt set that allows the destructive
rm -rf /fixture the policy forbids:And on a set using a constant string for every parent hash:
Also
A
.gitignoreforreceipts/.run.shwrites there, it is untracked, and it can contain node compile caches. I nearly committed several thousand of them while testing this.Not in this PR
Finding 4, that check 1 and check 2 accept disjoint sets of receipts, is real: the schema requires
pubkeyandpayload.type, while this repository's own reference receipts inaps-gateway-enforcement/carrykidand a top-leveltype: decision_receipt, so the schema rejects the receipts we ship.That needs a decision about which shape the suite accepts rather than a quiet edit inside a fix PR. Answering it in #12 so the second implementation is not blocked on it.
Finding 2, that the reference driver calls
protect-mcp evaluateandprotect-mcp sign, is also real. There is nosignsubcommand in the CLI at all. Separate fix.