fix(rbac): add app preview API key role#2698
Conversation
📝 WalkthroughWalkthroughChangesApp-scoped API keys now support the App Preview API Keys
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ApiKeysPage
participant APIKeyEndpoint
participant RBAC
participant AppDatabase
User->>ApiKeysPage: Select apps only and app_preview
ApiKeysPage->>APIKeyEndpoint: Submit app-scoped key
APIKeyEndpoint->>RBAC: Create app_preview binding
RBAC->>AppDatabase: Store role binding and permissions
AppDatabase-->>APIKeyEndpoint: Persisted key state
APIKeyEndpoint-->>ApiKeysPage: Return API key
ApiKeysPage-->>User: Display app-only scope
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
f513784 to
7d29c2d
Compare
Visual diff passedVisual changesGenerated at 2026-07-15T23:40:02.658Z. Threshold: 0.1% pixel difference.
Commit: Open |
ad195b4 to
7064018
Compare
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_5e8cdbb9-13e3-4f4d-9369-9ab2fa6c5118) |
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), and this RBAC/API-key binding change exceeds the low-risk approval threshold. Assigned Dalanir and WcaleNieWolny for human review.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Actionable comments posted: 3
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/public/apikey/put.ts (1)
102-103: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winMove the
app_previewgate ahead of the org role precheck.replaceApiKeyBindings()already requiresorg.update_user_rolesfor every affected org before callingassertApiKeyManagerCanAssignBindings(), so the new deny list in that helper never runs on this PUT path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@supabase/functions/_backend/public/apikey/put.ts` around lines 102 - 103, Move the app_preview deny-list gate before the org role precheck in the PUT flow, specifically before assertApiKeyManagerCanAssignBindings and its call to replaceApiKeyBindings. Ensure app_preview requests are rejected before org.update_user_roles validation can short-circuit the new gate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pages/ApiKeys.vue`:
- Around line 992-1017: Update validateApiKeyScope so the appOnlyScope branch
uses a dedicated “select at least one app” translation key when selectedAppIds
is empty, and add the corresponding select-at-least-one-app entry to
messages/en.json. Keep the existing role and organization validation messages
unchanged.
In `@supabase/migrations/20260708000000_prod_baseline.sql`:
- Around line 10111-10115: Remove the process_all_cron_tasks scheduler
registration from the prod baseline migration and add it to a new forward
migration using the same cron.schedule configuration. Ensure the new migration
creates the job for environments that have already applied the baseline.
In `@supabase/migrations/20260715175642_repair_rbac_function_grants.sql`:
- Line 10: Wrap the long REVOKE/GRANT statements in the migration, including the
function signatures and role lists, to comply with SQLFluff LT05 line-length
limits. Update the statements at the referenced ranges while preserving their
existing functions, privileges, and roles.
---
Outside diff comments:
In `@supabase/functions/_backend/public/apikey/put.ts`:
- Around line 102-103: Move the app_preview deny-list gate before the org role
precheck in the PUT flow, specifically before
assertApiKeyManagerCanAssignBindings and its call to replaceApiKeyBindings.
Ensure app_preview requests are rejected before org.update_user_roles validation
can short-circuit the new gate.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 098ef471-c4bc-4257-9873-2a1909e6bfc7
📒 Files selected for processing (26)
.github/pr-screenshots/app-preview-api-key.webpmessages/en.jsonplaywright/e2e/apikeys.spec.tsplaywright/visual-diff.config.tsread_replicate/schema_replicate.catalog.jsonread_replicate/schema_replicate.sqlscripts/visual-diff.tssrc/components/connect/ConnectAppPicker.vuesrc/components/permissions/ChannelPermissionOverridesPanel.vuesrc/pages/ApiKeys.vuesrc/services/apikeys.tssrc/stores/organization.tssupabase/functions/_backend/private/role_bindings.tssupabase/functions/_backend/public/apikey/post.tssupabase/functions/_backend/public/apikey/put.tssupabase/functions/_backend/public/apikey/scope.tssupabase/migrations/20260708000000_prod_baseline.sqlsupabase/migrations/20260715164834_add_app_preview_role.sqlsupabase/migrations/20260715175642_repair_rbac_function_grants.sqlsupabase/seed.sqltests/apikey-scope.unit.test.tstests/apikeys-service.unit.test.tstests/apikeys.test.tstests/cli-preview-lifecycle.test.tstests/frontend-channel-rbac-scope.test.tstests/rbac-permissions.test.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Cap-go/capacitor-updater(manual)
💤 Files with no reviewable changes (1)
- read_replicate/schema_replicate.sql
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_bb9abcb9-1840-423f-a939-e0c333f6c4fa) |
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), and this RBAC/API-key change exceeds the low-risk approval threshold. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), so the automated review signal is incomplete for this RBAC/API-key change. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_c4374092-4f55-4f87-b519-7de8fef29259) |
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), and this RBAC/API-key change exceeds the low-risk approval threshold. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver External
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), so the automated review signal is incomplete for this RBAC/API-key change. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver
d4c7e7c to
3bfc8c8
Compare
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_68a55b7a-949d-45d8-a1f5-6ea1e8d18752) |
There was a problem hiding this comment.
Stale comment
Risk: medium. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), so the automated review signal is incomplete for this RBAC/API-key change. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver
There was a problem hiding this comment.
Stale comment
Risk: high. Not approving: Cursor Bugbot did not complete (check skipped; usage limit reached), and this RBAC/API-key change exceeds the low-risk approval threshold. Dalanir and WcaleNieWolny are already assigned for human review.
Sent by Cursor Approval Agent: Pull Request Approver External
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_866551df-e6c3-41b2-a191-46ac8d007882) |
|
There was a problem hiding this comment.
1 issue found and verified against the latest diff
Confidence score: 5/5
- In
tests/rbac-permissions.test.ts, the awkward test description wording could slightly reduce readability and make intent less clear during future maintenance, but it does not affect runtime behavior; this PR is safe to merge, and you can optionally polish the phrasing in this PR or a quick follow-up.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/rbac-permissions.test.ts">
<violation number="1" location="tests/rbac-permissions.test.ts:349">
P3: Test description reads awkwardly — the preposition after "only" is missing. Consider: "allows an app preview key only for its deployment lifecycle" or "restricts an app preview key to its deployment lifecycle only" for clarity.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| expect(result.rows[0].settings_allowed).toBe(false) | ||
| }) | ||
|
|
||
| it('allows an app preview key only its deployment lifecycle', async () => { |
There was a problem hiding this comment.
P3: Test description reads awkwardly — the preposition after "only" is missing. Consider: "allows an app preview key only for its deployment lifecycle" or "restricts an app preview key to its deployment lifecycle only" for clarity.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/rbac-permissions.test.ts, line 349:
<comment>Test description reads awkwardly — the preposition after "only" is missing. Consider: "allows an app preview key only for its deployment lifecycle" or "restricts an app preview key to its deployment lifecycle only" for clarity.</comment>
<file context>
@@ -346,6 +346,111 @@ describe('rbac permission system', () => {
expect(result.rows[0].settings_allowed).toBe(false)
})
+ it('allows an app preview key only its deployment lifecycle', async () => {
+ const slug = randomUUID().slice(0, 8)
+ const orgId = randomUUID()
</file context>
| it('allows an app preview key only its deployment lifecycle', async () => { | |
| it('allows an app preview key only for its deployment lifecycle', async () => { |





Summary
App Previewrole for preview bundle upload/promotion and channel lifecycleorg_id--delete-bundledenialSecurity boundary
The role grants only
app.read,app.read_bundles,app.upload_bundle,app.create_channel,channel.read,channel.promote_bundle, andchannel.deletefor selected apps. It excludes organization-wide access, app settings, user/role management, device controls, rollbacks, channel settings, and bundle deletion.Every selected-app binding retains the owning organization
org_id. “App-only” means no organization-wide RBAC role, not no organization association.Dynamic PR channels mean this is app-scoped, not PR-name scoped: the key can operate on any channel in its selected app. A hard PR-only boundary would need a separate channel ownership/prefix model.
Rollout
npx @capgo/cli@latest.channel delete --delete-bundlewith this role.Validation
bun lint:backend,bun test:unit(1,047 tests), andbun test:db(252 tests) passed locally3bfc8c850c0ce693ece4a8f0e150c9a809f3e80eVisual
Note
High Risk
Changes API key RBAC binding behavior, introduces a new privileged-but-scoped role, and alters database function grants—areas that directly affect authorization and production security posture.
Overview
Introduces the assignable
app_previewapp role for CI preview workflows (bundle upload/promote, channel create/delete on selected apps, without org-wide access or bundle delete), wired through migration/seed, channel permission defaults, and i18n.The API Keys UI gains “Limit this key to selected apps”: org pickers become an app filter only, org-level roles and org-create grants are hidden, and create/update sends app-scoped bindings only while listing logic treats legacy
apikey_org_readeras non-display org access.Backend API key create/update stops enriching app-only keys with the compatibility
apikey_org_readerorg binding and drops theallowSystemRoleescape hatch on role binding creation.createAiApiKeymember keys now require at least one app and no longer addorg_memberorg bindings.assertApiKeyManagerCanAssignBindingsruns for JWT key managers (not only API-key auth) and treatsapp_previewlike other sensitive assignable roles unless the caller hasorg.update_user_roles.The forward migration adds the role/permissions, deletes compatibility org-reader bindings, reasserts several RPC
EXECUTEgrants, and restores theprocess_all_cron_taskscron job. Coverage includes Playwright, CLI lifecycle, RBAC SQL, unit tests, and a visual-diff route for the create-key dialog.Reviewed by Cursor Bugbot for commit b0db1d7. Bugbot is set up for automated code reviews on this repo. Configure here.