Skip to content

fix(rbac): add app preview API key role#2698

Open
riderx wants to merge 13 commits into
mainfrom
codex/app-preview-lifecycle-role
Open

fix(rbac): add app preview API key role#2698
riderx wants to merge 13 commits into
mainfrom
codex/app-preview-lifecycle-role

Conversation

@riderx

@riderx riderx commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

  • add the app-scoped App Preview role for preview bundle upload/promotion and channel lifecycle
  • let API keys be limited to selected apps while every app binding keeps the owning organization org_id
  • omit only the organization-wide RBAC role for app-only keys; do not create organization-less keys
  • remove the automatic compatibility org-reader role only for app-only keys, while preserving the organization on every app binding
  • verify the CLI create/delete path, including intentional --delete-bundle denial
  • consolidate the PR to one forward-only migration, including the scheduler repair

Security boundary

The role grants only app.read, app.read_bundles, app.upload_bundle, app.create_channel, channel.read, channel.promote_bundle, and channel.delete for 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

  1. Deploy the migration and dashboard.
  2. Create a new key with Limit this key to selected apps, select the preview app, and choose App Preview.
  3. Publish the CLI source containing the current exact channel permissions, then use npx @capgo/cli@latest.
  4. Do not use channel delete --delete-bundle with this role.

Validation

  • migration-order check passed with exactly one new migration
  • bun lint:backend, bun test:unit (1,047 tests), and bun test:db (252 tests) passed locally
  • GitHub CI and automated review are rerunning on 3bfc8c850c0ce693ece4a8f0e150c9a809f3e80e

Visual

Create API key dialog with selected-app scope enabled


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_preview app 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_reader as non-display org access.

Backend API key create/update stops enriching app-only keys with the compatibility apikey_org_reader org binding and drops the allowSystemRole escape hatch on role binding creation. createAiApiKey member keys now require at least one app and no longer add org_member org bindings. assertApiKeyManagerCanAssignBindings runs for JWT key managers (not only API-key auth) and treats app_preview like other sensitive assignable roles unless the caller has org.update_user_roles.

The forward migration adds the role/permissions, deletes compatibility org-reader bindings, reasserts several RPC EXECUTE grants, and restores the process_all_cron_tasks cron 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.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

App-scoped API keys now support the app_preview role, app-only creation and editing, restricted RBAC permissions, and organization filtering. Backend compatibility bindings were removed, database permissions and metadata were updated, and automated UI, API, CLI, and RBAC tests were added.

App Preview API Keys

Layer / File(s) Summary
Preview role and permission contract
supabase/migrations/..., supabase/seed.sql, src/components/..., src/stores/organization.ts, messages/en.json
Defines app_preview, its permissions, translations, role picker entry, and channel permission defaults.
Binding creation and assignment enforcement
supabase/functions/_backend/private/role_bindings.ts, supabase/functions/_backend/public/apikey/*
Removes legacy organization-reader binding enrichment and applies direct binding validation, including app-preview assignment restrictions.
App-only API key creation and editing
src/pages/ApiKeys.vue, src/services/apikeys.ts
Adds app-only scope state, conditional form sections, validation, app-only bindings, permission generation, edit detection, and selected-app requirements.
Preview scope validation and visual coverage
playwright/*, tests/*, scripts/visual-diff.ts, .github/pr-screenshots/*
Tests app-only persistence, RBAC boundaries, CLI lifecycle behavior, service validation, and visual-diff setup.
Replicated schema cleanup
read_replicate/schema_replicate.*
Reorders schema metadata and removes obsolete columns and indexes.
Database jobs and function grants
supabase/migrations/*
Schedules cron processing, removes an obsolete trigger function, and repairs RPC execution grants.

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
Loading
🚥 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
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.
Title check ✅ Passed The title clearly matches the main change: adding an app preview API key role.
Description check ✅ Passed The description is mostly complete and includes summary, validation, and screenshots, though it does not follow the template headings exactly.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@codspeed-hq

codspeed-hq Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/app-preview-lifecycle-role (b0db1d7) with main (6e178ee)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (caa8138) during the generation of this report, so 6e178ee 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/app-preview-lifecycle-role branch from f513784 to 7d29c2d Compare July 15, 2026 16:50
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Visual diff passed

Visual changes

Generated at 2026-07-15T23:40:02.658Z. Threshold: 0.1% pixel difference.

Route Diff % Status
login 0.000 unchanged
dashboard 3.014 changed
apps 0.214 changed
app-overview 1.303 changed
channels 0.294 changed
devices 0.439 changed
observe 0.000 unchanged
observe-plugins 0.000 unchanged
api-keys-app-preview 9.376 changed

Commit: b0db1d742330744cac30926fc60eec64fde5598c
Download the HTML report from workflow artifacts (artifact: visual-diff-report-b0db1d742330744cac30926fc60eec64fde5598c).

Open index.html from the artifact for side-by-side before/after/diff screenshots.

@riderx riderx force-pushed the codex/app-preview-lifecycle-role branch from ad195b4 to 7064018 Compare July 15, 2026 17:50
@riderx riderx marked this pull request as ready for review July 15, 2026 18:15
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor cursor Bot requested review from Dalanir and WcaleNieWolny July 15, 2026 18:16

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale comment

Risk: medium. Cursor Bugbot did not complete (usage limit reached), so I am not approving. Requested human review for the RBAC/API key migration changes.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@coderabbitai coderabbitai Bot added the codex label Jul 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 win

Move the app_preview gate ahead of the org role precheck. replaceApiKeyBindings() already requires org.update_user_roles for every affected org before calling assertApiKeyManagerCanAssignBindings(), 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

📥 Commits

Reviewing files that changed from the base of the PR and between d750a45 and 22a84f8.

📒 Files selected for processing (26)
  • .github/pr-screenshots/app-preview-api-key.webp
  • messages/en.json
  • playwright/e2e/apikeys.spec.ts
  • playwright/visual-diff.config.ts
  • read_replicate/schema_replicate.catalog.json
  • read_replicate/schema_replicate.sql
  • scripts/visual-diff.ts
  • src/components/connect/ConnectAppPicker.vue
  • src/components/permissions/ChannelPermissionOverridesPanel.vue
  • src/pages/ApiKeys.vue
  • src/services/apikeys.ts
  • src/stores/organization.ts
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/functions/_backend/public/apikey/post.ts
  • supabase/functions/_backend/public/apikey/put.ts
  • supabase/functions/_backend/public/apikey/scope.ts
  • supabase/migrations/20260708000000_prod_baseline.sql
  • supabase/migrations/20260715164834_add_app_preview_role.sql
  • supabase/migrations/20260715175642_repair_rbac_function_grants.sql
  • supabase/seed.sql
  • tests/apikey-scope.unit.test.ts
  • tests/apikeys-service.unit.test.ts
  • tests/apikeys.test.ts
  • tests/cli-preview-lifecycle.test.ts
  • tests/frontend-channel-rbac-scope.test.ts
  • tests/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

Comment thread src/pages/ApiKeys.vue
Comment thread supabase/migrations/20260708000000_prod_baseline.sql Outdated
Comment thread supabase/migrations/20260715175642_repair_rbac_function_grants.sql Outdated
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@riderx riderx force-pushed the codex/app-preview-lifecycle-role branch from d4c7e7c to 3bfc8c8 Compare July 15, 2026 21:44
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver External

@sonarqubecloud

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 () => {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>
Suggested change
it('allows an app preview key only its deployment lifecycle', async () => {
it('allows an app preview key only for its deployment lifecycle', async () => {
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant