fetch: strip all origin/chain headers so nothing leaks to targets

The fetch service only stripped the four X-Weircon-* control headers, so
any forwarding header injected upstream (X-Forwarded-For, X-Real-IP, Via,
CDN client-IP headers, …) passed straight through to the target — leaking
the caller's IP and proxy chain.

- Replace stripWeircon with stripIdentifying: removes the control headers
  plus all standard forwarding/origin-IP headers, with a prefix sweep for
  any vendor-specific X-Forwarded-* variant.
- NPM advanced.conf clears the same headers (defense in depth).
- Add TestStripIdentifying covering removal + survival of legit headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Asger Weirsøe
2026-05-30 21:23:01 +02:00
co-authored by Claude Opus 4.8
parent 012a279955
commit 5c1da02984
3 changed files with 108 additions and 4 deletions
+38 -4
View File
@@ -145,17 +145,51 @@ func (p *poolTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return p.pool[rc.proxyID].RoundTrip(req)
}
var weirconHeaders = []string{
// stripHeaders are removed from the outbound request so nothing about the
// caller or the proxy chain can reach the target. It covers our own control
// headers plus every standard forwarding / client-IP header that a fronting
// reverse proxy (NPM, nginx, Cloudflare, …) might inject. Any other
// "X-Forwarded-*" header is swept by prefix in stripIdentifying.
var stripHeaders = []string{
// weircon control headers
"X-Weircon-Random-Ip",
"X-Weircon-Random-Ip-Redirect",
"X-Weircon-Proxy-Id",
"X-Weircon-Forward-Method",
// generic forwarding / origin-IP headers
"Forwarded",
"Via",
"X-Real-Ip",
"X-Original-Forwarded-For",
"X-Client-Ip",
"X-Cluster-Client-Ip",
"X-Original-Url",
"X-Original-Host",
"X-Rewrite-Url",
"X-Proxy-Id",
// CDN / vendor client-IP headers
"Cf-Connecting-Ip",
"Cf-Ipcountry",
"Cf-Ray",
"True-Client-Ip",
"Fastly-Client-Ip",
"Fly-Client-Ip",
"X-Appengine-User-Ip",
}
func stripWeircon(h http.Header) {
for _, k := range weirconHeaders {
// stripIdentifying removes every header that could reveal the caller or the
// proxy chain. The explicit list catches the common ones; the prefix sweep
// catches any vendor-specific X-Forwarded-* we didn't enumerate. Header keys
// in an http.Header are already canonicalized, so prefix matching is exact.
func stripIdentifying(h http.Header) {
for _, k := range stripHeaders {
h.Del(k)
}
for k := range h {
if strings.HasPrefix(k, "X-Forwarded") {
h.Del(k)
}
}
}
func pickProxyID(req *http.Request, n int) (int, error) {
@@ -212,7 +246,7 @@ func newHandler(cfg Config, pool []*http.Transport) http.Handler {
pr.Out.Host = rc.target.Host
pr.Out.Method = rc.method
pr.Out.RequestURI = ""
stripWeircon(pr.Out.Header)
stripIdentifying(pr.Out.Header)
},
Transport: &poolTransport{pool: pool},
ModifyResponse: func(resp *http.Response) error {