-- Migration 024 — Revoke anon table discoverability (advisor: anon GraphQL/API access) -- Author: security follow-up item 4. Apply in the Supabase SQL editor. -- -- ARCHITECTURE FACT this rests on: the VYNDR frontend NEVER reads app-data -- tables with the Supabase anon key. All data flows browser -> Next proxy -> -- Express (service role, which bypasses RLS). The anon key is used ONLY for -- Supabase Auth (login/session). So revoking anon SELECT on app-data tables -- does not break the app — it just closes the discovery hole the advisor flags. -- -- REVOKE/KEEP decision (deliberate): -- REVOKE anon SELECT — every app-data table. The "public record" surfaces -- (model ledger, accuracy, public profiles) are served by Express under the -- service role, so even they need no direct anon read. -- KEEP — nothing needs direct anon table SELECT. (If a future feature reads a -- genuinely-public view straight from the browser, grant anon SELECT on -- that SPECIFIC view only, never a base table.) begin; -- Explicitly revoke on the advisor-named tables (definitely safe). do $$ declare t text; begin foreach t in array array[ 'accuracy_tracking', 'bets', 'cascade_alerts', 'closing_lines', 'coach_profiles', 'daily_scan' ] loop if exists (select 1 from information_schema.tables where table_schema = 'public' and table_name = t) then execute format('revoke select on public.%I from anon', t); end if; end loop; end $$; -- BROAD SWEEP (recommended — apply after a quick review). Revoke anon SELECT on -- EVERY existing public table, then leave anon with no base-table discovery. -- Uncomment to apply; the app does not read tables as anon so this is safe. -- -- revoke select on all tables in schema public from anon; -- alter default privileges in schema public revoke select on tables from anon; -- -- After applying, re-run the Security Advisor to confirm the anon-access lint is -- cleared. Grant anon SELECT back ONLY on a specific public VIEW if a browser -- feature ever needs one. commit;