Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #266Other

fix(receipt): refuse receipts over bundles that fail verification

One of nine independent PRs from a correctness audit of main at df0b5f1. Each merges cleanly with the others in any order; together they pass 908 tests locally on Python 3.11 and 3.14 (CI covers 3.11 and 3.12).

Author

JamesCarnley avatar

@JamesCarnley

github profile →
Status
Merged
Merged on
Sep 14
Branch
fix/receipt-bundle-integrity

Description

The pitch.

One of nine independent PRs from a correctness audit of `main` at `df0b5f1`. Each merges cleanly with the others in any order; together they pass 908 tests locally on Python 3.11 and 3.14 (CI covers 3.11 and 3.12).

Commits: `df8aa6c`, `2ea5adb`, `ce0a715`, `b2e5dbc`, `efea868`

### Problem

`nandatown receipt` signed claims copied from `manifest.json` without verifying
the bundle. `verify-receipt --bundle` compared only against those manifest
values. So a bundle edited in place after its receipt, or a failed result
relabelled "passed" without rehashing, still printed "receipt verifies … and the
bundle matches" or received a signed passing receipt, with exit 0. `proof`
already refused both.

### Change

- Receipt creation and bundle-aware receipt verification now run the `verify`
  checks first. They refuse, naming each problem, on:
  - record, hash, manifest or fingerprint problems;
  - cross-record binding problems;
  - an unsupported evaluator;
  - an evaluator replay mismatch;
  - an attestation failure.
- The one exception is a bundle recorded by an evaluator version that actually
  shipped earlier for that mode. The versions are listed in
  `SHIPPED_EVALUATOR_VERSIONS` in `bundle.py`, taken from git history. Every
  other check still runs, and the command output says
  `evaluator replay not checked: bundle X, local Y`. That line is not part of
  the signed receipt.
- A made-up or future evaluator version is refused with
  `unrecognised evaluator version …; replay not possible`.
- The exception is marked by a typed `EvaluatorVersionDiffers` problem, not by
  string matching.

### Compatibility

- `verify_bundle` output is unchanged for `proof`, `mirror`, `recover`, `board`
  and `verify`.
- The receipt payload is unchanged.
- `verify-receipt` without `--bundle` still checks only shape and signature.
- Partial and failed receipts over intact bundles still work.
- Receipts are no longer issued for bundles that fail current-evaluator replay.
  The existing proof-integrity test expects that refusal at receipt time. It
  also still shows that `proof` refuses a validly hand-signed receipt over a
  replay-mismatched bundle.
- **Remaining limit:** someone can still relabel a bundle with a genuinely old
  version string and get a receipt carrying the disclosure. This is by design,
  so honest historical bundles keep getting receipts; closing it would require
  replaying under old evaluators.
- **Merge note:** if `fix/track-response-correlation` also merges, add Track
  `0.3.0` to `SHIPPED_EVALUATOR_VERSIONS` when Track moves past it. If that is
  missed, those bundles are refused, not wrongly accepted.

### Verification

- Full suite on the branch: 835 passed.
- An adversarial reviewer ran 27 integrity attacks per bundle over Lab, Track
  and Path bundles. All are refused, and no honest bundle is refused.
- The made-up-version attack was also run through the real CLI: refused, exit
  1, no receipt written.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Try it

Open PR on GitHubView diff

Checkout locally

git fetch origin pull/266/head:pr-266
git checkout pr-266