Skip to content

fix(security): block SSRF bypass vectors in webhook URL validation#1726

Closed
artylobos wants to merge 1 commit intoCap-go:mainfrom
artylobos:fix/ssrf-webhook-validation
Closed

fix(security): block SSRF bypass vectors in webhook URL validation#1726
artylobos wants to merge 1 commit intoCap-go:mainfrom
artylobos:fix/ssrf-webhook-validation

Conversation

@artylobos
Copy link
Contributor

@artylobos artylobos commented Mar 1, 2026

Summary

This PR fixes SSRF (Server-Side Request Forgery) bypass vulnerabilities in the webhook URL validation.

Security Issue

The existing validation in getWebhookUrlValidationError() could be bypassed using:

  1. DNS rebinding services: 169.254.169.254.nip.io resolves to AWS/GCP metadata endpoint
  2. Known localhost domains: localtest.me, vcap.me resolve to 127.0.0.1
  3. Cloud metadata hostnames: metadata.google.internal

Impact

  • Access to cloud metadata endpoints (AWS/GCP credentials)
  • Access to internal services
  • Data exfiltration

Changes

Added blocklists for:

  • Known localhost domains (localtest.me, vcap.me, lvh.me, etc.)
  • DNS rebinding service suffixes (.nip.io, .xip.io, .sslip.io, etc.)
  • Cloud metadata hostnames (metadata.google.internal, etc.)

Testing

The following URLs are now correctly blocked:

  • https://169.254.169.254.nip.io/ - DNS rebinding to metadata
  • https://localtest.me/ - Resolves to 127.0.0.1
  • https://metadata.google.internal/ - GCP metadata

Summary by CodeRabbit

  • Bug Fixes
    • Webhook URL validation has been strengthened with additional endpoint checks. Some webhook URLs that previously worked may no longer be accepted during validation. If your webhooks fail validation, review and update your webhook URLs accordingly. Additional endpoint patterns are now blocked. Contact support if you need assistance with your webhook configurations.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Added SSRF hardening to webhook URL validation by introducing blocked-domain lists and helper predicates to prevent requests to localhost, DNS rebinding services, and cloud metadata endpoints, with checks running after hostname normalization but before HTTPS enforcement.

Changes

Cohort / File(s) Summary
Webhook SSRF Hardening
supabase/functions/_backend/utils/webhook.ts
Introduced three blocked-domain lists (localhost, DNS rebinding services, cloud metadata) with corresponding private helper functions (isBlockedLocalhostDomain, isDnsRebindingService, isCloudMetadataHostname) and extended URL validation flow to check and block these categories with specific error messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • fix(webhooks): validate webhook URLs on delivery #1571: Both modify webhook URL validation in the same file; the related PR centralizes validation error handling while this PR extends validation with SSRF-specific security checks (localhost, DNS rebinding, cloud metadata).

Suggested labels

🙋 Bounty claim

Poem

🐰✨ Bounce no more through localhost's door,
DNS tricks we now abhor,
Cloud metadata stays far away,
SSRF threats can't get through today! 🛡️

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Description check ❓ Inconclusive The PR description covers the security issue, impact, changes made, and testing examples, but the description template requires a Test plan section with reproduction steps and a Checklist section which are missing. Add a detailed Test plan section with steps to verify the SSRF protections, and complete the Checklist section to confirm code style, linting, and test coverage compliance.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main security-focused change: blocking SSRF bypass vectors in webhook URL validation.

✏️ 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

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
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)
supabase/functions/_backend/utils/webhook.ts (2)

68-74: Verify the bare metadata hostname doesn't cause false positives.

The entry 'metadata' without a domain suffix could theoretically block legitimate webhook endpoints if a user's internal service happens to be named metadata. While this provides defense-in-depth, consider whether the risk of false positives outweighs the security benefit for this specific entry.

Additionally, cloud providers primarily use IPs (blocked by isIpLiteral) or specific FQDNs, so the bare hostname may be unnecessary.

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

In `@supabase/functions/_backend/utils/webhook.ts` around lines 68 - 74, Remove
the bare 'metadata' entry from the BLOCKED_METADATA_HOSTNAMES set to avoid false
positives; update the constant (BLOCKED_METADATA_HOSTNAMES) to only include
explicit metadata FQDNs ('metadata.google.internal', 'metadata.goog',
'instance-data') and adjust any tests or callers that assumed 'metadata' was
blocked, or alternatively implement a stricter check (e.g., only block when host
resolves to known metadata IPs via isIpLiteral or explicit DNS resolution) if
you need defense-in-depth.

110-112: Consider adding subdomain checks for consistency with isBlockedLocalhostDomain.

Unlike isBlockedLocalhostDomain, this function only performs exact matches. While cloud metadata services typically only respond to exact hostnames, adding subdomain blocking would provide defense-in-depth and maintain consistency across the helper functions.

🛡️ Suggested enhancement
 function isCloudMetadataHostname(hostname: string): boolean {
-  return BLOCKED_METADATA_HOSTNAMES.has(hostname)
+  if (BLOCKED_METADATA_HOSTNAMES.has(hostname))
+    return true
+  for (const blocked of BLOCKED_METADATA_HOSTNAMES) {
+    if (hostname.endsWith(`.${blocked}`))
+      return true
+  }
+  return false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/utils/webhook.ts` around lines 110 - 112, The
isCloudMetadataHostname function currently only checks exact matches against
BLOCKED_METADATA_HOSTNAMES; change it to also block subdomains for
defense-in-depth and consistency with isBlockedLocalhostDomain by comparing a
normalized hostname (e.g., lowercased/trimmed) and returning true if it equals
any entry in BLOCKED_METADATA_HOSTNAMES or endsWith("." + entry); reference the
isCloudMetadataHostname function and the BLOCKED_METADATA_HOSTNAMES set and
mirror the subdomain logic used in isBlockedLocalhostDomain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/functions/_backend/utils/webhook.ts`:
- Around line 68-74: Remove the bare 'metadata' entry from the
BLOCKED_METADATA_HOSTNAMES set to avoid false positives; update the constant
(BLOCKED_METADATA_HOSTNAMES) to only include explicit metadata FQDNs
('metadata.google.internal', 'metadata.goog', 'instance-data') and adjust any
tests or callers that assumed 'metadata' was blocked, or alternatively implement
a stricter check (e.g., only block when host resolves to known metadata IPs via
isIpLiteral or explicit DNS resolution) if you need defense-in-depth.
- Around line 110-112: The isCloudMetadataHostname function currently only
checks exact matches against BLOCKED_METADATA_HOSTNAMES; change it to also block
subdomains for defense-in-depth and consistency with isBlockedLocalhostDomain by
comparing a normalized hostname (e.g., lowercased/trimmed) and returning true if
it equals any entry in BLOCKED_METADATA_HOSTNAMES or endsWith("." + entry);
reference the isCloudMetadataHostname function and the
BLOCKED_METADATA_HOSTNAMES set and mirror the subdomain logic used in
isBlockedLocalhostDomain.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1ed50a and e0841f4.

📒 Files selected for processing (1)
  • supabase/functions/_backend/utils/webhook.ts

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 1, 2026

@riderx
Copy link
Member

riderx commented Mar 3, 2026

do not make security fix on PR use proper security disclosure

@riderx riderx closed this Mar 3, 2026
@riderx
Copy link
Member

riderx commented Mar 3, 2026

  • it's useless in serverless env

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.

2 participants