Skip to content

core: validate download URLs and re-check them on redirect - #4035

Open
eliteprox wants to merge 2 commits into
livepeer:masterfrom
eliteprox:fix/open-redirect-ssrf
Open

core: validate download URLs and re-check them on redirect#4035
eliteprox wants to merge 2 commits into
livepeer:masterfrom
eliteprox:fix/open-redirect-ssrf

Conversation

@eliteprox

Copy link
Copy Markdown
Collaborator

Summary

Follow-up hardening for core.DownloadData, layered on top of the localhost-blocking fix in 5b7581c. Two gaps remained after that change:

  • The dial-time check only inspects the resolved IP, so a URL with a non-HTTP scheme, embedded credentials, or path-traversal segments was passed straight to the transport.
  • The dial check ran on redirects, but nothing re-validated the redirect URL itself.

This PR adds validateDownloadURL, applied both to the caller-supplied URL and, via CheckRedirect, to every redirect hop. It rejects:

  • schemes other than http/https (file:, gopher:, data:, javascript:, …)
  • embedded credentials (http://user:pass@host/)
  • .. path segments, including percent-encoded (%2e%2e) and backslash-separated forms
  • empty hosts and unparseable URLs

Deliberately unchanged

Reachability is not narrowed. The dial control stays loopback-and-unspecified only, and private/RFC1918, link-local, and CGNAT addresses remain reachable — orchestrators, broadcasters, and object stores routinely run on LAN, Docker, and Kubernetes addresses, so blocking those would break real deployments. TestDownloadDataAllowsPrivateAddressAndBlocksRedirectToLoopback is kept as the guard on that behavior.

DownloadDataAllowLocalhost is also untouched and keeps its existing opt-out semantics.

The diff against master is purely additive apart from one line: +79 / -1.

Test plan

  • TestValidateDownloadURL — new table test covering each rejected class plus valid http/https URLs
  • TestDownloadDataAllowsPrivateAddressAndBlocksRedirectToLoopback — extended with a /redirect-unsafe hop that 302s to file:///etc/passwd, asserting CheckRedirect rejects it; still asserts a private-address fetch succeeds and a redirect to loopback is refused with zero hits on the protected server
  • TestRejectLocalhostDial — unchanged, still passes
  • Full package green: go test -tags=mainnet,experimental ./core/ -count=1
  • go build -tags=mainnet,experimental ./core/... ./server/... and gofmt clean

Note for the merger: please squash. The first commit on this branch also blocked private addresses; the second walks that back for the reasons above.

- Renamed `TestRejectLocalhostDial` to `TestRejectNonPublicDial` and updated test cases to check for various non-public address types.
- Introduced `validateDownloadURL` function to enforce stricter URL validation, rejecting unsafe schemes and path traversal.
- Updated `DownloadData` to utilize the new validation and reject non-public addresses in `rejectNonPublicDial`.
- Enhanced error handling for private and unsafe URLs in the download process.
…achable

Blocking all non-public addresses broke a deliberate upstream decision:
orchestrators, broadcasters and object stores routinely run on RFC1918
LAN, Docker and Kubernetes addresses, so refusing them would break those
deployments. Restore the loopback-only dial check and the test that
asserts private addresses stay reachable while a redirect to loopback is
refused.

What remains is the defense-in-depth that does not change reachability:
reject non-HTTP(S) schemes, embedded credentials and path traversal, and
re-run those checks on every redirect hop via CheckRedirect.

Also drop the rejectLocalhostDial and localhostBlockedHTTPClient
compatibility shims, which had no callers.
@github-actions github-actions Bot added the go Pull requests that update Go code label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant