Conversation
The streaming subsystem (ActionRequest.stream) fetched scheduled_plan.download_url with no validation and followed redirects, so the response of an internal service could be streamed out to an action destination. Several actions also passed a request-supplied host straight to their client without validation (jira, marketo, salesforce), interpolated a request-supplied value into the destination host (digitalocean, azure), or validated the endpoint with a substring match that the real host could bypass (braze). Add src/hub/ssrf_filter.ts, which rejects hosts that resolve to loopback, link-local (including the cloud metadata endpoint), private, and other reserved address ranges. The streaming path validates every redirect hop with a custom lookup and an up-front literal check, and the affected actions validate their destination before making a request.
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
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.
Summary
Action Hub makes outbound HTTP requests to hosts that are supplied, in whole or in part, by the inbound action request. Several of these paths perform no validation of the destination, so a request can be pointed at loopback, link-local (including the cloud metadata endpoint at
169.254.169.254), or private address space — a Server-Side Request Forgery (SSRF) primitive.This change introduces a single egress guard in
src/huband applies it to the streaming subsystem and to the per-action destinations that lacked (or had bypassable) validation.Details
New module
src/hub/ssrf_filter.ts:isRestrictedAddress(ip)— classifies an address literal against loopback, link-local, private (RFC 1918), CGNAT, and other reserved IPv4/IPv6 ranges, including IPv4-mapped IPv6.ssrfSafeLookup(hostname, options, cb)— adns.lookup-compatible function that errors if the host resolves to a restricted address. Passed as thelookupoption of an outbound request, it validates the address the socket actually connects to, which also closes DNS-rebinding races.assertPublicUrl(url)— validates the protocol and resolved host ahead of a request, for callers that use a client library which cannot accept a custom lookup.Call sites:
ActionRequest.stream()(src/hub/action_request.ts) —scheduled_plan.download_urlis now fetched withssrfSafeLookup.requestdoes not re-apply thelookupoption to redirect targets, so redirects are followed manually and every hop is validated; this closes the302 -> http://<internal>/bypass.assertPublicUrlbefore constructing the client.^https?://(.*)\.braze\.(com|eu)$), whichhttps://169.254.169.254/x.braze.compasses while the real host is internal. It is now validated against the parsed URL hostname.Testing
test/test_ssrf_filter.ts— unit coverage for the address classifier,assertPublicUrl, andssrfSafeLookup.test/test_action_request.ts—stream()refuses a download url that targets an internal address, and refuses one that redirects toward an internal address.test/test_braze.ts— regression test for the endpoint-host bypass.yarn test(mocha +tsc --noEmit+ tslint) passes.Note for self-hosted deployments
The guard blocks private and reserved address space by default. A deployment that runs the Action Hub and its Looker instance entirely on an internal network may need the streaming download URL to reach a private address; such deployments would need an allowance mechanism. This change keeps the secure default and does not add configuration, matching the reported issue; an opt-in allowlist can be layered on if maintainers want to support that topology.