Skip to content

Commit 9fb64b3

Browse files
fix(security): restrict new SECURITY DEFINER functions to authenticated (#509)
Functions created in `public` carry a default PUBLIC execute grant, so the explicit grants to `authenticated` left both new helpers reachable over PostgREST as `anon` — flagged by the Supabase security advisor as `anon_security_definer_function_executable`. Neither leaks anything (`is_tenant_staff()` reports only on the caller and returns false for anon; `get_published_lesson_counts()` returns counts and is tenant-scoped), but neither has any anon caller either: the public catalog reads lesson metadata through the service-role client. Revoking PUBLIC/anon keeps them off the REST surface. Applied to the cloud database and re-applied locally; grants now match on both (`{postgres, authenticated, service_role}`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8ZG2FwbBprWLvFF4V8SVx
1 parent 704f233 commit 9fb64b3

1 file changed

Lines changed: 8 additions & 1 deletion

File tree

supabase/migrations/20260724150000_content_entitlement_rls.sql

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ $function$;
4747
COMMENT ON FUNCTION public.is_tenant_staff() IS
4848
'True when the caller is an active teacher or admin of the tenant in their JWT. Used by content RLS policies to let staff read courses they hold no entitlement for.';
4949

50+
-- Functions in `public` carry a default PUBLIC execute grant, which would
51+
-- expose this over PostgREST to `anon` too. It only ever reports on the
52+
-- caller (false for anon), but least privilege keeps it off the REST surface.
53+
REVOKE EXECUTE ON FUNCTION public.is_tenant_staff() FROM PUBLIC, anon;
5054
GRANT EXECUTE ON FUNCTION public.is_tenant_staff() TO authenticated;
5155

5256
-- lessons ---------------------------------------------------------------
@@ -134,4 +138,7 @@ $function$;
134138
COMMENT ON FUNCTION public.get_published_lesson_counts(integer[]) IS
135139
'Published-lesson counts for catalog listings. SECURITY DEFINER so a prospective buyer sees a real count for courses whose lesson rows content RLS hides from them (#509). Returns counts only — never lesson content.';
136140

137-
GRANT EXECUTE ON FUNCTION public.get_published_lesson_counts(integer[]) TO authenticated, anon;
141+
-- Authenticated only: the public catalog pages read lesson metadata through
142+
-- the service-role client, so anon never needs this.
143+
REVOKE EXECUTE ON FUNCTION public.get_published_lesson_counts(integer[]) FROM PUBLIC, anon;
144+
GRANT EXECUTE ON FUNCTION public.get_published_lesson_counts(integer[]) TO authenticated;

0 commit comments

Comments
 (0)