Follow HTTP redirects in transparent proxy#4454
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4454 +/- ##
==========================================
+ Coverage 68.82% 68.88% +0.05%
==========================================
Files 516 516
Lines 54153 54199 +46
==========================================
+ Hits 37272 37336 +64
+ Misses 14018 14000 -18
Partials 2863 2863 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
amirejaz
left a comment
There was a problem hiding this comment.
The redirect-following logic is well-structured and the test coverage is thorough. Two high-severity security issues need to be addressed before merging, plus a few smaller observations.
Security — must fix
1. SSRF via open redirect (High)
The forward loop follows any Location header without validating whether it points to the same host as the original target. In a Kubernetes deployment a compromised or malicious MCP server can return a redirect to http://kubernetes.default.svc/api/v1/secrets, http://169.254.169.254/ (IMDS), or any reachable pod/service. The proxy's pod SA typically has broad network access by design, so the blast radius is significant.
Mitigation: compare redirectURL.Host (normalized) against the original req.URL.Host and break (returning the 3xx as-is) if they differ. Operators who intentionally rename a server's URL should just update the configured target, not rely on redirect following.
2. Authorization header forwarded to a different host (High)
The comment explicitly acknowledges this (// request headers (including Authorization) are preserved across redirects). Combined with the SSRF issue, a bearer token is silently exfiltrated to whatever host the attacker redirects to. The Go stdlib's own http.Client strips Authorization on cross-host redirects precisely to prevent this. See net/http/client.go:shouldCopyHeaderOnRedirect.
Mitigation: either block cross-host redirects (see above) or, if cross-host is ever needed, strip Authorization, Cookie, and Proxy-Authorization when redirectURL.Host != originalHost.
Correctness — looks good
- Body replay via
bytes.NewReader(body)is correct;bodyis pre-read byreadRequestBodybefore the session guard, so the originalreq.Bodystream is intact on the first hop. - Connection pool hygiene (drain + close on each hop) is correct.
req.Host = redirectURL.Hostcorrectly mirrors the existing host-rewrite pattern inRoundTrip.- Interaction with post-
forwardlogic (401 handling, DELETE cleanup, session tracking) is correct — all of it only sees the final response. - 303 exclusion is correct and well-documented.
Minor
maxRedirects = 10matcheshttp.Clientbut for a proxy that is already warning operators on every hop, 3 would be more conservative and sufficient for the intended use case (HTTP→HTTPS redirects, single-hop canonical URL normalisation).- No test exercises a cross-host redirect. Adding one (even if it just asserts the current behavior) makes the security decision explicit and would catch a future regression if a host-check is added.
3411ec6 to
3e034e4
Compare
|
Thanks for the detailed review! Both security issues have been addressed in the follow-up commit 3e034e44. SSRF / cross-host redirect (both issues addressed together): The E2E failure: The failing test ( |
jhrozek
left a comment
There was a problem hiding this comment.
First, sorry to leave you waiting, @amirejaz is on PTO this week and we didn't hand off properly. We should have done that!
There are some merging conflicts so I can't approve, those need to be resolved first. I think the PR looks good overall, I left two nits/observations inline.
3e034e4 to
5207e55
Compare
fdf9dba to
940a6f7
Compare
|
@gkatz2 I'm sorry there's another conflict (I think this time with your own PR to add the timeout). If you rebase I'll re-ack right away. |
MCP clients expect JSON-RPC responses and cannot handle HTTP redirects. When a remote server returns a 3xx redirect, the proxy now follows it transparently instead of passing the redirect response to the client. Fixes stacklok#4453 Signed-off-by: Greg Katz <gkatz@indeed.com>
A malicious MCP server could redirect the proxy to internal cluster services (Kubernetes API, cloud IMDS). Restrict redirect following to same-host only and return the 3xx response as-is for cross-host redirects. Signed-off-by: Greg Katz <gkatz@indeed.com>
Signed-off-by: Greg Katz <gkatz@indeed.com>
940a6f7 to
2679b22
Compare
|
@jhrozek, I resolved the conflict. Please take another look. |
Summary
forwardmethod before returning the response to the client.isRedirectStatushelper that handles 301, 302, 307, and 308. The HTTP method and body are preserved across redirects viareq.Clone(). AmaxRedirectslimit of 10 prevents infinite loops.Fixes #4453
Type of change
Test plan
task test)task test-e2e)task lint-fix)Tested with a local HTTP server returning 308 redirects to a running MCP server (design-system-mcp). Verified:
initializecall succeeds through the redirectDoes this introduce a user-facing change?
Remote MCP servers behind HTTP redirects now work. Previously, the proxy passed redirect responses to the client, causing silent failures. No configuration needed — redirect following is automatic.
Special notes for reviewers
forwardrather thanRoundTripso that session tracking, 401 handling, and DELETE cleanup inRoundTriponly see the final response after all redirects are resolved.isRedirectStatusbecause it explicitly requires changing the method to GET, which would break JSON-RPC.Generated with Claude Code