← back to Bleachresistantfabrics
step2 vendor-leak fix: dual-key /sample resolver + handle_display hrefs
81e085bc5260b80fd92586e2b08a104ebcc55328 · 2026-06-09 13:47:39 -0700 · Steve
Files touched
M public/index.htmlM server.js
Diff
commit 81e085bc5260b80fd92586e2b08a104ebcc55328
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jun 9 13:47:39 2026 -0700
step2 vendor-leak fix: dual-key /sample resolver + handle_display hrefs
---
public/index.html | 2 +-
server.js | 35 ++++++++++++++++++++++++++++++++++-
2 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index f530805..5359ac7 100644
--- a/public/index.html
+++ b/public/index.html
@@ -480,7 +480,7 @@ document.querySelectorAll('#rails .chip').forEach(c => c.addEventListener('click
}));
function card(p) {
- const handle = encodeURIComponent(p.handle || p.sku || '');
+ const handle = encodeURIComponent(p.handle_display || p.handle || p.sku || '');
const url = p.product_url || ('https://designerwallcoverings.com/products/' + handle);
const sample = '/sample/' + handle;
// NOTE: vendor name intentionally NOT rendered — DW standing rule "never
diff --git a/server.js b/server.js
index ba47622..df6c65f 100644
--- a/server.js
+++ b/server.js
@@ -171,8 +171,40 @@ app.get('/api/rails', (req, res) => {
app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS.length }));
+// Step 2 of the vendor handle/sku text-leak fix (2026-06-09): the client's
+// /sample/ hrefs now point at the vendor-scrubbed `handle_display` slug
+// (emitted by _shared/api-vendor-redact since Step 1), so the raw vendor-bearing
+// handle never rides in the URL bar. This route is DUAL-KEY — it resolves the raw
+// handle/sku FIRST (old bookmarks + buy path, byte-identical behavior), then falls
+// back to a redacted->product map built after catalog load. If two raw handles
+// redact to the same display slug, that slug is logged and left raw-only.
+const { redactVendorText } = require('../_shared/vendor-text-redact');
+let SAMPLE_DISPLAY = new Map(); // redacted display slug -> product
+function buildSampleDisplayMap() {
+ const map = new Map();
+ const collided = new Set();
+ for (const p of PRODUCTS) {
+ for (const key of [p.handle, p.sku]) {
+ if (!key) continue;
+ const d = redactVendorText(String(key));
+ if (!d || d === key) continue; // nothing redacted — raw route already matches
+ const prev = map.get(d);
+ if (prev && prev !== p) { collided.add(d); continue; }
+ map.set(d, p);
+ }
+ }
+ for (const d of collided) {
+ map.delete(d);
+ console.warn(`[bleachresistantfabrics] /sample display-slug collision — keeping raw-only for "${d}"`);
+ }
+ SAMPLE_DISPLAY = map;
+ console.log(`[bleachresistantfabrics] /sample dual-key map: ${map.size} display slugs, ${collided.size} collisions (raw-only)`);
+}
+
app.get('/sample/:handle', (req, res) => {
- const p = PRODUCTS.find(x => x.handle === req.params.handle || x.sku === req.params.handle);
+ const key = req.params.handle;
+ const p = PRODUCTS.find(x => x.handle === key || x.sku === key) // raw first — old behavior intact
+ || SAMPLE_DISPLAY.get(key); // then the redacted display form
if (!p) return res.status(404).send('Not found');
res.redirect(302, p.product_url || `${DW_SHOPIFY}/products/${encodeURIComponent(p.handle)}#sample`);
});
@@ -209,6 +241,7 @@ if (catalog) catalog.mount(app, { siteSlug: SITE_SLUG, rails: SITE_RAILS });
PRODUCTS = pipeline(raw);
console.log(`[${SITE}] loaded ${raw.length} from data/products.json (static mode) → niche ${PRODUCTS.length}`);
}
+ buildSampleDisplayMap(); // Step 2: redacted->raw /sample resolver keys
app.listen(PORT, '127.0.0.1', () => {
console.log(`[${SITE}] listening on http://127.0.0.1:${PORT}`);
});
← b55e6c4 Scrub residual vendor names from products.json (title/vendor
·
back to Bleachresistantfabrics
·
banner: wire universal promo-banner require above express.st 5d69c44 →