Skip to content

feat(cli-warnings): fail fast when CLI < 7.107.0 hits the PR #2282 RBAC bug#2302

Closed
WcaleNieWolny wants to merge 1 commit into
mainfrom
feat/cli-warning-rbac-appid-bug
Closed

feat(cli-warnings): fail fast when CLI < 7.107.0 hits the PR #2282 RBAC bug#2302
WcaleNieWolny wants to merge 1 commit into
mainfrom
feat/cli-warning-rbac-appid-bug

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented May 20, 2026

Summary

Follow-up to #2282. That PR fixed the appid-passthrough bug on the server side and shipped the CLI fix in @capgo/cli@7.107.0. This PR adds a fail-fast fatal CLI warning for users still running older CLIs whose API keys would trigger the bug, so they see an actionable message instead of the misleading "Plan upgrade required for upload" billing error.

The warning fires only when ALL are true

  1. Caller's API key is RBAC v2 (apikeys.mode IS NULL with role_bindings).
  2. Key has limited_to_apps set (non-empty).
  3. Org has use_new_rbac = true.
  4. cli_version parses as X.Y.Z AND sits below 7.107.0.

These are the exact preconditions for the appid-passthrough denial in rbac_check_permission_direct. If any are false, the warning stays silent — including unparseable version strings like dev/next (we'd rather not nag on local builds).

Why fatal and why now

  • checkRemoteCliMessages runs before checkPlanValidUpload in cli/src/bundle/upload.ts:846 vs :850. A fatal warning throws and stops the upload before the misleading billing error can fire.
  • The bug currently surfaces as Error: Plan upgrade required for upload + an auto-opened plans page in the browser. Users waste time inspecting Stripe / their plan when the real issue is RBAC denying for app-context reasons.
  • The replacement message names the bug, the upgrade target (@capgo/cli@7.107.0), and the workaround (drop limited_to_apps on the key, leaving only the org restriction).

Why scoped to RBAC v2 keys only

Legacy keys (mode IN ('read','write','upload','all')) with limited_to_apps on a use_new_rbac org also hit the bug. I narrowed scope to RBAC v2 keys per the request — start tight, broaden later if support reports the same misclassification for legacy keys. Easy to extend the condition.

How the version check works

cli_version_match := regexp_match(cli_version, '^([0-9]+)\.([0-9]+)\.([0-9]+)');
IF cli_version_match IS NOT NULL THEN
  cli_version_parts := ARRAY[m[1]::int, m[2]::int, m[3]::int];
  IF cli_version_parts < ARRAY[7, 107, 0]::int[] THEN
    -- append fatal warning
  END IF;
END IF;

Postgres int[] compares lexicographically, so [7,106,0] < [7,107,0] and [7,107,0] < [7,107,0] is false. Pre-release suffixes (7.107.0-beta.1) parse to [7,107,0] and fall the right side of the cutoff.

What the user sees

Before (on, say, @capgo/cli@7.106.0):

Error: Plan upgrade required for upload
You need to upgrade your plan to continue to use capgo.
Upgrade here: https://capgo.app/settings/organization/plans

(browser opens the plans page — completely unrelated to the actual problem)

After:

Found 1 cli warnings for your organization.
Your CLI version (7.106.0) has a known bug affecting RBAC-managed API keys restricted to specific apps.
Uploads with this key fail with "Plan upgrade required for upload" even when your plan is healthy.
Fix: upgrade to @capgo/cli@7.107.0 or newer:
    npm i -g @capgo/cli@latest
Workaround if you cannot upgrade: remove the app restriction (limited_to_apps) on this API key, leaving only the org restriction. See https://github.com/Cap-go/capgo/pull/2282 for context.
Please fix the warnings and try again.

Test plan

tests/cli-warning-rbac-appid-bug.test.ts covers the full condition matrix end-to-end via PostgREST (anon + capgkey header — same path the CLI takes):

mode limited_to_apps use_new_rbac cli_version fires?
NULL non-empty true 7.106.0
NULL non-empty true 7.107.0 ❌ (cutoff)
NULL non-empty true 7.108.0 ❌ (above)
'all' non-empty true 7.106.0 ❌ (legacy)
NULL empty true 7.106.0 ❌ (no app restriction)
NULL non-empty false 7.106.0 ❌ (RBAC off)
NULL non-empty true dev/next/empty ❌ (unparseable)
  • bun typecheck — clean
  • bun run cli:lint / cli:typecheck / cli:build — clean
  • bun run supabase:with-env -- bunx vitest run tests/cli-warning-rbac-appid-bug.test.ts — 7/7 passing
  • Sanity: replaced the function with a no-op stub locally and re-ran the test — the positive case fails as expected; restoring the migration makes everything green again. The test actually exercises the function logic, not just confirms current behavior.

Out of scope

Summary by CodeRabbit

New Features

  • Introduced CLI warning system that provides real-time notifications about storage capacity constraints when limits are approached or exceeded, and alerts users to known RBAC v2 compatibility issues affecting specific older CLI versions. These warnings help users identify potential deployment issues early and take corrective action to maintain system stability.

Review Change Stack

#2282 RBAC bug

Adds a fail-fast warning in get_organization_cli_warnings that fires
when ALL of these are true for the caller:

  1. API key is RBAC v2 (apikeys.mode IS NULL).
  2. Key has limited_to_apps set.
  3. Org has use_new_rbac = true.
  4. CLI version parses as < 7.107.0.

These are the exact conditions that trigger the appid-passthrough bug
fixed in #2282. Without this warning, affected users see the misleading
"Plan upgrade required for upload" billing error and waste time chasing
plan/Stripe state when the real issue is RBAC denying the call because
the CLI didn't thread appid through the plan check.

checkRemoteCliMessages runs BEFORE checkPlanValidUpload in the upload
flow (cli/src/bundle/upload.ts), so the new fatal warning replaces the
useless billing error with one that names the bug, the upgrade target
(@capgo/cli@7.107.0), and the workaround (drop limited_to_apps on the
key) - the same workaround documented in the wiki triage runbook.

Scope deliberately narrowed to RBAC v2 keys per request - legacy mode
keys ('all','read','write','upload') with limited_to_apps would hit
the same bug, but we'd rather start tight and broaden later if needed.

Test (tests/cli-warning-rbac-appid-bug.test.ts) covers the full
condition matrix: fires for the bug case, silent on the cutoff, above
the cutoff, legacy mode, no app restriction, use_new_rbac=false, and
unparseable version strings (dev/next builds).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces a database RPC function to detect and warn CLI users about a specific RBAC v2 key incompatibility bug affecting versions below 7.107.0, along with a comprehensive regression test suite that validates the warning fires under the correct API key and organization conditions.

Changes

RBAC CLI version bug warning

Layer / File(s) Summary
RPC function and security setup
supabase/migrations/20260520040202_cli_warning_rbac_appid_bug.sql
public.get_organization_cli_warnings(orgid uuid, cli_version text) verifies caller API key has org read access, emits fatal warnings for exceeded storage limits and for the specific RBAC v2 CLI version incompatibility (mode IS NULL, use_new_rbac=true, limited_to_apps set, CLI version < 7.107.0), and is owned by postgres with execute grants to anon, authenticated, and service_role.
Test setup and helper functions
tests/cli-warning-rbac-appid-bug.test.ts (imports, provisioning, client, constants, lifecycle)
Provides test infrastructure: test configuration, provisionKey helper that creates RBAC v2 keys with required org_member bindings, capgkeyClient authenticated client, callWarnings RPC wrapper, warning detection constants, and beforeAll/afterAll lifecycle that provisions/cleans org, app, user, and Stripe test data. Suite skips when USE_CLOUDFLARE_WORKERS is enabled.
Regression test cases
tests/cli-warning-rbac-appid-bug.test.ts (test assertions)
Validates the RBAC bug warning fires for CLI 7.106.0 with RBAC v2 keys in the appid-passthrough scenario, does not fire for CLI ≥ 7.107.0, does not fire for legacy mode-all keys, does not fire when limited_to_apps is empty or org has use_new_rbac=false, and does not fire for unparseable CLI version strings (dev, next, empty, non-version text). Each test cleans up its API key and role bindings.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (version < 7.107.0)
  participant AuthClient as Supabase Client
  participant RPC as get_organization_cli_warnings RPC
  participant AuthDB as Auth DB
  participant OrgDB as Org DB
  
  CLI->>AuthClient: call RPC with orgid, cli_version
  AuthClient->>RPC: execute (SECURITY DEFINER)
  RPC->>AuthDB: check API key org read permission
  AuthDB-->>RPC: org read permission granted
  RPC->>OrgDB: check org RBAC v2 enabled, app-restricted key, org paying
  OrgDB-->>RPC: conditions match RBAC bug criteria
  RPC->>RPC: parse cli_version X.Y.Z, compare < 7.107.0
  RPC-->>AuthClient: return fatal warning array containing RBAC bug message
  AuthClient-->>CLI: display fatal warning to user
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Cap-go/capgo#2214: Both PRs modify the same public.get_organization_cli_warnings authorization logic, with #2214 fixing org-read checks for RBAC v2 keys and this PR extending it with the RBAC bug version-gated fatal warning.
  • Cap-go/capgo#2079: Adds automatic org_member role binding for API keys with only app-scoped RBAC, which directly affects the org.read permission checks inside the new RPC and the test scenarios.
  • Cap-go/capgo#2274: Updates the same public.get_organization_cli_warnings function to change how RBAC scope is interpreted when producing CLI warning messages.

Poem

🐰 A warning hops through bytes of code,
For CLI keys on the wrong road,
Version seven-point-one-oh-seven,
Brings a patch that feels like heaven,
No more RBAC trouble, tests show the way!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Description check ❓ Inconclusive The PR description is comprehensive, covering summary, test plan, and condition matrix. However, the checklist is incomplete—code style checks and test coverage boxes are not marked, which are required fields. Complete the checklist by checking boxes for code style compliance and test coverage verification, or clarify which items do not apply to this change.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a fail-fast CLI warning for older CLI versions hitting a known RBAC bug, matching the PR's primary purpose.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-warning-rbac-appid-bug

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.2.1)
supabase/migrations/20260520040202_cli_warning_rbac_appid_bug.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


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 20, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing feat/cli-warning-rbac-appid-bug (c48b2b4) with main (7632d62)

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

