Skip to content

fix(rbac): stop mapping TUS auth DB failures to 403 insufficient_permissions - #2843

Merged
riderx merged 4 commits into
mainfrom
cursor/fix-tus-upload-false-403-0dbe
Aug 4, 2026
Merged

fix(rbac): stop mapping TUS auth DB failures to 403 insufficient_permissions#2843
riderx merged 4 commits into
mainfrom
cursor/fix-tus-upload-false-403-0dbe

Conversation

@riderx

@riderx riderx commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Fixes intermittent 403 insufficient_permissions on files.capgo.app TUS uploads (#2842)
  • checkPermission / checkPermissionPg now throw 503 upstream_unavailable on transient Hyperdrive/Postgres connection failures (including statement timeout 57014) instead of returning false
  • Non-transient query failures (invalid UUID cast 22P02, etc.) still return false → ACL deny
  • Real ACL denials still return false403/401
  • Adds unit coverage for deny vs transient infra vs invalid UUID, plus JWT/non-rbac_id path
  • Hardens stats rollout-metadata test to seed/update channels via generic executeSQL

Motivation (AI generated)

In #2842, checksum + channel compatibility succeed with the same API key, then TUS POST /files/upload/attachments/ intermittently returns:

{"error":"insufficient_permissions","message":"You don't have permission to access this app"}

Retry usually succeeds. That is not a stable RBAC/legacy-key mismatch: CLI already passed app.upload_bundle on the PostgREST path before TUS starts.

Root cause: TUS checkWriteAppAccess calls checkPermissionPg. On DB/Hyperdrive errors, that helper caught the exception and returned false, which the files worker mapped to 403 insufficient_permissions. tus-js-client does not retry 403, only 5xx — so customers needed an outer retry loop.

Business Impact (AI generated)

  • Stops false permission failures during OTA uploads for CI customers
  • Lets built-in TUS retries work (retryDelays already present in CLI)
  • Makes real infra failures diagnosable as upstream_unavailable
  • Preserves existing deny semantics for bad/inaccessible org identifiers

Test Plan (AI generated)

  • bunx vitest run tests/rbac-permission-infra-errors.unit.test.ts
  • Confirm real ACL deny still returns 403/401 on TUS / statistics / invite paths
  • Confirm injected PG timeout on permission check returns 503 upstream_unavailable
  • Confirm invalid org UUID still denies (not 503)
  • Confirm tus-js-client retries the 503 and completes upload
  • Ops: for requestIds a2598a4a3e3a226d / a259b6da790e1566, look for historical checkPermissionPg error logs

Fixes #2842

Generated with AI

Open in Web Open in Cursor 

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved permission checks to distinguish temporary database or network outages from normal access denials.
    • Temporary infrastructure failures now return a clear service-unavailable error, while invalid requests and existing errors retain their expected behavior.
  • Tests

    • Added coverage for permission failures, transient outages, invalid identifiers, wrapped database errors, and client cleanup.
    • Updated rollout channel testing to validate database operations and cleanup behavior directly.

Transient Hyperdrive/Postgres errors in checkPermission(Pg) were
caught and returned as false, so TUS upload auth turned infra blips
into intermittent 403 insufficient_permissions. Surface them as
upstream_unavailable (503) so TUS can retry.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

RBAC permission checks now classify transient infrastructure failures separately from access denials. Transient failures return HTTP 503 errors, while other query failures return false. Tests cover wrapped errors, authentication paths, and exception preservation. The rollout channel test now uses typed direct SQL operations.

Changes

RBAC error handling

Layer / File(s) Summary
Shared transient error classification
supabase/functions/_backend/utils/rbac.ts
Added detection for transient database, network, timeout, and nested-cause errors. Existing HTTPException instances are rethrown.
Permission check integration and validation
supabase/functions/_backend/utils/rbac.ts, tests/rbac-permission-infra-errors.unit.test.ts
Both permission-check paths use the shared handler. Tests cover ACL denials, HTTP 503 responses, wrapped errors, authentication paths, client cleanup, and preserved exceptions.

Statistics test SQL access

Layer / File(s) Summary
Rollout channel SQL test flow
tests/test-utils.ts, tests/stats.test.ts
executeSQL now returns typed row arrays. The rollout channel test uses direct SQL for insertion, updates, and retrieval while retaining metadata assertions and cleanup.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • Cap-go/capgo.app#2713: Adds public-channel authorization guards that use the shared RBAC permission infrastructure.

Suggested labels: codex

Suggested reviewers: dalanir

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #2842 by mapping transient permission-check failures to 503 while preserving genuine permission denials.
Out of Scope Changes check ✅ Passed The code, tests, and executeSQL utility changes support the RBAC error-handling fix and the stated test hardening objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the primary RBAC change: preventing transient database failures from becoming 403 permission errors.
Description check ✅ Passed The description clearly covers the change, motivation, impact, linked issue, implementation details, and test plan; omitted screenshots and checklist are acceptable for this backend change.
✨ 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 Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing cursor/fix-tus-upload-false-403-0dbe (ec31f09) with main (c294277)

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.

Invalid org UUID casts (22P02) and other query errors were incorrectly
surfaced as upstream_unavailable, breaking ACL deny tests that pass
non-UUID org ids. Keep those as false (deny); reserve 503 for connection
timeouts and Hyperdrive failures.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
@cursor
cursor Bot temporarily deployed to deepsec-pr August 4, 2026 04:53 Inactive
@riderx
riderx marked this pull request as ready for review August 4, 2026 05:04
@cursor

cursor Bot commented Aug 4, 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_5d6fd37e-fb62-4051-a4bb-88c5a4b78a0c)

