← back to Dw Marketing Reels
scripts/scan-leaks.mjs
44 lines
#!/usr/bin/env node
// Fail-closed LEAK GATE for the reels pipeline. Scans the staged manifests
// (data/reels.json, data/new-arrivals.json) for any private-label / upstream
// vendor name using the single canonical-synced denylist (./leak-deny.mjs), and
// EXITS NON-ZERO on any hit. Wired into cron-run.sh BEFORE publish-social.mjs so a
// leaked caption can never reach a live IG/TikTok post — turning the old one-shot
// "0 leaks" manual assertion into a standing gate. $0 (local, read-only).
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { LEAK_DENY } from './leak-deny.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const load = f => { try { return JSON.parse(readFileSync(join(ROOT, 'data', f), 'utf8')); } catch { return null; } };
// Walk every string value in an object/array and yield [path, value].
function* strings(node, path = '') {
if (node == null) return;
if (typeof node === 'string') { yield [path, node]; return; }
if (Array.isArray(node)) { for (let i = 0; i < node.length; i++) yield* strings(node[i], `${path}[${i}]`); return; }
if (typeof node === 'object') { for (const k of Object.keys(node)) yield* strings(node[k], path ? `${path}.${k}` : k); }
}
const hits = [];
for (const f of ['reels.json', 'new-arrivals.json']) {
const data = load(f);
if (data == null) { console.log(`· ${f}: absent/unreadable — skipped`); continue; }
let n = 0;
for (const [path, val] of strings(data)) {
n++;
const m = val.match(LEAK_DENY);
if (m) hits.push({ file: f, path, match: m[0], sample: val.slice(0, 100) });
}
console.log(`· ${f}: scanned ${n} string fields`);
}
if (hits.length) {
console.error(`\n✗ LEAK GATE FAILED — ${hits.length} private-label name(s) staged:`);
for (const h of hits) console.error(` ${h.file} @ ${h.path} → "${h.match}" :: ${h.sample}`);
console.error(`\nRefusing to publish. Fix the source data / caption, then re-run.`);
process.exit(1);
}
console.log(`\n✓ LEAK GATE PASSED — no private-label names in staged manifests.`);