Skip to content

[codex] fix RBAC mixed-scope role escalation#1942

Merged
riderx merged 7 commits into
mainfrom
codex/fix-ghsa-55q2-p3m2-x66x
Apr 27, 2026
Merged

[codex] fix RBAC mixed-scope role escalation#1942
riderx merged 7 commits into
mainfrom
codex/fix-ghsa-55q2-p3m2-x66x

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 24, 2026

Summary (AI generated)

  • reject RBAC role bindings when the selected role family does not match the requested binding scope
  • block direct RBAC assignment to pending invitees before they accept their org invitation
  • enforce the same scope invariant in Postgres, delete malformed mixed-scope bindings, and ignore any scope-mismatched bindings in the RBAC permission resolver
  • prevent cross-scope role-hierarchy expansion in rbac_has_permission() and restrict its execute grant to service_role
  • add regression coverage for RBAC scope traversal, execute grants, and legacy pgTAP fixtures affected by newer ownership guards

Motivation (AI generated)

GHSA-55q2-p3m2-x66x reports that org-scoped roles could be assigned at app scope, including for pending invitees, and that the RBAC resolver later trusted those malformed bindings. That allowed a low-privilege invited user to inherit app-level privileges after accepting an invite.

Business Impact (AI generated)

This closes a confirmed privilege-escalation path in RBAC-enabled organizations. It protects tenant boundaries, reduces the risk of unauthorized app administration, and lets Capgo remediate the advisory with both application-level and database-level defenses.

Test Plan (AI generated)

  • bun lint
  • bunx eslint supabase/functions/_backend/private/role_bindings.ts tests/private-role-bindings.test.ts tests/rbac-permissions.test.ts
  • bunx vitest run tests/private-role-bindings.test.ts tests/rbac-permissions.test.ts
  • bunx supabase test db supabase/tests/13_test_plan_math.sql
  • bunx supabase test db supabase/tests/24_test_data_functions.sql
  • direct Postgres grant verification for public.rbac_has_permission(...) on the branch-local reset database

Screenshots (AI generated)

  • Not applicable.

Checklist (AI generated)

  • Advisory fix implemented in app-layer validation
  • Advisory fix implemented in database-layer permission resolution
  • Regression coverage added for scope-mismatch and cross-scope inheritance
  • Follow-up review comments addressed

Generated with AI

Summary by CodeRabbit

  • Bug Fixes

    • Prevented role-vs-binding scope mismatches and tightened access checks: active org members accepted, pending invites rejected, expired bindings ignored; permissions now respect scope boundaries.
  • New Features

    • Database-enforced role-binding scope integrity.
    • New scoped permission-evaluation function running with restricted, secure execution.
  • Tests

    • Added unit and regression tests covering principal access, role-scope validation, permission expansion, and function execute privileges.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds role/binding scope validation, tightens user principal access to prefer accepted org_membership (rejecting invites) with RBAC fallback, introduces a SECURITY DEFINER public.rbac_has_permission and a trigger to enforce role scope, and adds unit and SQL tests covering these behaviors.

Changes

Cohort / File(s) Summary
Backend Validation Logic
supabase/functions/_backend/private/role_bindings.ts
Added exported validateRoleScope(roleScopeType, bindingScopeType); updated validatePrincipalAccess(...) for principalType === 'user' to prefer active schema.org_users (reject invite_*), fallback to schema.role_bindings joined to schema.roles (matching scope_type, non-expired); tightened getCallerMaxPriorityRank join; wired validators into POST/PATCH flows.
DB Migration & RBAC Functionality
supabase/migrations/20260424090111_fix_rbac_scope_mismatch_escalation.sql
Added public.enforce_role_binding_role_scope() trigger to prevent role<->binding scope_type mismatches (removes mismatched rows); added public.rbac_has_permission(...) (SECURITY DEFINER) that normalizes scope identifiers, computes recursive scope catalog, filters expired bindings, expands roles via public.role_hierarchy, aggregates permissions, and restricts EXECUTE to service_role.
Tests & SQL checks
tests/private-role-bindings.test.ts, tests/rbac-permissions.test.ts, supabase/tests/13_test_plan_math.sql, supabase/tests/50_test_rbac_has_permission_execute_grants.sql
Added Vitest suite verifying validatePrincipalAccess and validateRoleScope behaviors (accepted invites, invite rejection, RBAC fallback, expired bindings ignored); added RBAC regression tests for cross-scope exclusions; adjusted SQL test to toggle USER triggers on public.apps; added SQL test ensuring only service_role has EXECUTE on rbac_has_permission.

