Skip to content

fix(api): harden API key management auth#2403

Open
riderx wants to merge 2 commits into
mainfrom
codex/rbac-apikey-management-hardening
Open

fix(api): harden API key management auth#2403
riderx wants to merge 2 commits into
mainfrom
codex/rbac-apikey-management-hardening

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Jun 3, 2026

Summary (AI generated)

  • Removed stale route-level key-mode authorization such as middlewareV2(['all', 'write']); handlers now authenticate first and enforce authorization through RBAC checks, API route guards, and RLS.
  • Removed app-side use_new_rbac runtime gating while keeping the compatibility output field and old SQL compatibility helpers.
  • Hardened /apikey management mutations: API-key sessions are rejected for update/regenerate/delete, while /apikey read/list API-key compatibility remains and limited scoped keys are still filtered.
  • Hardened org settings writes: direct anonymous/API-key RLS updates to orgs are closed, use_new_rbac is forced true, rollback cannot disable RBAC, and cli organization set now calls PUT /organization instead of writing orgs directly.
  • Added explicit backend validation for password_policy_config so moving CLI org settings to the API route does not drop the password-policy feature.
  • Fixed the events notify-console path so it relies on the verified org/app resolution instead of an org-only preflight that broke app-scoped authorization.

Motivation (AI generated)

RBAC is now the authoritative authorization model. Leaving old key-mode write labels, app-side RBAC feature flags, and CLI direct org writes made the active security boundary ambiguous and could preserve upgrade/downgrade paths that no longer match the product model. This PR removes those stale surfaces while preserving intentional compatibility for old API-key read paths and old org payload shape.

Fix Justification (AI generated)

  • Auth-only middleware defaults: old ['all', 'write'] route declarations are no longer the security boundary and were misleading.
  • API-key mutation guard: a leaked API key, even a broad one, must not be able to update/regenerate/delete API keys or upgrade its own API-key RBAC bindings.
  • /apikey read/list compatibility: keeping API-key reads avoids breaking old compatible callers; scoped keys still use the existing limited-scope filter.
  • Org RLS hardening: orgs UPDATE is now authenticated-only and named-RBAC guarded; API-key org settings writes must go through the backend route where middlewareV2, checkPermission, and API-key org policy checks run before the service-role write.
  • CLI org settings route migration: cli organization set no longer relies on direct Supabase orgs.update(...), so it does not need the old write RLS policy to stay open.
  • Password-policy route validation: the backend route now accepts only a typed policy object with min_length bounded to 6..72, matching the DB constraint.
  • Compatibility helpers: rbac_enable_for_org remains service-role callable for old automation, but always returns RBAC enabled; rbac_rollback_org is retained but cannot disable RBAC or delete bindings.

Business Impact (AI generated)

This reduces the chance of API-key self-escalation, leaked-key damage, or accidental reactivation of legacy write behavior, while avoiding a breaking change for compatible API-key read/list callers and current CLI org-settings workflows.

Test Plan (AI generated)

  • bun lint:backend
  • bun typecheck:backend
  • commit hook: bun run cli:typecheck && bun run typecheck:backend && bun run typecheck:frontend
  • bun run cli:check
  • bun run supabase:db:reset
  • PGSSLMODE=disable bunx supabase test db --db-url postgresql://postgres:postgres@127.0.0.1:60642/postgres supabase/tests/00-supabase_test_helpers.sql supabase/tests/26_test_rls_policies.sql supabase/tests/48_test_rbac_admin_rpc_execute_grants.sql
  • bun test --timeout 60000 tests/events.test.ts tests/apikeys.test.ts tests/apikeys-expiration.test.ts tests/organization-api.test.ts tests/audit-logs.test.ts tests/password-policy.test.ts
  • bunx vitest run tests/organization-put-stripe-sync.unit.test.ts

Generated with AI

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@riderx, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 63 minutes and 50 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 965d16e9-8334-4628-9f4d-49cc203e3530

📥 Commits

Reviewing files that changed from the base of the PR and between fea72cb and d3e8ae7.