CF shard flakes with "invalid response from the upstream server" when
this test seeds/updates channels through PostgREST. Use executeSQL like
createAppVersions already does under shard load.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
@cursor
cursor Bot temporarily deployed to deepsec-pr August 4, 2026 05:06 Inactive
@cursor

cursor Bot commented Aug 4, 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_acdfc2ab-343e-4e97-b0e9-601af0b2a588)

@coderabbitai coderabbitai Bot added the codex label Aug 4, 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: 4

🤖 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/utils/rbac.ts`:
- Around line 133-164: Confirm the intended timeout behavior for permission
checks; if statement timeouts must not produce an ACL denial, update
TRANSIENT_PG_SQLSTATES to include SQLSTATE 57014 so
isTransientPermissionCheckError recognizes query-canceled statement timeouts as
transient. Preserve the existing matching logic and behavior for other error
codes.
- Around line 182-200: Compute isTransientPermissionCheckError(error) once in
the surrounding permission-error handling flow, store the result in a local
variable, and reuse that variable for both cloudlogErr’s transient field and the
subsequent quickError branch.

In `@tests/rbac-permission-infra-errors.unit.test.ts`:
- Around line 24-31: Extend the tests using makeContext to cover the non-rbac_id
query path in checkPermission and checkPermissionPg: add a transient-failure
case with authType set to jwt or apikey.rbac_id omitted, ensuring it exercises
handlePermissionCheckError, and add a successful fallback-query case asserting
true.

In `@tests/stats.test.ts`:
- Around line 894-925: Update executeSQL and its call sites in the affected
stats tests so the generic type arguments at the channelRows and updatedRows
queries are valid: either add a generic T returning Promise<T[]> to executeSQL,
or remove those type arguments and apply local row casts. Ensure backend
typechecking no longer reports TS2558 while preserving the existing row shapes.
🪄 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: ffcad180-9c47-443a-81df-23458457ea4f

📥 Commits

Reviewing files that changed from the base of the PR and between c294277 and 3a6614f.

📒 Files selected for processing (3)
  • supabase/functions/_backend/utils/rbac.ts
  • tests/rbac-permission-infra-errors.unit.test.ts
  • tests/stats.test.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

Comment thread supabase/functions/_backend/utils/rbac.ts
Comment thread supabase/functions/_backend/utils/rbac.ts
Comment thread tests/rbac-permission-infra-errors.unit.test.ts
Comment thread tests/stats.test.ts
Treat statement timeouts (57014) as transient 503s, classify once,
cover the JWT/non-rbac_id permission path, and make executeSQL generic
so typed channel seed queries typecheck.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
@cursor
cursor Bot temporarily deployed to deepsec-pr August 4, 2026 05:22 Inactive
@cursor

cursor Bot commented Aug 4, 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_32b60568-4db5-4f2f-a953-44b260fd3d1c)

@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.

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/utils/rbac.ts (1)

194-202: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve the public status-code contract.

The PR changes the TUS upload failure response from 403 to 503. handlePermissionCheckError applies this change to every caller and has no plugin-version or client-capability input. Existing clients can use 403 as a terminal ACL result.

  • supabase/functions/_backend/utils/rbac.ts#L194-L202: move response-status selection to a version-aware public endpoint boundary, or pass a validated compatibility decision into this handler. Preserve 403 for existing clients.
  • tests/rbac-permission-infra-errors.unit.test.ts#L187-L228: add legacy-client 403 coverage and version-enabled 503 coverage.

As per coding guidelines, “Public API and plugin changes must remain backward compatible: do not remove or change existing fields, meanings, formats, or status codes; use plugin version detection when behavior must differ.”

🤖 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/utils/rbac.ts` around lines 194 - 202, Preserve
the existing 403 response for legacy clients by moving status selection to the
version-aware public endpoint boundary or passing a validated compatibility
decision into handlePermissionCheckError; only version-enabled clients may
receive 503 for transient permission failures. Update
supabase/functions/_backend/utils/rbac.ts:194-202 accordingly, and add
legacy-client 403 plus version-enabled 503 coverage in
tests/rbac-permission-infra-errors.unit.test.ts:187-228.