Sequence Diagram

sequenceDiagram
    participant Client
    participant API as RoleBindings API
    participant OrgUsers as schema.org_users
    participant Bindings as schema.role_bindings
    participant Roles as schema.roles
    participant RBAC as public.rbac_has_permission

    Client->>API: POST/PATCH role_binding request
    API->>API: validateRoleScope(role.scope_type, binding.scope_type)
    alt scope mismatch
        API-->>Client: 400 Role scope_type does not match binding scope
    else scope match
        API->>OrgUsers: SELECT ... FROM schema.org_users WHERE user_id=... LIMIT 10
        alt active org membership found
            API-->>Client: proceed (ok)
        else invite_* membership found
            API-->>Client: 400 User has not accepted the org invitation yet
        else no qualifying org_users rows
            API->>Bindings: SELECT b JOIN Roles r ON b.role_id=r.id WHERE r.scope_type = b.scope_type AND not expired
            alt matching RBAC binding found
                API-->>Client: proceed (ok)
            else
                API-->>Client: 400 User is not a member of this org
            end
        end
    end

    Note over API,RBAC: Permission checks delegated to public.rbac_has_permission
    API->>RBAC: rbac_has_permission(permission_key, caller_org, caller_app, caller_channel, target_scope, timestamp)
    RBAC->>Bindings: resolve direct + group-derived bindings within computed scope catalog
    RBAC->>Roles: expand via role_hierarchy and aggregate permissions
    RBAC-->>API: boolean (permission granted?)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐰 I hopped through scopes and matched each role,

Sniffed out invites and guarded every hole.
Triggers stand watch, permissions bloom and sing,
Tests roll back softly — now safe is everything.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main security fix: preventing RBAC privilege escalation through mixed-scope role assignments, which is the core objective of the changeset.
Description check ✅ Passed The description includes well-structured Summary, Motivation, Business Impact, and Test Plan sections that comprehensively document the security fix, its rationale, and testing approach, matching the template requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-ghsa-55q2-p3m2-x66x

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 24, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-ghsa-55q2-p3m2-x66x (eda1fdb) with main (b917a8a)

Open in CodSpeed

@riderx riderx marked this pull request as ready for review April 24, 2026 09:46
@riderx
Copy link
Copy Markdown
Member Author

riderx commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
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: 3

