Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2312 min read

My store manager opened the multi-location panel and saw a "hi" popup — brand logo_url was missing safeHref() (stored XSS via zod .url() bypass)

Lieke (40) runs Frituur De Vrouw, a three-location chain in Ghent Patershol. Tuesday night setting up a new sub-brand "Vrouw to Go", she accidentally pasted a Stack Overflow CSP-article snippet — `javascript:alert('hi')` — into the "Logo URL" field instead of the file upload, saved, went to bed. Thursday morning her store manager Sven opened the multi-location panel and got a "hi" popup. thMenu support 3-layer forensic: (1) Why does zod `.url()` accept `javascript:`? Answer: zod calls Node's `new URL()` which parses RFC 3986 valid schemes (javascript:, data:, file:, blob: all pass). (2) React `<img src>` is safe but `<a href>` isn't — anchor tags interpret script schemes on click and on some hover-prefetch paths. (3) thMenu's codebase already had `safeHref()` from PR #334 wired into `restaurants.{wifi_url, logo_url, cover_url}` + `social_links.url`, but the new `brands.logo_url` column (shipped PR #515 multi-location) was missed via helper-vs-new-feature drift. **PR #661 batch XI F3** fix: brands POST + PATCH now wrap `parsed.data.logo_url` through `safeHref(url, { maxLen: 500 })`; invalid scheme → 422 `invalid_logo_url`; PATCH preserves null (clear) vs undefined (no-touch) distinction. The same vector in malicious-operator hands could chain store-manager session hijack → affiliate commission theft → super-admin compromise. Pattern: when a new URL-storing column ships, apply safeHref() (or equivalent scheme allowlist) on POST AND PATCH in the same PR; never rely on render-layer escaping as the only defense — storage-side rejection is the only durable place.

th

thMenu Team

thmenu.com

Found this helpful? Share it.