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

I found a PostgREST URL injection — affiliate handler Supabase RLS bypass vector (PR #646 VI F2)

Margaux (33) security researcher in Bordeaux Saint-Pierre, part-time French consultancy + weekend bug bounty as @margauxsec. Sunday poking at thMenu affiliate dashboard with Burp Suite. Normal GET /api/affiliate/transactions?affiliate_id=margaux2025 → 38 rows own transactions. URL manipulated: ?affiliate_id=margaux2025&or=affiliate_id.eq.competitor789 → response 38 + 27 = 65 rows own + competitor s transactions. **Cross-tenant data exposure via PostgREST URL injection**. PostgREST filter syntax (eq, or, in, gt, lt) allows operator composition; client-supplied input directly URL-interpolated allows injection. Engineering reproduced + PostgreSQL slow query log: "SELECT * FROM affiliate_transactions WHERE (affiliate_id = margaux2025) OR (affiliate_id = competitor789)". RLS policy auth.uid() = affiliate_id should have filtered competitor s rows — but didn t. Code: `const url = ${supabaseUrl}/rest/v1/affiliate_transactions?affiliate_id=eq.${affiliateId}&${qs}; fetch(url, { headers: { Authorization: Bearer ${SUPABASE_SERVICE_ROLE_KEY}, apikey: SUPABASE_SERVICE_ROLE_KEY } })`. **Two compounding issues**: (1) URL manual template literal + qs forwards entire client query string verbatim → Margaux s or= injected. (2) **Service-role key used → RLS bypassed entirely**. Combined: OR injection + service-role bypass = cross-tenant exposure. **PR #646 batch VI F2** two-layer fix: (1) supabase-js client query builder: `supabase.from(table).select().eq(column, value)` properly escapes inputs; operator injection prevented. (2) Anon key + user JWT in Authorization header — RLS enforced at PostgreSQL level. Each layer independently closes the vector; both together is defense-in-depth. Margaux: Critical severity + €500 Wise transfer + Hall of Fame + 1-year unlimited Pro tier. Impact assessment: 90-day audit log review showed no prior exploits — Margaux was first to find + first to disclose. Pattern: never construct PostgREST URLs manually (template literal, string concat); always use supabase-js client query builder. Reserve service-role keys for admin-only flows (cron jobs, webhook handlers, scheduled tasks); user-facing endpoints use anon key + user JWT so RLS enforces. Defense-in-depth: layers should be independent — each alone should close the vector.

th

thMenu Team

thmenu.com

Found this helpful? Share it.