Skip to content

Fix webhook org-scope precedence#1753

Closed
riderx wants to merge 9 commits intomainfrom
riderx/fix-webhook-org-scope
Closed

Fix webhook org-scope precedence#1753
riderx wants to merge 9 commits intomainfrom
riderx/fix-webhook-org-scope

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 8, 2026

Summary (AI generated)

  • Added migration 20260308235013_webhook-org-scope-api-key-org-check.sql to enforce API-key-first authorization on webhook and webhook_delivery RLS paths, including org-scoped checks and safe update validation.
  • Added regression coverage in tests/hashed-apikey-rls.test.ts for webhook read precedence and unauthorized webhook_deliveries.org_id updates when both user auth and an out-of-scope key are present.

Motivation (AI generated)

  • API-key-limited org scope should be authoritative so an authenticated user cannot bypass it when a capgkey header is supplied.

Business Impact (AI generated)

  • Prevents cross-organization webhook data access and unauthorized webhook delivery mutations with mismatched user/key identity, reducing the risk of webhook data leakage.

Test Plan (AI generated)

  • bun lint:backend
  • bun test tests/hashed-apikey-rls.test.ts (targeted regression)

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened webhook access control to enforce organization-scoped permissions
    • Improved API key handling in webhook operations to prioritize key-based authentication

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

Adds two SQL migrations that drop and recreate row-level security policies for public.webhooks and public.webhook_deliveries, enforcing organization-scoped authorization that prefers API-key identity checks when an API key header is present, and falls back to user-session authorization otherwise.

Changes

Cohort / File(s) Summary
Webhook policies (hardening)
supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.sql
Adds/drops RLS policies for public.webhooks and public.webhook_deliveries across SELECT, INSERT, UPDATE, DELETE. Policies route auth via API-key identity checks (get_apikey_header, get_identity_org_allowed, check_min_rights) when an API key is present, else fallback to auth.uid().
Webhook policies (API-key org check)
supabase/migrations/20260308235013_webhook-org-scope-api-key-org-check.sql
Creates API-key-aware, org-scoped RLS policies for public.webhooks and public.webhook_deliveries (SELECT, UPDATE, WITH CHECK). Uses get_apikey_header to detect key presence and calls get_identity_org_allowed/check_min_rights for authorization; no exported entities changed.

Sequence Diagram

sequenceDiagram
    participant Client
    participant RLS as RLS Policy Engine
    participant Key as get_apikey_header()
    participant Org as get_identity_org_allowed()
    participant Rights as check_min_rights()
    participant User as auth.uid() (User Session)

    Client->>RLS: Request read/write on webhooks/deliveries (may include API key)
    RLS->>Key: Is API key header present?
    alt API key present
        Key-->>RLS: API key info
        RLS->>Org: validate org scope for key
        Org-->>RLS: org allowed / not allowed
        alt org allowed
            RLS->>Rights: verify required rights (read/write/admin)
            Rights-->>RLS: rights granted / denied
            RLS-->>Client: allow or deny operation
        else org not allowed
            RLS-->>Client: deny
        end
    else No API key
        Key-->>RLS: no API key
        RLS->>User: evaluate user session (auth.uid())
        User-->>RLS: user id / not authenticated
        RLS->>Rights: verify rights for user (via check_min_rights)
        Rights-->>RLS: allow or deny
        RLS-->>Client: allow or deny operation
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through schemas, nudged policies tight,
Keys first, then users, to keep orgs right,
Webhooks now guarded, deliveries too,
Secrets tucked safe, access routed true,
A rabbit's small patch to sleep well tonight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes a summary of changes, motivation, and business impact, but lacks a detailed test plan with specific steps and is missing the code style checklist items required by the template. Expand the test plan section with detailed steps to reproduce and verify the fix, and complete the checklist section with clear yes/no indicators for each requirement.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix webhook org-scope precedence' directly and concisely summarizes the main change: correcting the precedence of API-key organization scope in webhook authorization policies.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/fix-webhook-org-scope
📝 Coding Plan
  • Generate coding plan for human review comments

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.0.4)
supabase/migrations/20260308235013_webhook-org-scope-api-key-org-check.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

supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.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.

