[object Object]

← back to Dw Domain Fleet

render.js productSlug: guard both slug returns with hasVendorToken() — closes the 936 residual URL-slug leaks (935 wolfgordon + 1 gpjbaker) the separator-requiring scrubVendor regex missed on CONCATENATED handle spellings (wolfgordonwallcovering) + per-initial splits (g-p-j-baker). Falls back to clean scrubbed-sku/hash slug. Normal products unaffected; route still dual-key resolves old URLs.

0ac4d913be36394d8637daa45195acb556c42711 · 2026-05-30 08:16:50 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 0ac4d913be36394d8637daa45195acb556c42711
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat May 30 08:16:50 2026 -0700

    render.js productSlug: guard both slug returns with hasVendorToken() — closes the 936 residual URL-slug leaks (935 wolfgordon + 1 gpjbaker) the separator-requiring scrubVendor regex missed on CONCATENATED handle spellings (wolfgordonwallcovering) + per-initial splits (g-p-j-baker). Falls back to clean scrubbed-sku/hash slug. Normal products unaffected; route still dual-key resolves old URLs.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 shared/render.js | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/shared/render.js b/shared/render.js
index f31bdd5..e222209 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -24,15 +24,18 @@ function productSlug(p) {
   let s = scrubVendor(h).replace(/wallpapers/gi, 'wallcoverings').replace(/wallpaper/gi, 'wallcovering')
     .replace(/\s+/g, '-');   // scrubVendor leaves a space where each vendor token was — re-hyphenate for a clean slug
   s = s.replace(/-{2,}/g, '-').replace(/^-+|-+$/g, '');
-  if (s) return s;
-  // The ENTIRE handle was a vendor name (e.g. "brunschwig-fils") → scrubbing it
-  // empties out. Do NOT fall back to the raw handle (that re-leaks the vendor in
-  // the URL). Derive a clean, deterministic, vendor-free slug: scrubbed sku if
-  // usable, else a stable hash of the raw handle. The route recomputes the same
-  // productSlug AND still resolves old /product/<raw-handle> links via x.handle.
+  if (s && !hasVendorToken(s)) return s;
+  // Reaching here means scrubVendor either EMPTIED the handle (whole handle was a
+  // vendor, e.g. "brunschwig-fils") OR left a CONCATENATED vendor token its
+  // separator-requiring regex can't catch (e.g. "wolfgordonwallcovering" — no
+  // separator between wolf+gordon, so \bword[^a-z0-9]+word\b never matches). Either
+  // way, do NOT return the raw/dirty handle (re-leaks the vendor in the URL).
+  // Derive a clean, deterministic, vendor-free slug: scrubbed sku if usable AND
+  // itself vendor-free, else a stable hash of the raw handle. The route recomputes
+  // the same productSlug AND still resolves old /product/<raw-handle> links via x.handle.
   const skuSlug = scrubVendor(String((p && p.sku) || '').toLowerCase())
     .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
-  if (skuSlug) return skuSlug;
+  if (skuSlug && !hasVendorToken(skuSlug)) return skuSlug;
   let hash = 0;
   for (let i = 0; i < h.length; i++) hash = (hash * 31 + h.charCodeAt(i)) >>> 0;
   return 'design-' + hash.toString(36);

← 5aee355 Close C: 'A is enough' (Steve 2026-05-29). Source handle ren  ·  back to Dw Domain Fleet  ·  scrubVendor: optional inter-token separator ([^a-z0-9]*) cat 1bdbba0 →