Repository navigation
feat(security): mcp_reject_query_apikey refuses ?apikey= credentials on MCP endpoints - #1696
Conversation
Opt-in setting (default false). When true every /mcp* endpoint answers a request carrying ?apikey= with 401 instead of honouring it, so an agent token or the API key is never carried in a URL. The refusal is explicit because with require_mcp_auth off an unrecognised credential is served as the anonymous administrator. The REST API, Web UI and /events are unchanged. Fixes smart-mcp-proxy#1695
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…protect, and the Connect caveat The option forces clients off ?apikey= on MCP endpoints (including the legacy /v1/tool_code aliases); it cannot keep a URL that was already sent out of upstream logs. The Connect wizard still writes Codex and generic entries with ?apikey= in the URL, so those clients need a header carrier before the option is turned on. Related smart-mcp-proxy#1696
|
Thanks @james-crowley, the implementation is tight: the refusal sits before token handling, the header and bearer paths are untouched, it hot-reloads, and the live matrix (every MCP route, off/on/on+require_mcp_auth, header, bearer, agent token) behaved as described. I pushed one docs commit on top of your branch: the option forces clients to migrate rather than keeping an already-sent URL out of upstream logs, the legacy /v1/tool_code aliases are covered too, and there is a caveat that Connect still writes Codex and generic entries with ?apikey= (tracked in #1709). I also merged main so the Build Binaries flake is gone. Auto-merge is armed; it lands when CI is green. |
…-query # Conflicts: # oas/docs.go
Head branch was pushed to by a user without write access
|
Pushed a merge of main onto your branch with the OpenAPI artifacts regenerated ( |
Dumbris
left a comment
There was a problem hiding this comment.
Verified on a merge with main: build, CI lint config, race tests for config, runtime and the MCP auth middleware (mutation check: disabling the guard fails TestMCPAuthMiddleware_RejectQueryAPIKey); cross-reviewed clean; live matrix checked earlier.
|
Merged, and #1695 is closed with it. Thank you, @james-crowley! Contributions like this are what keep MCPProxy moving, and they matter a lot to the project. One small ask, only if you feel like it: a short public note about your change (a tweet, a LinkedIn post, a line in a community you are part of) helps more people discover MCPProxy. No pressure at all, and I would be glad to repost it. |
Description
Fixes #1695.
Adds the opt-in config key
mcp_reject_query_apikey(defaultfalse, so behaviour is unchanged unless set). Whentrue, every/mcp*endpoint (/mcp,/mcp/all,/mcp/call,/mcp/code,/mcp/p/...) answers a request that carries anapikeyquery parameter with401and a message that names the header forms, before any credential lookup. A credential in a URL ends up in reverse-proxy logs, browser history andRefererheaders; this lets a deployment whose agents can all send a header make that impossible.The refusal is explicit rather than "treat as no token": with
require_mcp_authoff an unrecognised credential is served as the anonymous administrator, so silently ignoring the parameter would downgrade a caller that believes it is authenticated.Scope: MCP endpoints only. The REST API, Web UI and
/eventskeep accepting?apikey=(the UI builds such URLs for SSE and exports). The key is live (listed inundiffedHotConfigFields), no restart.Changes:
internal/config/config.go,internal/server/server.go(mcpAuthMiddleware),internal/runtime/config_hotreload.go, docs (agent-tokens.md,config-file.md), CHANGELOG, regeneratedoas/(viamake swagger, which is clean onmainwithout this change).Checked with a built binary: with the key on,
/mcp,/mcp/alland/mcp/p/xwith?apikey=return 401 with the message, a header credential is not refused, andGET /api/v1/status?apikey=still returns 200.go build -tags server ./...builds.Testing
internal/server/mcp_reject_query_apikey_test.go: refused on/mcp,/mcp/all,/mcp/codefor populated, empty and agent-token values with the handler never reached; header credentials and unrelated query parameters unaffected; default keeps the query form working and authenticated, not anonymous)go test ./internal/config ./internal/runtime ./cmd/release-gate, and theTestMCPAuthMiddleware*tests ininternal/server; I did not run the fullinternal/servere2e tests that need a built binary ornpx)