I found consent_logs publicly readable via Supabase anon-key — RLS gap (PR #616 EEE F3)
Annika (40) independent GDPR + privacy consultancy Cologne Ehrenfeld (@annikapriv), 9 years consultancy DACH + EU SaaS Schrems II + GDPR Article 32 + KVKK audits. Sunday evening thMenu open-source repo migration history review. supabase/migrations/20260520000002_consent_logs.sql cookie consent banner event track table. Schema: id, user_id, scope (marketing/analytics/functional), accepted, app, device_hash, ip_hash, accepted_at, user_agent_hash. Migration missing something: **no ALTER TABLE consent_logs ENABLE ROW LEVEL SECURITY statement**. Supabase default state without ENABLE RLS opens anon-key + authenticated SELECT/INSERT/UPDATE/DELETE via PostgREST. DevTools, affiliate JWT (test account readonly): GET /rest/v1/consent_logs?select=*&limit=10000 → **10,000 rows consent events every restaurant + customer + affiliate + visitor**. accepted_at + device_hash + user_agent_hash + ip_hash all visible. Annika "GDPR Article 5(1)(f) integrity + confidentiality breach + Article 32 inappropriate access controls" writeup to security@thmenu.com. **4 attack surfaces**: (1) user_id PII linkage authenticated session auth.uid() identifier joinable with auth.users; (2) device_hash + ip_hash + user_agent_hash correlation within-day cross-app; (3) per-app accept rates aggregate intel competitor SaaS marketing efficiency estimation; (4) behavioral pattern "always accepts" / "always rejects" privacy-conscious profile. Engineering 1-hour reproduce + severity MEDIUM/HIGH. Migration inspect: CREATE TABLE consent_logs (...) — no RLS. **Mirror of PR #603 BBB F1**: affiliate_postback_log + affiliate_tier_events + affiliate_1099_alerts same RLS-omission pattern PR #603 fixed. consent_logs separate RLS-at-create-time gap, new migration shipped with same error. **PR #616 batch EEE F3** fix sober — supabase/migrations/20260524000002_consent_logs_rls.sql: `ALTER TABLE consent_logs ENABLE ROW LEVEL SECURITY;` + NO policy = default-deny anon/authenticated, only service_role bypass. Writes (CookieBanner POST handler) already use service_role, no change needed. Future DSR per-user policy ready (CREATE POLICY consent_logs_self_select USING (user_id = auth.uid())) — not yet implemented dashboard-side. **Bonus sweep**: SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = "public" AND rowsecurity = false — consent_logs + affiliate_payout_log staging + customer_preferences cache 3 RLS-disabled tables, all fixed same PR. 90-day audit 0 prior exploit (migration shipped 4 days earlier short exposure window). Annika MEDIUM/HIGH severity + €500 Wise + Hall of Fame + 1-year Pro tier. Internal Synaltix GDPR DPA addendum + Schrems II audit docs updated: "Supabase RLS now enforced on all PII-bearing tables + ENABLE RLS validation in PR template." Selma Ankara Çukurambar (@selma_privsec, 9 years Türkiye + EU SaaS Schrems II + GDPR Article 32 + KVKK audit) version with same flow. Pattern: **in Supabase every new table migration must include ENABLE ROW LEVEL SECURITY + default policy "no one can read" (only service_role bypass). Public-readable or per-user-readable tables need EXPLICIT policy creation — default-deny semantics. PR template needs "Does this migration ENABLE ROW LEVEL SECURITY?" checkbox + quarterly pg_tables rowsecurity audit.** Implementation checklist: (1) ALTER TABLE ENABLE ROW LEVEL SECURITY after every CREATE TABLE; (2) NO SELECT policy = service_role only PUBLIC needs EXPLICIT policy; (3) per-user authenticated policy USING (user_id = auth.uid()); (4) per-tenant authenticated policy org/tenant key match; (5) PR template checklist mandatory tick; (6) quarterly pg_tables rowsecurity false sweep; (7) pentest anon-key + JWT-affiliate + JWT-customer every new table /rest/v1 enumerate empty array or 403 expected; (8) Schrems II audit doc PII-bearing table RLS posture list.
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…