From 7455da8e47f6fd38a0af983cf9dc9ad6a41e546f Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 22 Apr 2026 15:30:50 +0530 Subject: [PATCH 1/6] fix: prevent timing attack in webhook signature verification Replace vulnerable string comparison (===) with crypto.timingSafeEqual() to prevent timing-based side-channel attacks on signature verification. The === operator short-circuits on first mismatch, allowing attackers to measure response times and potentially guess signatures character by character. timingSafeEqual() always compares all bytes in constant time regardless of input. --- lib/utils/razorpay-utils.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/utils/razorpay-utils.js b/lib/utils/razorpay-utils.js index 6ba6468..de82726 100644 --- a/lib/utils/razorpay-utils.js +++ b/lib/utils/razorpay-utils.js @@ -99,7 +99,16 @@ function validateWebhookSignature (body, signature, secret) { .update(body) .digest('hex'); - return expectedSignature === signature; + // Use constant-time comparison to prevent timing attacks + var expectedBuffer = Buffer.from(expectedSignature, 'utf8'); + var signatureBuffer = Buffer.from(signature, 'utf8'); + + // timingSafeEqual requires equal length buffers + if (expectedBuffer.length !== signatureBuffer.length) { + return false; + } + + return crypto.timingSafeEqual(expectedBuffer, signatureBuffer); } function validatePaymentVerification(params={}, signature, secret){ From 9ea75c955f9b5fb7f04ef8b8c4cad237f119672e Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 22 Apr 2026 15:41:25 +0530 Subject: [PATCH 2/6] refactor: use raw bytes comparison instead of hex strings - Compare raw HMAC bytes (32 bytes) instead of hex strings (64 chars) - Decode incoming signature from hex to raw bytes - Handle invalid hex input gracefully with try/catch --- lib/utils/razorpay-utils.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/utils/razorpay-utils.js b/lib/utils/razorpay-utils.js index de82726..a269027 100644 --- a/lib/utils/razorpay-utils.js +++ b/lib/utils/razorpay-utils.js @@ -95,19 +95,25 @@ function validateWebhookSignature (body, signature, secret) { body = body.toString(); - var expectedSignature = crypto.createHmac('sha256', secret) - .update(body) - .digest('hex'); + // Compute HMAC as raw bytes (32 bytes for SHA-256) + var expectedBuffer = crypto.createHmac('sha256', secret) + .update(body) + .digest(); - // Use constant-time comparison to prevent timing attacks - var expectedBuffer = Buffer.from(expectedSignature, 'utf8'); - var signatureBuffer = Buffer.from(signature, 'utf8'); + // Decode incoming hex signature to raw bytes + var signatureBuffer; + try { + signatureBuffer = Buffer.from(signature, 'hex'); + } catch (e) { + return false; + } - // timingSafeEqual requires equal length buffers + // Both should be 32 bytes (SHA-256 output) if (expectedBuffer.length !== signatureBuffer.length) { return false; } + // Constant-time comparison to prevent timing attacks return crypto.timingSafeEqual(expectedBuffer, signatureBuffer); } From 01b26362a4df2c4e1e0698942086cf5c4e57f8e3 Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 29 Apr 2026 14:48:25 +0530 Subject: [PATCH 3/6] refactor: remove dead try/catch in signature verification Buffer.from(str, 'hex') never throws - it returns empty/partial Buffer on invalid hex input. The length check below catches this case. --- lib/utils/razorpay-utils.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/utils/razorpay-utils.js b/lib/utils/razorpay-utils.js index a269027..bdf0251 100644 --- a/lib/utils/razorpay-utils.js +++ b/lib/utils/razorpay-utils.js @@ -101,12 +101,9 @@ function validateWebhookSignature (body, signature, secret) { .digest(); // Decode incoming hex signature to raw bytes - var signatureBuffer; - try { - signatureBuffer = Buffer.from(signature, 'hex'); - } catch (e) { - return false; - } + // Note: Buffer.from(hex) returns empty/partial Buffer on invalid hex (no throw), + // which is caught by the length check below + var signatureBuffer = Buffer.from(signature, 'hex'); // Both should be 32 bytes (SHA-256 output) if (expectedBuffer.length !== signatureBuffer.length) { From 192076a6f98a58eb762ad685f7bc5b725b4e6f18 Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 29 Apr 2026 14:56:30 +0530 Subject: [PATCH 4/6] Fix typo in CI workflow node version matrix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4232a6f..91dddf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [12.x, 14.x, 16.x, 18.x, 19,x] + node-version: [12.x, 14.x, 16.x, 18.x, 19.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v2 From e89189495f9a3f9c5be6f5fe2c52af44613eadc1 Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 29 Apr 2026 15:06:57 +0530 Subject: [PATCH 5/6] Upgrade actions/checkout and setup-node to v4 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91dddf3..15e531a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,9 @@ jobs: node-version: [12.x, 14.x, 16.x, 18.x, 19.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -40,9 +40,9 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: sudo apt-get install -y oathtool - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: node-version: 16 registry-url: https://registry.npmjs.org/ From 41f808fff2e4d968b03ab492f61bfdd379e6115d Mon Sep 17 00:00:00 2001 From: Vaibhav Chopra Date: Wed, 29 Apr 2026 15:12:05 +0530 Subject: [PATCH 6/6] Upgrade actions in node.js.yml to v4 --- .github/workflows/node.js.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 3f165c1..a653bd1 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -20,9 +20,9 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm'