fix(signing): structured-headers parser + bucketed replay store (#581, #582)#596
Merged
Conversation
…#582) #581: swap the hand-rolled Signature-Input / Signature / Content-Digest parsers for the maintained `structured-headers` library. AdCP-profile checks (required params, tag match, alg allowlist, quoted-string typing for nonce/keyid/alg/tag, integer typing for created/expires) stay in this package as thin typed wrappers. Signature byte-sequence values remain base64url-tolerant (normalised before handing to the strict parser); Content-Digest keeps its regex fallback so a malformed filler member (truncated sha-512) does not mask the sha-256 entry we verify against. ~90 lines of bespoke state-machine code removed. #582: time-bucket the in-memory replay store so has() / insert() / isCapHit() stay O(1) amortized on a hot keyid pinned near the per-keyid cap. Entries group by floor(expiresAt / bucketSizeSeconds) (default 60s); whole buckets evict in one step when their latest expiry has passed, eliminating the per-call O(N) filter sweep that turned a near-cap keyid into a quadratic DoS target. Default maxEntriesPerKeyid drops 1,000,000 → 100,000 (still ample; configurable). ReplayStore interface unchanged. All 28 conformance vectors + review-hardening suite pass. Perf suite in test/request-signing-replay-perf.test.js asserts sub-linear growth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
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.
Closes #581 (parser swap). Closes #582 (replay store).
#581 — swap to
structured-headersHand-rolled
Signature-Input/Signature/Content-Digestparsers replaced with the maintainedstructured-headerslibrary (RFC 8941 / RFC 9651).src/lib/signing/parser.ts+content-digest.ts).tagmatch,algallowlist, quoted-string typing fornonce/keyid/alg/tag, integer typing forcreated/expires.-/_→+//only inside:…:delimiters before handing to the strict SF parser.Content-Digestkeeps a regex fallback so a malformed filler member (e.g. truncatedsha-512) does not mask thesha-256entry we actually verify against.signatureParamsValueis nowserializeInnerList(entry)instead of raw substring splicing — verified byte-identical on the reordered-params review-hardening vector.\"escape sequence, the library decodes to"). The one escape-handling test assertion updated to match correct behaviour.#582 — time-bucketed replay store
InMemoryReplayStorenow groups entries byfloor(expiresAt / bucketSizeSeconds)(default 60s). Whole buckets evict in one step when their latest expiry has passed, eliminating the per-call O(N) filter sweep that turned a near-cap keyid into a quadratic DoS target.has()/insert()/isCapHit()are O(1) amortized per keyid regardless of entries-per-keyid.maxEntriesPerKeyid: 1,000,000 → 100,000 (still ample;new InMemoryReplayStore({ maxEntriesPerKeyid })overrides).ReplayStoreinterface unchanged.Test plan
test/request-signing-review-fixes.test.jspasses (with 2 assertion tweaks for the correct RFC 8941 escape-decoding + library-surfaced parse-error messages; intent preserved, + a newcreated=1.5integer-rejection assertion)test/request-signing-e2e.test.jspassestest/request-signing-replay-perf.test.jsasserts sub-linear (< 4×) growth ofhas()/insert()latency when entries-per-keyid grows 50×, plus a bucket-eviction correctness checknpm testgreen (3601 passing)patchentries)🤖 Generated with Claude Code