Source: Coding guidelines

🤖 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.

Outside diff comments:
In `@supabase/functions/_backend/utils/rbac.ts`:
- Around line 194-202: Preserve the existing 403 response for legacy clients by
moving status selection to the version-aware public endpoint boundary or passing
a validated compatibility decision into handlePermissionCheckError; only
version-enabled clients may receive 503 for transient permission failures.
Update supabase/functions/_backend/utils/rbac.ts:194-202 accordingly, and add
legacy-client 403 plus version-enabled 503 coverage in
tests/rbac-permission-infra-errors.unit.test.ts:187-228.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ee2ba17d-7815-4aa9-bdde-9761eea12424

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6614f and ec31f09.

📒 Files selected for processing (3)
  • supabase/functions/_backend/utils/rbac.ts
  • tests/rbac-permission-infra-errors.unit.test.ts
  • tests/test-utils.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

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 across 3 files (changes from recent commits).

Confidence score: 3/5

  • In supabase/functions/_backend/utils/rbac.ts, treating all PostgreSQL 57014 (query_canceled) errors as transient and returning 503 can misclassify non-timeout cancellations, causing incorrect auth/error behavior and noisy retry patterns for clients; narrow the mapping to true statement timeouts (or add context checks) so RBAC failures keep the right status.
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="supabase/functions/_backend/utils/rbac.ts">

<violation number="1" location="supabase/functions/_backend/utils/rbac.ts:118">
P2: A permission query canceled for any reason is now classified as transient and converted to a 503. PostgreSQL's `57014` is the general `query_canceled` SQLSTATE, not a statement-timeout-only code, so client/request cancellations or other operator cancellations can be mislabeled as `upstream_unavailable`, producing misleading retry behavior and diagnostics. Restrict this code to errors whose message or other metadata confirms a timeout, and handle the distinct lock-timeout SQLSTATE separately; add a regression case for a non-timeout `57014` cancellation.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

'57P03', // cannot_connect_now
'53300', // too_many_connections
'53400', // configuration_limit_exceeded
'57014', // query_canceled (statement_timeout / lock_timeout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A permission query canceled for any reason is now classified as transient and converted to a 503. PostgreSQL's 57014 is the general query_canceled SQLSTATE, not a statement-timeout-only code, so client/request cancellations or other operator cancellations can be mislabeled as upstream_unavailable, producing misleading retry behavior and diagnostics. Restrict this code to errors whose message or other metadata confirms a timeout, and handle the distinct lock-timeout SQLSTATE separately; add a regression case for a non-timeout 57014 cancellation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/functions/_backend/utils/rbac.ts, line 118:

<comment>A permission query canceled for any reason is now classified as transient and converted to a 503. PostgreSQL's `57014` is the general `query_canceled` SQLSTATE, not a statement-timeout-only code, so client/request cancellations or other operator cancellations can be mislabeled as `upstream_unavailable`, producing misleading retry behavior and diagnostics. Restrict this code to errors whose message or other metadata confirms a timeout, and handle the distinct lock-timeout SQLSTATE separately; add a regression case for a non-timeout `57014` cancellation.</comment>

<file context>
@@ -115,9 +115,10 @@ const TRANSIENT_PG_SQLSTATES = new Set([
   '57P03', // cannot_connect_now
   '53300', // too_many_connections
   '53400', // configuration_limit_exceeded
+  '57014', // query_canceled (statement_timeout / lock_timeout)
 ])
 
</file context>

@riderx
riderx merged commit b0026b5 into main Aug 4, 2026
88 of 91 checks passed
@riderx
riderx deleted the cursor/fix-tus-upload-false-403-0dbe branch August 4, 2026 05:41
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.

bundle upload: intermittent 403 insufficient_permissions from files.capgo.app TUS endpoint (same key passes checksum + compatibility checks)

2 participants