Skip to content

fix(security): prevent stale RBAC demotion privileges#2020

Open
riderx wants to merge 1 commit intomainfrom
codex/fix-rbac-demotion-advisory
Open

fix(security): prevent stale RBAC demotion privileges#2020
riderx wants to merge 1 commit intomainfrom
codex/fix-rbac-demotion-advisory

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 2, 2026

Summary (AI generated)

  • Fixed GHSA-rvvc-rvxv-qcrh by preventing stale legacy org_users.user_right values from preserving super-admin access after RBAC demotion.
  • Added a migration that syncs org-scope user role_bindings back to the legacy org membership row and updates encrypted-bundle cleanup RPCs to authorize through RBAC.
  • Added regression tests for the role-binding delete path, stale legacy super-admin RPC denial, and new security-definer helper grants.

Motivation (AI generated)

RBAC-enabled orgs could delete or downgrade a role binding while leaving the legacy org_users.user_right row as super_admin. The bundle cleanup RPCs trusted that stale legacy value, so a demoted user could still count or delete non-compliant bundles.

Business Impact (AI generated)

This closes a high-severity authorization gap that could allow demoted org users to perform destructive bundle cleanup actions, protecting customer OTA delivery and org access controls.

Test Plan (AI generated)

  • bun lint
  • bun lint:backend
  • bun run supabase:with-env -- bunx vitest run tests/private-role-bindings.test.ts tests/enforce-encrypted-bundles.test.ts tests/security-definer-execute-hardening.test.ts
  • bun run typecheck
  • git diff --check

Generated with AI

Summary by CodeRabbit

  • Bug Fixes

    • Role binding deletion now correctly clears associated legacy org membership rights and roles
    • Legacy user privileges now synchronize automatically when role bindings change
  • Security

    • Strengthened access control for bundle compliance reporting and cleanup operations
    • Improved authorization validation to prevent unauthorized bundle management

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

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

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1f33bae-65a9-4bda-bf87-d65e925cd550

📥 Commits

Reviewing files that changed from the base of the PR and between a945562 and 7e4e1fc.

📒 Files selected for processing (4)
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql
  • tests/enforce-encrypted-bundles.test.ts
  • tests/private-role-bindings.test.ts
📝 Walkthrough

Walkthrough

This PR adds RBAC synchronization and encrypted bundle cleanup functionality. A database migration introduces trigger-based synchronization between legacy org_users fields and new RBAC role bindings, plus two RPC functions for identifying and deleting non-compliant encrypted bundles. The backend DELETE handler for role bindings now performs transactional updates to clear legacy org membership fields when org-scope user bindings are removed.

Changes

RBAC Legacy Sync & Bundle Cleanup

Layer / File(s) Summary
Database Functions & Trigger
supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql
Adds sync_org_user_legacy_from_rbac() SECURITY DEFINER function to sync legacy org_users rights with org-scope role bindings; creates trigger wrapper and AFTER trigger on role_bindings to auto-sync on INSERT/UPDATE/DELETE; adds count_non_compliant_bundles() and delete_non_compliant_bundles() RPC functions with rbac_perm_org_delete() gating for encrypted bundle maintenance.
Backend Transaction Handling
supabase/functions/_backend/private/role_bindings.ts
DELETE /:binding_id handler wraps deletion in transaction; conditionally clears org_users.user_right and rbac_role_name (sets to null) when deleting org-scope user bindings, matching on org/user with app_id/channel_id null and non-invite user_right.
Security Hardening Tests
tests/security-definer-execute-hardening.test.ts
Adds two new service-only procs (sync_org_user_legacy_from_rbac, sync_org_user_legacy_from_rbac_trigger) to SERVICE_ONLY_PROCS list to verify they block direct anon/authenticated execution.
Role Binding Deletion Tests
tests/private-role-bindings.test.ts
Updates suite label; adds test that deletes org-scoped user role binding and verifies legacy org_users fields are cleared (user_right and rbac_role_name set to null).
Bundle Cleanup RPC Tests
tests/enforce-encrypted-bundles.test.ts
Adds second authenticated user (authHeadersUser2), creates createStaleLegacySuperAdminFixture() to seed stale legacy RBAC state, adds callBundleCleanupRpc() helper, and new test asserting both bundle cleanup RPCs return "Unauthorized" for non-privileged users; verifies app version remains undeleted.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client / Handler
    participant DB as Database Transaction
    participant SyncFunc as sync_org_user_legacy_from_rbac()
    participant OrgUsers as org_users Table
    
    Client->>DB: BEGIN transaction
    Note over DB: DELETE role_binding
    DB->>DB: delete role_bindings row
    
    alt binding.principal_type = 'user' AND binding.scope_type = 'org'
        Client->>SyncFunc: Check if sync needed
        SyncFunc->>OrgUsers: UPDATE user_right/rbac_role_name to NULL
        OrgUsers-->>SyncFunc: Row updated
    end
    
    DB->>DB: COMMIT
    DB-->>Client: Success