📒 Files selected for processing (45)
  • cli/package.json
  • cli/src/organization/set.ts
  • cli/src/utils.ts
  • cli/test/test-organization-set-api-host.mjs
  • supabase/functions/_backend/files/files.ts
  • supabase/functions/_backend/private/admin_credits.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/delete_failed_version.ts
  • supabase/functions/_backend/private/devices.ts
  • supabase/functions/_backend/private/events.ts
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/functions/_backend/private/set_org_email.ts
  • supabase/functions/_backend/private/stats.ts
  • supabase/functions/_backend/private/upload_link.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • supabase/functions/_backend/public/apikey/post.ts
  • supabase/functions/_backend/public/apikey/put.ts
  • supabase/functions/_backend/public/apikey/scope.ts
  • supabase/functions/_backend/public/app/demo.ts
  • supabase/functions/_backend/public/app/index.ts
  • supabase/functions/_backend/public/build/index.ts
  • supabase/functions/_backend/public/bundle/index.ts
  • supabase/functions/_backend/public/bundle/update_metadata.ts
  • supabase/functions/_backend/public/channel/index.ts
  • supabase/functions/_backend/public/device/index.ts
  • supabase/functions/_backend/public/organization/audit.ts
  • supabase/functions/_backend/public/organization/index.ts
  • supabase/functions/_backend/public/organization/put.ts
  • supabase/functions/_backend/public/statistics/index.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/hono.ts
  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/utils/pg_files.ts
  • supabase/functions/_backend/utils/rbac.ts
  • supabase/functions/_backend/utils/supabase.ts
  • supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql
  • supabase/schemas/prod.sql
  • supabase/seed.sql
  • supabase/tests/26_test_rls_policies.sql
  • supabase/tests/48_test_rbac_admin_rpc_execute_grants.sql
  • tests/apikeys-expiration.test.ts
  • tests/apikeys.test.ts
  • tests/organization-api.test.ts
  • tests/test-utils.ts
📝 Walkthrough

Walkthrough

This PR hardens role-based access control (RBAC) authorization by refactoring middleware APIs to use optional defaults, removing legacy permission checks, updating route handlers across the platform to use simplified middleware wiring, enforcing RBAC at the database level through new policies and triggers, and expanding authorization tests with RLS-based validation.

Changes

RBAC and authorization system hardening

