Skip to content

[codex] fix direct API key channel updates#1949

Merged
riderx merged 6 commits into
mainfrom
codex/fix-ghsa-ph9c-vwjq-pqhj
Apr 24, 2026
Merged

[codex] fix direct API key channel updates#1949
riderx merged 6 commits into
mainfrom
codex/fix-ghsa-ph9c-vwjq-pqhj

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Apr 24, 2026

Summary (AI generated)

  • block direct PostgREST public.channels updates for write-scoped API keys
  • preserve supported bundle promotion through the /bundle API by moving its final channel version write onto a server-side path that keeps API-key audit attribution intact
  • add regression coverage for the blocked direct-write path, the supported write-scoped /bundle flow, and the resulting channel audit log attribution

Motivation (AI generated)

A write-scoped API key could update protected public.channels fields directly through PostgREST because the update policy treated {write, all} keys the same, while the immutability guard skipped the NULL-auth path used by API-key requests. The RLS change closes that direct table-write path, and the /bundle follow-up keeps the existing permission-checked deployment flow working without dropping the API-key actor from audit history.

Business Impact (AI generated)

This removes a channel-configuration integrity gap for scoped API keys while preserving the supported bundle-promotion workflow and its audit trail that existing customer and CLI automation relies on.

Test Plan (AI generated)

  • bun run supabase:db:reset
  • bunx eslint supabase/functions/_backend/public/bundle/set_channel.ts tests/bundle.test.ts tests/hashed-apikey-rls.test.ts tests/audit-logs.test.ts
  • bun run supabase:with-env -- bunx vitest run tests/hashed-apikey-rls.test.ts -t \"channels rls blocks direct api-key updates|webhook and webhook_delivery rls with api-key org scope precedence\"
  • bun run supabase:with-env -- bunx vitest run tests/bundle.test.ts -t \"should set bundle to channel successfully|should keep the supported write-scoped API key bundle promotion flow working\"
  • bun run supabase:with-env -- bunx vitest run tests/audit-logs.test.ts -t \"app_version INSERT via API creates audit log with user_id from API key|app_version UPDATE via API creates audit log with user_id from API key|app_version soft-DELETE via API creates UPDATE audit log with user_id from API key|channel UPDATE via API key bundle promotion keeps audit user_id attribution\"
  • GitHub PR checks passed

Checklist (AI generated)

  • code follows repository conventions
  • focused regression tests were added for the affected security and audit paths
  • no customer-facing documentation update is required for this backend-only change
  • no schema change beyond the single migration in this PR
  • no UI/manual browser validation is required for this backend-only change

Screenshots (AI generated)

Not applicable. This PR only changes backend security policy, backend request handling, and automated tests.

Generated with AI

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Updates channel row-level security to restrict UPDATEs to anon and authenticated roles using public.get_identity_org_appid + public.check_min_rights; adjusts server function to use admin client for channel updates with an owner_org constraint; updates tests to exercise hashed API-key RLS behavior and introduces a reusable test helper for executing SQL with dynamic role/claims/headers.

Changes

Cohort / File(s) Summary
Database Migration
supabase/migrations/20260424090727_block_apikey_channel_updates.sql
Drops the prior UPDATE policy on public.channels and creates a new UPDATE policy scoped to anon, authenticated that enforces USING and WITH CHECK via public.get_identity_org_appid(... ) and public.check_min_rights(..., 'write').
RLS Tests & Helpers
tests/hashed-apikey-rls.test.ts
Replaces fixed auth helper with generic execWithRoleClaims(role, claims, headers) transactional helper; adds channels RLS tests verifying write-scoped hashed API keys are denied while all-scoped keys succeed; refactors webhook/webhook_delivery tests to use the new helper.
Server function
supabase/functions/_backend/public/bundle/set_channel.ts
Switches channel UPDATE to use supabaseAdmin(c) instead of write-scoped apikey client, adds owner_org equality constraint, selects id to ensure a row was returned and throws if none.
Integration tests
tests/bundle.test.ts
Creates and tears down a write-scoped API key for tests, uses write-scoped headers to call PUT /bundle, asserts success and verifies channel version updated in DB.

