← back to Wallco Ai
audit: extend hard-rule gate to vendor-names + FLIEPAPER (4 rules, one snapshot load)
222ab358e4dcb05f9b0d56a0ce59ebcd664794f0 · 2026-06-04 09:18:00 -0700 · Steve Abrams
Rule 3 (no vendor names in public UI — the legal premise of wallco.ai) and
rule 4 (no FLIEPAPER word in any customer-facing surface) were CLAUDE.md hard
rules with no standing programmatic guard. Folded into the umbrella (no new
file): scans the 9 customer-visible snapshot fields x 26,060 rows against the
server.js VENDOR_DENYLIST + a fliepaper regex, loading designs.json once for
rules 2-4. Current state: ALL CLEAR on all four.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/audit-hard-rules.js
Diff
commit 222ab358e4dcb05f9b0d56a0ce59ebcd664794f0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jun 4 09:18:00 2026 -0700
audit: extend hard-rule gate to vendor-names + FLIEPAPER (4 rules, one snapshot load)
Rule 3 (no vendor names in public UI — the legal premise of wallco.ai) and
rule 4 (no FLIEPAPER word in any customer-facing surface) were CLAUDE.md hard
rules with no standing programmatic guard. Folded into the umbrella (no new
file): scans the 9 customer-visible snapshot fields x 26,060 rows against the
server.js VENDOR_DENYLIST + a fliepaper regex, loading designs.json once for
rules 2-4. Current state: ALL CLEAR on all four.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/audit-hard-rules.js | 76 +++++++++++++++++++++++++++++++--------------
1 file changed, 53 insertions(+), 23 deletions(-)
diff --git a/scripts/audit-hard-rules.js b/scripts/audit-hard-rules.js
index e7f1237..5e39161 100644
--- a/scripts/audit-hard-rules.js
+++ b/scripts/audit-hard-rules.js
@@ -10,6 +10,9 @@
* — a tile kind in a mural category renders as a broken tiny repeating tile.
* 2. data/designs.json MUST NOT leak local_path (or any /Users/<home> path)
* — that column leaks absolute home paths through the PUBLIC /api/designs.
+ * 3. No vendor names in public UI (the legal premise of wallco.ai)
+ * — a denylisted vendor name in any customer-visible field is an exposure.
+ * 4. No "FLIEPAPER" word in any customer-facing surface.
*
* Read-only. Never writes the DB or any file. Exit 0 = all clear, 1 = a rule is
* violated (so it is usable as a pre-deploy gate / cron canary).
@@ -34,32 +37,59 @@ section('1. mural categories are never tileable');
else console.log(' -> PASS');
}
-// --- Rule 2: no local_path / home-path leak in the public snapshot ---
-section('2. data/designs.json carries no local_path / home-path leak');
-{
- const jsonPath = path.join(__dirname, '..', 'data', 'designs.json');
- if (!fs.existsSync(jsonPath)) {
- console.log(' data/designs.json absent — skipped (nothing to leak)');
- } else {
- const parsed = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
- const rows = Array.isArray(parsed) ? parsed : (parsed.designs || parsed.items || []);
- const withKey = [];
- const withHome = [];
- for (const row of rows) {
- if (!row || typeof row !== 'object') continue;
- if ('local_path' in row) withKey.push(row.id);
- for (const [k, v] of Object.entries(row)) {
- if (typeof v === 'string' && v.includes('/Users/stevestudio2')) { withHome.push([row.id, k]); break; }
- }
+// --- Rules 2-4: scan the public snapshot once, run all field-level checks ---
+// Customer-visible string fields that reach the public PDP / grid / /api/designs.
+const PUBLIC_FIELDS = ['title', 'category', 'handle', 'ai_title', 'name', 'ai_pattern', 'ai_color_name', 'colorway', 'pattern_name'];
+// Vendor denylist — KEEP IN SYNC with server.js (the VENDOR_DENYLIST regex, ~line 875).
+const VENDOR_DENYLIST = /\b(thibaut|koroseal|schumacher|maya[ -]romanoff|scalamandre|arte[ -]international|cole[ -]?(and|&)[ -]?son|phillip[ -]jeffries|osborne[ -]?(and|&)?[ -]?little|de[ -]gournay|fromental|sister[ -]parish|dedar|brewster|mind[ -]the[ -]gap|hygge|designtex|wolf[ -]gordon|carnegie|coordonn|daisy[ -]bennett|china[ -]seas|york|marburg|romo|fabricut|stout|knoll|arteriors|timorous[ -]beasties|hangups)\b/i;
+const FLIEPAPER = /fliepaper/i;
+
+const jsonPath = path.join(__dirname, '..', 'data', 'designs.json');
+if (!fs.existsSync(jsonPath)) {
+ section('2-4. public snapshot checks');
+ console.log(' data/designs.json absent — skipped (nothing to leak)');
+} else {
+ const parsed = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
+ const rows = Array.isArray(parsed) ? parsed : (parsed.designs || parsed.items || []);
+ const withKey = []; // rule 2: local_path key
+ const withHome = []; // rule 2: /Users home-path leak
+ const vendorHits = [];// rule 3
+ const flieHits = []; // rule 4
+ for (const row of rows) {
+ if (!row || typeof row !== 'object') continue;
+ if ('local_path' in row) withKey.push(row.id);
+ for (const [k, v] of Object.entries(row)) {
+ if (typeof v !== 'string') continue;
+ if (v.includes('/Users/stevestudio2')) withHome.push([row.id, k]);
}
- console.log(` rows=${rows.length} local_path-key=${withKey.length} home-path-leak=${withHome.length}`);
- if (withKey.length || withHome.length) {
- failed = true;
- console.log(` -> FAIL local_path ids: ${withKey.slice(0, 10).join(', ')} home-leak: ${JSON.stringify(withHome.slice(0, 10))}`);
- } else {
- console.log(' -> PASS');
+ for (const f of PUBLIC_FIELDS) {
+ const v = row[f];
+ if (typeof v !== 'string') continue;
+ const m = VENDOR_DENYLIST.exec(v);
+ if (m) vendorHits.push([row.id, f, m[0], v.slice(0, 50)]);
+ if (FLIEPAPER.test(v)) flieHits.push([row.id, f, v.slice(0, 50)]);
}
}
+
+ section('2. data/designs.json carries no local_path / home-path leak');
+ console.log(` rows=${rows.length} local_path-key=${withKey.length} home-path-leak=${withHome.length}`);
+ if (withKey.length || withHome.length) {
+ failed = true;
+ console.log(` -> FAIL local_path ids: ${withKey.slice(0, 10).join(', ')} home-leak: ${JSON.stringify(withHome.slice(0, 10))}`);
+ } else console.log(' -> PASS');
+
+ section('3. no vendor names in public-facing snapshot fields');
+ console.log(` scanned ${PUBLIC_FIELDS.length} public fields x ${rows.length} rows`);
+ if (vendorHits.length) {
+ failed = true;
+ console.log(` -> FAIL ${vendorHits.length} hit(s): ${JSON.stringify(vendorHits.slice(0, 10))}`);
+ } else console.log(' -> PASS');
+
+ section('4. no FLIEPAPER word in public-facing snapshot fields');
+ if (flieHits.length) {
+ failed = true;
+ console.log(` -> FAIL ${flieHits.length} hit(s): ${JSON.stringify(flieHits.slice(0, 10))}`);
+ } else console.log(' -> PASS');
}
console.log(`\n=== HARD-RULE AUDIT: ${failed ? 'FAIL — a rule is violated (see above)' : 'ALL CLEAR'} ===`);
← d249fc8 audit: umbrella hard-rule gate (mural-kinds + designs.json l
·
back to Wallco Ai
·
test: make npm test a real preflight gate (node -c server.js 83390fc →