Skip to content

fix(api): harden webhook and store metadata fetches#2196

Closed
tngus6007 wants to merge 2 commits into
Cap-go:mainfrom
tngus6007:harden-webhook-store-metadata-fetches
Closed

fix(api): harden webhook and store metadata fetches#2196
tngus6007 wants to merge 2 commits into
Cap-go:mainfrom
tngus6007:harden-webhook-store-metadata-fetches

Conversation

@tngus6007
Copy link
Copy Markdown

@tngus6007 tngus6007 commented May 11, 2026

/claim #1667

Summary

  • Keep local webhook test mode constrained to HTTP(S) schemes so enabling CAPGO_ALLOW_LOCAL_WEBHOOK_URLS does not also allow unsupported schemes such as file: or ftp:.
  • Add a bounded icon response reader for store metadata imports so allowlisted app-store icon URLs cannot force unbounded buffering before conversion to a data URL.
  • Avoid logging raw blocked webhook URLs; blocked deliveries now log URL shape metadata instead of query strings or credentials.
  • Add focused regression coverage for local webhook URL scheme validation, redacted blocked-delivery logging, and oversized store metadata icon responses.

Security note

This is public-safe hardening. The webhook changes preserve the intended local HTTP testing path while blocking non-HTTP schemes and avoiding secret-bearing URL retention in blocked-delivery logs. The store metadata change preserves existing allowlisted app-store behavior while capping fetched icon payload size.

Test plan

  • ./node_modules/.bin/vitest.exe run tests/webhook-delivery-security.unit.test.ts tests/store-metadata.unit.test.ts --reporter=verbose
  • git diff --check

Screenshots

Not applicable; backend/security hardening only.

Checklist

  • Code style checked
  • Focused tests added
  • Public-safe security details only

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 11, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing tngus6007:harden-webhook-store-metadata-fetches (4f4a0e7) with main (e85eca6)

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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This PR adds two security-hardening features: icon response size limiting and webhook protocol validation. Icon fetching now rejects oversized payloads via a streaming buffer helper, and local webhook URLs are restricted to HTTP/HTTPS protocols. Both features include unit tests validating their enforcement.

Changes

Icon Size Limiting

Layer / File(s) Summary
Size Constant
supabase/functions/_backend/public/app/store_metadata.ts
MAX_ICON_BYTES constant sets the icon payload upper bound.
Bounded Buffer Reader
supabase/functions/_backend/public/app/store_metadata.ts
readArrayBufferWithLimit helper checks content-length and streams chunks, aborting if cumulative size exceeds the limit.
Icon Fetch Integration
supabase/functions/_backend/public/app/store_metadata.ts
fetchIconDataUrl now uses readArrayBufferWithLimit and returns null for oversized responses.
Tests
tests/store-metadata.unit.test.ts
Unit tests verify oversized icon rejection and small icon base64 encoding, including fetch call counting.

Webhook Protocol Validation

Layer / File(s) Summary
Protocol Check Helper
supabase/functions/_backend/utils/webhook.ts
isLocalWebhookProtocol helper centralizes http:/https: protocol recognition.
Webhook URL Validation
supabase/functions/_backend/utils/webhook.ts
getWebhookUrlValidationError now rejects local URLs unless protocol is http: or https:.
Tests
tests/webhook-delivery-security.unit.test.ts
Unit tests verify HTTP(S) local URLs pass when enabled, non-HTTP schemes are rejected, and HTTP URLs still fail when local webhooks are disabled.

Possibly related PRs

  • Cap-go/capgo#2090: Touches the same getWebhookUrlValidationError function with explanatory comments on validation boundaries.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Size and protocol, now confined,
Icons buffered, webhooks aligned,
No oversized payloads shall pass,
Only HTTP through the glass,
Security tightened, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 and concisely summarizes the main changes: hardening security for webhook and store metadata fetches.
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 PR description includes all required template sections: Summary, Test plan, Screenshots (marked N/A), and Checklist items completed.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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.

🧹 Nitpick comments (1)
tests/webhook-delivery-security.unit.test.ts (1)

108-148: ⚡ Quick win

Solid test coverage for the protocol validation hardening.

The three test cases effectively verify the key security constraint: when local webhook testing is enabled, only HTTP(S) protocols are accepted, rejecting schemes like ftp:// and file:// that could potentially be exploited.

Consider adding edge case coverage for completeness

While not critical for this PR, the following test cases would strengthen coverage:

it('allows public HTTPS URLs when local webhooks are enabled', async () => {
  mockGetEnv.mockImplementation((_c: unknown, key: string) =>
    key === 'CAPGO_ALLOW_LOCAL_WEBHOOK_URLS' ? 'true' : '',
  )

  const { getWebhookUrlValidationError } = await import('../supabase/functions/_backend/utils/webhook.ts')

  expect(
    getWebhookUrlValidationError(createContext(), 'https://example.com/webhook'),
  ).toBeNull()
})

it('rejects localhost URLs when local webhooks are disabled', async () => {
  mockGetEnv.mockReturnValue('')

  const { getWebhookUrlValidationError } = await import('../supabase/functions/_backend/utils/webhook.ts')

  expect(
    getWebhookUrlValidationError(createContext(), 'https://localhost/webhook'),
  ).toBe('Webhook URL must point to a public host')
})

These cases verify that the early-return logic doesn't inadvertently change behavior for public URLs and that localhost is properly rejected when the flag is disabled.

🤖 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 `@tests/webhook-delivery-security.unit.test.ts` around lines 108 - 148, The
tests currently cover protocol validation but miss two edge cases; add two new
it() tests that call getWebhookUrlValidationError (imported from
../supabase/functions/_backend/utils/webhook.ts) using the existing mockGetEnv
and createContext helpers: 1) when CAPGO_ALLOW_LOCAL_WEBHOOK_URLS is 'true',
assert getWebhookUrlValidationError(createContext(),
'https://example.com/webhook') returns null (public HTTPS allowed); 2) when
CAPGO_ALLOW_LOCAL_WEBHOOK_URLS is '', assert
getWebhookUrlValidationError(createContext(), 'https://localhost/webhook')
returns 'Webhook URL must point to a public host' to verify localhost is
rejected when the flag is disabled.
🤖 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.

Nitpick comments:
In `@tests/webhook-delivery-security.unit.test.ts`:
- Around line 108-148: The tests currently cover protocol validation but miss
two edge cases; add two new it() tests that call getWebhookUrlValidationError
(imported from ../supabase/functions/_backend/utils/webhook.ts) using the
existing mockGetEnv and createContext helpers: 1) when
CAPGO_ALLOW_LOCAL_WEBHOOK_URLS is 'true', assert
getWebhookUrlValidationError(createContext(), 'https://example.com/webhook')
returns null (public HTTPS allowed); 2) when CAPGO_ALLOW_LOCAL_WEBHOOK_URLS is
'', assert getWebhookUrlValidationError(createContext(),
'https://localhost/webhook') returns 'Webhook URL must point to a public host'
to verify localhost is rejected when the flag is disabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a57eba3-fefc-46c9-b565-a81e8f9757a4

📥 Commits

Reviewing files that changed from the base of the PR and between e85eca6 and bed3a8c.

📒 Files selected for processing (4)
  • supabase/functions/_backend/public/app/store_metadata.ts
  • supabase/functions/_backend/utils/webhook.ts
  • tests/store-metadata.unit.test.ts
  • tests/webhook-delivery-security.unit.test.ts

Copy link
Copy Markdown

@mingisrookie mingisrookie left a comment

Choose a reason for hiding this comment

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

One logging path in the webhook hardening still keeps the raw webhook URL: when getWebhookUrlValidationError() blocks delivery, deliverWebhook() calls cloudlogErr({ ..., url, error: urlValidationError, ... }).

That means a rejected URL such as https://example.test/webhook?token=secret or a malformed/local URL with embedded credentials/query state can still be retained verbatim in operational logs, even though the delivery is correctly blocked before fetch(). This is especially easy to miss because the new tests assert the validation result but do not inspect the blocked-delivery log payload.

I would log URL shape metadata here instead (scheme category, host present, path segment count, hasQuery) or at least strip search/credentials before logging. A regression can call deliverWebhook() with a URL containing a fake query token that is rejected and assert the serialized cloudlogErr calls do not include the token.

Copy link
Copy Markdown
Author

Addressed the logging concern in the latest commit.

Blocked webhook deliveries no longer log the raw URL. The validation log now records URL shape metadata only (protocol, hostname presence/length, path segment count, query/credential presence), so query strings and credentials are not retained when validation blocks delivery.

Also added regression coverage for a blocked URL containing a fake query token and the two validation edge cases CodeRabbit suggested.

Validation:

  • ./node_modules/.bin/vitest.exe run tests/webhook-delivery-security.unit.test.ts tests/store-metadata.unit.test.ts --reporter=verbose
  • git diff --check

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@mingisrookie mingisrookie left a comment

Choose a reason for hiding this comment

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

Rechecked the current head (1c4231f7) and this addresses my logging concern: blocked deliveries now log urlInfo metadata instead of the raw url, and the regression asserts a query token is absent from serialized cloudlogErr calls. I also ran the focused tests locally:

bun x vitest run tests/webhook-delivery-security.unit.test.ts tests/store-metadata.unit.test.ts

Result: 2 files passed, 10 tests passed.

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

Closing as AI-generated spam. Part of a 50+ PR wave of duplicate redact logs PRs from disposable accounts. If you're human, open an issue first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants