My nephew found script tags in my menu HTML — stored XSS JSON-LD breakout (PR #565 TT F1+F2)
Margaret Bristol Clifton 47-yo 17-yr Clifton Tavern 50-cover traditional British gastropub off Whiteladies Road thMenu Pro 21 months. Nephew Henry 21-yo CS University of Bristol summer evening helping bar opens menu page DevTools view-source HTML 3 minutes leans over Aunt Maggie product name </script><script>alert('thmenu')</script> stored XSS. Margaret remembered month ago menu update YouTube menu test attacks video curious paste similar string see what happens saved forgot. Henry script type application/ld+json block JSON.stringify doesn't escape < > your </script> real script-close tag for HTML parser following <script>alert script block separate live JavaScript executing menu.thmenu.com cookies session token geolocation attacker URL compromised CSP unsafe-inline doesn't block. Margaret emailed support patch fast. Engineering 30 minutes reproduce 3 wrong theories (1) DOMPurify product.name designed HTML context JSON string entity-encode < > make page visibly read </script> break legitimate < 250kcal salad; (2) CSP nonce-based drop unsafe-inline large refactor every inline script + style nonce signing long-term fragile immediate patch; (3) reject product.name containing < validation breaks < 250kcal Vegan Salad. Correct pattern JSON.stringify wrapper helper escape < > & U+2028 U+2029 \uXXXX field value valid JSON script tag boundaries safe. Forensic apps/web-menu/src/app/[locale]/[restaurant_slug]/page.tsx script type application/ld+json dangerouslySetInnerHTML __html JSON.stringify menuLd classic React anti-pattern RFC 8259 JSON spec only quote backslash control chars < > valid JSON string chars. Same pattern about/page.tsx PR #635 III F1 web-landing JSON-LD render escapeJsonForScriptTag web-menu sweep missed sibling-surface coverage gap. Database scan stored XSS SELECT product_name FROM products WHERE LIKE %</script% OR %<script% Margaret 1 hit production audit 90 days thMenu Pro+ 7 restaurants 12 products similar patterns curious operator self-test no real attacker observed. PR #565 batch TT F1+F2 3-layer fix Layer 1 escapeJsonForScriptTag shared helper apps/web-menu/src/lib/sanitize.ts mirror PR #635 III F1 < \u003c > \u003e & \u0026 U+2028 \u2028 U+2029 \u2029. All JSON-LD render paths dangerouslySetInnerHTML __html escapeJsonForScriptTag JSON.stringify menuLd. Layer 2 AI-translate echo path sanitize PR #565 F2 AI-translate operator-controlled string AI return value manual_translations stored operator </script> AI translation echo stored XSS round-trip sanitizeAiInput helper XSS pattern detection input <script or </script send AI return value reject + log. Layer 3 production database scan + retro purge 12 affected products operator outreach detected script tags didn't mean test escaping render remove tags update regular name 7 operators removed voluntarily. Production audit 90-day Cloudflare access log + Sentry breadcrumbs Margaret-pattern operator-set 0 third-party attacker reproductions bug fortunately self-created curious operators no actual attacker discovered. Margaret thank-you email + Henry Hall of Fame nephew first responsible disclosure XSS thMenu + £500 bounty Henry student 1-year Pro tier credit Clifton Tavern Margaret Twitter 2.7k. Vedat Aydin Soke Soke Bizim Ev Yemekleri 38-cover Aegean home-cooking 22-yr nephew Berkay 22-yo ITU CS parallel same Hall of Fame + 1-year Pro tier credit. Pattern script type application/ld+json JSON content embed JSON.stringify NOT enough escapeJsonForScriptTag helper escape < > & U+2028 U+2029 operator-controlled or user-uploaded break out script tag stored XSS. Sibling sweep web-landing PR #635 III F1 + web-menu menu + about PR #565 TT F1 + web-menu AI translate echo PR #565 TT F2 + web-admin none PR template checkbox. Implementation escapeJsonForScriptTag shared lib + every JSON-LD render route helper + AI/translation echo sanitize input + output + database scan + operator outreach + retro cleanup + PR template checkbox + quarterly grep audit dangerouslySetInnerHTML + JSON.stringify co-occurrence. PR #565 reference.
thMenu Team
thmenu.com
Found this helpful? Share it.
Related articles
Why Digital Menus Increase Restaurant Revenue by Up to 30%
Studies show restaurants using digital QR menus see measurable increases in aver…
When a Customer Downgrades, What Happens to Old Features? — The Silent Feature-Drift Problem in SaaS
Most SaaS apps run a single line of code when a customer downgrades — but old fe…
JWT alg-confusion attack — why Supabase's HS256 → RS256/JWKS migration breaks legacy verifiers
Verifiers that never decode the JWT header are wide open to `alg=none` and alg-c…