Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5322603
fix(db): revoke remaining anon oracle RPC execute grants
cursoragent Sep 8, 2026
045b3f1
chore(ci): retrigger workflow after Cloudflare Workers startup flake
cursoragent Sep 8, 2026
3769895
chore(ci): retrigger after backend shard 2 edge 502 flake
cursoragent Sep 8, 2026
a1dbc80
test: retry edge 502 flakes in app and cron_stat_org shards
cursoragent Sep 8, 2026
0b52ce5
test: add retryUnsafe to remaining cron_stat_org POST calls
cursoragent Sep 8, 2026
728e290
chore(ci): retrigger after runner cancel and playwright startup flake
cursoragent Sep 8, 2026
623ba93
test: retry POST /apikey 502 flakes in apikeys shard
cursoragent Sep 8, 2026
afe8213
test: drop non-idempotent retryUnsafe; assert auth execute on revoked…
TorichanCapgo Sep 9, 2026
afe9d2e
fix(db): ORG_NOT_FOUND for internal missing org on member helpers
TorichanCapgo Sep 9, 2026
cac5528
chore(schema): sync prod.sql with oracle RPC migration bodies
cursoragent Sep 9, 2026
27a4f2e
test(security): reject OK in missing-invite oracle assertions
cursoragent Sep 9, 2026
7dbb549
style(db): wrap long REVOKE/GRANT lines for SQLFluff LT05
cursoragent Sep 9, 2026
1ae3639
test(apikeys): harden POST /apikey against cold-isolate 502 flakes
cursoragent Sep 9, 2026
244593c
test(apikeys): abort hung POST /apikey fetches at deadline
cursoragent Sep 9, 2026
b610745
chore(ci): re-trigger CodeRabbit review on 244593c fixes
cursoragent Sep 9, 2026
6589af2
test(apikeys): abort cleanup fetches at postApiKey deadline
cursoragent Sep 9, 2026
c521f54
test(apikeys): keep abort budget through response body reads
cursoragent Sep 9, 2026
b98aae9
test(apikeys): propagate abort during 502 body read
cursoragent Sep 9, 2026
473ec4f
chore(ci): retrigger after edge 502 flake on PR workflow shard 6/6
cursoragent Sep 9, 2026
6fcd9f2
test(apikeys): require deadline in deleteApiKeysByName cleanup
cursoragent Sep 9, 2026
61f009b
chore(ci): retrigger after plugin serial 502 flake
cursoragent Sep 9, 2026
708114a
test(apikeys): cap retry delay and bound POST warmup by deadline
cursoragent Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
-- Complete the anon oracle RPC hardening started in
-- 20260824144021_revoke_anon_oracle_rpc_execute.sql.
--
-- 1) Revoke anonymous EXECUTE on SECURITY DEFINER helpers that enumerate or
-- infer org/app/member state and are only needed from signed-in console JWT
-- traffic (authenticated role). Published CLI keeps anon EXECUTE on
-- get_user_id(text) and the capgkey-scoped helpers it still calls.
-- 2) Remove distinguishable "Organization does not exist" vs "NO_RIGHTS"
-- outcomes from org-member helpers that must remain anon-callable for CLI.
-- Internal/service_role callers still get ORG_NOT_FOUND for a missing org.

-- ---------------------------------------------------------------------------
-- Fix org-member helpers: same denial for missing org and missing permission.
-- ---------------------------------------------------------------------------

CREATE OR REPLACE FUNCTION public.check_org_members_2fa_enabled(org_id uuid)
RETURNS TABLE(user_id uuid, "2fa_enabled" boolean)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
BEGIN
-- Internal callers keep a distinguishable missing-org signal; non-internal
-- callers still collapse missing-org into NO_RIGHTS (anon oracle closed).
IF public.is_internal_request_role(public.current_request_role()) THEN
IF NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_2fa_enabled.org_id
) THEN
RAISE EXCEPTION 'ORG_NOT_FOUND';
Comment thread
cursor[bot] marked this conversation as resolved.
END IF;
ELSIF (
NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_2fa_enabled.org_id
)
OR NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_2fa_enabled.org_id,
NULL::character varying,
NULL::bigint
)
)
THEN
RAISE EXCEPTION 'NO_RIGHTS';
END IF;

RETURN QUERY
SELECT DISTINCT
rb.principal_id AS user_id,
COALESCE(public.has_2fa_enabled(rb.principal_id), false) AS "2fa_enabled"
FROM public.role_bindings rb
JOIN public.roles r ON r.id = rb.role_id
AND r.scope_type = rb.scope_type
WHERE rb.principal_type = public.rbac_principal_user()
AND rb.scope_type = public.rbac_scope_org()
AND rb.org_id = check_org_members_2fa_enabled.org_id
AND (rb.expires_at IS NULL OR rb.expires_at > now())
AND r.name LIKE 'org_%';
END;
$$;

