← back to Dw Domain Fleet
fix(fleet): route scrubTags through canonical isVendorTag + extend scrubVendor denylist — kills dwf vendor tag-chip + slug leaks (Anna French, Christian Lacroix, Ralph Lauren, AS Creation, Gaston Y Daniela, etc); verified 0 vendor names in barwallpaper DOM across 3267 products
45f2fce2ac6db5cb625a0ee57eb06b0004c2fd70 · 2026-06-03 21:24:36 -0700 · Steve
Files touched
Diff
commit 45f2fce2ac6db5cb625a0ee57eb06b0004c2fd70
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jun 3 21:24:36 2026 -0700
fix(fleet): route scrubTags through canonical isVendorTag + extend scrubVendor denylist — kills dwf vendor tag-chip + slug leaks (Anna French, Christian Lacroix, Ralph Lauren, AS Creation, Gaston Y Daniela, etc); verified 0 vendor names in barwallpaper DOM across 3267 products
---
shared/render.js | 53 +++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 43 insertions(+), 10 deletions(-)
diff --git a/shared/render.js b/shared/render.js
index fcae9c5..eefd1f0 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -4,6 +4,13 @@
*/
const crypto = require('crypto');
const { dominantHex } = require('./sort');
+// Canonical vendor-tag predicate — single source of truth shared with the
+// API-redact middleware. scrubTags() routes through this so the fleet can never
+// drift to a stale SUBSET of the denylist again (the 2026-06-03 leak: render.js
+// kept its own narrower list and leaked Anna French / Christian Lacroix / Ralph
+// Lauren / Laura Ashley / Mind the Gap chips + Color:/Series:/Type metadata that
+// the canonical isVendorTag already drops). DTD verdict A (Claude+Codex 2/2).
+const { isVendorTag } = require('../../_shared/api-vendor-redact');
/* imgToken / proxyImg — vendor-neutral image URL.
* The raw Shopify CDN image_url embeds the 3rd-party vendor name in the
@@ -66,16 +73,26 @@ function productSlug(p) {
return 'design-' + hash.toString(36);
}
-/* scrubTags — drop any tag carrying a 3rd-party vendor token before rendering.
- * Mirrors the VENDORS list in _shared/sku-redact.js (which only covers .ven/.r-sku
- * card-meta lines). Tag-chip surfaces (data-tags attribute on cards + productPage
- * <span> chip strip) were leaking "Brunschwig & Fils", "Kravet", "Lee Jofa" etc
- * straight from p.tags[] to the rendered DOM (audited 2026-05-28). This filter
+/* scrubTags — drop any tag carrying a 3rd-party vendor token (or internal
+ * Color:/Series:/Type/Collection metadata) before rendering. Tag-chip surfaces
+ * (the data-tags attribute on cards + the productPage <span> chip strip) were
+ * leaking vendor names straight from p.tags[] to the rendered DOM. This filter
* runs at the render boundary so both the data-tags attribute and the
- * productPage chips see the scrubbed list. House brands (Designer Wallcoverings,
- * Hollywood, Phillipe Romano, Retro Walls, Maison Lustre) are NEVER scrubbed —
- * they're not on the denylist. Read-mostly source-of-truth: keep VENDORS in sync
- * with _shared/sku-redact.js; if you add a vendor there, add it here too. */
+ * productPage chips see the scrubbed list.
+ *
+ * 2026-06-03 (DTD verdict A): scrubTags now delegates to the CANONICAL
+ * isVendorTag() in _shared/api-vendor-redact.js — the same predicate the API
+ * middleware uses — instead of a private, narrower copy. Maintaining a second
+ * list here caused subset drift: the local list lagged the canonical one and
+ * leaked Anna French / Christian Lacroix / Ralph Lauren / Laura Ashley /
+ * Mind the Gap / Dolce & Gabbana / A.S. Création chips, plus internal Color:/
+ * Series:/Type metadata chips. One source of truth ⇒ no more drift. House
+ * brands (Designer Wallcoverings, Hollywood, Phillipe Romano, Retro Walls,
+ * Maison Lustre) are still NEVER scrubbed — isVendorTag keeps them.
+ *
+ * The VENDORS_DENYLIST / scrubVendor / hasVendorToken trio below is a DIFFERENT
+ * operation (in-place vendor-token removal from a displayed title and the
+ * /product URL slug) that isVendorTag does not expose, so it stays. */
const VENDORS_DENYLIST = [
'wolf gordon', 'nina campbell', 'jeffrey stevens', 'versace',
'arte international', 'lee jofa modern', 'lee jofa', 'innovations usa',
@@ -87,6 +104,18 @@ const VENDORS_DENYLIST = [
'phillip jeffries', 'fromental', 'westport', 'breegan', 'les ensembliers',
'roger thomas', 'stacy garcia', 'de gournay', 'romo', 'farrow and ball',
'colefax and fowler', 'sanderson', 'morris and co', 'osborne and little',
+ // 2026-06-03: vendors the canonical isVendorTag (api-vendor-redact) carries
+ // but scrubVendor's slug/title stripper was missing — they leaked into the
+ // /product URL slug + data-handle (e.g. ".../as-creation", ".../gaston-y-daniela").
+ // Tag-chips are now handled by isVendorTag; these keep scrubVendor in lockstep
+ // so the URL slug + visible title never carry a 3rd-party vendor either.
+ 'anna french', 'christian lacroix', 'ralph lauren', 'laura ashley',
+ 'mind the gap', 'as creation', 'a s creation', 'gaston y daniela',
+ 'brand mckenzie', 'dolce and gabbana', 'dolce gabbana', 'versa designed',
+ 'pierre frey', 'clarence house', 'donghia', 'elitis', 'mulberry', 'nobilis',
+ 'phyllis morris', 'scion', 'zoffany', 'marimekko', 'missoni', 'roberto cavalli',
+ 'kelly wearstler', 'winfield thybony', 'villa nova', 'wallquest', 'vahallan',
+ 'lincrusta', 'newmor', 'china seas', 'fabricut', 'baker lifestyle',
];
const VENDORS_DENYLIST_NORM = VENDORS_DENYLIST.map(s =>
String(s).toLowerCase().replace(/[^a-z0-9]+/g, '')
@@ -101,7 +130,11 @@ function hasVendorToken(s) {
}
function scrubTags(tags) {
if (!Array.isArray(tags)) return [];
- return tags.filter(t => t && !hasVendorToken(t));
+ // Canonical predicate (drops vendors + internal metadata tags, keeps house
+ // brands). Belt-and-suspenders: also apply the local slug-oriented
+ // hasVendorToken so any vendor present in scrubVendor's list but momentarily
+ // absent from the canonical one is still caught.
+ return tags.filter(t => t && !isVendorTag(t) && !hasVendorToken(t));
}
/* scrubVendor — REMOVE any 3rd-party vendor name from a DISPLAYED string
← bd7303f Vendor-neutral image proxy: render emits /img/<token>, serve
·
back to Dw Domain Fleet
·
snapshot before task-52 verification (standalone hero alloca 1b277d8 →