Summary
The utcp-http plugin is vulnerable to a blind Server-Side Request Forgery (SSRF) caused by a trust-boundary inconsistency between manual discovery and tool invocation. register_manual() validates the discovery URL against an HTTPS / loopback allowlist, but call_tool() and call_tool_streaming() reuse the resolved tool_call_template.url directly without revalidating, and the OpenAPI converter blindly trusts whatever servers[0].url an attacker-hosted spec declares. An attacker who hosts a malicious OpenAPI spec on a legitimate HTTPS endpoint can declare e.g. servers: [{ url: "http://127.0.0.1:9090" }] or servers: [{ url: "http://169.254.169.254" }]; the OpenAPI converter then produces tools whose URL points at internal services on the agent host.
All three HTTP-class protocols (utcp_http.http, utcp_http.streamable_http, utcp_http.sse) shared the same gap, plus a separate prefix-bypass: the previous startswith("http://localhost") check let URLs like http://localhost.evil.com through.
Versions and patch state
<= 1.1.1 — fully vulnerable. Both the loopback-redirect (http://127.0.0.1) and the non-loopback internal-IP variants (e.g. http://169.254.169.254, http://10.0.0.5) succeed.
1.1.2 — partial fix only. The runtime ensure_secure_url rejects non-loopback HTTP, so cloud-metadata and RFC1918 variants are blocked. The loopback-redirect variant (http://127.0.0.1:9090) still succeeds because ensure_secure_url legitimately allows loopback HTTP for local-dev. Users on 1.1.2 should still upgrade.
1.1.3 — full fix. The OpenAPI converter rejects, at conversion time, any spec fetched from a non-loopback source that declares a loopback servers[0].url. This is the only check that can distinguish a malicious remote spec redirecting at loopback from a legitimate local-dev call, because the spec's origin is the only signal available, and that signal exists only at conversion time.
Impact
A remote attacker who can convince the agent (via the LLM context, prompt injection, or a tool-discovery surface) to register their HTTPS OpenAPI URL can:
- Map internal networks behind the agent.
- Read AWS/GCP IAM credentials from cloud metadata endpoints (
http://169.254.169.254, http://metadata.google.internal) — fixed in 1.1.2.
- Reach unauthenticated internal services exposed on loopback (Elasticsearch, Redis HTTP, internal admin panels, the agent's own HTTP server) — only fixed in 1.1.3.
- Have responses returned to the LLM, which combined with prompt injection enables exfiltration back to the attacker.
Patch
Commits on dev:
- 5b16e43 — runtime URL revalidation in all three protocols (shipped as 1.1.2, blocks the non-loopback variants).
- e356ea7 — OpenAPI converter rejects remote spec → loopback server (shipped as 1.1.3, blocks the loopback-redirect variant).
- f873ed6 — comment correction; no behavior change.
See utcp_http._security for the validation helpers (is_secure_url, is_loopback_url, ensure_secure_url).
Workarounds
For users who cannot upgrade immediately:
- Refuse to call
register_manual with any URL controlled by an untrusted party, even over HTTPS.
- Restrict outbound network access from the host running the agent so internal addresses (RFC1918, 169.254.0.0/16, loopback) are unreachable.
Credit
Discovered and reported by @YLChen-007 in #83.
Summary
The
utcp-httpplugin is vulnerable to a blind Server-Side Request Forgery (SSRF) caused by a trust-boundary inconsistency between manual discovery and tool invocation.register_manual()validates the discovery URL against an HTTPS / loopback allowlist, butcall_tool()andcall_tool_streaming()reuse the resolvedtool_call_template.urldirectly without revalidating, and the OpenAPI converter blindly trusts whateverservers[0].urlan attacker-hosted spec declares. An attacker who hosts a malicious OpenAPI spec on a legitimate HTTPS endpoint can declare e.g.servers: [{ url: "http://127.0.0.1:9090" }]orservers: [{ url: "http://169.254.169.254" }]; the OpenAPI converter then produces tools whose URL points at internal services on the agent host.All three HTTP-class protocols (
utcp_http.http,utcp_http.streamable_http,utcp_http.sse) shared the same gap, plus a separate prefix-bypass: the previousstartswith("http://localhost")check let URLs likehttp://localhost.evil.comthrough.Versions and patch state
<= 1.1.1— fully vulnerable. Both the loopback-redirect (http://127.0.0.1) and the non-loopback internal-IP variants (e.g.http://169.254.169.254,http://10.0.0.5) succeed.1.1.2— partial fix only. The runtimeensure_secure_urlrejects non-loopback HTTP, so cloud-metadata and RFC1918 variants are blocked. The loopback-redirect variant (http://127.0.0.1:9090) still succeeds becauseensure_secure_urllegitimately allows loopback HTTP for local-dev. Users on 1.1.2 should still upgrade.1.1.3— full fix. The OpenAPI converter rejects, at conversion time, any spec fetched from a non-loopback source that declares a loopbackservers[0].url. This is the only check that can distinguish a malicious remote spec redirecting at loopback from a legitimate local-dev call, because the spec's origin is the only signal available, and that signal exists only at conversion time.Impact
A remote attacker who can convince the agent (via the LLM context, prompt injection, or a tool-discovery surface) to register their HTTPS OpenAPI URL can:
http://169.254.169.254,http://metadata.google.internal) — fixed in 1.1.2.Patch
Commits on
dev:See
utcp_http._securityfor the validation helpers (is_secure_url,is_loopback_url,ensure_secure_url).Workarounds
For users who cannot upgrade immediately:
register_manualwith any URL controlled by an untrusted party, even over HTTPS.Credit
Discovered and reported by @YLChen-007 in #83.