[object Object]

← back to Costa Rica

costa-rica: Cody-gate fixes on plaid/whatsapp tests — fix real type bug in whatsapp.verifySignature (returned '' not false on missing header → now !!()); add the HIGH-value prod-misconfig test (LIVE=true + no APP_SECRET must REJECT unsigned webhooks, not accept); strict-boolean assertions (drop !! masking); plaid tests now booby-trap global.fetch to PROVE no live bank call is made; require-cache cleanup after(). Suite 61/61 green — TK-10346

59dfc760cea9ad3b62eaf7971641e403c40ada4d · 2026-08-07 15:35:19 -0700 · Steve

Files touched

Diff

commit 59dfc760cea9ad3b62eaf7971641e403c40ada4d
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Aug 7 15:35:19 2026 -0700

    costa-rica: Cody-gate fixes on plaid/whatsapp tests — fix real type bug in whatsapp.verifySignature (returned '' not false on missing header → now !!()); add the HIGH-value prod-misconfig test (LIVE=true + no APP_SECRET must REJECT unsigned webhooks, not accept); strict-boolean assertions (drop !! masking); plaid tests now booby-trap global.fetch to PROVE no live bank call is made; require-cache cleanup after(). Suite 61/61 green — TK-10346
---
 lib/whatsapp.js       |  2 +-
 test/plaid.test.js    |  7 ++++++-
 test/whatsapp.test.js | 42 ++++++++++++++++++++++++++++++------------
 3 files changed, 37 insertions(+), 14 deletions(-)

