From ae01f7d60b63ec30c4f4786544c966a6036c03e3 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 22 Sep 2026 23:40:17 +0000 Subject: [PATCH] fix(database): restrict workspace service RPCs Co-Authored-By: Claude Code --- ...22233256_harden_workspace_function_acl.sql | 11 ++++++++++ supabase/tests/workspace_practice_test.sql | 20 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 supabase/migrations/20260922233256_harden_workspace_function_acl.sql diff --git a/supabase/migrations/20260922233256_harden_workspace_function_acl.sql b/supabase/migrations/20260922233256_harden_workspace_function_acl.sql new file mode 100644 index 000000000..7e112a070 --- /dev/null +++ b/supabase/migrations/20260922233256_harden_workspace_function_acl.sql @@ -0,0 +1,11 @@ +REVOKE ALL ON FUNCTION public.start_workspace_thread_generation(uuid, uuid, uuid, uuid, uuid, uuid, text), + public.complete_workspace_thread_generation(uuid, uuid, uuid, uuid, text, text) +FROM PUBLIC, anon, authenticated; + +GRANT EXECUTE ON FUNCTION public.start_workspace_thread_generation(uuid, uuid, uuid, uuid, uuid, uuid, text), + public.complete_workspace_thread_generation(uuid, uuid, uuid, uuid, text, text) +TO service_role; + +REVOKE ALL ON FUNCTION private.capture_workspace_version_text(), + private.restore_workspace_state_text() +FROM PUBLIC, anon, authenticated, service_role; diff --git a/supabase/tests/workspace_practice_test.sql b/supabase/tests/workspace_practice_test.sql index 10c9f3e17..ac48de43d 100644 --- a/supabase/tests/workspace_practice_test.sql +++ b/supabase/tests/workspace_practice_test.sql @@ -1,5 +1,5 @@ BEGIN; -SELECT plan(30); +SELECT plan(32); SELECT has_table('public', 'workspace_sources', 'shared sources exist'); SELECT has_table('public', 'workspace_threads', 'shared threads exist'); @@ -11,6 +11,24 @@ SELECT has_function('public', 'save_workspace_thread_message', ARRAY['uuid','uui SELECT has_function('public', 'start_workspace_thread_generation', ARRAY['uuid','uuid','uuid','uuid','uuid','uuid','text'], 'thread generation starts atomically'); SELECT has_function('public', 'complete_workspace_thread_generation', ARRAY['uuid','uuid','uuid','uuid','text','text'], 'assistant completion stays server-owned'); SELECT has_function('public', 'save_workspace_artifact', ARRAY['uuid','uuid','uuid','uuid','bigint','text','text','jsonb','jsonb','jsonb'], 'artifacts use an authorized RPC'); +SELECT ok( + NOT has_function_privilege('anon', 'public.start_workspace_thread_generation(uuid,uuid,uuid,uuid,uuid,uuid,text)', 'EXECUTE') + AND NOT has_function_privilege('anon', 'public.complete_workspace_thread_generation(uuid,uuid,uuid,uuid,text,text)', 'EXECUTE') + AND NOT has_function_privilege('authenticated', 'public.start_workspace_thread_generation(uuid,uuid,uuid,uuid,uuid,uuid,text)', 'EXECUTE') + AND NOT has_function_privilege('authenticated', 'public.complete_workspace_thread_generation(uuid,uuid,uuid,uuid,text,text)', 'EXECUTE') + AND has_function_privilege('service_role', 'public.start_workspace_thread_generation(uuid,uuid,uuid,uuid,uuid,uuid,text)', 'EXECUTE') + AND has_function_privilege('service_role', 'public.complete_workspace_thread_generation(uuid,uuid,uuid,uuid,text,text)', 'EXECUTE'), + 'thread generation RPCs are service-role only' +); +SELECT ok( + NOT has_function_privilege('anon', 'private.capture_workspace_version_text()', 'EXECUTE') + AND NOT has_function_privilege('authenticated', 'private.capture_workspace_version_text()', 'EXECUTE') + AND NOT has_function_privilege('service_role', 'private.capture_workspace_version_text()', 'EXECUTE') + AND NOT has_function_privilege('anon', 'private.restore_workspace_state_text()', 'EXECUTE') + AND NOT has_function_privilege('authenticated', 'private.restore_workspace_state_text()', 'EXECUTE') + AND NOT has_function_privilege('service_role', 'private.restore_workspace_state_text()', 'EXECUTE'), + 'workspace text projection triggers are not directly executable' +); INSERT INTO auth.users (id) VALUES ('91111111-1111-4111-8111-111111111111'),