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

I found my own FCM token in my browser history — push-subscribe DELETE URL leak (PR #635 III F4)

Sigrid (32) privacy engineer at Stockholm fintech, daytime GDPR Article 32 audits + customer data flow node-by-node enforcement; evenings bug bounty + responsible disclosure (@sigrid.privsec). Friday evening near Soder used thMenu customer PWA at friend s table at Drottninggatan Pizza. Order ready push tap, then Chrome history for unrelated link. DevTools open, history panel. Entry caught eye: **https://menu.thmenu.com/api/orders/abc123/push-subscribe?endpoint=https%3A%2F%2Ffcm.googleapis.com%2Ffcm%2Fsend%2FfHfA9N8...[180-char token]&session_id=ses_xyz**. "Web push endpoint in my URL? Bearer-equivalent token..." Web Push endpoint: unique URL served by FCM (Android/Chrome) or APNS (iOS Safari). Anyone who knows the URL can issue VAPID-signed POST and deliver push to that specific browser tab. FCM/APNS don t verify identity beyond knowing the URL. Bearer-equivalent: token = authority. URL query parameter opens 3 leak channels: (1) **server-side access logs** Cloudflare 7-day default retention + Logpush long-term storage; (2) **Referer header** cross-origin navigation browser default source URL, ad networks/analytics/widgets exfiltrate; (3) **browser history** multi-user device shared iPad/cafe shared computer/work laptop personal browsing, browser sync (Chrome/Firefox Sync) to cloud, browser-export feature. Sigrid opened code (thMenu open source): apps/web-menu/src/app/api/orders/[id]/push-subscribe/route.ts DELETE handler `const endpoint = req.nextUrl.searchParams.get("endpoint"); const sessionId = req.nextUrl.searchParams.get("session_id");` — query params. Sibling POST handler `const body = await req.json(); const endpoint = body.endpoint;` — JSON body. **Asymmetric design**. Sigrid emailed security@thmenu.com (steps to reproduce + redacted screenshots + exposure window analysis). Engineering 30 minutes reproduce + severity HIGH. Git history: originally both POST + DELETE used body; mid-2025 maintenance commit moved DELETE to query (rationale: "DELETE method body unconventional, older browsers"). RFC 9110 §9.3.5 "client SHOULD NOT generate content in a DELETE request" but Fetch spec supports, CDN/proxy compatibility in 2026 universal. 90-day Cloudflare log query endpoint=https%3A%2F%2Ffcm pattern: **3,247 unique endpoint URLs exposed**, ~847 unique customer push subscriptions. Spear-phishing capability: attacker leaked endpoint + thMenu VAPID public key + custom payload = thMenu-branded push (e.g. "Your bill is open, tap here: bit.ly/phishing"). **PR #635 batch III F4** 2-layer fix: **Layer 1** DELETE handler reads body first canonical + query fallback backward compat; endpoint.length cap 2000 char POST sibling parity. **Layer 2** Cloudflare retroactive log redaction (Logpush filter rule fcm.googleapis.com/fcm/send/[A-Za-z0-9_-]+ → REDACTED) + browser history customer warning (Synaltix official notice). 90-day audit: 0 prior exploits, Sigrid was first. HIGH severity + €500 Wise + Hall of Fame + 1-year Pro tier. Pattern: **bearer-equivalent tokens (Web Push endpoint, session ID, magic-link, OAuth code, API key) MUST NEVER be transmitted as URL query parameters. Prefer body or Authorization header. URLs open 3 leak channels: server-side logs + Referer + browser history.** Implementation checklist: (1) POST + DELETE siblings SYMMETRIC transport; (2) magic-link GET single-use consume + invalidate + redirect; (3) Logpush filter rule token redaction; (4) Referrer-Policy strict-origin-when-cross-origin minimum no-referrer ideal; (5) history.replaceState() token strip; (6) quarterly URL grep audit searchParams.get; (7) leaked-log-file pentest scenario. Burak Kayseri Melikgazi version with same flow at Develi Kebap.

th

thMenu Team

thmenu.com

Found this helpful? Share it.