fix: percent-encode caller-supplied identifiers in request paths - #44
Open
tas50 wants to merge 1 commit into
Open
fix: percent-encode caller-supplied identifiers in request paths#44tas50 wants to merge 1 commit into
tas50 wants to merge 1 commit into
Conversation
3 tasks
tas50
force-pushed
the
fix/escape-path-segments
branch
3 times, most recently
from
September 8, 2026 16:53
b52be71 to
dc240a3
Compare
5 tasks
Every service built its request path by string concatenation, so a name reached the URL verbatim. Two things go wrong. The v1.3 signature covers the canonical path, but net/http re-derives the wire path from the parsed URL. A name Go encodes differently from what we signed - a space, a non-ASCII rune - is signed one way and sent another, and the server answers 401 with no hint as to why. A name containing "%" did not even build a request. Worse, a name containing "/" or ".." walked out of the collection its service owns: Nodes.Delete(ctx, "../clients/validator") emitted a validly signed DELETE against a path outside /nodes. Escape each identifier as a single path segment at the point it enters the path, and escape the org name in orgPath. Every identifier Chef itself considers legal is unreserved, so this is the identity function for valid input and nothing on the wire changes. The new test verifies the signature server-side over the path that actually arrived, which is the only way to catch a client that signs one path and sends another. Signed-off-by: Tim Smith <tim@mondoo.com>
tas50
force-pushed
the
fix/escape-path-segments
branch
from
September 10, 2026 16:37
dc240a3 to
1607d80
Compare
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.
Every service built its request path by concatenation, so a caller-supplied name reached the URL verbatim.
Why this is a bug
The v1.3 signature covers the canonical path, but
net/httpre-derives the wire path from the parsed*url.URL. Where those two derivations disagree, the request is signed one way and sent another:namea b…/nodes/a b…/nodes/a%20b→ 401, no useful errora#b…/nodes/a#b…/nodes/a→ 401 and wrong resourceünïcode…/nodes/ünïcode…/nodes/%C3%BCn%C3%AFcode→ 401a%bcinc: build request: invalid URL escape "%b"And a name containing
/or..escaped its collection entirely:Behind a proxy that normalises
..before erchef re-verifies the signature that lands as a 401; without normalisation it acts on a different object.The fix
esc()(url.PathEscape) applied at every point an identifier enters a path, plus the org name inorgPath. Every identifier Chef considers legal is unreserved, so this is the identity function for valid input — no wire change, no behaviour change for correct callers.Test plan
TestRequestPath_NameCannotEscapeItsCollection—..%2Fclients%2Fvalidatorstays one segmentTestRequestPath_SignatureCoversWirePath— re-verifies the RSA signature server-side againstr.URL.EscapedPath()forplain,a b,a#b,a?b,a/b,a%b,ünïcode; all failed before this change exceptplainTestOrgPath_EscapesOrgNamego vet ./...cleango test ./... -race -count=2cd integration && go test ./...(real signed wire protocol against cinc-zero)