ALTER FUNCTION public.check_org_members_2fa_enabled(uuid) OWNER TO postgres;

CREATE OR REPLACE FUNCTION public.check_org_members_password_policy(org_id uuid)
RETURNS TABLE(
user_id uuid,
email text,
first_name text,
last_name text,
password_policy_compliant boolean
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
BEGIN
-- Internal callers keep a distinguishable missing-org signal; non-internal
-- callers still collapse missing-org into NO_RIGHTS (anon oracle closed).
IF public.is_internal_request_role(public.current_request_role()) THEN
IF NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_password_policy.org_id
) THEN
RAISE EXCEPTION 'ORG_NOT_FOUND';
END IF;
ELSIF (
NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_password_policy.org_id
)
OR NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_password_policy.org_id,
NULL::character varying,
NULL::bigint
)
)
THEN
RAISE EXCEPTION 'NO_RIGHTS';
END IF;

RETURN QUERY
SELECT DISTINCT
rb.principal_id AS user_id,
au.email::text,
u.first_name::text,
u.last_name::text,
public.user_meets_password_policy(
rb.principal_id,
check_org_members_password_policy.org_id
) AS password_policy_compliant
FROM public.role_bindings rb
JOIN public.roles r ON r.id = rb.role_id
AND r.scope_type = rb.scope_type
JOIN auth.users au ON au.id = rb.principal_id
LEFT JOIN public.users u ON u.id = rb.principal_id
WHERE rb.principal_type = public.rbac_principal_user()
AND rb.scope_type = public.rbac_scope_org()
AND rb.org_id = check_org_members_password_policy.org_id
AND (rb.expires_at IS NULL OR rb.expires_at > now())
AND r.name LIKE 'org_%';
END;
$$;

ALTER FUNCTION public.check_org_members_password_policy(uuid) OWNER TO postgres;

-- ---------------------------------------------------------------------------
-- Revoke anonymous EXECUTE on org/member oracle RPCs (console uses JWT).
-- exist_app / exist_app_v2 stay anon-callable: they return false without a
-- valid capgkey and do not distinguish missing apps from denied access.
-- ---------------------------------------------------------------------------

REVOKE ALL ON FUNCTION public.get_org_members_rbac(uuid) FROM anon;

REVOKE ALL ON FUNCTION public.is_member_of_org(uuid, uuid) FROM anon;
Comment thread
TorichanCapgo marked this conversation as resolved.

REVOKE ALL ON FUNCTION public.update_org_invite_role_rbac(
uuid, uuid, text
) FROM anon;

REVOKE ALL ON FUNCTION public.update_tmp_invite_role_rbac(
uuid, text, text
) FROM anon;

GRANT EXECUTE ON FUNCTION public.get_org_members_rbac(uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.get_org_members_rbac(uuid) TO service_role;

GRANT EXECUTE ON FUNCTION public.is_member_of_org(uuid, uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.is_member_of_org(uuid, uuid) TO service_role;

GRANT EXECUTE ON FUNCTION public.update_org_invite_role_rbac(
uuid, uuid, text
) TO authenticated;
GRANT EXECUTE ON FUNCTION public.update_org_invite_role_rbac(
uuid, uuid, text
) TO service_role;

GRANT EXECUTE ON FUNCTION public.update_tmp_invite_role_rbac(
uuid, text, text
) TO authenticated;
GRANT EXECUTE ON FUNCTION public.update_tmp_invite_role_rbac(
uuid, text, text
) TO service_role;
77 changes: 48 additions & 29 deletions supabase/schemas/prod.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3178,16 +3178,28 @@ CREATE OR REPLACE FUNCTION "public"."check_org_members_2fa_enabled"("org_id" "uu
SET "search_path" TO ''
AS $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM public.orgs WHERE public.orgs.id = check_org_members_2fa_enabled.org_id) THEN
RAISE EXCEPTION 'Organization does not exist';
END IF;

IF NOT public.is_internal_request_role(public.current_request_role())
AND NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_2fa_enabled.org_id,
NULL::character varying,
NULL::bigint
-- Internal callers keep a distinguishable missing-org signal; non-internal
-- callers still collapse missing-org into NO_RIGHTS (anon oracle closed).
IF public.is_internal_request_role(public.current_request_role()) THEN
IF NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_2fa_enabled.org_id
) THEN
RAISE EXCEPTION 'ORG_NOT_FOUND';
END IF;
ELSIF (
NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_2fa_enabled.org_id
)
OR NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_2fa_enabled.org_id,
NULL::character varying,
NULL::bigint
)
)
THEN
RAISE EXCEPTION 'NO_RIGHTS';
Expand Down Expand Up @@ -3217,32 +3229,43 @@ CREATE OR REPLACE FUNCTION "public"."check_org_members_password_policy"("org_id"
SET "search_path" TO ''
AS $$
BEGIN
IF NOT public.is_internal_request_role(public.current_request_role())
AND NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_password_policy.org_id,
NULL::character varying,
NULL::bigint
-- Internal callers keep a distinguishable missing-org signal; non-internal
-- callers still collapse missing-org into NO_RIGHTS (anon oracle closed).
IF public.is_internal_request_role(public.current_request_role()) THEN
IF NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_password_policy.org_id
) THEN
RAISE EXCEPTION 'ORG_NOT_FOUND';
END IF;
ELSIF (
NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_password_policy.org_id
)
OR NOT public.rbac_check_permission_request(
public.rbac_perm_org_update_settings(),
check_org_members_password_policy.org_id,
NULL::character varying,
NULL::bigint
)
)
THEN
RAISE EXCEPTION 'NO_RIGHTS';
END IF;

