vendor: github.com/moby/moby/client v0.6.1, moby/api v1.56.1 - #7348
Conversation
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟡 NEEDS ATTENTION
| } | ||
|
|
||
| if len(t.respHooks) > 0 { | ||
| hookResp := *resp |
There was a problem hiding this comment.
[medium] Shallow copy of http.Response shares Header map with hooks
At line 51, hookResp := *resp is a struct shallow-copy. Because http.Header is map[string][]string (a reference type), hookResp.Header and resp.Header point to the same underlying map. Any response hook that modifies headers (adds, removes, or mutates values) will silently mutate the response that gets returned to the caller — even though the intent is clearly to give hooks a sandboxed view of the response (the explicit hookResp.Body = hookBody{} guard on the very next line shows this intent for the body).
The asymmetry is stark: the request-hook path uses req.Clone() which does a proper deep copy of headers, but the response-hook path skips the equivalent step. The ResponseHook type docs only forbid reading/closing the body — header mutation is not prohibited, so hook authors writing header-inspecting/modifying hooks would have no warning of this bleed-through.
| hookResp := *resp | |
| hookResp := *resp | |
| hookResp.Header = resp.Header.Clone() |
| Confidence | Score |
|---|---|
| 🟢 strong | 100/100 |
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
| } | ||
|
|
||
| if len(t.respHooks) > 0 { | ||
| hookResp := *resp |
There was a problem hiding this comment.
[low] Response hooks receive a shallow copy; scalar field mutations are silently discarded
hookTransport.RoundTrip creates a shallow copy of the response (hookResp := *resp), stubs out the body on the copy to prevent hooks from reading it, and then passes &hookResp to each ResponseHook. However, after the hooks run, the original resp is returned to the caller — not &hookResp. This means:
- Header mutations (
Add/Set/Del) propagate correctly becausehttp.Headeris amap[string][]string(reference type) — the shallow copy shares the same underlying map. - Scalar field mutations (
StatusCode,Status,Proto,ContentLength, etc.) are silently lost because those are value-copied.
This creates an inconsistent API contract: header mutations work, but status-code mutations don't. The analogous request-hook path explicitly propagates mutations via req = hookReq before passing to the transport. The response path has no equivalent.
If ResponseHook is intended as a read-only observation hook (for logging/tracing), the docs should state "hooks must not mutate the response" explicitly to avoid surprising callers. If mutation is intended, scalar fields should be copied back to resp after the hooks run (or resp itself should be passed directly, with body stubbing applied there).
| Confidence | Score |
|---|---|
| 🟡 moderate | 70/100 |
There was a problem hiding this comment.
The contact is for hooks to not make modifications; the shallow copy exists so hooks can access response metadata while being unable to consume/close the actual body. It wasn't introduced as a general sandbox for arbitrary mutations.
Summary
Release notes (optional)
A picture of a cute animal (not mandatory but encouraged)