UTCP Patched the Same SSRF Four Times — and the Redirect Hop Was Still Open

NVD published five CVE records on 27 September 2026 for the Universal Tool Calling Protocol — the open-source tool-calling framework positioned as an alternative to MCP, with 652 stars on its Python implementation. Four of the five are server-side request forgery. Three of those four are incomplete-fix variants of an SSRF class the project already patched in May.

That is the story. Not a new bug class, not a novel technique: one validation gap, re-found in three places the original patch did not cover, four months after the first fix shipped. The highest-scoring record in the batch says so in its own advisory text.

The batch

  • CVE-2026-101060 (CVSS 4.0 8.4, High) — HttpCommunicationProtocol.call_tool validates the pre-redirect tool URL, then issues the request with aiohttp's default allow_redirects=True and never re-checks the destination. Fixed in utcp-http 1.1.4.
  • CVE-2026-101058 (7.1) — hand-written UTCP manuals fetched from a remote origin may declare tool URLs on the agent's own loopback interface, which ensure_secure_url deliberately permits for local development. Fixed in utcp-http 1.1.12.
  • CVE-2026-101059 (7.1) — the OAuth2 tokenUrl field from a remote OpenAPI spec is never validated, so the client POSTs its client_id and client_secret to whatever endpoint the spec names. Fixed in utcp-http 1.1.4.
  • CVE-2026-101061 (2.3) — utcp-gql used a prefix check defeated by hostnames like http://127.0.0.1.attacker.example, and utcp-websocket performed no URL validation at all, despite documented requirements. Fixed in utcp-gql 1.1.1 and utcp-websocket 1.1.1.
  • CVE-2026-101057 (2.3) — utcp-mcp dialed HTTP and WebSocket MCP server URLs from mcpServers configuration without the validation the HTTP-family plugins apply. Fixed in utcp-mcp 1.1.3.

All five were assigned by VulnCheck. The GHSA advisories behind them were published earlier — GHSA-9qhg-99ww-9mqc, the redirect SSRF, is dated 14 June 2026, more than three months before the CVE record appeared. That gap is now routine enough that we have written about it in the 72-CVE OpenClaw batch and the Obot batch two days ago: if your vulnerability intake begins at NVD, you were blind to these for a quarter.

The incomplete fix, stated plainly

CVE-2026-44661, published 14 May 2026, described a trust-boundary inconsistency in utcp-http: register_manual() validated the discovery URL against an HTTPS/loopback allowlist, but call_tool() reused the resolved template URL without revalidating, and the OpenAPI converter trusted whatever servers[0].url an attacker-hosted spec declared. The fix in 1.1.3 added an invocation-time URL check.

The advisory for the new redirect bug does not hedge about what happened next. In its own words, this is "the redirect invariant of the SSRF class fixed in GHSA-39j6-4867-gg4w; that fix added an invocation-time URL check but left the redirect hop unguarded." The validator runs once, at line 281 of http_communication_protocol.py, on the URL the caller supplies. The request that follows carries aiohttp's default redirect behaviour with no per-hop revalidation. An attacker who controls the endpoint a registered tool points at answers with a 302 and a Location header, and the client follows it into the internal network — returning the response body to the caller.

The preconditions are worth reading closely, because they are ordinary UTCP usage rather than misconfiguration. The initial URL only has to pass the validator, which any https:// host satisfies. The attacker supplies the redirect from their own server. The pattern that makes this reachable — registering a manual or OpenAPI spec discovered from a runtime-supplied URL — is described in the advisory as a core UTCP usage pattern. And the tool's return value flowing back to the agent, which is what makes this a full-read SSRF rather than a blind one, is simply how agentic tool calling works.

The advisory is careful about the credential-theft outcome in a way that deserves credit: IAM credential exfiltration through 169.254.169.254 requires IMDSv1, and IMDSv2-only hosts block that specific result because the session token needs a PUT. Other internal targets — admin panels, unauthenticated datastores, link-local endpoints — remain reachable regardless.

Loopback was an exception the patch could not see past

CVE-2026-101058 is the subtler of the two remaining high-severity records, and it illustrates why allowlists with development carve-outs are hard to secure. ensure_secure_url intentionally permits loopback HTTP so developers can point a client at a service on their own machine. Native, hand-written manuals bypassed the loopback check the OpenAPI converter performed. Put those together and an attacker who can serve a UTCP manual that a victim registers can declare tool URLs on 127.0.0.1 — reaching services on the victim's host that are bound to loopback precisely because their operators considered them unreachable.

