Summary
verifyWebhook in @inkbox/sdk calls Node's timingSafeEqual with no length check. When X-Inkbox-Signature starts with sha256= but the hex digest is not exactly 64 characters, verification throws RangeError: Input buffers must have the same byte length instead of returning false.
Python (hmac.compare_digest) and Rust (explicit length guard) already return false for unequal lengths. The TypeScript path does not.
A webhook receiver that trusts verifyWebhook to return a boolean and does not wrap it in try/catch will 500 on malformed or attack traffic instead of rejecting with 403.
Reproduction
import { verifyWebhook } from "@inkbox/sdk";
verifyWebhook({
payload: Buffer.from('{"event":"message.received"}'),
headers: {
"x-inkbox-signature": "sha256=abcd", // truncated
"x-inkbox-request-id": "req-1",
"x-inkbox-timestamp": "1741737600",
},
secret: "test-signing-key",
});
// throws RangeError — should return false
Same throw for sha256=, odd-length digests, and digests longer than 64 hex chars. Wrong digests of the correct length already return false correctly.
Evidence in tree
// sdk/typescript/src/signing_keys.ts
const expected = createHmac("sha256", key).update(message).digest("hex");
const received = signature.slice("sha256=".length);
return timingSafeEqual(Buffer.from(expected), Buffer.from(received));
Rust already documents the divergence:
// sdk/rust/src/signing_keys.rs
// Differing lengths short-circuit to `false`
// (compare_digest never raises here, unlike TS `timingSafeEqual`).
if expected_bytes.len() != received_bytes.len() {
return Ok(false);
}
Existing TS tests only cover equal-length failures, so CI stays green.
Proposed fix
Guard length before timingSafeEqual and return false, matching Python/Rust. Add a regression test for a truncated sha256=… signature.
Impact
- Security-adjacent: auth helper must not throw on attacker-controlled header lengths.
- Affects any TS/CLI webhook receiver using
verifyWebhook (including cli and examples that call the shared helper).
Summary
verifyWebhookin@inkbox/sdkcalls Node'stimingSafeEqualwith no length check. WhenX-Inkbox-Signaturestarts withsha256=but the hex digest is not exactly 64 characters, verification throwsRangeError: Input buffers must have the same byte lengthinstead of returningfalse.Python (
hmac.compare_digest) and Rust (explicit length guard) already returnfalsefor unequal lengths. The TypeScript path does not.A webhook receiver that trusts
verifyWebhookto return a boolean and does not wrap it intry/catchwill 500 on malformed or attack traffic instead of rejecting with 403.Reproduction
Same throw for
sha256=, odd-length digests, and digests longer than 64 hex chars. Wrong digests of the correct length already returnfalsecorrectly.Evidence in tree
Rust already documents the divergence:
Existing TS tests only cover equal-length failures, so CI stays green.
Proposed fix
Guard length before
timingSafeEqualand returnfalse, matching Python/Rust. Add a regression test for a truncatedsha256=…signature.Impact
verifyWebhook(includingcliand examples that call the shared helper).