From 31a82574771530f6b8d92ec47fd37aae1937926c Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 25 Mar 2026 09:48:23 -0400 Subject: [PATCH] chore: replace scmp with crypto.timingSafeEqual --- package.json | 1 - src/webhooks/webhooks.ts | 20 +++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 56281ca1d..023127dec 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "https-proxy-agent": "^5.0.0", "jsonwebtoken": "^9.0.3", "qs": "^6.14.1", - "scmp": "^2.1.0", "xmlbuilder": "^13.0.2" }, "devDependencies": { diff --git a/src/webhooks/webhooks.ts b/src/webhooks/webhooks.ts index a0cffb689..85e84f25b 100644 --- a/src/webhooks/webhooks.ts +++ b/src/webhooks/webhooks.ts @@ -1,4 +1,3 @@ -const scmp = require("scmp"); import crypto from "crypto"; import urllib from "url"; import { IncomingHttpHeaders } from "http2"; @@ -257,7 +256,10 @@ function validateSignatureWithUrl( params ); - return scmp(Buffer.from(twilioHeader), Buffer.from(signatureWithoutPort)); + return timingSafeEqual( + Buffer.from(twilioHeader), + Buffer.from(signatureWithoutPort) + ); } export function validateBody( @@ -265,7 +267,19 @@ export function validateBody( bodyHash: any[] | string | Buffer ): boolean { var expectedHash = getExpectedBodyHash(body); - return scmp(Buffer.from(bodyHash), Buffer.from(expectedHash)); + return timingSafeEqual(Buffer.from(bodyHash), Buffer.from(expectedHash)); +} + +function timingSafeEqual(userValue: Buffer, secretValue: Buffer) { + // Do not return early when lengths differ — that leaks the secret's + // length through timing. Instead, always perform a constant-time + // comparison: when the lengths match compare directly; otherwise + // compare the user input against itself (always true) and negate. + // Source: https://developers.cloudflare.com/workers/examples/protect-against-timing-attacks/ + const lengthsMatch = userValue.length === secretValue.length; + return lengthsMatch + ? crypto.timingSafeEqual(userValue, secretValue) + : !crypto.timingSafeEqual(userValue, userValue); } /**