← 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
M lib/whatsapp.jsM test/plaid.test.jsM test/whatsapp.test.js
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 →