Layer / File(s) Summary
Middleware API and authorization utilities refactoring
supabase/functions/_backend/utils/hono_middleware.ts, supabase/functions/_backend/utils/hono.ts, supabase/functions/_backend/utils/rbac.ts, supabase/functions/_backend/utils/supabase.ts
Middleware factory functions transition from positional/array parameters to optional object-based configuration with internal defaults (AUTH_COMPAT_KEY_MODES). RBAC context middleware is removed, and deprecated authorization helpers (hasOrgRight, hasOrgRightApikey, isRbacEnabledForOrg, getLegacyRightForPermission) are deleted.
Route handler middleware wiring simplification
supabase/functions/_backend/public/apikey/*, supabase/functions/_backend/public/app/*, supabase/functions/_backend/public/build/*, supabase/functions/_backend/public/bundle/*, supabase/functions/_backend/public/channel/*, supabase/functions/_backend/public/device/*, supabase/functions/_backend/public/organization/index.ts, supabase/functions/_backend/public/statistics/index.ts, supabase/functions/_backend/private/admin_credits.ts, supabase/functions/_backend/private/delete_failed_version.ts, supabase/functions/_backend/private/devices.ts, supabase/functions/_backend/private/role_bindings.ts, supabase/functions/_backend/files/files.ts
Across 20+ route handler files, middleware calls are updated to use the new default middleware configuration without explicit permission arrays, removing scoped authorization constraints from the middleware layer itself.
Handler authorization logic refactoring
supabase/functions/_backend/private/create_device.ts, supabase/functions/_backend/private/set_org_email.ts, supabase/functions/_backend/private/events.ts, supabase/functions/_backend/public/organization/audit.ts, supabase/functions/_backend/public/app/demo.ts, supabase/functions/_backend/public/webhooks/index.ts, supabase/functions/_backend/private/upload_link.ts, supabase/functions/_backend/private/stats.ts
Route handlers that enforced org or app permissions replace legacy hasOrgRight and RPC-based check_min_rights calls with the unified checkPermission(c, 'org.permission_name', {orgId}) pattern, centralizing RBAC permission evaluation.
Database RBAC hardening and org enforcement
supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql
The migration enforces use_new_rbac=true as default and immutable at the column level via a trigger, adds restrictive INSERT and UPDATE RLS policies that deny writes when the flag is false, defines a permissive UPDATE policy gated by RBAC permission checks with additional validation for 2FA and password policy, and provides compatibility functions (rbac_enable_for_org and rbac_rollback_org) for managed org transitions.
Authorization and RLS test validation
supabase/tests/26_test_rls_policies.sql, supabase/tests/48_test_rbac_admin_rpc_execute_grants.sql, tests/apikeys-expiration.test.ts, tests/apikeys.test.ts
SQL tests verify policy names, RESTRICTIVE markers, and trigger existence; assert rbac_rollback_org(...) returns not_supported. API key expiration tests refactor HTTP status assertions to RLS-based read checks via Supabase clients using the API key. Authorization tests add coverage for org-scoped API key denial and direct-table RLS enforcement.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Cap-go/capgo#1984: Backfills RBAC bindings and enables use_new_rbac for existing orgs via the shared rbac_enable_for_org(...) mechanism defined in the hardening migration.
  • Cap-go/capgo#2391: Extends the API key PUT /:id route to parse and atomically replace RBAC role bindings, overlapping at the same endpoint modified by this PR's middleware wiring change.
  • Cap-go/capgo#2020: Updates rbac_rollback_org test expectations to align with this PR's database hardening that enforces rollback as not_supported.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is largely missing the required template sections and lacks sufficient detail on test plan and manual testing steps. Add a formal Summary section, expand the Test plan section with step-by-step testing instructions, and clarify manual testing steps as required by the template.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main objective: hardening API key management authentication by removing stale route-level authorization and centralizing enforcement via RBAC/RLS.
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.


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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Jun 3, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/rbac-apikey-management-hardening (d3e8ae7) with main (6d3a5f8)

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.

@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 2d98712 to 0ceea8a Compare June 3, 2026 12:52
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 0ceea8a to 7489bf9 Compare June 3, 2026 13:05
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 7489bf9 to 15a2742 Compare June 3, 2026 13:18
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 15a2742 to 0f07372 Compare June 3, 2026 13:26
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 0f07372 to 6d7e2b2 Compare June 3, 2026 13:30
@riderx riderx marked this pull request as ready for review June 3, 2026 13:48
@coderabbitai coderabbitai Bot added the codex label Jun 3, 2026
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/tests/26_test_rls_policies.sql (1)

377-393: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Assert the new deny policies are actually restrictive.

This only proves the three policy names exist. If a future migration recreates any of them as PERMISSIVE, the test still passes while the deny becomes ineffective against the existing owner allow-policies. Please add pg_policies assertions for permissive = 'RESTRICTIVE' and the expected roles/cmd on these three new policies.

As per coding guidelines: Add explicit deny policies for operations that must be impossible for user-facing roles, using RESTRICTIVE policies instead of relying on implicit deny.

🤖 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/tests/26_test_rls_policies.sql` around lines 377 - 393, The test
currently only checks that policy names exist for table apikeys via policies_are
but does not verify they are restrictive; add assertions that query pg_policies
for the three deny policies (e.g., 'Deny anon delete on apikeys', 'Deny anon
select on apikeys', 'Deny anon update on apikeys') and assert permissive =
'RESTRICTIVE' and that the role(s) and cmd columns match the expected values for
each policy; locate this near the existing policies_are call for apikeys and add
one assertion per deny policy checking pg_policies.permissive, pg_policies.roles
and pg_policies.cmd to ensure the denies are actually restrictive.
🤖 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/functions/_backend/public/apikey/auth.ts`:
- Around line 18-21: The code reads auth.authType without guarding for missing
auth; change the declaration to allow undefined (e.g., const auth =
c.get('auth') as AuthInfo | undefined) and update the guard to check for missing
auth first (if (!auth || auth.authType !== 'jwt' || !auth.userId) { ... }), and
when building the quickError payload use optional chaining for the authType
field (auth?.authType) so a missing auth produces the intended 401 instead of a
500; keep the same quickError call, errorCode, action and moreInfo variables.

In `@tests/apikeys-expiration.test.ts`:
- Around line 807-812: The test block removed an endpoint-level check, leaving
only direct Supabase/RLS reads (expectApiKeyCannotReadBaseOrg and
expectApiKeyCanReadBaseOrg); restore one HTTP-path assertion against the /apikey
endpoint in this block so middleware/header-parsing is exercised—add a single
assertion that the expiredKeyValue is rejected when calling the /apikey HTTP
route (alongside the RLS helper) and keep the validKeyValue HTTP-path check in
the other test block, referencing the existing helpers for RLS reads and the
/apikey route to locate where to add it.

In `@tests/apikeys.test.ts`:
- Around line 1030-1077: The test "plain key cannot update apikeys table
directly through RLS" leaves the created API key if an assertion fails; wrap the
cleanup DELETE call in a finally block so the seeded key is always removed.
Specifically, after creating the key (createResponse / createData), move the
fetch DELETE for `/apikey/${createData.id}` into a finally section that runs
regardless of test success, keeping the existing authHeaders and preserving the
rest of the assertions in the try block; ensure createData.id is available in
the finally (declare it in the outer scope if needed) so cleanup always
executes.

---

Outside diff comments:
In `@supabase/tests/26_test_rls_policies.sql`:
- Around line 377-393: The test currently only checks that policy names exist
for table apikeys via policies_are but does not verify they are restrictive; add
assertions that query pg_policies for the three deny policies (e.g., 'Deny anon
delete on apikeys', 'Deny anon select on apikeys', 'Deny anon update on
apikeys') and assert permissive = 'RESTRICTIVE' and that the role(s) and cmd
columns match the expected values for each policy; locate this near the existing
policies_are call for apikeys and add one assertion per deny policy checking
pg_policies.permissive, pg_policies.roles and pg_policies.cmd to ensure the
denies are actually restrictive.
🪄 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: 9d385eca-b762-452d-a898-93a462ad60c4

📥 Commits

Reviewing files that changed from the base of the PR and between e64b645 and 6d7e2b2.

📒 Files selected for processing (10)
  • cli/src/types/supabase.types.ts
  • supabase/functions/_backend/public/apikey/auth.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • supabase/functions/_backend/public/apikey/put.ts
  • supabase/functions/_backend/public/apikey/scope.ts
  • supabase/migrations/20260603120805_deny_apikey_management_from_api_key_auth.sql
  • supabase/tests/26_test_rls_policies.sql
  • tests/apikeys-expiration.test.ts
  • tests/apikeys.test.ts
💤 Files with no reviewable changes (1)
  • supabase/functions/_backend/public/apikey/scope.ts

Comment thread supabase/functions/_backend/public/apikey/auth.ts Outdated
Comment thread tests/apikeys-expiration.test.ts Outdated
Comment thread tests/apikeys.test.ts Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 6d7e2b2 to ed6303b Compare June 3, 2026 14:06
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from ed6303b to a8fc38e Compare June 3, 2026 14:14
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from a8fc38e to 3991392 Compare June 3, 2026 14:22
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

🤖 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 `@cli/src/types/supabase.types.ts`:
- Around line 3037-3045: The RPC type for app_versions_has_app_permission
incorrectly requires both p_apikey and p_user_id as non-null strings; update the
SQL RPC signature so the inactive auth parameter is nullable (make p_apikey OR
p_user_id NULLABLE/optional in the function definition for
app_versions_has_app_permission), deploy the migration, then regenerate the
TypeScript types (run the project type generation command, e.g. `bun types`) so
the generated supabase.types.ts reflects p_apikey and p_user_id as
nullable/optional and callers no longer need unsafe casts or dummy values.
🪄 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: 81ad7474-945a-4192-9133-1c57dfd028ca

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7e2b2 and a8fc38e.

📒 Files selected for processing (10)
  • cli/src/types/supabase.types.ts
  • supabase/functions/_backend/public/apikey/auth.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • supabase/functions/_backend/public/apikey/put.ts
  • supabase/functions/_backend/public/apikey/scope.ts
  • supabase/migrations/20260603120805_deny_apikey_management_from_api_key_auth.sql
  • supabase/tests/26_test_rls_policies.sql
  • tests/apikeys-expiration.test.ts
  • tests/apikeys.test.ts
💤 Files with no reviewable changes (1)
  • supabase/functions/_backend/public/apikey/scope.ts

Comment thread cli/src/types/supabase.types.ts Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 3991392 to d1b7c51 Compare June 3, 2026 15:34
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from d1b7c51 to fea72cb Compare June 3, 2026 15:41
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fea72cb875

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/public/organization/audit.ts Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from fea72cb to 507af66 Compare June 3, 2026 15:52
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
supabase/functions/_backend/private/events.ts (1)

131-153: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't preflight notifyConsole with org-only access.

Line 152 now rejects notifyConsole requests before appId is derived and before resolveTrackingUserId() can authorize via app.read + app ownership. App-scoped roles/API keys that are valid for one app but do not have org.read will now get a false 403 on this legacy flow.

Suggested fix
-  // Legacy notifyConsole still sends the target org in `user_id`, so keep this
-  // preflight scoped to notifyConsole. Non-notify v2 events validate `org_id`
-  // inside resolveTrackingUserId(), where app ownership and org access diverge.
-  if (body.notifyConsole && requestedOrgId && !(await canAccessRequestedOrg(c, requestedOrgId)))
-    throw quickError(403, 'Forbidden', 'You cannot send events for this organization')
-
   const requestedUserId = typeof body.user_id === 'string' ? body.user_id : undefined
   const appId = typeof body.tags?.['app-id'] === 'string'
     ? body.tags['app-id']
     : typeof body.tags?.app_id === 'string'
       ? body.tags.app_id
       : undefined
   const { trackingUserId, orgId: verifiedOrgId } = await resolveTrackingUserId(c, requestedUserId, requestedOrgId, appId, trackingV2, Boolean(body.notifyConsole))

Based on learnings: All changes to public APIs and plugin interfaces MUST be backward compatible; support both old and new behavior using version detection

🤖 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/private/events.ts` around lines 131 - 153, The
preflight check currently calls canAccessRequestedOrg for notifyConsole requests
and can incorrectly reject app-scoped keys; change the app.post handler so it
does NOT run the org-only canAccessRequestedOrg/403 path for legacy
notifyConsole flows and instead defers authorization to the later
resolveTrackingUserId/app-level checks: specifically, only run the
canAccessRequestedOrg check for the new trackingV2/org-id flow (when
isTrackingV2(body.tracking_version) is true and requestedOrgId is present), and
skip that check when body.notifyConsole is true so resolveTrackingUserId (or
app.read + app ownership checks) can authorize the request.
supabase/functions/_backend/public/apikey/get.ts (1)

18-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Require JWT sessions for API-key list/read routes too.

Both handlers still only reject limited-scope keys, so full-scope API-key sessions can enter /apikey list/read management flows. That contradicts the hardening goal here and risks either continued access or misleading downstream 404/500 responses when the real failure is authorization. Add an upfront auth.authType === 'apikey' rejection in both routes.

Also applies to: 41-47

🤖 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/get.ts` around lines 18 - 24, The
GET route handler registered with app.get('/', middlewareV2(), async (c) => {
... } currently only blocks limited-scope API keys via apiKeyHasLimitedScope,
but must reject any request authenticated via API key; update the handler (and
the similar read/list handler around the 41-47 range) to first check if
c.get('auth') as AuthInfo has auth.authType === 'apikey' and immediately throw
quickError(401, 'cannot_list_apikeys', 'You cannot do that as an API key', {
apikeyId: apikey?.id }) before calling apiKeyHasLimitedScope or proceeding, so
that full-scope API-key sessions are not allowed into the list/read flows; keep
the existing limited-scope check for more specific messaging if desired.
supabase/functions/_backend/public/apikey/delete.ts (1)

18-24: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject all API-key sessions before the delete path.

This only blocks limited-scope keys, so full-scope API-key callers still reach API-key deletion. That breaks the JWT-only management contract for this PR and can either preserve delete access or turn an authorization failure into a misleading 404/500 downstream. Reject auth.authType === 'apikey' up front here.

🤖 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/delete.ts` around lines 18 - 24,
The route handler in app.delete('/:id', middlewareV2(), ...) currently only
blocks limited-scope API keys but still allows full-scope API-key sessions to
proceed; update the handler to reject any session where auth.authType ===
'apikey' up front. Locate the app.delete handler and the auth variable (const
auth = c.get('auth') as AuthInfo) and add a guard that throws quickError(401,
'cannot_delete_apikey', 'You cannot do that as an API key', { apikeyId:
auth?.apikeyId ?? authApikey?.id }) (or similar) before calling
apiKeyHasLimitedScope; remove or keep the existing limited-scope check only as a
secondary/defensive measure so no API-key-based caller can reach the delete
logic.
🤖 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/functions/_backend/public/apikey/put.ts`:
- Around line 225-227: Add an upfront guard that rejects any non-JWT auth before
performing API-key mutations: check c.get('auth')?.authType (or auth.authType)
and if it's not 'jwt' throw quickError(401, 'invalid_auth_type', 'API key
management requires JWT', { requestId }). Replace relying solely on
apiKeyHasLimitedScope(authApikey) with this JWT-only check in the current
mutation handler (the block using auth.authType, apiKeyHasLimitedScope and
authApikey) and replicate the same JWT-only guard in the other mutation handler
that currently uses the limited-scope check (the analogous block later in the
file).

In `@supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql`:
- Around line 108-113: In the SECURITY DEFINER functions that return a JSON
object (the RETURN using jsonb_build_object), fully qualify the function call by
replacing jsonb_build_object(...) with pg_catalog.jsonb_build_object(...) so
that all references inside these functions are explicitly qualified; update the
RETURN statements that build the object containing 'status', 'org_id'
(p_org_id), 'migration_result' (v_migration_result) and 'rbac_enabled' to call
pg_catalog.jsonb_build_object, and apply the same change to the other instance
noted (the block around the second RETURN).
- Around line 55-58: The RLS policy "Allow org settings update via RBAC" on
public.orgs currently grants TO "anon", which allows API-key-authenticated
requests to bypass the intended restriction; update the policy to remove "anon"
so it only grants TO "authenticated" (keep the policy name and FOR UPDATE on
"public"."orgs" and leave any calls to public.rbac_check_permission_request(...)
intact—resolve API-key identity inside that helper instead of changing the
policy).

---

Outside diff comments:
In `@supabase/functions/_backend/private/events.ts`:
- Around line 131-153: The preflight check currently calls canAccessRequestedOrg
for notifyConsole requests and can incorrectly reject app-scoped keys; change
the app.post handler so it does NOT run the org-only canAccessRequestedOrg/403
path for legacy notifyConsole flows and instead defers authorization to the
later resolveTrackingUserId/app-level checks: specifically, only run the
canAccessRequestedOrg check for the new trackingV2/org-id flow (when
isTrackingV2(body.tracking_version) is true and requestedOrgId is present), and
skip that check when body.notifyConsole is true so resolveTrackingUserId (or
app.read + app ownership checks) can authorize the request.

In `@supabase/functions/_backend/public/apikey/delete.ts`:
- Around line 18-24: The route handler in app.delete('/:id', middlewareV2(),
...) currently only blocks limited-scope API keys but still allows full-scope
API-key sessions to proceed; update the handler to reject any session where
auth.authType === 'apikey' up front. Locate the app.delete handler and the auth
variable (const auth = c.get('auth') as AuthInfo) and add a guard that throws
quickError(401, 'cannot_delete_apikey', 'You cannot do that as an API key', {
apikeyId: auth?.apikeyId ?? authApikey?.id }) (or similar) before calling
apiKeyHasLimitedScope; remove or keep the existing limited-scope check only as a
secondary/defensive measure so no API-key-based caller can reach the delete
logic.

In `@supabase/functions/_backend/public/apikey/get.ts`:
- Around line 18-24: The GET route handler registered with app.get('/',
middlewareV2(), async (c) => { ... } currently only blocks limited-scope API
keys via apiKeyHasLimitedScope, but must reject any request authenticated via
API key; update the handler (and the similar read/list handler around the 41-47
range) to first check if c.get('auth') as AuthInfo has auth.authType ===
'apikey' and immediately throw quickError(401, 'cannot_list_apikeys', 'You
cannot do that as an API key', { apikeyId: apikey?.id }) before calling
apiKeyHasLimitedScope or proceeding, so that full-scope API-key sessions are not
allowed into the list/read flows; keep the existing limited-scope check for more
specific messaging if desired.
🪄 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: ab68b309-0943-4f4d-9fef-b38190517594

📥 Commits

Reviewing files that changed from the base of the PR and between a8fc38e and fea72cb.

📒 Files selected for processing (34)
  • supabase/functions/_backend/files/files.ts
  • supabase/functions/_backend/private/admin_credits.ts
  • supabase/functions/_backend/private/create_device.ts
  • supabase/functions/_backend/private/delete_failed_version.ts
  • supabase/functions/_backend/private/devices.ts
  • supabase/functions/_backend/private/events.ts
  • supabase/functions/_backend/private/role_bindings.ts
  • supabase/functions/_backend/private/set_org_email.ts
  • supabase/functions/_backend/private/stats.ts
  • supabase/functions/_backend/private/upload_link.ts
  • supabase/functions/_backend/public/apikey/delete.ts
  • supabase/functions/_backend/public/apikey/get.ts
  • supabase/functions/_backend/public/apikey/post.ts
  • supabase/functions/_backend/public/apikey/put.ts
  • supabase/functions/_backend/public/app/demo.ts
  • supabase/functions/_backend/public/app/index.ts
  • supabase/functions/_backend/public/build/index.ts
  • supabase/functions/_backend/public/bundle/index.ts
  • supabase/functions/_backend/public/bundle/update_metadata.ts
  • supabase/functions/_backend/public/channel/index.ts
  • supabase/functions/_backend/public/device/index.ts
  • supabase/functions/_backend/public/organization/audit.ts
  • supabase/functions/_backend/public/organization/index.ts
  • supabase/functions/_backend/public/statistics/index.ts
  • supabase/functions/_backend/public/webhooks/index.ts
  • supabase/functions/_backend/utils/hono.ts
  • supabase/functions/_backend/utils/hono_middleware.ts
  • supabase/functions/_backend/utils/rbac.ts
  • supabase/functions/_backend/utils/supabase.ts
  • supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql
  • supabase/tests/26_test_rls_policies.sql
  • supabase/tests/48_test_rbac_admin_rpc_execute_grants.sql
  • tests/apikeys-expiration.test.ts
  • tests/apikeys.test.ts
💤 Files with no reviewable changes (2)
  • supabase/functions/_backend/utils/hono.ts
  • supabase/functions/_backend/utils/supabase.ts

Comment thread supabase/functions/_backend/public/apikey/put.ts Outdated
Comment thread supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql Outdated
Comment thread supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 507af66 to 721ad2b Compare June 3, 2026 16:43
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 721ad2be20

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/migrations/20260603152530_harden_rbac_compat_cleanup.sql
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 721ad2b to 3bdcec9 Compare June 3, 2026 16:59
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bdcec952b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/schemas/prod.sql Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 3bdcec9 to 4afc795 Compare June 3, 2026 17:16
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4afc7956b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/organization/set.ts Outdated
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 4afc795 to e894097 Compare June 3, 2026 17:26
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e894097d41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/functions/_backend/private/set_org_email.ts
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from e894097 to 85614a9 Compare June 3, 2026 17:33
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85614a9926

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/src/organization/set.ts
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from 85614a9 to a808090 Compare June 3, 2026 18:01
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from a808090 to ea45cf7 Compare June 3, 2026 18:27
@riderx riderx force-pushed the codex/rbac-apikey-management-hardening branch from ea45cf7 to e579add Compare June 3, 2026 18:41
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e579add339

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread supabase/seed.sql
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 3, 2026

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