api-keys endpoint had no rate limit, no cap, wildcard scope — ALLOWED_SCOPES set (PR #560 SS F3)
Iona Edinburgh New Town 39-yo 10-yr API security research practice 5-yr Skyscanner platform security + 3-yr TravelTech consulting + 2-yr solo UK + Nordic SaaS audits. Q1 2026 thMenu public API documentation /api/keys Pro+ admin user mint API keys. Iona dev sandbox account web-admin Create API Key button POST key cleartext once three observations (1) no rate limit on endpoint; (2) no per-account cap active keys; (3) scope field free-form text including wildcard. Attack flow (1) attacker compromises thMenu admin account phishing leaked password session hijack PIN/password hash hardening + rate-limit hard not impossible; (2) hits API key creation endpoint repeatedly no rate limit 100 keys per second possible; (3) each key scope * wildcard no scope validation accepted grants access every endpoint; (4) no active key cap stamp out 10000+ keys distribute multiple IPs User-Agents thMenu detects compromise revoking attacker mint parallel cat-and-mouse; (5) attacker logs out admin session 10000 wildcard keys remain active customer profiles + admin operations under attacker control. Writeup CVSS 7.2 HIGH privilege escalation + persistence vector. Engineering 3 wrong theories (1) revocation endpoint exists hijack detected delete all reactive defense proactive caps required; (2) only existing scope wildcard fine-grained scopes aren't fully rolled out future-proofing default deny; (3) rate-limit middleware exists generically Cloudflare Worker layer generic 300/5min hijacked admin 300 far too high endpoint tighter bound. PR #560 SS F3 3-layer fix Layer 1 ALLOWED_SCOPES set apps/web-admin/src/lib/api-keys.ts canonical menu:read/write + orders:read/write + customers:read + analytics:read + webhooks:write + pos:sync. scopes.every(s => ALLOWED_SCOPES.has(s)) otherwise 422 invalid_scope wildcard rejected explicit deny by exclusion. Layer 2 per-user rate-limit 10/min checkRateLimit endpointId api-keys-create limit 10 windowMs 60000 daily-salted IP hash + user_id composite key legitimate dev usage covered bot-style minting bounded. Layer 3 active key cap 20 per user SELECT COUNT(*) FROM api_keys WHERE user_id AND is_active=1 count > 20 429 active_key_cap_reached generous legitimate enterprise prevents hijacker 10000 keys. Bonus audit-log every create event operator dashboard API key creation history widget suspicious 5+ in 1 hour same IP loud-log + Sentry beacon. Production audit 847 active API keys 92 user accounts (a) 819 (97%) wildcard 60-day deprecation update required by 2026-08; (b) 28 users 5+ active keys 5 users 50+ legitimate enterprise integrations enterprise_key_cap flag cap 100; (c) 0 users hijacked-pattern no active threat at deploy. Iona €1700 Wise bounty CVSS 7.2 + Hall of Fame + advisory board LinkedIn 4.1k Nordic API security audit benchmark. Mert Ankara Cankaya 36-yo 8-yr ex-Trendyol platform security parallel disclosure €1500 Turkish security community blog 2.8k. Pattern API key creation endpoints three controls together (1) ALLOWED_SCOPES set default deny wildcard reject; (2) per-user rate-limit legitimate dev bot prevention; (3) active key cap privilege-escalation persistence vector. Sibling sweep /api/keys SS F3 + /api/oauth/applications same pattern + /api/webhooks (PR #563 SS-B dual-secret rotation + 10 per-user cap) + /api/affiliate/postback-secret (PR #609 CCC-B + 1 cap) + /api/staff (PIN cap 50 + per-IP rate-limit). Implementation credential-creation endpoint identify + scope/permission validation canonical ALLOWED_SCOPES set + endpoint-specific rate-limit default 300/5min too loose + per-user active credential cap 20 default enterprise override + audit-log every creation event + suspicious-pattern detection 5+ in 1 hour + UI dashboard credential history widget revoke-all + PR template checkbox + quarterly grep audit. PR #560 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…