Skip to content

Comments

Fix org member RPC access controls#1669

Merged
riderx merged 2 commits intomainfrom
riderx/fix-update-bug
Feb 25, 2026
Merged

Fix org member RPC access controls#1669
riderx merged 2 commits intomainfrom
riderx/fix-update-bug

Conversation

@riderx
Copy link
Member

@riderx riderx commented Feb 24, 2026

Summary (AI generated)

  • Fixed privilege bypass in the org RPC migration by replacing current_user checks with session_user-based service-role/admin bypass logic in all affected SECURITY DEFINER functions.
  • Removed redundant member validation in get_org_members(user_id, guild_id) and made temporary invitation aid values stable.
  • Removed execute grant to authenticated for the two-argument inner get_org_members overload.
  • Added missing NO_RIGHTS audit logging in check_org_members_password_policy before denial.

Motivation (AI generated)

  • The previous migration allowed potential authorization bypass and exposed sensitive organization member and password-policy data.
  • The inner overload was callable by authenticated roles and included an ineffective authorization branch due current_user semantics under SECURITY DEFINER.
  • This PR restores defense-in-depth and aligns behavior with other hardened RPC access patterns.

Business Impact (AI generated)

  • Prevents cross-organization member enumeration and sensitive org-data leakage.
  • Restores audit visibility for denied password-policy checks.
  • Reduces exposed attack surface by keeping inner RPC implementation functions non-public.

Test Plan (AI generated)

  • Run bun lint:backend.
  • Run sqlfluff lint --dialect postgres supabase/migrations/20260224000000_fix_org_member_rpc_access.sql (tooling run; reports repository-wide SQL style constraints for context).
  • Confirm an authenticated caller with no access to a target org cannot call get_org_members or check_org_members_password_policy without NO_RIGHTS.
  • Confirm service_role/postgres paths still function for internal jobs/administrative callers as intended.
  • Confirm invite rows return stable negative aid values and non-stable collision-prone arithmetic is removed.
  • Verify authenticated can only execute the one-argument wrapper get_org_members path, and not the inner (user_id, guild_id) overload.

Generated with AI

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Warning

Rate limit exceeded

@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 45 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c098cf8 and c867652.

📒 Files selected for processing (1)
  • supabase/migrations/20260224000000_fix_org_member_rpc_access.sql
📝 Walkthrough

Walkthrough

A database migration introduces two overloaded get_org_members RPC functions with role-based access control and a check_org_members_password_policy function. Both enforce authorization checks, log unauthorized attempts, and manage execute privileges via grants and revokes.

Changes

Cohort / File(s) Summary
Database Migration
supabase/migrations/20260224000000_fix_org_member_rpc_access.sql
Adds two overloaded get_org_members functions with access control validation, org member and pending invitation retrieval, and a check_org_members_password_policy function requiring super_admin rights. Includes ownership changes to postgres and selective execute grants for authenticated and service_role, with public access revoked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 A rabbit hops through SQL rows,
Guarding members, access flows,
Passwords checked and roles defined,
Org members safe, none left behind!
With RPC spells cast just right,
Security shines, oh what a sight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing access control issues for org member RPC functions, which directly aligns with the migration's core purpose of restricting and hardening these functions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed PR description provides comprehensive summary, motivation, business impact, and detailed test plan covering security fixes and access control improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-update-bug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c098cf8482

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

