Skip to content

fix: block app-scoped API keys from org webhook actions#1812

Merged
riderx merged 1 commit into
mainfrom
fix/webhook-org-scope-apikey
Mar 18, 2026
Merged

fix: block app-scoped API keys from org webhook actions#1812
riderx merged 1 commit into
mainfrom
fix/webhook-org-scope-apikey

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 17, 2026

Summary (AI generated)

  • block app-scoped API keys from org-scoped webhook actions
  • apply the same scope guard to both /webhooks/test and /webhooks/deliveries/retry
  • add regression coverage proving app-limited keys are rejected for these org-level operations

Motivation (AI generated)

App-scoped API keys were being authorized for organization-wide webhook test and retry actions because the webhook permission helper only checked org admin rights plus limited_to_orgs, while ignoring limited_to_apps. That breaks the intended delegation boundary for scoped keys.

Business Impact (AI generated)

This closes a high-severity authorization gap that could let lower-scope API keys trigger org webhook traffic outside their declared app boundary. Fixing it reduces cross-app privilege leakage and protects downstream customer integrations.

Test Plan (AI generated)

  • bunx eslint supabase/functions/_backend/public/webhooks/index.ts tests/webhooks.test.ts
  • bun run supabase:with-env -- bunx vitest run tests/webhooks.test.ts
  • bun run test:backend

Generated with AI

Summary by CodeRabbit

  • Bug Fixes

    • App-scoped API keys are now properly restricted from accessing organization-scoped webhook operations.
  • Tests

    • Added test coverage for app-scoped API key restrictions on webhook operations.

@riderx riderx added the codex label Mar 17, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Introduces a centralized helper function assertOrgWebhookScope to consolidate organization-scoped API key permission checks, replacing inline validation in two webhook permission functions. Adds tests verifying that app-scoped API keys are properly rejected for org-scoped webhook operations.

Changes

Cohort / File(s) Summary
Permission Check Refactoring
supabase/functions/_backend/public/webhooks/index.ts
Extracts organization webhook scope validation into a dedicated assertOrgWebhookScope helper function. Replaces inline apikeyHasOrgRight checks in checkWebhookPermission and checkWebhookPermissionV2 with calls to the new helper, centralizing enforcement that app-scoped keys cannot access org webhooks.
App-Scoped API Key Tests
tests/webhooks.test.ts
Adds test infrastructure for app-scoped API keys including app creation, key generation, and cleanup. Introduces tests verifying that POST /webhooks/test and POST /webhooks/deliveries/retry endpoints reject app-scoped keys with appropriate error messages. Captures delivery_id from responses for downstream test usage.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested labels

💰 Rewarded

Poem

🐰 A rabbit hops through webhook gates,
App-scoped keys? They can't innervate!
Permission checks, now fresh and clean,
The most centralized they've seen! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: blocking app-scoped API keys from organization webhook actions, which directly matches the primary objective and code changes.
Description check ✅ Passed The description covers the summary, motivation, business impact, and a complete test plan with verification steps, satisfying all required template sections despite being AI-generated.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/webhook-org-scope-apikey
📝 Coding Plan
  • Generate coding plan for human review comments

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

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 `@tests/webhooks.test.ts`:
- Around line 482-502: Convert the two permission tests (the one using
createdWebhookId and appScopedKey and its counterpart) to use it.concurrent()
and avoid shared mutable state by creating isolated fixtures inside each test:
generate a fresh webhook (replace reliance on createdWebhookId by calling the
same seeding function used elsewhere) and a fresh app-scoped API key (instead of
using appScopedKey) at the start of the test, then call the /webhooks/test
endpoint and assert the 400/no_permission response; modify the tests that
reference createdWebhookId and appScopedKey to seed their own webhook and API
key locally before making the request so the tests are fully independent and
safe to run in parallel.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3606255e-b1f7-4c43-967f-49e38416737f

📥 Commits

Reviewing files that changed from the base of the PR and between 877c35b and 57a693c.

📒 Files selected for processing (2)
  • supabase/functions/_backend/public/webhooks/index.ts
  • tests/webhooks.test.ts

Comment thread tests/webhooks.test.ts
Comment on lines +482 to +502
it('rejects app-scoped API keys for org-scoped webhook tests', async () => {
if (!createdWebhookId || !appScopedKey)
throw new Error('Webhook test prerequisites were not created')

const response = await fetch(`${BASE_URL}/webhooks/test`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': appScopedKey,
},
body: JSON.stringify({
orgId: WEBHOOK_TEST_ORG_ID,
webhookId: createdWebhookId,
}),
})

expect(response.status).toBe(400)
const data = await response.json() as { error: string, message: string }
expect(data.error).toBe('no_permission')
expect(data.message).toContain('App-scoped API keys')
})
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.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify shared mutable globals used by tests
rg -n "^\s*let\s+(createdWebhookId|lastDeliveryId|appScopedKey|appScopedKeyId)\b" tests/webhooks.test.ts

# Verify non-concurrent vs concurrent test declarations
rg -n "^\s*it\(" tests/webhooks.test.ts
rg -n "^\s*it\.concurrent\(" tests/webhooks.test.ts || true

Repository: Cap-go/capgo

Length of output: 2437


Use it.concurrent() and isolated fixtures for API key permission tests.

The tests at lines 482–502 and 581–601 use it() and rely on shared mutable state (createdWebhookId, appScopedKey). Per coding guidelines, convert these to it.concurrent() and create dedicated seed data for each test instead of depending on earlier test mutations. This enables parallel execution and prevents test order dependencies.

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

In `@tests/webhooks.test.ts` around lines 482 - 502, Convert the two permission
tests (the one using createdWebhookId and appScopedKey and its counterpart) to
use it.concurrent() and avoid shared mutable state by creating isolated fixtures
inside each test: generate a fresh webhook (replace reliance on createdWebhookId
by calling the same seeding function used elsewhere) and a fresh app-scoped API
key (instead of using appScopedKey) at the start of the test, then call the
/webhooks/test endpoint and assert the 400/no_permission response; modify the
tests that reference createdWebhookId and appScopedKey to seed their own webhook
and API key locally before making the request so the tests are fully independent
and safe to run in parallel.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 87c4f61 into main Mar 18, 2026
15 checks passed
@riderx riderx deleted the fix/webhook-org-scope-apikey branch March 18, 2026 19:01
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