The fix in utcp-http 1.1.12 is the right shape: reject manuals fetched from a non-loopback origin that declare loopback tool URLs, keyed off the final post-redirect discovery URL. That last clause is the lesson from CVE-2026-101060 being applied here — the origin that matters is where the fetch landed, not where it started.

CVE-2026-101059 is a different failure with the same root: a URL from remote data used without validation. Register an attacker's OpenAPI spec, invoke a generated OAuth2-protected tool, and the library POSTs your client_id and client_secret to the tokenUrl the spec named. This is not SSRF into the internal network; it is credential delivery out of it. Any operator who registered a third-party OpenAPI spec on utcp-http before 1.1.4 and used OAuth2-protected tools should treat those client credentials as disclosed and rotate them — the upgrade stops future submissions but tells you nothing about past ones.

The two low-severity records are the honest ones

CVE-2026-101057 and CVE-2026-101061 both score 2.3, and both descriptions do something unusual: they narrow the original researcher's claim. The utcp-mcp record states outright that "the OAuth2 token_url credential path described in the original report was not reachable in the affected versions, because the OAuth2 handler was never invoked and the call template's auth field was not read," and notes that mcpServers configuration is operator-authored rather than remote data. What remains is a real but limited issue — a plain-HTTP MCP handshake to a non-loopback host, exposed to network interception.

That correction is the opposite of the pattern we documented in the mcp-remote batch, where CVE descriptions overstated what the cited advisories said. Here the record is more conservative than the report it came from. Defenders benefit either way only if they read the record rather than the score — a 2.3 that accurately describes cleartext MCP handshakes to internal hosts is more useful than an inflated number.

CVE-2026-101061 carries the batch's most familiar mistake. utcp-gql checked whether a URL started with an allowed prefix, which http://127.0.0.1.attacker.example satisfies while resolving to whatever the attacker's DNS says. This is the string-versus-resolution confusion that also produced the mcp-atlassian header SSRF and, published the same day as this batch, the fast-mcp-telegram denylist that checked a hostname string and never resolved it (CVE-2026-55096, CVSS 7.1, fixed in 30.1). Meanwhile utcp-websocket performed no validation at all while the documentation said it should.

What to do

  • Upgrade per plugin, not per framework. The fixed versions differ: utcp-http needs 1.1.12 or later to cover all three of its records (1.1.4 fixes the redirect and tokenUrl bugs but not the loopback manual), utcp-mcp needs 1.1.3+, utcp-gql and utcp-websocket need 1.1.1+. Current PyPI releases are ahead of all of these. Pin and verify each installed plugin separately — a single utcp version tells you nothing about the plugin wheels beside it.
  • Inventory every manual and OpenAPI spec you register from a remote URL. That is the attacker-controlled input in three of these five. If a spec came from a third party, assume its declared tool URLs, server URLs and tokenUrl were never validated on the affected versions.
  • Rotate OAuth2 client credentials used with third-party specs. CVE-2026-101059 delivered them to an attacker-named endpoint with no logging on your side that would distinguish it from a normal token request.
  • Enforce IMDSv2 and default-deny egress on agent hosts. Both were explicitly noted as limiting factors in the advisories. Neither depends on the framework getting URL validation right, which — on this record — is the point.
  • Treat "redirects enabled" as a validation bypass by default. Any check performed before an HTTP request is void if the client follows redirects without re-checking. Audit your own tool-calling code for this exact shape; it is one line of default behaviour in most HTTP clients.
  • Watch advisory feeds, not CVE feeds, for agent tooling. The June advisory reached NVD in late September. Subscribe to GHSA and VulnCheck for the agent frameworks you run.

The uncomfortable summary: UTCP's maintainers have now fixed this SSRF class in the Python HTTP plugin twice, the TypeScript HTTP package once, and the GraphQL and WebSocket plugins once each. Each patch was correct for the path it inspected. The class survived because the validation lives at call sites rather than at a single chokepoint that every outbound request must pass through — and every new protocol plugin is a new call site. For anyone building tool-calling infrastructure, that is the design note worth taking: URL validation that must be remembered will eventually be forgotten.

Sources: