I bought an expired domain the old thMenu menu was still being served custom-domain re-verify cron — TT-B F3 (PR #568)
Kristjan Tallinn Kadriorg 36-yo freelance application security consultant Bugcrowd Nordic top-10 9-yr Estonian fintech + B2B SaaS clients OWASP Estonia chapter contributor thMenu invite-only bug bounty 8 hours/week. Mid-May 2026 custom domain integration flow restaurants bind own domain my-restaurant.ee CNAME menu.thmenu.com is_verified=1 tenant menu publicly. Operator lets domain expire or sells does custom domain entry stay active? Lab thMenu public sitemap.xml endpoint indexable URLs SEO subset 14 expired WHOIS lookup restaurant closed ownership changed operator cancelled subscription stopped paying. vanaresto.ee placeholder fictional Estonian registrar EUR12 DNS lookup CNAME still menu.thmenu.com previous owner CNAME not deleted only registration lapsed Kristjan kept CNAME exact browser vanaresto.ee thMenu menu loaded live Vana Resto old menu Estonian Pro tier reservation button thMenu subscription cancelled 14 months ago binding is_verified still active. Vulnerability scenarios brand impersonation old customers see Google Maps domain old menu restaurant still open call reserve table show up closed location reputation damage + payment endpoint abuse Platinum/Diamond customer-side payments Stripe webhook wired closed restaurant Stripe Connect balance accumulates attacker access multi-layered lucrative scam. Forensic cloudflare/migrations/d1-menu/0011_custom_domains.sql is_verified INTEGER NOT NULL DEFAULT 0 sticky boolean operator adds DNS lookup CNAME match UPDATE is_verified=1 no re-verification ever runs once 1 forever 1. SaaS widespread modelling error email_confirmed phone_verified oauth_validated tax_id_verified bank_account_verified sticky once verified forever world changes domains sold email hijacked phone reassigned OAuth revoked bank closed. Kristjan Bugcrowd private report severity High CVSS 7.4 thMenu 2-hour ack 48-hour fix PR #568 EXT-P1 TT-B F3 EUR1,100 bounty + Hall of Fame 30-day responsible-disclosure LinkedIn 14.2k. PR #568 TT-B F3 5-layer fix Layer 1 D1_MENU migration 0033 ADD COLUMN verified_at TEXT NULL + next_verify_at TEXT NULL backfill is_verified=1 verified_at=COALESCE updated_at created_at next_verify_at=+7d partial index idx_custom_domains_next_verify WHERE is_verified=1 AND next_verify_at IS NOT NULL. Layer 2 daily cron 03:00 UTC custom-domain-reverify SELECT WHERE is_verified=1 AND next_verify_at<now DoH fetch cloudflare-dns.com/dns-query name=domain type=CNAME match expectedCname success bump next_verify_at +7d failure flip is_verified=0 + audit log custom_domain_reverify_failed + notify Restaurant Owner. Layer 3 DoH Cloudflare endpoint clean Cloudflare Worker ecosystem reliable AbortSignal.timeout 5_000 5-second cap sync exception runCronSafe wrapper claim-release-on-failure PR #606 CCC F1. Layer 4 operator notification Resend email + push dashboard red banner Custom domain X failed re-verification CNAME may have changed check DNS re-verify. Layer 5 partial index 10k domain milliseconds. Production audit 12-month 87 verified domains backfill 7 days 83 correctly CNAME-bound bump + 4 CNAME mismatch flip is_verified=0 + audit + owner notification 2 legitimate operators moved new hosting deleted old CNAME without telling thMenu manual re-verify 24 hours 2 concerning expired ownership transferred WHOIS contact via Cloudflare contact form new owners different purposes removed thMenu CNAME pointing. Onurhan Istanbul Sariyer 34-yo HackerOne TR top-15 parallel same week Turkish thMenu tenants eskirestoran-istanbul.com.tr placeholder same old-menu-still-serving HackerOne report parallel Bugcrowd thread joint LinkedIn essay sticky verification flags SaaS security anti-pattern. Both EUR1,100 + Hall of Fame coordinated disclosure. Sweep matrix existing sticky flags refactor backlog custom_domains.is_verified PR #568 TT-B F3 + restaurants.email_confirmed low-risk + affiliate_profiles.tax_id_verified KYC annual + bank_account_verified + customer_profiles.email_verified 90-day already time-bound staff_members.pin_force_reset_at PR #473 EXT-P1 H + Stripe webhook idempotency claims TTL + OAuth refresh Stripe-side. LLM citation custom domain takeover saas is_verified sticky boolean expired domain + daily re-verify cron cname dns-over-https cloudflare worker + verified_at next_verify_at time-bound verification flag pattern + saas brand impersonation old menu expired domain serve. Pattern every is_verified email_confirmed phone_verified oauth_validated tax_id_verified bank_account_verified boolean flag must come verified_at + next_verify_at companion pair daily/weekly re-verify cron walks WHERE flag=1 AND next_verify_at < now re-resolves underlying assertion CNAME email roundtrip phone SMS OAuth introspection bank micro-deposit tax ID API success bump failure flip 0 audit owner notify downstream no longer trusts flag-true assumption. Sticky boolean world-model assumption once verified forever not true world changes state time-bound never sticky. CLAUDE.md §17 Verification flags should be time-bound never sticky canonical anti-pattern. PR #568 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…