diff --git a/lib/whatsapp.js b/lib/whatsapp.js
index 9f21fe3..b129acc 100644
--- a/lib/whatsapp.js
+++ b/lib/whatsapp.js
@@ -125,7 +125,7 @@ function verifySignature(headers, rawBody) {
   if (!APP_SECRET) return !LIVE; // sandbox accepts
   const sig = (headers['x-hub-signature-256'] || '').replace('sha256=', '');
   const expect = crypto.createHmac('sha256', APP_SECRET).update(rawBody).digest('hex');
-  try { return sig && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect)); } catch { return false; }
+  try { return !!(sig && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect))); } catch { return false; }
 }
 // Parse an inbound webhook body into normalized events + persist inbound msgs.
 async function handleInbound(body) {
diff --git a/test/plaid.test.js b/test/plaid.test.js
index 9223c01..1ba9885 100644
--- a/test/plaid.test.js
+++ b/test/plaid.test.js
@@ -3,17 +3,22 @@
 // LIVE-MONEY (bank ACH) integration; the critical invariant is that WITHOUT creds
 // it never makes a live API call — liveMode is false and every entrypoint returns
 // deterministic sandbox values. Run: node --test
-const { test, before } = require('node:test');
+const { test, before, after } = require('node:test');
 const assert = require('node:assert');
 
 let plaid;
+const realFetch = global.fetch;
 before(() => {
   // Guarantee no live creds leak in from the ambient env for this assertion.
   delete process.env.PLAID_CLIENT_ID;
   delete process.env.PLAID_SECRET;
+  // Booby-trap fetch: if any sandbox entrypoint ever reaches _post(), the test
+  // fails LOUDLY instead of silently making a (would-be live) network call.
+  global.fetch = () => { throw new Error('LIVE PLAID CALL ATTEMPTED — sandbox guard failed'); };
   delete require.cache[require.resolve('../lib/plaid')];
   plaid = require('../lib/plaid');
 });
+after(() => { global.fetch = realFetch; });
 
 test('SAFETY: liveMode is false and ENV defaults to sandbox when no creds', () => {
   assert.equal(plaid.liveMode, false);
diff --git a/test/whatsapp.test.js b/test/whatsapp.test.js
index 05645a7..f982758 100644
--- a/test/whatsapp.test.js
+++ b/test/whatsapp.test.js
@@ -3,7 +3,7 @@
 // verifySignature is the gate that keeps FORGED WhatsApp webhooks out (HMAC-SHA256
 // over the raw body, timing-safe compare). verifyChallenge is the GET handshake.
 // Run: node --test
-const { test } = require('node:test');
+const { test, after } = require('node:test');
 const assert = require('node:assert');
 const crypto = require('crypto');
 
@@ -26,29 +26,47 @@ test('verifyChallenge: correct verify_token returns the challenge; wrong/missing
   assert.equal(wa.verifyChallenge({}).ok, false);
 });
 
-test('SECURITY: verifySignature accepts a correctly-signed body', () => {
+test('SECURITY: verifySignature returns strict boolean true for a correctly-signed body', () => {
   const body = JSON.stringify({ entry: [{ id: '1' }] });
-  assert.equal(!!wa.verifySignature({ 'x-hub-signature-256': sign(body) }, body), true);
+  assert.strictEqual(wa.verifySignature({ 'x-hub-signature-256': sign(body) }, body), true);
 });
 
-test('SECURITY: verifySignature rejects a wrong signature, a tampered body, and a missing header', () => {
+test('SECURITY: verifySignature returns strict boolean false for wrong sig, tampered body, missing/malformed header', () => {
   const body = JSON.stringify({ entry: [{ id: '1' }] });
   const good = sign(body);
   // wrong sig (same length, last hex char flipped) → timing-safe compare fails
   const wrong = good.slice(0, -1) + (good.slice(-1) === '0' ? '1' : '0');
-  assert.equal(wa.verifySignature({ 'x-hub-signature-256': wrong }, body), false);
+  assert.strictEqual(wa.verifySignature({ 'x-hub-signature-256': wrong }, body), false);
   // right sig but tampered body
-  assert.equal(wa.verifySignature({ 'x-hub-signature-256': good }, body + ' '), false);
-  // missing header
-  assert.equal(!!wa.verifySignature({}, body), false);
+  assert.strictEqual(wa.verifySignature({ 'x-hub-signature-256': good }, body + ' '), false);
+  // missing header → strict false (not the empty-string the old `sig && ...` returned)
+  assert.strictEqual(wa.verifySignature({}, body), false);
   // malformed/short sig must not throw (length mismatch caught → false)
-  assert.equal(wa.verifySignature({ 'x-hub-signature-256': 'sha256=abcd' }, body), false);
+  assert.strictEqual(wa.verifySignature({ 'x-hub-signature-256': 'sha256=abcd' }, body), false);
 });
 
-test('SANDBOX: with no APP_SECRET and not live, verifySignature accepts (dev convenience)', () => {
+test('SANDBOX: no APP_SECRET + NOT live → accepts (dev convenience)', () => {
   delete process.env.WHATSAPP_APP_SECRET;
   delete require.cache[require.resolve('../lib/whatsapp')];
   const waSbx = require('../lib/whatsapp');
-  assert.equal(waSbx.liveMode, false);
-  assert.equal(waSbx.verifySignature({}, 'anything'), true); // !APP_SECRET → returns !LIVE (true)
+  assert.strictEqual(waSbx.liveMode, false);
+  assert.strictEqual(waSbx.verifySignature({}, 'anything'), true); // !APP_SECRET → !LIVE (true)
 });
+
+test('SECURITY: no APP_SECRET + LIVE=true → REJECTS (prod misconfig must not become a bypass)', () => {
+  const saved = { t: process.env.WHATSAPP_TOKEN, p: process.env.WHATSAPP_PHONE_ID };
+  process.env.WHATSAPP_TOKEN = 'fake-token';       // force LIVE=true
+  process.env.WHATSAPP_PHONE_ID = 'fake-phone-id';
+  delete process.env.WHATSAPP_APP_SECRET;          // but the signing secret is missing
+  delete require.cache[require.resolve('../lib/whatsapp')];
+  const waProd = require('../lib/whatsapp');
+  assert.strictEqual(waProd.liveMode, true);
+  assert.strictEqual(waProd.verifySignature({}, 'anything'), false); // live + no secret → reject, NOT accept
+  // restore env so we don't poison anything else in-process
+  if (saved.t === undefined) delete process.env.WHATSAPP_TOKEN; else process.env.WHATSAPP_TOKEN = saved.t;
+  if (saved.p === undefined) delete process.env.WHATSAPP_PHONE_ID; else process.env.WHATSAPP_PHONE_ID = saved.p;
+});
+
+// Leave the module cache clean so a later-added test file can't inherit this file's
+// mutated (no-secret / faked-live) whatsapp instance.
+after(() => { delete require.cache[require.resolve('../lib/whatsapp')]; });

← 96cd339 costa-rica: CORS on /api/app for web build + browser preview  ·  back to Costa Rica  ·  yoloforever: cycle 5 ledger — plaid/whatsapp sandbox+securit 470b561 →