http/websocket: a host chosen after registration can never be loopback

Merged
H3
7d ago
+912-47

Summary

Loopback is the trust boundary of the HTTP plugins: plain HTTP to loopback is allowed for local development, and only for requests the user addressed there. This closes the ways a host decided after registration could still land a request on the agent's own interface, and ports the same rules to the WebSocket plugin.

utcp-http 1.1.14 → 1.1.15

  • Call time: after path-parameter substitution, a resolved URL may be loopback only if the call template literally names a loopback host (ensure_loopback_was_declared). http://localhost:8080/items/{id} keeps working; http://{host}:7777/admin called with host=127.0.0.1 is refused before any request leaves. Applied to http, SSE and streamable HTTP. Templates may still take their host from an argument for any other destination; nothing is rejected at registration for having a placeholder host.
  • Remote manuals: reject_remote_loopback_tool_urls now also refuses a loopback OAuth2 token_url (the client-credentials POST is a request made on the manual's behalf). Covers GHSA-pg2m-h8f5-6ppq.
  • Remote OpenAPI specs: a loopback tokenUrl, absolute or resolved from a relative/scheme-relative value, is refused for a spec fetched from a non-loopback origin. Covers GHSA-32vv-p32r-hxxp; flips the test that documented this gap. Server variables with a default are now resolved into servers[0].url, as the OpenAPI spec intends.
  • The converter receives the final post-redirect URL as spec_url, so a loopback discovery URL that redirected to a remote origin does not keep the local-dev exemption (the manual path already did this).

utcp-websocket 1.2.0 → 1.2.1

  • The session that opens the socket refuses to follow a redirect of the WebSocket upgrade (an aiohttp.TraceConfig fails the connection on the first 3xx, before the redirected request is built), so a 3xx answer can no longer steer the handshake and its auth headers at a host ensure_secure_ws_url never saw. Covers GHSA-6mfx-q9f2-jwx8.
  • WebSocket discovery applies the remote-loopback manual rule (tool URLs and token_url), which it was missing.

GHSA-2r98-cv5v-cj65 (redirect into loopback) was already closed in 1.1.14 (#109).

Test plan

  • New test_remote_loopback_bypass.py (classifier table, call-time rule, remote-manual token_url, converter rules, end-to-end attack and control per protocol against an aiohttp server that records hits)
  • New test_websocket_redirect_security.py (upgrade redirect refused with the internal target never contacted and the API key never sent; direct handshake control; remote-manual rule)
  • pytest core plugins: 626 passed, 9 skipped before the last revision; http + websocket plugins 364 passed after it

šŸ¤– Generated with Claude Code