Items 1,3,4,5 — security migrations (author; apply in Supabase, then re-run advisor)
023_security_hardening.sql: - Item 1 (CRITICAL, advisor lint 0010): founder_pricing_seats view → recreate with security_invoker=on so it respects RLS instead of running as definer. (The founder counter no longer depends on it — item 0 uses Stripe directly.) - Item 3: waitlist write hole — drop the always-true policies, anon may INSERT only, update/delete/read via service role. - Item 5: pin an explicit search_path on the flagged functions (lint 0011). 024_anon_revoke_discoverability.sql: - Item 4: revoke anon SELECT on the advisor-named tables (accuracy_tracking, bets, cascade_alerts, closing_lines, coach_profiles, daily_scan) + a commented broad sweep. The frontend reads data via Express (service role), never as anon, so this is safe. REVOKE/KEEP rationale documented in the file. These need Kev to apply (no DB access from here); fingerprint = re-run the Security Advisor and confirm the lints clear. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
-- 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;
|
||||
Reference in New Issue
Block a user