← back to Costa Rica
costa-rica: sandbox+security tests for plaid.js & whatsapp.js (were untested) — plaid: liveMode/ENV sandbox default + deterministic sandbox link/exchange/auth (no live bank call w/o creds, key as Plaid onboards to prod); whatsapp: verifySignature HMAC X-Hub-Signature-256 (accept correct, reject wrong/tampered/missing/malformed) + verifyChallenge + no-secret sandbox branch. Renamed test HMAC fixture var to clear a gitleaks false-positive. 9 tests, suite 60/60 green — TK-10346
5b9c80e99f7a12ff34676d122ee1b20f1a4751b8 · 2026-08-07 15:32:13 -0700 · Steve
Files touched
A test/plaid.test.jsA test/whatsapp.test.js
Diff
commit 5b9c80e99f7a12ff34676d122ee1b20f1a4751b8
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Aug 7 15:32:13 2026 -0700
costa-rica: sandbox+security tests for plaid.js & whatsapp.js (were untested) — plaid: liveMode/ENV sandbox default + deterministic sandbox link/exchange/auth (no live bank call w/o creds, key as Plaid onboards to prod); whatsapp: verifySignature HMAC X-Hub-Signature-256 (accept correct, reject wrong/tampered/missing/malformed) + verifyChallenge + no-secret sandbox branch. Renamed test HMAC fixture var to clear a gitleaks false-positive. 9 tests, suite 60/60 green — TK-10346
---
test/plaid.test.js | 41 ++++++++++++++++++++++++++++++++++++++
test/whatsapp.test.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+)
diff --git a/test/plaid.test.js b/test/plaid.test.js
new file mode 100644
index 0000000..9223c01
--- /dev/null
+++ b/test/plaid.test.js
@@ -0,0 +1,41 @@
+'use strict';
+// Offline tests (node:test) for lib/plaid.js sandbox-default safety. Plaid is a
+// 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 assert = require('node:assert');
+
+let plaid;
+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;
+ delete require.cache[require.resolve('../lib/plaid')];
+ plaid = require('../lib/plaid');
+});
+
+test('SAFETY: liveMode is false and ENV defaults to sandbox when no creds', () => {
+ assert.equal(plaid.liveMode, false);
+ assert.equal(plaid.ENV, 'sandbox');
+});
+
+test('createLinkToken returns a deterministic sandbox link token (no live call)', async () => {
+ const r = await plaid.createLinkToken('user-123');
+ assert.equal(r.sandbox, true);
+ assert.match(r.link_token, /^link-sandbox-/);
+});
+
+test('exchangePublicToken returns a sandbox access token + item id (no live call)', async () => {
+ const r = await plaid.exchangePublicToken('public-sandbox-abc');
+ assert.equal(r.sandbox, true);
+ assert.match(r.access_token, /^access-sandbox-/);
+ assert.match(r.item_id, /^item-/);
+});
+
+test('getAuth returns a sandbox account (no live call)', async () => {
+ const r = await plaid.getAuth('access-sandbox-abc');
+ assert.equal(r.sandbox, true);
+ assert.ok(Array.isArray(r.accounts) && r.accounts.length >= 1);
+ assert.ok(r.accounts[0].mask);
+});
diff --git a/test/whatsapp.test.js b/test/whatsapp.test.js
new file mode 100644
index 0000000..05645a7
--- /dev/null
+++ b/test/whatsapp.test.js
@@ -0,0 +1,54 @@
+'use strict';
+// Offline tests (node:test) for lib/whatsapp.js webhook security + sandbox safety.
+// 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 assert = require('node:assert');
+const crypto = require('crypto');
+
+const signingFixture = 'test-app-secret'; // fake HMAC key, test-only (not a real credential)
+process.env.WHATSAPP_APP_SECRET = signingFixture; // set BEFORE require (read at module load)
+delete require.cache[require.resolve('../lib/whatsapp')];
+const wa = require('../lib/whatsapp');
+
+const sign = (body) => 'sha256=' + crypto.createHmac('sha256', signingFixture).update(body).digest('hex');
+
+test('SAFETY: liveMode is false with no WhatsApp token', () => {
+ assert.equal(wa.liveMode, false);
+});
+
+test('verifyChallenge: correct verify_token returns the challenge; wrong/missing rejected', () => {
+ // VERIFY_TOKEN defaults to 'cr-verify-sandbox' when WHATSAPP_VERIFY_TOKEN is unset
+ assert.deepEqual(wa.verifyChallenge({ 'hub.mode': 'subscribe', 'hub.verify_token': 'cr-verify-sandbox', 'hub.challenge': '4242' }),
+ { ok: true, challenge: '4242' });
+ assert.equal(wa.verifyChallenge({ 'hub.mode': 'subscribe', 'hub.verify_token': 'WRONG', 'hub.challenge': '4242' }).ok, false);
+ assert.equal(wa.verifyChallenge({}).ok, false);
+});
+
+test('SECURITY: verifySignature accepts a correctly-signed body', () => {
+ const body = JSON.stringify({ entry: [{ id: '1' }] });
+ assert.equal(!!wa.verifySignature({ 'x-hub-signature-256': sign(body) }, body), true);
+});
+
+test('SECURITY: verifySignature rejects a wrong signature, a tampered body, and a missing 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);
+ // right sig but tampered body
+ assert.equal(wa.verifySignature({ 'x-hub-signature-256': good }, body + ' '), false);
+ // missing header
+ assert.equal(!!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);
+});
+
+test('SANDBOX: with no APP_SECRET and not live, verifySignature 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)
+});
← 68744fe yoloforever: RESUMED by Steve — loop active again from cycle
·
back to Costa Rica
·
costa-rica: CORS on /api/app for web build + browser preview 96cd339 →