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