Sequence Diagram(s)

sequenceDiagram
    participant Client as API Client (hashed API key)
    participant PostgREST as PostgREST / Request Context
    participant RLS as channels RLS Policy
    participant GetIdentity as public.get_identity_org_appid()
    participant CheckRights as public.check_min_rights()
    participant DB as public.channels

    Client->>PostgREST: HTTP PATCH/UPDATE /channels (with headers/jwt)
    PostgREST->>RLS: Evaluate UPDATE policy for row
    RLS->>GetIdentity: resolve identity/org/app from session/claims
    GetIdentity-->>RLS: identity (org_id, app_id, actor)
    RLS->>CheckRights: verify actor has 'write' (or 'all') for target app/org
    CheckRights-->>RLS: allow / deny
    alt allow
        RLS->>DB: permit UPDATE (operation proceeds)
        DB-->>Client: 1 row affected (success)
    else deny
        RLS-->>Client: 0 rows affected (access denied)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐇
I hop the code and nibble keys at night,
Tightened gates gleam in the moonlight,
Scoped hops denied, true writes take flight,
A tidy patch, secure and bright —
Hooray, channels safe and right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: blocking direct API key channel updates through PostgREST, which is the primary security fix in this PR.
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.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering summary, motivation, business impact, test plan, and checklist aligned with the template.

✏️ 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 codex/fix-ghsa-ph9c-vwjq-pqhj

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 24, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-ghsa-ph9c-vwjq-pqhj (109f02f) with main (633aeea)

Open in CodSpeed

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

ℹ️ 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/20260424090727_block_apikey_channel_updates.sql Outdated
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.

🧹 Nitpick comments (1)
tests/hashed-apikey-rls.test.ts (1)

89-128: Consider collapsing the anon/auth SQL runners into one helper.

execWithAnonCapgkey() now duplicates the same transaction, rollback, and connection-lifecycle flow from execWithAuthAndCapgkey(). A shared executor with role/claims parameters would reduce fixture drift the next time this session setup changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hashed-apikey-rls.test.ts` around lines 89 - 128, The test helpers
execWithAnonCapgkey and execWithAuthAndCapgkey duplicate
connection/transaction/rollback logic; refactor them into a single helper (e.g.,
execWithRoleClaims) that takes parameters for role, jwt claims object, and
headers (capgkey) and performs the shared flow (pool.connect(), BEGIN, SET LOCAL
ROLE, set_config for request.jwt.claims and request.headers, run query,
COMMIT/ROLLBACK, release). Replace calls to execWithAnonCapgkey and
execWithAuthAndCapgkey with calls to the new helper, keeping the same return
shape ({ rows, rowCount }) and preserving error handling and JSON.stringify
usage for set_config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/hashed-apikey-rls.test.ts`:
- Around line 89-128: The test helpers execWithAnonCapgkey and
execWithAuthAndCapgkey duplicate connection/transaction/rollback logic; refactor
them into a single helper (e.g., execWithRoleClaims) that takes parameters for
role, jwt claims object, and headers (capgkey) and performs the shared flow
(pool.connect(), BEGIN, SET LOCAL ROLE, set_config for request.jwt.claims and
request.headers, run query, COMMIT/ROLLBACK, release). Replace calls to
execWithAnonCapgkey and execWithAuthAndCapgkey with calls to the new helper,
keeping the same return shape ({ rows, rowCount }) and preserving error handling
and JSON.stringify usage for set_config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37541011-5684-4a27-a8f3-87a111d8bfcc

📥 Commits

Reviewing files that changed from the base of the PR and between 633aeea and df4585f.

📒 Files selected for processing (2)
  • supabase/migrations/20260424090727_block_apikey_channel_updates.sql
  • tests/hashed-apikey-rls.test.ts

@riderx riderx marked this pull request as draft April 24, 2026 10:12
@riderx riderx marked this pull request as ready for review April 24, 2026 10:22
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: 432a44237f

