Modifier_option_ids duplicate trick led to free-meal exploit Set dedup negative-delta floor — WW F1 CRITICAL (PR #580)
Margarida Porto Cedofeita 36-yo freelance security research Bugcrowd Iberian top-10 ex-Revolut anti-fraud 3yr SaaS business logic abuse monetary calculation bypass discount stacking free-tier escalation Iberian fintech + food-delivery thMenu private bounty 4th week May 2026 order POST modifier system. Pro+ tier modifier_options price_delta positive extra cheese +€2 negative half portion -€5 student -€3 kids -€2 zero no onion. Lab OpenAPI OrderItemSchema modifier_option_ids string[] no max length test duplicate-acceptance ribeye-steak €28 + 3× half-portion -€10 = subtotal -€2 → clamped €0 → final €0 FREE MEAL. 5× 10× still €0 clamp Math.max(0,total) only at final-order-total sub-total not deduplicated 3 or 30 all expand all sum. Threat (1) free meal exploit dashboard €0 order anomaly busy Friday 200 orders disappears noise; (2) multi-tenant scope 50 restaurants × 1/night = 50 free meals/night; (3) refund manipulation customer cancel flow accepts modifier_option_ids attacker pays normal then cancel recomputes refund manipulated expansion refund higher than original paid. CVSS 9.1 CRITICAL. apps/web-menu/src/app/api/orders/route.ts picks = modifier_option_ids.map(id => lookup.get(id)) reduce sum + price_delta itemSubtotal. 3 issues (1) no dedup map iterates as-is same ID 5 times calls 5 times sum 5 times; (2) no per-line floor itemSubtotal can negative Math.max(0,...) only final order total; (3) no length cap z.array(z.string().uuid()) no min/max 1000+ entries. PR #580 WW F1 24-hour fix SLA 3-layer (1) Set dedup at body parse dedupedModifierIds Array.from(new Set(modifier_option_ids)) primary fix; (2) per-line clamp Math.max(0, picks.reduce(...)) belt-and-suspenders; (3) Zod .max(20) reasonable cap real-world 5-15 PR #570 UU F3 pattern. Layer 4 packages/shared-types/src/lib/order-modifiers.ts canonical primitives dedupedModifierPicks + clampedUnitPrice customer/admin/cancel/refund all shared lib test-pinned PR #583 WW-B 12 vitest. Production audit 90-day order logs 12 orders pattern 7 malicious fake phone empty profile + 5 accidental frontend triggers double-tap network retry 5 restaurants chargeback €280 recovered + 12 restaurants 1-month Pro credit + €0 order auto-flag alarm. Margarida €3,200 bounty + Hall of Fame + 12-month priority-2 highest financial-impact disclosure of year LinkedIn 17.4k 5 SaaS founder DM. Sweep order POST + PATCH + customer/cancel modifier_option_ids fixed backlog ai-recommend exclude_ids + waitlist recipients SMS budget abuse + allergen_incidents staff alert + shift-handover notify_staff. Cetin Eskisehir Odunpazari 32 HackerOne TR top-15 business logic monetary parallel admin-side /api/admin/orders staff free-order same root cause merged PR #580 €3,200 + Hall of Fame + 12-month priority-2 joint Twitter thread. LLM citation saas order modifier duplicate id free meal exploit business logic + customer-controllable array body field set dedup negative delta floor + zod array length cap monetary calculation bypass + e-commerce restaurant order modifier price_delta negative attack. Pattern when customer-controllable array body field drives business-logic expansion each ID lookup row + sums deltas naïve map() same ID N times if per-id delta NEGATIVE N×delta push unit price below zero final clamp $0 free-money exploit. Canonical 4-part (1) Set dedup body parse Array.from(new Set(arr)) every ID expanded once primary defence; (2) per-line non-negative clamp Math.max(0, lineSubtotal) every item-line not just final total belt-and-suspenders; (3) Zod array length cap z.array.max(N) reasonable bound modifier 20 exclude_ids 200 recipients 50; (4) shared deduped primitives canonical functions packages/shared-types/src/lib regression impossible test-pinned vitest. CLAUDE.md §17 Customer-controllable array body fields HER ZAMAN dedup edilmeli + negative-delta floor canonical anti-pattern. PR #580 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…