test(webhooks): add unit tests for verifyWebhookSignature timing-safe comparison - #674
Merged
Chucks1093 merged 1 commit intoJul 27, 2026
Conversation
… comparison Adds the verifyWebhookSignature helper (HMAC-SHA256, constant-time comparison via crypto.timingSafeEqual) and unit test coverage for its timing-safe comparison behavior, closing accesslayerorg#666: - Signature differing in the last character returns false - Signature differing in the first character returns false - Signature one character shorter than expected returns false without throwing - Signature one character longer than expected returns false without throwing - Valid signature returns true - Malformed/empty header returns false without throwing
|
@Ajibose Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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 #666
Summary
Adds unit test coverage for the timing-safe signature comparison performed by
verifyWebhookSignature, and adds the helper itself (HMAC-SHA256 verification of asha256=<hex>webhook signature header usingcrypto.timingSafeEqual), since it did not yet exist in the codebase.Files
New files
src/modules/webhooks/webhook-signature.utils.ts—verifyWebhookSignature(payload, header, secret)helper. Parses thesha256=<hex>header format, computes the expected HMAC-SHA256 digest of the raw payload with the given secret, and compares it to the provided signature usingcrypto.timingSafeEqualfor constant-time comparison. Returnsfalse(rather than throwing) for missing headers, malformed headers, and length-mismatched signatures — the length check happens beforetimingSafeEqualis called, since that API throws on differing buffer lengths.src/modules/webhooks/webhook-signature.utils.test.ts— unit tests for the helper (see below).Modified files
Implementation details
sha256=<hex-digest>, matched with/^sha256=([0-9a-f]+)$/i.crypto.createHmac('sha256', secret).update(payload).digest('hex').false, avoiding theRangeErrorthatcrypto.timingSafeEqualthrows when given buffers of unequal length.crypto.timingSafeEqual, which performs the actual constant-time comparison.Tests added
All tests live in
webhook-signature.utils.test.tsand use a real HMAC-SHA256 signature computed from a fixed test secret/payload as the baseline "valid" signature:returns true for a valid signature— sanity check that a correctly computed signature verifies.returns false when the signature differs in the last character— flips the final hex character of a valid signature and confirms it's rejected.returns false when the signature differs in the first character— flips the first hex character after thesha256=prefix and confirms it's rejected.returns false without throwing when the signature is one character shorter than expected— truncates the last character of a valid signature; asserts it neither throws nor verifies.returns false without throwing when the signature is one character longer than expected— appends an extra character to a valid signature; asserts it neither throws nor verifies.returns false without throwing for a malformed header— a header that doesn't match thesha256=<hex>pattern at all.returns false for an empty header— empty string input.How to test
All 7 tests pass. Existing test suites are unaffected (no existing files were modified).