ℹ️ 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/20260424090727_block_apikey_channel_updates.sql
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.

🧹 Nitpick comments (2)
tests/hashed-apikey-rls.test.ts (2)

620-633: Prefer explicit null checks in teardown guards.

Using truthy checks on numeric ids is a bit brittle; !== null is safer and clearer for cleanup logic.

Suggested patch
-    if (channelId) {
+    if (channelId !== null) {
       await pool.query('DELETE FROM public.channels WHERE id = $1', [channelId])
     }

-    if (versionId) {
+    if (versionId !== null) {
       await pool.query('DELETE FROM public.app_versions WHERE id = $1', [versionId])
     }

-    if (allKey)
+    if (allKey !== null)
       await deleteApiKey(allKey.id)

-    if (writeKey)
+    if (writeKey !== null)
       await deleteApiKey(writeKey.id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hashed-apikey-rls.test.ts` around lines 620 - 633, Teardown guards use
truthy checks which can mis-handle numeric zero or other falsy values; change
the conditions to explicit null checks: replace "if (channelId)" with "if
(channelId !== null)", "if (versionId)" with "if (versionId !== null)", and
similarly use "if (allKey !== null)" and "if (writeKey !== null)" before calling
deleteApiKey or pool.query so cleanup always runs correctly for valid
zero/false-y ids or objects.

745-771: Consider it.concurrent(...) for these two webhook tests if they remain independent.

They look parallelizable and this would align with the test parallelism guideline.

As per coding guidelines: tests/**/*.{ts,js}: Use it.concurrent() instead of it() when possible to run tests in parallel within the same file, maximizing parallelism for faster CI/CD.

Also applies to: 779-791

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/hashed-apikey-rls.test.ts` around lines 745 - 771, Replace the
sequential tests that independently query webhooks and deliveries with parallel
tests by using it.concurrent(...) instead of it(...) for the tests that call
execWithRoleClaims to fetch webhookRows and deliveryRows (the tests referencing
webhook_deliveries and public.webhooks via execWithRoleClaims); ensure those
tests do not share or mutate common state (or isolate any shared setup/fixtures)
before switching to it.concurrent, and apply the same change to the related
independent tests around the other occurrence noted (the tests covering the
similar webhook/webhook_delivery assertions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/hashed-apikey-rls.test.ts`:
- Around line 620-633: Teardown guards use truthy checks which can mis-handle
numeric zero or other falsy values; change the conditions to explicit null
checks: replace "if (channelId)" with "if (channelId !== null)", "if
(versionId)" with "if (versionId !== null)", and similarly use "if (allKey !==
null)" and "if (writeKey !== null)" before calling deleteApiKey or pool.query so
cleanup always runs correctly for valid zero/false-y ids or objects.
- Around line 745-771: Replace the sequential tests that independently query
webhooks and deliveries with parallel tests by using it.concurrent(...) instead
of it(...) for the tests that call execWithRoleClaims to fetch webhookRows and
deliveryRows (the tests referencing webhook_deliveries and public.webhooks via
execWithRoleClaims); ensure those tests do not share or mutate common state (or
isolate any shared setup/fixtures) before switching to it.concurrent, and apply
the same change to the related independent tests around the other occurrence
noted (the tests covering the similar webhook/webhook_delivery assertions).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c78d3ee1-4312-43d4-b3db-9241747405f0

📥 Commits

Reviewing files that changed from the base of the PR and between df4585f and 432a442.

📒 Files selected for processing (2)
  • supabase/migrations/20260424090727_block_apikey_channel_updates.sql
  • tests/hashed-apikey-rls.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • supabase/migrations/20260424090727_block_apikey_channel_updates.sql

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

ℹ️ 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/bundle/set_channel.ts Outdated
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit f3f4f70 into main Apr 24, 2026
16 checks passed
@riderx riderx deleted the codex/fix-ghsa-ph9c-vwjq-pqhj branch April 24, 2026 12:46
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