[object Object]

← back to Dw Domain Fleet

fix: strip 3rd-party vendor tokens from product slug/handle in fleet UI (redactVendorSlug) — closes the slug-level vendor leak across the dwf fleet that the 2026-05-28 chip scrub missed; route-safe (server.js resolves via productSlug(x)===key + real-handle fallback)

ee30c6ade42ad05bff209703960b0288754ff696 · 2026-05-29 17:05:00 -0700 · Steve Abrams

Files touched

Diff

commit ee30c6ade42ad05bff209703960b0288754ff696
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 29 17:05:00 2026 -0700

    fix: strip 3rd-party vendor tokens from product slug/handle in fleet UI (redactVendorSlug) — closes the slug-level vendor leak across the dwf fleet that the 2026-05-28 chip scrub missed; route-safe (server.js resolves via productSlug(x)===key + real-handle fallback)
---
 shared/render.js | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/shared/render.js b/shared/render.js
index d0ce0b5..3a763ca 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -21,7 +21,8 @@ function clean(s) {
  */
 function productSlug(p) {
   const h = (p && p.handle) || '';
-  return h.replace(/wallpapers/gi, 'wallcoverings').replace(/wallpaper/gi, 'wallcovering');
+  const wc = h.replace(/wallpapers/gi, 'wallcoverings').replace(/wallpaper/gi, 'wallcovering');
+  return redactVendorSlug(wc);   // strip 3rd-party vendor tokens — server.js resolves via productSlug(x)===key so links stay resolvable; old real-handle URLs still resolve via the handle branch
 }
 
 /* scrubTags — drop any tag carrying a 3rd-party vendor token before rendering.
@@ -62,6 +63,30 @@ function scrubTags(tags) {
   return tags.filter(t => t && !hasVendorToken(t));
 }
 
+/* redactVendorSlug — strip any hyphen-delimited segment carrying a 3rd-party
+ * vendor token from a slug/handle/sku shown in the fleet UI. Audit 2026-05-29:
+ * vendor names leaked via the product handle into /product/<slug> hrefs, og:url
+ * and the visible sku — a vector the 2026-05-28 chip scrub (scrubTags) never
+ * touched. Driven by the same VENDORS_DENYLIST. Used by productSlug (the fleet's
+ * OWN /product/ route key — server.js resolves via render.productSlug(x)===key,
+ * so recomputing the redacted value keeps links resolvable; old real-handle URLs
+ * still resolve via the handle branch). The REAL p.handle is left intact for the
+ * main-store canonical + buy-sample links (they legitimately point at
+ * designerwallcoverings.com/products/<real-handle>). House brands carry no
+ * denylist token, so they pass through untouched. Longer vendor names are listed
+ * before their prefixes in VENDORS_DENYLIST ("lee jofa modern" before "lee jofa")
+ * so the longer match strips first. */
+const VENDOR_SLUG_RES = VENDORS_DENYLIST.map(v => {
+  const s = String(v).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
+  return new RegExp('(^|-)' + s + '(?=-|$)', 'gi');
+});
+function redactVendorSlug(slug) {
+  let out = String(slug == null ? '' : slug);
+  for (let i = 0; i < VENDOR_SLUG_RES.length; i++) out = out.replace(VENDOR_SLUG_RES[i], '$1');
+  out = out.replace(/-{2,}/g, '-').replace(/^-+|-+$/g, '');
+  return out || String(slug == null ? '' : slug);   // never empty (route key must be non-empty)
+}
+
 /* displayTitle / scrubTitleLead — strip a 3rd-party vendor baked into the HEAD
  * of a product name before it reaches a visible surface (card-title, data-title
  * attr → quick-view modal title + aria-label, productPage <h1>). scrubTags only
@@ -335,8 +360,9 @@ function card(p) {
   const hx = dominantHex(p);
   const t = displayTitle(p);
   const href = '/product/' + encodeURIComponent(productSlug(p));
+  const dsku = redactVendorSlug(p.sku || p.handle);   // visible/meta sku — vendor stripped (real handle kept on data-handle for the main-store buy link)
   return `<div class="card" data-handle="${esc(p.handle)}"
-    data-title="${esc(t)}" data-sku="${esc(p.sku||p.handle)}"
+    data-title="${esc(t)}" data-sku="${esc(dsku)}"
     data-img="${esc(p.image_url)}" data-tags="${esc(scrubTags(p.tags).slice(0,12).join(', '))}"
     data-price="${esc(p.price)}">
   <a class="card-link" href="${esc(href)}" aria-label="${esc(t)}">
@@ -344,7 +370,7 @@ function card(p) {
     <div class="card-body">
       <div class="card-title">${esc(t)}</div>
       <div class="card-meta"><span class="dot" style="background:${hx}"></span>
-        Designer Wallcoverings · ${esc(p.sku||p.handle)}</div>
+        Designer Wallcoverings · ${esc(dsku)}</div>
       <div class="card-sample">View Details</div>
     </div>
   </a></div>`;
@@ -612,7 +638,7 @@ function heroRotate(n){
  */
 function productPage(cfg, p, related) {
   const name = displayTitle(p);
-  const sku = p.sku || p.handle;
+  const sku = redactVendorSlug(p.sku || p.handle);   // displayed sku (title/meta/schema) — vendor stripped; canonical + buy-link below keep the REAL handle
   const canonical = `${DW}/products/${encodeURIComponent(p.handle)}`;
   // per-site-unique title + meta — site name makes each fleet copy distinct
   const title = `${name} — ${sku} | ${cfg.siteName}`;

← 34fbe0e fix(fleet pdp): toggle hidden until ?theme=N opt-in  ·  back to Dw Domain Fleet  ·  render.js: scrubVendor() removes vendor names from displayed 09c24d7 →