Repository navigation
fix(httpapi): percent-decode the {tool} path param before the approval lookup - #1750
Conversation
…l lookup Co-authored-by: Cursor <cursoragent@cursor.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Dumbris
left a comment
There was a problem hiding this comment.
Verified: the PR test fails on main for the reason described and passes on this branch merged with main; race tests for the touched packages and CI lint (bare and server tags) are clean. Also built and tested together with the rest of this batch, independently reviewed, and cross-reviewed with a second model.
…1751) # Pull Request ## Description Official-registry server ids are `namespace/name` (`io.github.owner/repo`, `com.pulsemcp/google-flights`). The daemon handles them on `/api/v1/servers/{id}/*` by decoding a `%2F`-encoded `{id}` (`decodeServerIDParam`, MCP-1118), and `GetServerLogs` already sends `url.PathEscape(serverName)`. Every other per-server method in `internal/cliclient` splices the raw name into the URL with `fmt.Sprintf`, so the slash splits the name across path segments and the request lands on a different route (usually a 404): ``` mcpproxy upstream enable io.github.owner/repo -> POST /api/v1/servers/io.github.owner/repo/enable (route: {id}=io.github.owner, then /repo/enable) ``` Affected: `ServerAction` (enable/disable/restart), `GetServerTools`, `SetToolEnabled`, `SetAllToolsEnabled`, `TriggerOAuthLoginWithResult`, `TriggerOAuthLogout`, `PatchServer`, `RemoveServer`, `GetToolApprovals`, `GetToolDiff`, `ApproveTools`, `BlockTools`. Most of these declare a local `url` variable that shadows `net/url`, which is probably how the escape was missed. `ServerAction`'s `var url string` is renamed to `reqURL`; the `url := fmt.Sprintf(..., url.PathEscape(...))` sites are fine as written because the new variable's scope starts after the statement. Tool names in `SetToolEnabled` and `GetToolDiff` are escaped too. For a `/` in a tool name, the daemon also has to decode `{tool}`; that is #1750. ## Testing - [x] I have tested these changes locally - [x] I have added/updated tests that prove my fix is effective or my feature works - [x] All existing tests pass `TestPerServerRequests_EscapeServerAndToolNames` (`internal/cliclient/server_path_escape_test.go`) calls 12 methods against an `httptest` server and checks `r.URL.EscapedPath()`. ``` CGO_ENABLED=0 go vet ./internal/cliclient # clean CGO_ENABLED=0 go test ./internal/cliclient -count=1 # ok # with client.go reverted: CGO_ENABLED=0 go test ./internal/cliclient -run TestPerServerRequests -count=1 # FAIL, 12 of 12 cases gofmt -l internal/cliclient # clean ``` Co-authored-by: Cursor <cursoragent@cursor.com>
|
Merged. Thank you, @rudycelekli! The Review diff and the per-tool enable toggle now work for tool names the Web UI percent-encodes ( |
Pull Request
Description
chi routes on
RawPath, so path params arrive percent-encoded whenever the client's encoding differs from Go's default. MCP-1118 fixed this for{id}withdecodeServerIDParamon the/servers/{id}subtree, but the nested{tool}param is still read raw by its two handlers:GET /api/v1/servers/{id}/tools/{tool}/diff(handleGetToolDiff)POST /api/v1/servers/{id}/tools/{tool}/enabled(handleSetToolEnabled)Trigger. The Web UI builds both URLs with
encodeURIComponent(toolName)(frontend/src/services/api.ts:577,:618), which encodes:as%3Aand/as%2F. Go's default path encoding leaves:alone, so for a tool namedns:erasethe request has a non-emptyRawPath, chi hands the handlerns%3Aerase, and the exact-match approval lookup misses. The Review diff for that tool never loads and the per-tool enable/disable toggle fails. Tool names carrying:or/are upstream-defined and already accounted for elsewhere (seecanonicalIdentityininternal/profile/glob.go).decodeServerIDParamcannot cover{tool}: it runs as subtree middleware, before the nested route has been matched and the param populated. So the fix is the samedecodePathParamcall in the two handlers, ashandleAddFromRegistryalready does for{serverId}.Testing
TestToolSubresource_EncodedToolNameUnescaped(internal/httpapi/tool_path_param_decode_test.go) drives both routes throughServer.ServeHTTPwithns%3Aeraseandns%2Feraseand asserts the decoded name reaches the lookup.(
CGO_ENABLED=0only because my local macOS SDK cannot link cgo test binaries; nothing here uses cgo.)Note on the CodeQL alert
CodeQL reports
go/reflected-xssatinternal/httpapi/middleware.go:85(statusRecorder.Write) because the decoded tool name now reaches the response. The only sinks arewriteSuccess/writeError, both of which go throughwriteJSON:Content-Type: application/jsonplusjson.Encoderwith its default HTML escaping (<,>,&become\u003cand so on). The rawchi.URLParamvalue already reached the same sinks before this change. The decode only turns%3Ainto:so the controller lookup matches.