Skip to content

fix: restore bundle upload for API keys with app-only RBAC bindings#2079

Merged
riderx merged 3 commits intomainfrom
fix-org-read-permission-check
May 8, 2026
Merged

fix: restore bundle upload for API keys with app-only RBAC bindings#2079
riderx merged 3 commits intomainfrom
fix-org-read-permission-check

Conversation

@Dalanir
Copy link
Copy Markdown
Contributor

@Dalanir Dalanir commented May 8, 2026

Summary

  • Bug 1 — null owner_org (23502): The BEFORE INSERT trigger on app_versions called get_user_main_org_id_by_app_id which, since migration 20260203150000, runs auth checks that fail under PostgREST's authenticator role (auth.uid() = NULL, auth.role() = 'anon'). The trigger fires after RLS has already validated the insert, so re-checking auth is redundant and causes owner_org to be set to NULL. Fix: new minimal SECURITY DEFINER helper get_owner_org_by_app_id_internal that resolves owner_org from apps without auth logic.

  • Bug 2 — no read access: get_organization_cli_warnings requires org.read. API keys created with only app-level bindings (e.g. app_uploader) don't have org_member and therefore lack org.read. For users, org_member + app_uploader are always created together — API keys must respect the same invariant. Fix: post.ts now auto-inserts an org_member binding for each org that has app-level bindings but no explicit org-level binding.

Both bundle list (unaffected) and bundle upload (broken) used the same API keys, confirming auth was valid but upload-specific paths were broken.

Test plan

  • Create an API key with only app_uploader binding (no org role selected in the UI)
  • Verify the key's role_bindings now includes an org_member entry for the org
  • Run capgo bundle upload with that key → no "no read access" error
  • Run capgo bundle upload with any valid key → app_versions.owner_org is never NULL
  • Run capgo bundle list → still works
  • Existing keys without org_member need to be recreated by affected users

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • API key creation now auto-adds a corresponding organization-scoped permission when app-scoped permissions are included, avoiding missing org permissions.
    • App records now reliably set owner organization during create/update and guard against unexpected app ID changes.
  • Security

    • Hardened database trigger/helper logic by tightening ownership and revoking public privileges for sensitive functions.

Dalanir and others added 2 commits May 8, 2026 12:34
Two bugs prevented OTA uploads via API keys while bundle list still worked:

1. **null owner_org (23502)**: The BEFORE INSERT trigger on app_versions called
   get_user_main_org_id_by_app_id which, since migration 20260203150000, runs
   auth checks that fail under PostgREST's authenticator role (no auth.uid(),
   auth.role()='anon'). The trigger fires after RLS has already validated
   access, so auth re-checks are redundant. Replaced with a minimal SECURITY
   DEFINER helper get_owner_org_by_app_id_internal that simply resolves the
   owning org from the apps table.

2. **no read access**: API keys created with only app-level role bindings
   (e.g. app_uploader) lack org_member and therefore org.read, which
   get_organization_cli_warnings requires. Users always receive org_member
   alongside app-level roles; API keys must respect the same invariant.
   post.ts now auto-adds an org_member binding for each org that has
   app-level bindings but no explicit org-level binding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa7b83e3-9a3f-4c23-8e3d-34a4f08c642c

📥 Commits

Reviewing files that changed from the base of the PR and between 21f986f and c70af54.

📒 Files selected for processing (2)
  • supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql
  • supabase/schemas/prod.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql
  • supabase/schemas/prod.sql

📝 Walkthrough

Walkthrough

Adds a SECURITY DEFINER helper to resolve an app's owner_org for triggers, updates the auto_owner_org_by_app_id trigger to use it (with an app_id-change guard and privilege hardening), and enriches API key binding creation to auto-add org-scoped org_member bindings when app-scoped bindings exist.

Changes

Bindings and App Ownership

Layer / File(s) Summary
SQL Helper Functions
supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql, supabase/schemas/prod.sql
New internal helper public.get_owner_org_by_app_id_internal(p_app_id text) defined as SECURITY DEFINER/STABLE that selects owner_org from public.apps, with ownership/privilege revocation.
Trigger Function Update
supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql, supabase/schemas/prod.sql
public.auto_owner_org_by_app_id() replaced to call the new helper for NEW.owner_org, adds a guard against app_id changes, and hardens owner/privileges.
API Binding Enrichment
supabase/functions/_backend/public/apikey/post.ts
Build enrichedBindings ensuring any org referenced by app-scoped bindings also has an org-scoped org_member binding; role-binding creation iterates over enrichedBindings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Cap-go/capgo#2012: Modifies the same API key POST flow and binding creation logic.
  • Cap-go/capgo#1950: Related RBAC binding ownership and validation changes for app/channel-scoped bindings.
  • Cap-go/capgo#2015: Related changes to app_versions trigger behavior and owner_org resolution.

Suggested labels

codex

Poem

🐰 In code I hop where bindings grow,

I stitch an org where app-scopes flow,
A helper finds each app's true home,
Triggers set owner_org with no roam,
Hooray — roles and orgs now safely known.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing bundle upload functionality for API keys with app-only RBAC bindings, which directly aligns with the two bugs being addressed in the PR.
Description check ✅ Passed The description includes a detailed summary of both bugs, their root causes, and fixes, but the test plan section is incomplete with unchecked items and lacks clear verification steps for the owner_org fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 fix-org-read-permission-check

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 SQLFluff (4.1.0)
supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql

User Error: No dialect was specified. You must configure a dialect or specify one on the command line using --dialect after the command. Available dialects:
ansi, athena, bigquery, clickhouse, databricks, db2, doris, duckdb, exasol, flink, greenplum, hive, impala, mariadb, materialize, mysql, oracle, postgres, redshift, snowflake, soql, sparksql, sqlite, starrocks, teradata, trino, tsql, vertica

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 8, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing fix-org-read-permission-check (c70af54) with main (8912755)

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.

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 (1)
supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql (1)

15-21: Add the required execution-model and EXPLAIN evidence for this helper.

This function now sits on the app_versions write path, but the supplied PR notes only cover functional verification. Please add where it runs, how often it runs, expected cardinality/indexes, and EXPLAIN (ANALYZE, BUFFERS) for the worst-case lookup.

As per coding guidelines, “Every PostgreSQL function must be proven to scale before shipping; document execution model (where it runs, how often, which roles, cardinality, and indexes) and provide EXPLAIN (ANALYZE, BUFFERS) output for worst-case scenarios in PR notes”.

🤖 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/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql`
around lines 15 - 21, Document execution model and worst‑case query evidence for
the helper function get_owner_org_by_app_id_internal: update the PR notes to
state where it runs (the app_versions write path/trigger), how often (per
app_versions write), which role/privileges run it (SECURITY DEFINER caller
context), expected cardinality (number of rows in public.apps and expected
selectivity of app_id) and required indexes (ensure public.apps has a
unique/index on app_id); then run and paste EXPLAIN (ANALYZE, BUFFERS) for the
worst‑case lookup against public.apps (large table, no useful cache) and include
any follow‑ups (e.g., create an index on apps.app_id or change query to use
indexed column) so reviewers can confirm the function scales.
🤖 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 `@supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql`:
- Around line 23-24: The trigger function auto_owner_org_by_app_id currently
runs as SECURITY INVOKER and cannot call get_owner_org_by_app_id_internal
(EXECUTE revoked from PUBLIC); change the trigger function definition to be
SECURITY DEFINER so it runs with the postgres role and can call the internal
helper, i.e., alter or recreate function auto_owner_org_by_app_id(...) with
SECURITY DEFINER (keeping owner as postgres) so the trigger executes with
elevated privileges when invoking get_owner_org_by_app_id_internal.

---

Nitpick comments:
In `@supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql`:
- Around line 15-21: Document execution model and worst‑case query evidence for
the helper function get_owner_org_by_app_id_internal: update the PR notes to
state where it runs (the app_versions write path/trigger), how often (per
app_versions write), which role/privileges run it (SECURITY DEFINER caller
context), expected cardinality (number of rows in public.apps and expected
selectivity of app_id) and required indexes (ensure public.apps has a
unique/index on app_id); then run and paste EXPLAIN (ANALYZE, BUFFERS) for the
worst‑case lookup against public.apps (large table, no useful cache) and include
any follow‑ups (e.g., create an index on apps.app_id or change query to use
indexed column) so reviewers can confirm the function scales.
🪄 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: 5a53993c-b6af-48b7-b31e-519436ad1a87

📥 Commits

Reviewing files that changed from the base of the PR and between 8912755 and 21f986f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • supabase/functions/_backend/public/apikey/post.ts
  • supabase/migrations/20260508122137_fix_app_versions_trigger_owner_org.sql
  • supabase/schemas/prod.sql

The trigger function was SECURITY INVOKER, so it ran under the caller's
role (authenticated/anon) and could not call get_owner_org_by_app_id_internal
after EXECUTE was revoked from PUBLIC. Making the trigger SECURITY DEFINER
lets it run as postgres (the owner), which can call the internal helper
without any grants to unprivileged roles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

@riderx riderx merged commit 0a21d65 into main May 8, 2026
52 checks passed
@riderx riderx deleted the fix-org-read-permission-check branch May 8, 2026 11:22
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.

2 participants