IF NOT EXISTS (
SELECT 1
FROM public.orgs
WHERE public.orgs.id = check_org_members_password_policy.org_id
) THEN
RAISE EXCEPTION 'Organization does not exist';
END IF;

RETURN QUERY
SELECT DISTINCT
rb.principal_id AS user_id,
au.email::text,
u.first_name::text,
u.last_name::text,
public.user_meets_password_policy(rb.principal_id, check_org_members_password_policy.org_id) AS password_policy_compliant
public.user_meets_password_policy(
rb.principal_id,
check_org_members_password_policy.org_id
) AS password_policy_compliant
FROM public.role_bindings rb
JOIN public.roles r ON r.id = rb.role_id
AND r.scope_type = rb.scope_type
Expand Down Expand Up @@ -27228,7 +27251,6 @@ GRANT ALL ON FUNCTION "public"."get_org_members"("user_id" "uuid", "guild_id" "u


REVOKE ALL ON FUNCTION "public"."get_org_members_rbac"("p_org_id" "uuid") FROM PUBLIC;
GRANT ALL ON FUNCTION "public"."get_org_members_rbac"("p_org_id" "uuid") TO "anon";
GRANT ALL ON FUNCTION "public"."get_org_members_rbac"("p_org_id" "uuid") TO "authenticated";
GRANT ALL ON FUNCTION "public"."get_org_members_rbac"("p_org_id" "uuid") TO "service_role";

Expand Down Expand Up @@ -27643,7 +27665,6 @@ GRANT ALL ON FUNCTION "public"."is_mau_exceeded_by_org"("org_id" "uuid") TO "ser

REVOKE ALL ON FUNCTION "public"."is_member_of_org"("user_id" "uuid", "org_id" "uuid") FROM PUBLIC;
GRANT ALL ON FUNCTION "public"."is_member_of_org"("user_id" "uuid", "org_id" "uuid") TO "service_role";
GRANT ALL ON FUNCTION "public"."is_member_of_org"("user_id" "uuid", "org_id" "uuid") TO "anon";
GRANT ALL ON FUNCTION "public"."is_member_of_org"("user_id" "uuid", "org_id" "uuid") TO "authenticated";


Expand Down Expand Up @@ -28919,7 +28940,6 @@ GRANT ALL ON FUNCTION "public"."update_apps_build_timeout_updated_at"() TO "serv


REVOKE ALL ON FUNCTION "public"."update_org_invite_role_rbac"("p_org_id" "uuid", "p_user_id" "uuid", "p_new_role_name" "text") FROM PUBLIC;
GRANT ALL ON FUNCTION "public"."update_org_invite_role_rbac"("p_org_id" "uuid", "p_user_id" "uuid", "p_new_role_name" "text") TO "anon";
GRANT ALL ON FUNCTION "public"."update_org_invite_role_rbac"("p_org_id" "uuid", "p_user_id" "uuid", "p_new_role_name" "text") TO "authenticated";
GRANT ALL ON FUNCTION "public"."update_org_invite_role_rbac"("p_org_id" "uuid", "p_user_id" "uuid", "p_new_role_name" "text") TO "service_role";

Expand All @@ -28938,7 +28958,6 @@ GRANT ALL ON FUNCTION "public"."update_sso_providers_updated_at"() TO "authentic


REVOKE ALL ON FUNCTION "public"."update_tmp_invite_role_rbac"("p_org_id" "uuid", "p_email" "text", "p_new_role_name" "text") FROM PUBLIC;
GRANT ALL ON FUNCTION "public"."update_tmp_invite_role_rbac"("p_org_id" "uuid", "p_email" "text", "p_new_role_name" "text") TO "anon";
GRANT ALL ON FUNCTION "public"."update_tmp_invite_role_rbac"("p_org_id" "uuid", "p_email" "text", "p_new_role_name" "text") TO "authenticated";
GRANT ALL ON FUNCTION "public"."update_tmp_invite_role_rbac"("p_org_id" "uuid", "p_email" "text", "p_new_role_name" "text") TO "service_role";

Expand Down
Loading
Loading