Skip to content

fix: address 5 audit findings (receipt binding, pin handling, strict DoS bound, CI pinning)#9

Merged
EulBite merged 2 commits into
mainfrom
fix/audit-findings
Jun 1, 2026
Merged

fix: address 5 audit findings (receipt binding, pin handling, strict DoS bound, CI pinning)#9
EulBite merged 2 commits into
mainfrom
fix/audit-findings

Conversation

@EulBite

@EulBite EulBite commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

Five reported findings, each verified by a concrete reproduction before fixing. None change canonical output or the demo WAL chain root, so existing signatures and cross-language vectors still verify.

Findings and fixes (in priority order)

F1 - Receipts not bound to their entry (high)

A genuine, validly signed server receipt issued for event A verified Ok when copied verbatim onto a different entry B, because verify_receipt_signature only checked the receipt's own signed fields and never compared them to the entry. The receipt is also outside the chain hash, so the staple was undetectable. This defeated the receipt layer's "the server attested to THIS event" guarantee.

Fix: after the signature check, bind the receipt to the entry (payload_hash, plus event_id when the entry declares one). A mismatch is ReceiptError::EntryMismatch, surfaced by the lenient verifier as receipt_entry_mismatch (so valid becomes false). The module doc, which previously overstated the protection, is corrected.

F3 - Malformed --trusted-pubkey failed open (medium)

A fat-fingered pin made the lenient core warn and silently fall back to record-declared keys, returning valid=true / exit 0. The strict path already rejected the same input.

Fix: the CLI validates the pin (64 hex, 32 bytes, optional 0x) up front and returns a usage error (exit 2) on a malformed value, consistent with strict.

F2 - Pin did not require records to be signed (low)

With a pin set, the lenient verifier still accepted unsigned (None, None) records, contradicting the --trusted-pubkey help text ("MUST have signed every record").

Fix: when a pin is set, an unsigned record is flagged as unsigned_record. The help text is corrected to describe the exact behavior (and notes that --strict is the all-signed profile).

F4 - Strict DoS caps applied after parse (low)

MAX_RECORDS_DEMO / MAX_PAYLOAD_BYTES only bounded the result of parsing: a single oversized line was fully parsed (and its payload canonicalized) before the payload cap could reject it.

Fix: a new public MAX_LINE_BYTES (16x the payload cap) is checked before parsing each line, rejecting an oversized line as LineTooLarge without parsing it. The MAX_PAYLOAD_BYTES doc no longer claims to bound parse cost.

F5 - CI installed wasm-pack via curl | sh (low)

Both jobs piped a live, version-unpinned remote script into sh.

Fix: cargo install wasm-pack --locked --version 0.14.0, a reproducible checksummed install from crates.io (verified it compiles cleanly).

Tests and verification

  • New regression tests: receipt entry-binding (match, payload_hash mismatch, event_id mismatch), pinned-unsigned rejection, malformed-pin usage error, oversized-line rejection.
  • cargo fmt --all --check clean; cargo clippy --workspace --all-targets -- -D warnings clean.
  • cargo test --workspace --all-features: all pass (97 core, 22 CLI smoke, 7 cross-language vectors, 4 parity, 3 wasm).
  • wasm-pack build, demo-seeder, and the Node integration test all pass; demo chain root unchanged.

EulBite added 2 commits June 1, 2026 04:29
Verified reproductions for each. None change canonical output or the demo
chain root, so existing signatures and cross-language vectors still verify.

F1 (receipt replay, high): verify_receipt_signature now binds a receipt to
the entry it is attached to (payload_hash, plus event_id when the entry
declares one) after the signature check. Previously a genuine, validly
signed receipt issued for one event verified Ok when copied verbatim onto
a different entry, because verification only checked the receipt's own
fields. Now surfaces as ReceiptError::EntryMismatch / receipt_entry_mismatch.

F3 (fail-open pin, medium): the CLI rejects a malformed --trusted-pubkey as
a usage error (exit 2) instead of letting the core silently fall back to
record-declared keys with valid=true.

F2 (pin vs unsigned, low): when a trusted pubkey is pinned, the lenient
verifier flags an unsigned record as unsigned_record instead of passing it,
matching the documented "must have signed every record" contract; the
--trusted-pubkey help text is corrected accordingly.

F4 (strict DoS bound, low): the strict verifier rejects a line longer than
MAX_LINE_BYTES (16x the payload cap) BEFORE parsing it, so a single
oversized line cannot force an unbounded parse or canonicalization. The
MAX_PAYLOAD_BYTES doc no longer overstates that it bounds parse cost.

Adds a regression test for each.
Both jobs installed wasm-pack via `curl ... init.sh | sh`, executing a
live, version-unpinned remote script on every run. Replace with
`cargo install wasm-pack --locked --version 0.14.0`, a reproducible
checksummed install from crates.io. Verified it compiles cleanly.
@EulBite EulBite merged commit 2cb235a into main Jun 1, 2026
3 checks passed
@EulBite EulBite deleted the fix/audit-findings branch June 1, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant