security(mcp): migrate to rmcp 2 and enforce Host and Origin validation - #29
Merged
Conversation
Clears RUSTSEC-2026-0189 (CVSS 8.8) -- verified: cargo audit passes without the exemption, which is now removed from ci.yml rather than left in place. The advisory names rmcp's Streamable HTTP transport, and this app used SSE, so it was flagged on version rather than reachability. The underlying weakness was real regardless and lived in our code: no Host or Origin validation anywhere, so a page the user visited could POST to 127.0.0.1 and invoke tools. rmcp 0.1.5 offered no seam to add either -- SseServer built its axum router internally and App was private. rmcp 2 removed the SSE server transport entirely, so this is a transport migration as well as a version bump. Streamable HTTP is also where the advisory's fix landed, which is what makes the checks available: allowed_hosts defaults to loopback and closes DNS rebinding. allowed_origins defaults to EMPTY, and empty means Origin validation is DISABLED. It has to be set explicitly or the cross-origin hole stays open. A Host check alone is not enough -- a page can fetch loopback with mode:'no-cors', the Host header matches because the request really is going to 127.0.0.1, and only the Origin reveals another site sent it. Listing only our own origins keeps genuine MCP clients working: they are not browsers and send no Origin at all, while anything from a browser tab is rejected unless truly same-origin. Both lists are built by named functions with tests, because the failure mode is silent: an emptied allowed_origins turns validation off while everything still appears to work, and a schemeless entry matches nothing under RFC 6454. API changes: #[tool(tool_box)] -> #[tool_router], #[tool(aggr)] -> Parameters<T>. ServerHandler is written out rather than generated by tool_router(server_handler), which would have silently dropped the instructions the client shows its model. StreamableHttpServerConfig and ServerInfo are both non-exhaustive now, so both are built via their builder/Default rather than struct literals. BREAKING for existing MCP clients: the endpoint moves from /sse to /mcp and the transport from sse to http. Docs, the AI Integration view and the copy-paste client configs are all updated, with a warning explaining both changes. Not verified by the launch test here -- that script lives on the gui-launch branch and is not on this one yet. Build, clippy, the full suite and cargo audit all pass.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
thinkutils | 4eead03 | Jul 19 2026, 03:33 PM |
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.
Clears RUSTSEC-2026-0189 (CVSS 8.8). Verified:
cargo auditpasses without the exemption, which is removed fromci.ymlrather than left in place.What was actually wrong
The advisory names rmcp's Streamable HTTP transport, and this app used SSE — so it was flagged on version, not reachability. I said so at the time rather than overstating it.
But the underlying weakness was real and lived in our code: no
HostorOriginvalidation anywhere, so a page you visited could POST to127.0.0.1and invoke tools. rmcp 0.1.5 offered no seam to add either —SseServerbuilt its axum router internally andAppwas private. That, not the advisory, is why the upgrade was necessary.Why this is a transport migration
rmcp 2 removed the SSE server transport entirely. Streamable HTTP is also where the advisory's fix landed, which is what makes the checks available:
allowed_hostsdefaults to loopback → closes DNS rebinding.allowed_originsdefaults to EMPTY, and empty means Origin validation is DISABLED. It has to be set explicitly or the cross-origin hole stays open.That second point is the one worth dwelling on. A Host check alone is not enough: a page can fetch the loopback endpoint with
mode: 'no-cors', theHostheader matches because the request really is going to 127.0.0.1, and only theOriginreveals another site sent it.Listing only our own origins keeps genuine MCP clients working — they aren't browsers and send no
Originat all — while anything from a browser tab is rejected unless truly same-origin.Tests
Both allowlists are built by named functions with tests, because every failure here is silent:
allowed_origins_is_never_emptyallowed_origins_carry_a_scheme_and_the_right_portallowed_hosts_cover_the_port_qualified_formsHost: 127.0.0.1:8779, not a bare hostallowed_hosts_are_loopback_onlya_foreign_origin_is_not_allowedAPI changes
#[tool(tool_box)]→#[tool_router],#[tool(aggr)]→Parameters<T>.ServerHandleris written out rather than generated bytool_router(server_handler), which would have silently dropped the instructions the client shows its model.StreamableHttpServerConfigandServerInfoare both non-exhaustive now, so both use their builder/Default.The endpoint moves
/sse→/mcpand the transportsse→http. Docs, the AI Integration view and the copy-paste client configs are updated with a warning explaining both.Not verified here
The container launch test lives on the
gui-launchbranch and isn't on this one. Build, clippy, the full suite andcargo auditall pass; end-to-end launch verification happens when the branches meet.