Loading
sequenceDiagram
    participant Trigger as role_bindings Trigger
    participant SyncFunc as sync_org_user_legacy_from_rbac_trigger()
    participant SyncImpl as sync_org_user_legacy_from_rbac()
    participant OrgUsers as org_users Table
    participant RoleBindings as role_bindings Table
    
    Note over RoleBindings: INSERT/UPDATE/DELETE event
    RoleBindings->>Trigger: Fire AFTER trigger
    Trigger->>SyncFunc: Invoke trigger wrapper
    SyncFunc->>SyncImpl: Call sync implementation
    SyncImpl->>RoleBindings: SELECT best active org-scope binding
    RoleBindings-->>SyncImpl: Return binding or null
    SyncImpl->>OrgUsers: UPDATE user_right/rbac_role_name
    OrgUsers-->>SyncImpl: Success
    SyncImpl-->>SyncFunc: Complete
    SyncFunc-->>Trigger: Done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A binding deleted, the ledger now clean,
When admins descend from their perch up above,
The trigger springs swift with RBAC's true sheen,
And dusty old legacies vanish like doves,
While bundles once broken now rest in the vault. 📦

🚥 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 clearly and specifically describes the main security fix: preventing stale RBAC demotion privileges that could bypass access controls.
Description check ✅ Passed The description addresses most required template sections with clear motivation, business impact, and comprehensive test plan, though the Checklist section is incomplete.
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-rbac-demotion-advisory

Review rate limit: 0/5 reviews remaining, refill in 8 minutes and 46 seconds.

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

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.

🧹 Nitpick comments (1)
supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql (1)

176-180: 💤 Low value

Complex key_id matching logic could use a clarifying comment.

The bidirectional prefix matching (key_id = LEFT(required_key, 20) OR LEFT(key_id, length) = required_key) handles both truncated and full key comparisons, but the intent isn't immediately obvious. Consider adding an inline comment explaining why both directions are checked.

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

In
`@supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql`
around lines 176 - 180, Add a clarifying inline comment next to the complex key
matching condition that explains the bidirectional prefix checks: that av.key_id
may be a 20-char truncated ID or a full-length required_key, so the condition
checks either that the truncated required_key matches av.key_id (av.key_id =
LEFT(required_key, 20)) or that a truncated av.key_id matches the full
required_key (LEFT(av.key_id, LENGTH(required_key)) = required_key); place this
comment immediately above or inline with the condition containing av.key_id and
required_key to make the intent clear for future maintainers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql`:
- Around line 176-180: Add a clarifying inline comment next to the complex key
matching condition that explains the bidirectional prefix checks: that av.key_id
may be a 20-char truncated ID or a full-length required_key, so the condition
checks either that the truncated required_key matches av.key_id (av.key_id =
LEFT(required_key, 20)) or that a truncated av.key_id matches the full
required_key (LEFT(av.key_id, LENGTH(required_key)) = required_key); place this
comment immediately above or inline with the condition containing av.key_id and
required_key to make the intent clear for future maintainers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8bf208f1-263f-4ff0-84d5-0b7d44a1de5e

📥 Commits

Reviewing files that changed from the base of the PR and between 97ee8f0 and a945562.

📒 Files selected for processing (5)
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/migrations/20260502134355_fix_rbac_role_binding_demoted_super_admin.sql
  • tests/enforce-encrypted-bundles.test.ts
  • tests/private-role-bindings.test.ts
  • tests/security-definer-execute-hardening.test.ts

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 2, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-rbac-demotion-advisory (7e4e1fc) with main (17d36c6)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (97ee8f0) during the generation of this report, so 17d36c6 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@riderx riderx force-pushed the codex/fix-rbac-demotion-advisory branch from a945562 to b6e7694 Compare May 2, 2026 14:15
@riderx riderx force-pushed the codex/fix-rbac-demotion-advisory branch from b6e7694 to 7e4e1fc Compare May 2, 2026 14:25
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 2, 2026

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