cloneRequest in config/http_config.go intends a deep copy of the header but does not make one:
r2 := new(http.Request)
*r2 = *r
// Deep copy of the Header.
maps.Copy(r.Header, r2.Header)
After *r2 = *r, r2.Header is the same map as r.Header, so maps.Copy copies it onto itself. The clone shares the caller's header map.
Every round tripper that touches headers is affected, since they all clone first and then modify. headersRoundTripper uses Header.Add, so a reused request gains another copy of every configured header on each round trip. It grows without bound until the server rejects it — a downstream report in Alloy (grafana/alloy#7016) shows ~1000 identical X-Api-Key lines in one request and scrapes failing with 431.
There is a second effect: Header.Set on the clone of a request built without a header panics with assignment to entry in nil map, because the shared nil map is never replaced.
Introduced in 56870db ("Modernize Go"), which replaced the manual copy loop:
- r2.Header = make(http.Header)
- for k, s := range r.Header {
- r2.Header[k] = s
- }
+ maps.Copy(r.Header, r2.Header)
The arguments are also the wrong way round, but swapping them is not enough — the clone needs its own map.
Fix in #982.
cloneRequestinconfig/http_config.gointends a deep copy of the header but does not make one:After
*r2 = *r,r2.Headeris the same map asr.Header, somaps.Copycopies it onto itself. The clone shares the caller's header map.Every round tripper that touches headers is affected, since they all clone first and then modify.
headersRoundTripperusesHeader.Add, so a reused request gains another copy of every configured header on each round trip. It grows without bound until the server rejects it — a downstream report in Alloy (grafana/alloy#7016) shows ~1000 identicalX-Api-Keylines in one request and scrapes failing with 431.There is a second effect:
Header.Seton the clone of a request built without a header panics withassignment to entry in nil map, because the shared nil map is never replaced.Introduced in 56870db ("Modernize Go"), which replaced the manual copy loop:
The arguments are also the wrong way round, but swapping them is not enough — the clone needs its own map.
Fix in #982.