🤖 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/functions/_backend/private/role_bindings.ts`:
- Around line 116-129: The current membership check accepts any role_bindings
row; modify the query that produces targetRbacAccess to ignore expired or
scope-mismatched bindings by adding conditions that role_bindings.expire_at IS
NULL OR role_bindings.expire_at > now() and ensure the binding's scope_type
matches the referenced role's scope_type (join schema.roles on role_id and
require eq(schema.role_bindings.scope_type, schema.roles.scope_type) or
equivalent); this ensures only active, scope-valid direct bindings for
principal_id/org_id are treated as membership proof before returning
success/failure.

In `@supabase/migrations/20260424090111_fix_rbac_scope_mismatch_escalation.sql`:
- Around line 87-100: When p_channel_id is provided, don’t let caller-supplied
p_app_id/p_org_id bleed into scope_catalog; after resolving the channel row
(v_channel_uuid, v_channel_app_id, v_channel_org_id) always canonicalize scope
by deriving v_app_uuid (lookup apps.id by v_channel_app_id) and setting v_org_id
:= v_channel_org_id before building scope_catalog, and if the caller supplied
p_app_id or p_org_id that does not match the channel’s app/org, return FALSE (or
fail) instead of silently using the caller values; update the block that
currently only sets v_app_uuid/v_org_id when NULL to enforce this
canonicalization and early-fail on mismatch.
- Around line 50-165: The SECURITY DEFINER function public.rbac_has_permission
currently lacks explicit OWNER and still has the default PUBLIC EXECUTE
privilege; after creating the function (rbac_has_permission(text, uuid, text,
uuid, character varying, bigint)) set its owner to the intended role (e.g.
postgres), revoke all privileges from PUBLIC, and then explicitly GRANT EXECUTE
only to the specific roles that must call it (do not leave PUBLIC or extra roles
with execute); update the migration to run those ALTER FUNCTION/REVOKE/GRANT
statements immediately after the function definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79f912db-5fdc-462a-8859-fb7a11a50b6e

📥 Commits

Reviewing files that changed from the base of the PR and between 633aeea and 06a9ad2.

📒 Files selected for processing (4)
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/migrations/20260424090111_fix_rbac_scope_mismatch_escalation.sql
  • tests/private-role-bindings.test.ts
  • tests/rbac-permissions.test.ts

Comment thread supabase/functions/_backend/private/role_bindings.ts
Copy link
Copy Markdown
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: 2

🤖 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/20260424090111_fix_rbac_scope_mismatch_escalation.sql`:
- Around line 177-181: The migration currently grants EXECUTE on the SECURITY
DEFINER helper rbac_has_permission(text, uuid, text, uuid, character varying,
bigint) to the authenticated role; remove that grant so only definer/service
roles can call it (retain REVOKE ALL ... FROM PUBLIC/anon and keep GRANT to
service_role or owner only), and if callers need client access implement a safe
wrapper (e.g., rbac_has_permission_for_current_user) that derives principal from
the auth context instead of accepting p_principal_type/p_principal_id as
parameters.
- Around line 151-167: The recursive CTE role_closure must carry the role's
scope_type (or scope_family) to prevent cross-scope inheritance: modify
combined_roles to select both role_id and scope_type, change role_closure to be
(role_id, scope_type) seeded from combined_roles, and in the recursive arm join
public.role_hierarchy rh ON rh.parent_role_id = rc.role_id AND rh.scope_type =
rc.scope_type (or rh.scope_family = rc.scope_type/family) so only descendants
from the same scope family are traversed before selecting permissions in
perm_set; update downstream joins (role_closure usage in role_permissions join)
to reference rc.role_id accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd762378-9559-4a9f-9b01-23ea20107b87

📥 Commits

Reviewing files that changed from the base of the PR and between 06a9ad2 and 0822adf.

📒 Files selected for processing (4)
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/migrations/20260424090111_fix_rbac_scope_mismatch_escalation.sql
  • tests/private-role-bindings.test.ts
  • tests/rbac-permissions.test.ts
✅ Files skipped from review due to trivial changes (1)
  • supabase/functions/_backend/private/role_bindings.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/private-role-bindings.test.ts

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (2)
supabase/tests/13_test_plan_math.sql (1)

43-43: Disable specific triggers instead of TRIGGER USER to prevent hiding unrelated regressions

DISABLE TRIGGER USER suppresses all user-defined triggers on apps, including on_app_update and cleanup_onboarding_app_data_on_complete which won't even fire during an owner_org update. Only handle_updated_at and audit_apps_trigger are affected by this operation. Follow the pattern already used on line 44 and explicitly disable only the necessary triggers.

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

In `@supabase/tests/13_test_plan_math.sql` at line 43, The ALTER TABLE apps
DISABLE TRIGGER USER call is too broad; instead disable only the two triggers
you intend to suppress by replacing that statement with explicit DISABLE TRIGGER
calls for handle_updated_at and audit_apps_trigger (following the same pattern
used on the next line), e.g., use ALTER TABLE apps DISABLE TRIGGER
handle_updated_at; and ALTER TABLE apps DISABLE TRIGGER audit_apps_trigger; so
only those triggers are disabled and other user triggers like on_app_update and
cleanup_onboarding_app_data_on_complete remain active.
tests/rbac-permissions.test.ts (1)

344-352: Avoid mutating the shared role hierarchy fixture in this regression.

This insert rewires the seeded public.role_hierarchy graph directly. It works under rollback, but it still couples the test to global fixture shape and makes future parallelization harder. Prefer dedicated test roles for this case, or at least make the added edge test-local and idempotent.