@riderx riderx changed the title Fix webhook key org-scope and policy Fix webhook scope enforcement for API keys Mar 8, 2026
@riderx riderx force-pushed the riderx/fix-webhook-org-scope branch from 3c11624 to a1be230 Compare March 8, 2026 18:03
@riderx riderx changed the title Fix webhook scope enforcement for API keys fix: enforce API-key precedence for webhook RLS Mar 8, 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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260308091000_restrict_webhook_key_modes.sql`:
- Around line 138-153: The UPDATE policy "Allow admin to update
webhook_deliveries" on public.webhook_deliveries is missing a WITH CHECK clause
so new values (e.g., org_id) aren't validated; add a WITH CHECK that mirrors the
USING condition but evaluates the proposed row (use NEW.org_id or equivalent)
and calls public.check_min_rights(...) with
public.get_identity_org_allowed('{all,write,upload}'::public.key_mode[],
NEW.org_id) so updates are authorized against the target org; reference the
existing webhooks UPDATE policy for the exact predicate structure and use the
same functions public.check_min_rights and public.get_identity_org_allowed.
- Around line 1-11: The migration 20260308091000_restrict_webhook_key_modes.sql
references the function public.get_identity_org_allowed() which doesn't exist
until migration 20260309091500_enforce_api_key_app_scoped_org_access.sql,
causing the migration to fail; fix by ensuring the function exists before this
policy migration — either rename 20260308091000 to a timestamp after
20260309091500 so it runs later, or merge the two migrations into one file with
the CREATE FUNCTION public.get_identity_org_allowed(...) placed before the ALTER
POLICY/GRANT statements that reference it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 992b48c6-5fb1-44ad-9ea8-5c4b6c78ed88

📥 Commits

Reviewing files that changed from the base of the PR and between 2af3a08 and b28a0c5.

📒 Files selected for processing (2)
  • supabase/migrations/20260308091000_restrict_webhook_key_modes.sql
  • supabase/migrations/20260309091500_enforce_api_key_app_scoped_org_access.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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.sql`:
- Around line 23-24: The SELECT allowlist for API keys mistakenly includes the
"read" mode; update the calls to public.get_identity_org_allowed that pass
'{read,upload,write,all}'::public.key_mode[] (seen around the
webhook/webhook_deliveries allowlist) to remove "read" so they pass
'{upload,write,all}'::public.key_mode[] instead; apply the same change to the
other occurrence referenced (lines near the second call to
public.get_identity_org_allowed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dba9de6b-5484-47ab-b7d8-79a06c9d080b

📥 Commits

Reviewing files that changed from the base of the PR and between b28a0c5 and a1be230.

📒 Files selected for processing (1)
  • supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.sql

@riderx riderx changed the title fix: enforce API-key precedence for webhook RLS Fix webhook org-scope precedence Mar 8, 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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.sql`:
- Around line 9-17: This migration recreates the webhook SELECT policy but
forgot to remove the earlier overlapping policy "Allow org members to select
webhooks", causing a follow-up cleanup migration; add a DROP POLICY IF EXISTS
"Allow org members to select webhooks" ON public.webhooks; at the top of this
migration alongside the other DROP POLICY lines, then ensure you have only one
SELECT policy for public.webhooks by merging any org-member condition into the
single CREATE POLICY "Allow admin to select webhooks" (use OR in the policy
USING expression if both admin and org-member checks are needed); finally delete
the follow-up migration file named
20260308235013_webhook-org-scope-api-key-org-check.sql so the cleanup is folded
into this migration.
- Around line 14-17: Several webhook RLS CREATE POLICY statements (e.g., "Allow
admin to select webhooks" and the other webhook admin policies) currently grant
TO authenticated, anon; remove the anon role from each of these policies so they
read TO authenticated only. Locate the nine CREATE POLICY statements that target
the public.webhooks table (all webhook admin policies across the two migration
files) and update their TO clauses from "TO authenticated, anon" to "TO
authenticated", keeping the rest of each policy unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5c0b099-b22c-4f50-aecf-98ab680b2aec

📥 Commits

Reviewing files that changed from the base of the PR and between a1be230 and 884d68d.

📒 Files selected for processing (2)
  • supabase/migrations/20260308180224_webhook-api-key-org-scope-hardening.sql
  • supabase/migrations/20260308235013_webhook-org-scope-api-key-org-check.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: f84ba4c73a

ℹ️ 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".

@riderx
Copy link
Copy Markdown
Member Author

riderx commented Mar 19, 2026

PR #1753: I checked the webhook policy review comment. The overlapping-policy cleanup was valid on the original branch, but the 'remove anon' part is not safe because these policies rely on anon for API-key-authenticated RLS access. The logic here has also been superseded by later webhook migrations, so I’m not applying a patch on this PR.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx closed this Mar 19, 2026
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