v_user_id := public.get_identity('{read,upload,write,all}'::public.key_mode[]);
v_is_service_role := (
((SELECT auth.jwt() ->> 'role') = 'service_role')
OR ((SELECT current_user) IS NOT DISTINCT FROM 'postgres')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove current_user bypass from RPC authorization

This function is SECURITY DEFINER and owned by postgres, so current_user resolves to postgres while it runs; that makes v_is_service_role always true and skips the check_min_rights authorization path for normal callers. As a result, any role with execute permission on get_org_members can query members for arbitrary org IDs, and the same current_user guard is repeated in the 2-argument overload below, leaving both entry points effectively unchecked.

Useful? React with 👍 / 👎.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
supabase/migrations/20260224000000_fix_org_member_rpc_access.sql (2)

90-90: Use UNION ALL — the two result sets are disjoint and deduplication is needlessly expensive.

The org_users rows (with real aid values) and tmp_users rows (with synthetic aid values) can never produce identical tuples, so UNION's implicit dedup scan is pure overhead.

♻️ Proposed fix
- UNION
+ UNION ALL
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260224000000_fix_org_member_rpc_access.sql` at line 90,
Replace the standalone "UNION" between the two SELECTs combining org_users and
tmp_users with "UNION ALL" because the result sets are disjoint; update the SQL
in the migration that constructs the combined result from org_users and
tmp_users to use UNION ALL to avoid the unnecessary deduplication overhead.

131-141: Double identity resolution — get_identity() and get_identity_org_allowed() are called separately.

v_user_id (line 131) is resolved via get_identity() and used only for the IS NULL short-circuit, while check_min_rights (line 141) re-resolves the identity via get_identity_org_allowed(). If an org-scoped API key yields a different identity from the generic one, the NULL guard operates on a different UUID than the one passed to check_min_rights, creating a subtle mismatch. Pass v_user_id directly to check_min_rights and resolve identity only once using get_identity_org_allowed().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/migrations/20260224000000_fix_org_member_rpc_access.sql` around
lines 131 - 141, The code resolves identity twice causing a mismatch: replace
the separate call to public.get_identity() with a single resolution using
public.get_identity_org_allowed('{read,upload,write,all}'::public.key_mode[],
check_org_members_password_policy.org_id) and store that UUID in v_user_id, then
pass v_user_id into public.check_min_rights(...) instead of re-calling
get_identity_org_allowed(); ensure the NULL guard checks this same v_user_id and
remove the duplicate identity call so get_identity_org_allowed is invoked only
once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260224000000_fix_org_member_rpc_access.sql`:
- Line 93: The synthetic aid calculation using ((SELECT COALESCE(MAX(id), 0)
FROM public.org_users) + tmp.id)::bigint is non-deterministic and can collide;
change the approach in this migration/query to produce stable out-of-band IDs
instead of adding tmp.id to MAX(id). Replace the MAX-based formula for aid with
a stable scheme such as a large negative offset (e.g., -1 * tmp.id) or expose
tmp.id together with an is_tmp discriminator so callers use (tmp.id, is_tmp)
instead of treating aid as a persistent key; update any references to aid,
tmp.id, public.org_users/org_users.id and consumers that expect stable IDs
accordingly.
- Around line 111-112: The two-argument overload get_org_members("user_id" uuid,
"guild_id" uuid) is an internal implementation and must not be granted EXECUTE
to the authenticated role; remove the GRANT EXECUTE ... TO "authenticated" for
that overload and leave the GRANT for "service_role" intact, and ensure only the
wrapper/no-arg (and any one-arg) get_org_members wrapper functions that perform
auth checks are granted to "authenticated" instead; update the migration so only
the wrapper function names/overloads intended for end users receive EXECUTE for
"authenticated".
- Around line 137-148: Add a denial audit log call before the exception in
check_org_members_password_policy: when the branch that raises RAISE EXCEPTION
'NO_RIGHTS' is taken (inside check of v_is_service_role / v_user_id), call
public.pg_log with the same denial message and contextual parameters used by the
get_org_members overloads (e.g., 'deny: NO_RIGHTS', the requesting user id
variable v_user_id, the org id from check_org_members_password_policy.org_id,
the rpc endpoint name and/or 'rpc' context) immediately before raising the
exception so unauthorized attempts are recorded.
- Line 89: The condition AND public.is_member_of_org(users.id, o.org_id) is
redundant because the query already joins org_users o to users (users.id =
o.user_id) and filters o.org_id = get_org_members.guild_id; remove the
is_member_of_org(...) predicate so the membership check relies on the existing
join (refer to the is_member_of_org function, the org_users alias o, users table
and get_org_members.guild_id to locate the clause).
- Around line 20-23: The v_is_service_role check uses CURRENT_USER which, in
these SECURITY DEFINER functions, is always the function owner and makes auth
checks ineffective; update the v_is_service_role assignment to use SELECT
session_user instead of SELECT current_user in all three functions that declare
v_is_service_role (the blocks where v_is_service_role is computed and then used
in the IF NOT v_is_service_role checks). Also remove the GRANT EXECUTE ON
FUNCTION get_org_members(user_id, guild_id) TO authenticated so the inner
function with user-supplied parameters is not directly callable by authenticated
users. Ensure both changes are applied consistently across the migration.

---

Nitpick comments:
In `@supabase/migrations/20260224000000_fix_org_member_rpc_access.sql`:
- Line 90: Replace the standalone "UNION" between the two SELECTs combining
org_users and tmp_users with "UNION ALL" because the result sets are disjoint;
update the SQL in the migration that constructs the combined result from
org_users and tmp_users to use UNION ALL to avoid the unnecessary deduplication
overhead.
- Around line 131-141: The code resolves identity twice causing a mismatch:
replace the separate call to public.get_identity() with a single resolution
using
public.get_identity_org_allowed('{read,upload,write,all}'::public.key_mode[],
check_org_members_password_policy.org_id) and store that UUID in v_user_id, then
pass v_user_id into public.check_min_rights(...) instead of re-calling
get_identity_org_allowed(); ensure the NULL guard checks this same v_user_id and
remove the duplicate identity call so get_identity_org_allowed is invoked only
once.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38ce641 and c098cf8.

📒 Files selected for processing (1)
  • supabase/migrations/20260224000000_fix_org_member_rpc_access.sql

@sonarqubecloud
Copy link

@riderx riderx merged commit cc5587c into main Feb 25, 2026
13 of 14 checks passed
@riderx riderx deleted the riderx/fix-update-bug branch February 25, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant