fix(webhook): Block SSRF via customer webhook endpoints - #7459
Merged
Conversation
Webhook endpoints are customer-controlled, and Press — the control plane that holds fleet-wide credentials — is what sends the request. A plain requests.post lets the endpoint point at the cloud metadata service, a private range, or localhost and reflect the response back. ssrf.post resolves the host, refuses any non-global address, pins the connection to the resolved IP (so the name can't rebind between the check and the connect), and never follows redirects. TLS SNI and the certificate check stay bound to the original hostname, so legitimate HTTPS endpoints are unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The endpoint validation only rejected literal private IPs, so a hostname resolving to a private address (or an encoded IP) passed, and requests.post followed redirects — letting "Validate Webhook" reach http://169.254.169.254 via a 302 and return the host's cloud metadata to the user. The background delivery path leaked the same way into Press Webhook Attempt. Route both call sites — validate_endpoint and _send_webhook_call — through ssrf.post so the request is refused unless it resolves to a public address. Also fix a pre-existing wrong annotation (save: True) and allowlist the test fixture secret, both surfaced by the hooks on the touched files. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #7459 +/- ##
===========================================
+ Coverage 61.94% 62.09% +0.14%
===========================================
Files 1067 1069 +2
Lines 102449 102758 +309
Branches 1777 1778 +1
===========================================
+ Hits 63460 63805 +345
+ Misses 38944 38908 -36
Partials 45 45
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The pinning adapter built the Host header from parts.netloc, which includes any user:password@ userinfo. Endpoints with Basic Auth in the URL were sent a malformed "Host: user:password@example.com" and failed validation and delivery. requests already moves the credentials into the Authorization header, so build the Host (and the pinned IP authority) from the host and port only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
regdocs
added a commit
that referenced
this pull request
Sep 11, 2026
fix(webhook): Block SSRF via customer webhook endpoints (backport #7459)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Press Webhook feature (Settings → Developer) lets any team configure an endpoint URL that Press then sends HTTP requests to — on Validate Webhook and on every event delivery. The request is made by the Press host, which is the control plane and holds fleet-wide credentials, so an endpoint that points inward turns Press into a proxy for internal resources.
validate_endpoint_url_formatonly rejected the private ranges when the URL contained a literal IP. Three gaps let a request reach internal addresses anyway:http://2130706433/also passed and resolve to127.0.0.1.requests.postfollowed redirects in both the validate and the delivery path, so a public endpoint could answer302 → http://169.254.169.254/….validate_endpointreturned the response body to the user, and every delivery stored it in Press Webhook Attempt.Chained: set the endpoint to a public host that 302-redirects to the cloud metadata service, click Validate, and the host's metadata (IAM credentials on some providers,
user-data, SSH keys) comes back in the dashboard.Fix
New
press/utils/ssrf.pywithssrf.post(url, **kwargs):allow_redirects=False.Both call sites —
PressWebhook.validate_endpointandPressWebhookLog._send_webhook_call— now go through it and surfaceSSRFErroras a normal failure.Follow-ups (infrastructure, not in this PR)
These close the exposure that already exists and can't be done in a press PR — flagging for whoever owns the control-plane hosts:
user-data; assume they may already have been read.169.254.169.254egress on the Press hosts.Tests
press/utils/test_ssrf.py— resolver refuses the metadata IP, private ranges, mixed-record rebinding, IPv4-mapped loopback, and unresolvable hosts;postrefuses before opening any socket.test_press_webhook.py—validate_endpointrefuses a host resolving to the metadata IP and leaks no body.🤖 Generated with Claude Code