🧹 Nitpick comments (1)
supabase/migrations/20260520040202_cli_warning_rbac_appid_bug.sql (1)

41-41: ⚡ Quick win

Remove dead code: PERFORM cli_version; is a no-op.

This line executes PERFORM on a parameter reference, which evaluates the expression and discards the result. Since cli_version is already a function parameter, this does nothing useful.

♻️ Proposed fix
 BEGIN
-    PERFORM cli_version;
-
     has_org_read := public.cli_check_permission(
🤖 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/20260520040202_cli_warning_rbac_appid_bug.sql` at line
41, The statement PERFORM cli_version; is a no-op because cli_version is already
a function parameter; remove that line from the migration so the function no
longer evaluates-and-discards the parameter. Locate the PERFORM cli_version;
statement in the migration and delete it (leave any surrounding logic intact) so
the code no longer executes the redundant PERFORM on the cli_version parameter.
🤖 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/20260520040202_cli_warning_rbac_appid_bug.sql`:
- Around line 111-113: Update the comment text in the migration SQL where the
phrase "Unparseable versions (empty string, "dev","next")" appears: change the
word "Unparseable" to the standard spelling "Unparsable" so the comment reads
"Unparsable versions (empty string, "dev", "next")". Locate and edit that
comment block (the multi-line comment containing "Parse leading X.Y.Z.") to
apply the correction.

In `@tests/cli-warning-rbac-appid-bug.test.ts`:
- Line 295: The test description string in the it() block currently uses
"unparseable" but should use "unparsable"; update the test name in the it('does
NOT fire on unparseable CLI version strings (dev/next builds)') to use
"unparsable" so it reads it('does NOT fire on unparsable CLI version strings
(dev/next builds)') to fix the typo and keep spelling consistent with the
migration file.
- Line 143: The test suite title in the describe.skipIf call ("CLI warning: RBAC
v2 + limited_to_apps + old CLI (PR `#2282`)") starts with an uppercase letter
which violates the lint rule; update the string passed to describe.skipIf to
start with a lowercase character (e.g., change "CLI warning..." to "cli
warning...") so the describe title begins with lowercase, leaving the rest of
the text unchanged and keeping the same describe.skipIf invocation.

---

Nitpick comments:
In `@supabase/migrations/20260520040202_cli_warning_rbac_appid_bug.sql`:
- Line 41: The statement PERFORM cli_version; is a no-op because cli_version is
already a function parameter; remove that line from the migration so the
function no longer evaluates-and-discards the parameter. Locate the PERFORM
cli_version; statement in the migration and delete it (leave any surrounding
logic intact) so the code no longer executes the redundant PERFORM on the
cli_version parameter.
🪄 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: 93c9f4fc-0c13-4ed1-a009-cc2811fd6f95

📥 Commits

Reviewing files that changed from the base of the PR and between 7632d62 and c48b2b4.

📒 Files selected for processing (2)
  • supabase/migrations/20260520040202_cli_warning_rbac_appid_bug.sql
  • tests/cli-warning-rbac-appid-bug.test.ts

Comment on lines +111 to +113
-- Parse leading X.Y.Z. Unparseable versions (empty string, "dev",
-- "next") fall through without firing the warning - safer to be
-- silent than to nag on non-release builds.
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Minor typo: "Unparseable" → "Unparsable".

Static analysis flagged the spelling. The standard form is "unparsable".

📝 Proposed fix
-            -- Parse leading X.Y.Z. Unparseable versions (empty string, "dev",
+            -- Parse leading X.Y.Z. Unparsable versions (empty string, "dev",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Parse leading X.Y.Z. Unparseable versions (empty string, "dev",
-- "next") fall through without firing the warning - safer to be
-- silent than to nag on non-release builds.
-- Parse leading X.Y.Z. Unparsable versions (empty string, "dev",
-- "next") fall through without firing the warning - safer to be
-- silent than to nag on non-release builds.
🧰 Tools
🪛 GitHub Check: Run tests

[warning] 111-111:
"Unparseable" should be "Unparsable".

🤖 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/20260520040202_cli_warning_rbac_appid_bug.sql` around
lines 111 - 113, Update the comment text in the migration SQL where the phrase
"Unparseable versions (empty string, "dev","next")" appears: change the word
"Unparseable" to the standard spelling "Unparsable" so the comment reads
"Unparsable versions (empty string, "dev", "next")". Locate and edit that
comment block (the multi-line comment containing "Parse leading X.Y.Z.") to
apply the correction.

await serviceRoleSupabase.from('apikeys').delete().eq('id', handle.id)
}

describe.skipIf(USE_CLOUDFLARE_WORKERS)('CLI warning: RBAC v2 + limited_to_apps + old CLI (PR #2282)', () => {
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

ESLint: describe title should begin with lowercase.

Per project linting rules, test suite descriptions should start with a lowercase letter.

📝 Proposed fix
-describe.skipIf(USE_CLOUDFLARE_WORKERS)('CLI warning: RBAC v2 + limited_to_apps + old CLI (PR `#2282`)', () => {
+describe.skipIf(USE_CLOUDFLARE_WORKERS)('cli warning: RBAC v2 + limited_to_apps + old CLI (PR `#2282`)', () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe.skipIf(USE_CLOUDFLARE_WORKERS)('CLI warning: RBAC v2 + limited_to_apps + old CLI (PR #2282)', () => {
describe.skipIf(USE_CLOUDFLARE_WORKERS)('cli warning: RBAC v2 + limited_to_apps + old CLI (PR `#2282`)', () => {
🧰 Tools
🪛 ESLint

[error] 143-143: describes should begin with lowercase

(test/prefer-lowercase-title)

🤖 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 `@tests/cli-warning-rbac-appid-bug.test.ts` at line 143, The test suite title
in the describe.skipIf call ("CLI warning: RBAC v2 + limited_to_apps + old CLI
(PR `#2282`)") starts with an uppercase letter which violates the lint rule;
update the string passed to describe.skipIf to start with a lowercase character
(e.g., change "CLI warning..." to "cli warning...") so the describe title begins
with lowercase, leaving the rest of the text unchanged and keeping the same
describe.skipIf invocation.

}
})

it('does NOT fire on unparseable CLI version strings (dev/next builds)', async () => {
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Minor typo: "unparseable" → "unparsable".

Static analysis flagged this—same spelling issue as in the migration file.

📝 Proposed fix
-  it('does NOT fire on unparseable CLI version strings (dev/next builds)', async () => {
+  it('does NOT fire on unparsable CLI version strings (dev/next builds)', async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('does NOT fire on unparseable CLI version strings (dev/next builds)', async () => {
it('does NOT fire on unparsable CLI version strings (dev/next builds)', async () => {
🧰 Tools
🪛 GitHub Check: Run tests

[warning] 295-295:
"unparseable" should be "unparsable".

🤖 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 `@tests/cli-warning-rbac-appid-bug.test.ts` at line 295, The test description
string in the it() block currently uses "unparseable" but should use
"unparsable"; update the test name in the it('does NOT fire on unparseable CLI
version strings (dev/next builds)') to use "unparsable" so it reads it('does NOT
fire on unparsable CLI version strings (dev/next builds)') to fix the typo and
keep spelling consistent with the migration file.

@sonarqubecloud
Copy link
Copy Markdown

@WcaleNieWolny
Copy link
Copy Markdown
Contributor Author

Closing in favor of #2279.

After tracing through #2279's migration carefully, the bug this PR warns about is structurally fixed by #2279 — not just patched. The relevant chain:

  1. apikeys.limited_to_apps column is dropped entirely.
  2. The migration backfills role_bindings so every existing app-restricted key gets an org-scope org_member binding in addition to its app-scope binding(s).
  3. rbac_check_permission_direct is rewritten without the limited_to_apps gate.

For an old CLI (< 7.107.0) calling is_allowed_action_org_action(orgid, ['storage']) after #2279 lands, the trace goes:

  • check_min_rights('read', user_id, orgid, NULL /* app_id */, NULL)
  • rbac_check_permission_direct('org.read', ..., orgid, NULL, NULL, apikey)
  • rbac_has_permission(apikey, rbac_id, 'org.read', orgid, NULL, NULL)
  • → finds the org_member binding at org scope → returns true

So the bug can no longer manifest on the old CLI's call path. The 3-arg overload from #2282 stays harmless. The warning this PR adds becomes:

There's a separate concern about a privilege-widening side effect of #2279's migration (previously app-restricted keys gain org.read / org.read_members / org.create_app via the org_member binding). I'll raise that on #2279 directly rather than gate it on this PR.

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