[object Object]

← 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

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 →