As per coding guidelines, "When creating tests that interact with shared resources, create dedicated seed data when your test modifies resources; reuse existing seed data only when you read without modification and create your own child resources".

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

In `@tests/rbac-permissions.test.ts` around lines 344 - 352, This test mutates the
shared public.role_hierarchy by inserting an edge; instead create dedicated test
roles and add the hierarchy edge only between those test-local roles (or make
the insert idempotent and scoped) instead of using public.roles and
public.rbac_role_*(); specifically, use the test setup to INSERT two new roles
(e.g., test_parent/test_child) and then INSERT INTO public.role_hierarchy
selecting those new role IDs, or wrap the insert in a conditional upsert that
only affects test-local role names so you never alter the global seeded graph.
🤖 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/tests/13_test_plan_math.sql`:
- Around line 43-47: The ALTER and UPDATE statements use unqualified table
names; change all references to schema-qualified names (e.g., replace apps and
app_versions with public.apps and public.app_versions or the appropriate schema)
so the ALTER TABLE USER/force_valid_owner_org_app_versions and UPDATE target
fully qualified tables; update the three lines referencing apps and app_versions
to use the schema prefix while keeping the same trigger names and UPDATE
predicate (owner_org/app_id) intact.

---

Nitpick comments:
In `@supabase/tests/13_test_plan_math.sql`:
- Line 43: The ALTER TABLE apps DISABLE TRIGGER USER call is too broad; instead
disable only the two triggers you intend to suppress by replacing that statement
with explicit DISABLE TRIGGER calls for handle_updated_at and audit_apps_trigger
(following the same pattern used on the next line), e.g., use ALTER TABLE apps
DISABLE TRIGGER handle_updated_at; and ALTER TABLE apps DISABLE TRIGGER
audit_apps_trigger; so only those triggers are disabled and other user triggers
like on_app_update and cleanup_onboarding_app_data_on_complete remain active.

In `@tests/rbac-permissions.test.ts`:
- Around line 344-352: This test mutates the shared public.role_hierarchy by
inserting an edge; instead create dedicated test roles and add the hierarchy
edge only between those test-local roles (or make the insert idempotent and
scoped) instead of using public.roles and public.rbac_role_*(); specifically,
use the test setup to INSERT two new roles (e.g., test_parent/test_child) and
then INSERT INTO public.role_hierarchy selecting those new role IDs, or wrap the
insert in a conditional upsert that only affects test-local role names so you
never alter the global seeded graph.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: faa7016e-5dd6-4112-ba9e-9f072ff0a9bb

📥 Commits

Reviewing files that changed from the base of the PR and between 0822adf and 3ba18c7.

📒 Files selected for processing (4)
  • supabase/migrations/20260424090111_fix_rbac_scope_mismatch_escalation.sql
  • supabase/tests/13_test_plan_math.sql
  • supabase/tests/50_test_rbac_has_permission_execute_grants.sql
  • tests/rbac-permissions.test.ts
✅ Files skipped from review due to trivial changes (1)
  • supabase/tests/50_test_rbac_has_permission_execute_grants.sql

Comment thread supabase/tests/13_test_plan_math.sql Outdated
Copy link
Copy Markdown

@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: 3ba18c7f77

ℹ️ 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".

Comment thread supabase/functions/_backend/private/role_bindings.ts Outdated
Copy link
Copy Markdown
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: 1

🤖 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/tests/13_test_plan_math.sql`:
- Around line 44-49: The trigger force_valid_owner_org_app_versions is disabled
before the fixture updates but never re-enabled; add a matching ALTER TABLE
public.app_versions ENABLE TRIGGER force_valid_owner_org_app_versions
immediately after the UPDATE that sets app_versions (after the UPDATE where
id=7) so the trigger is restored for subsequent statements (ensure the enable
uses the exact trigger name force_valid_owner_org_app_versions and table
public.app_versions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6fd86fc-de56-49c3-8053-fc226339674e

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba18c7 and 71f3543.

📒 Files selected for processing (1)
  • supabase/tests/13_test_plan_math.sql

Comment thread supabase/tests/13_test_plan_math.sql
Copy link
Copy Markdown
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/functions/_backend/private/role_bindings.ts (1)

171-194: ⚠️ Potential issue | 🔴 Critical

Keep the caller rank ceiling limited to active bindings in the same scope family.

getCallerMaxPriorityRank() still takes MAX(priority_rank) over all of the caller’s bindings in the org. That means an expired high-rank binding, or a high-rank binding from another scope family, can still inflate the POST/PATCH/DELETE ceiling once org.update_user_roles is satisfied by some other active role.

🔒 Suggested hardening
 async function getCallerMaxPriorityRank(
   drizzle: ReturnType<typeof getDrizzleClient>,
   authType: 'apikey' | 'jwt',
   principalId: string,
   orgId: string,
+  scopeType: RoleBindingBody['scope_type'],
 ): Promise<number> {
   const principalType = authType === 'apikey' ? 'apikey' : 'user'
   const result = await drizzle
     .select({ max_rank: sql<number>`MAX(${schema.roles.priority_rank})` })
     .from(schema.role_bindings)
     .innerJoin(schema.roles, and(
       eq(schema.role_bindings.role_id, schema.roles.id),
       eq(schema.role_bindings.scope_type, schema.roles.scope_type),
     ))
     .where(
       and(
         eq(schema.role_bindings.principal_type, principalType),
         eq(schema.role_bindings.principal_id, principalId),
         eq(schema.role_bindings.org_id, orgId),
+        eq(schema.role_bindings.scope_type, scopeType),
+        sql`(${schema.role_bindings.expires_at} IS NULL OR ${schema.role_bindings.expires_at} > now())`,
       ),
     )
     .limit(1)

   return result[0]?.max_rank ?? 0
 }
const callerMaxRank = await getCallerMaxPriorityRank(drizzle, auth.authType, callerPrincipalId, org_id, role.scope_type)

const callerMaxRank = await getCallerMaxPriorityRank(
  drizzle,
  auth!.authType,
  callerPrincipalId,
  binding.org_id!,
  binding.scope_type as RoleBindingBody['scope_type'],
)

Also applies to: 331-331, 488-488, 565-565

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

In `@supabase/functions/_backend/private/role_bindings.ts` around lines 171 - 194,
getCallerMaxPriorityRank currently returns MAX(priority_rank) across all
bindings in the org; change it to accept an additional scopeType parameter and
restrict the query to bindings with role_bindings.scope_type = scopeType and
only "active" bindings (e.g., where role_bindings.expires_at IS NULL OR
role_bindings.expires_at > now()) so expired or other-scope-family bindings
cannot inflate the caller ceiling; update call sites (the places that call
getCallerMaxPriorityRank) to pass the binding.scope_type (e.g., the
binding.org_id and binding.scope_type shown in the review) and ensure you still
join to schema.roles as before (use the same function name
getCallerMaxPriorityRank, drizzle, schema.role_bindings and schema.roles
identifiers to locate and modify the code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@supabase/functions/_backend/private/role_bindings.ts`:
- Around line 171-194: getCallerMaxPriorityRank currently returns
MAX(priority_rank) across all bindings in the org; change it to accept an
additional scopeType parameter and restrict the query to bindings with
role_bindings.scope_type = scopeType and only "active" bindings (e.g., where
role_bindings.expires_at IS NULL OR role_bindings.expires_at > now()) so expired
or other-scope-family bindings cannot inflate the caller ceiling; update call
sites (the places that call getCallerMaxPriorityRank) to pass the
binding.scope_type (e.g., the binding.org_id and binding.scope_type shown in the
review) and ensure you still join to schema.roles as before (use the same
function name getCallerMaxPriorityRank, drizzle, schema.role_bindings and
schema.roles identifiers to locate and modify the code).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff331ad9-0192-475d-9694-315462b8de94

📥 Commits

Reviewing files that changed from the base of the PR and between 71f3543 and 75bc308.

📒 Files selected for processing (3)
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/tests/13_test_plan_math.sql
  • tests/private-role-bindings.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/private-role-bindings.test.ts

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit f3a1627 into main Apr 27, 2026
16 checks passed
@riderx riderx deleted the codex/fix-ghsa-55q2-p3m2-x66x branch April 27, 2026 12:39
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