fix(requests): quote only the received body in status errors - #1
Merged
Conversation
The status-mismatch branches built their message as
res.Status + string(body[:50]) and passed it as the format argument to
fmt.Errorf. Two defects:
- body[:50] reads past len(body) on bodies shorter than 50 bytes. It does
not panic, since io.ReadAll returns a slice with cap >= 512, so the
re-slice stays inside the allocation and exposes the buffer's slack
instead: a {"error":"x"} body rendered as 404{"error":"x"} plus 37 NUL
bytes.
- A body containing % was interpreted as a format verb, so 100% failed
rendered as 100%!f(MISSING)ailed.
Both branches now use a constant format string and quote exactly the
received body, truncated at maxErrorBodyLen (512) with an ellipsis marker
so a large HTML error page cannot land in a log line verbatim.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Both status-mismatch branches in
Exec()built their error asres.Status + string(body[:50])and passed the result as the format argumentto
fmt.Errorf. They now use a constant format string and quote exactly thereceived body, truncated at
maxErrorBodyLen(512) with an…marker.Why
Two defects, both measured against the shipped code:
body[:50]reads pastlen(body). It does not panic, contrary to whatone might expect:
io.ReadAllalways allocates withcap >= 512, so there-slice stays inside the allocation and exposes the read buffer's slack
instead. A
{"error":"x"}body rendered as404{"error":"x"}followed by 37NUL bytes; an empty body as
404followed by 50 NUL bytes. The padding isNUL today only because the buffer is freshly allocated — the code has no
right to those bytes either way.
100% failedbody rendered as100%!f(MISSING)ailed.The 512-byte bound is new (the old 50 was an accident of the broken slice); it
keeps a typical JSON error payload intact while stopping a multi-megabyte HTML
error page from landing in a log line verbatim.
Tests
TestTruncateBody—len == 0,len < limit,len == limit,len == limit + 1; the marker appears only in the last case.TestExecStatusError— 4 bodies (empty, short JSON, one containing%, oneof 2×
maxErrorBodyLen) × 2 assertion paths (ResponseCodeOkvs 404,ResponseCodeFailvs 500) = 8 cases. Asserted by equality on the fullmessage, not
Contains: the defect is extra bytes appended to the body,which a substring check would pass.
Red-first check: with the two branches reverted to the old expression, all 8
TestExecStatusErrorcases fail.Local gates:
go test -count=1 ./...(40 tests),go vet ./...— clean, whichis itself part of the proof, since vet flags the old non-constant format string
— and
gofmt -l ..Notes
truncateBodyandmaxErrorBodyLenare unexported, sogorequests-proxyandgorequests-retryare unaffected.go 1.18;io/ioutiland the Go baseline are left alonedeliberately (separate modernisation task).
Url(format, args...)runs the URL throughfmt.Sprintf, so a%in the URL itself (e.g. percent-escaping) is mangledinto
%!f(MISSING).🤖 Generated with Claude Code