Summary
VerificationReceipt.get_signature() is presented as "cryptographic proof of verification… required for regulatory compliance (SEC, OCC, FinCEN)" but is an unkeyed SHA-256 over only 5 of 15 fields — computed_value, llm_value, difference, status, violations, proof_steps, formula_used, metadata are all outside it. Receipts can be tampered in place, or regenerated wholesale by anyone.
Found in the 2026-08-22 adversarial audit of v2.1.0 @ 26d6dd0. This was S-09 in closed #16 (filed P3); elevated to P1 because receipts are the only evidence artifact this package emits and they feed CrossGuard/UCP audit trails marketed as regulator-facing. Input on the format decision already posted on #37.
Location
qwed_finance/models/receipt.py:99-111 — signed payload = {receipt_id, timestamp, input_hash, verified, engine_used} only.
Evidence (CONFIRMED BY EXECUTION)
After calling get_signature():
- mutate
rec.computed_value ($100.00 → $1.00)
- append fake entries to
rec.violations and rec.proof_steps
- flip
rec.status to REJECTED
→ get_signature() returns the identical hash. None of those mutations are detected.
Impact
Any holder of a receipt (including an attacker who modified it) can alter the verified value, violations, proof steps, and status without invalidating the signature. For the stated use cases (regulator submission, dispute resolution) this is worse than no signature — it creates a false sense of tamper-evidence.
Fix
Minimum stopgap: HMAC-SHA256 over the full canonical-JSON receipt (all fields), key held by the verifier instance.
Target state (per #37): align with qwed-verification's ES256 attestation so receipts carry issuer identity and third-party verifiability — one envelope across the ecosystem (ties into the VC v1.1 signature/issuer decision, see QWED-AI/qwed-verification#319).
Until either lands, soften the docstring claims.
Summary
VerificationReceipt.get_signature()is presented as "cryptographic proof of verification… required for regulatory compliance (SEC, OCC, FinCEN)" but is an unkeyed SHA-256 over only 5 of 15 fields —computed_value,llm_value,difference,status,violations,proof_steps,formula_used,metadataare all outside it. Receipts can be tampered in place, or regenerated wholesale by anyone.Found in the 2026-08-22 adversarial audit of v2.1.0 @
26d6dd0. This was S-09 in closed #16 (filed P3); elevated to P1 because receipts are the only evidence artifact this package emits and they feed CrossGuard/UCP audit trails marketed as regulator-facing. Input on the format decision already posted on #37.Location
qwed_finance/models/receipt.py:99-111— signed payload ={receipt_id, timestamp, input_hash, verified, engine_used}only.Evidence (CONFIRMED BY EXECUTION)
After calling
get_signature():rec.computed_value($100.00 → $1.00)rec.violationsandrec.proof_stepsrec.statusto REJECTED→
get_signature()returns the identical hash. None of those mutations are detected.Impact
Any holder of a receipt (including an attacker who modified it) can alter the verified value, violations, proof steps, and status without invalidating the signature. For the stated use cases (regulator submission, dispute resolution) this is worse than no signature — it creates a false sense of tamper-evidence.
Fix
Minimum stopgap: HMAC-SHA256 over the full canonical-JSON receipt (all fields), key held by the verifier instance.
Target state (per #37): align with qwed-verification's ES256 attestation so receipts carry issuer identity and third-party verifiability — one envelope across the ecosystem (ties into the VC v1.1 signature/issuer decision, see QWED-AI/qwed-verification#319).
Until either lands, soften the docstring claims.