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