[object Object]

← 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 →