CVE-2026-12210
universal-tool-calling-protocol python-utcp utcp-gql/utcp-websocket server-side request forgery
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
5.3
universal-tool-calling-protocol python-utcp utcp-gql/utcp-websocket server-side request forgery
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
- Record
CVE-2026-12210- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L/E:P/RL:X/RC:R - CVSS 3.0
- 6.3 Medium
CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L/E:P/RL:X/RC:R - CVSS 2.0
- 6.5
AV:N/AC:L/Au:S/C:P/I:P/A:P/E:POC/RL:ND/RC:UR - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-36684
5.3
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
- Record
EUVD-2026-36684- CVSS 4.0
- 5.3 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-ppx3-28rw-8fpf
4.7
utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Summary
The fix for CVE-2026-44661 (commit 5b16e43) added the ensure_secure_url() / is_secure_url() helpers and wired them into the three HTTP-family plugins, but it did not reach the GraphQL or WebSocket plugins. The GraphQL plugin (utcp-gql) still uses the startswith prefix check that the fix explicitly replaced, so http://127.0.0.1.attacker.example and http://localhost.evil.com pass it. The WebSocket plugin (utcp-websocket) performs no URL validation at all, even though its own docstrings state it enforces "WSS or localhost only." Both plugins reach the same SSRF that CVE-2026-44661 was filed for, and because both attach the call template's configured auth headers to the outbound connection, the SSRF can also leak API keys and OAuth tokens to an attacker-controlled host.
Details
In the CVE-2026-44661 fix (commit 5b16e43 ("fix(http): block SSRF via attacker-controlled OpenAPI servers[0].url")), two things in it pointed at sibling issues. The commit message says the change is "replacing the duplicated prefix check", and the new utcp_http/_security.py docstring names the exact bug:
URLs whose hostname starts with
localhost/127.0.0.1but isn't actually loopback (e.g.http://localhost.evil.com,http://127.0.0.1.attacker.example). The earlierstartswithcheck let these through.
The word "duplicated" says the vulnerable check existed in more than one place. The fix only updated the three HTTP-family plugins (http, streamable_http, sse). The other communication-protocol plugins were also inspected.
GraphQL plugin (utcp-gql). plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py still has the pre-fix check at line 43:
def _enforce_https_or_localhost(self, url: str) -> None:
if not (
url.startswith("https://")
or url.startswith("http://localhost")
or url.startswith("http://127.0.0.1")
):
raise ValueError("Security error: URL must use HTTPS or start with ...")
It is called on manual_call_template.url in register_manual (line 102) and on tool_call_template.url in call_tool (line 181). The URL then goes into AIOHTTPTransport(url=...) and a live GraphQL request.
"http://127.0.0.1.attacker.example/graphql".startswith("http://127.0.0.1") is True, so the check passes. If the attacker controls DNS for attacker.example, that hostname resolves to any address they choose, including 169.254.169.254, 127.0.0.1, or an internal 192.168.x.x host, and the GraphQL client sends a plain-HTTP request there. http://localhost.evil.com/graphql behaves the same way. This is the exact prefix bypass CVE-2026-44661 was filed for.
WebSocket plugin (utcp-websocket). plugins/communication_protocols/websocket/src/utcp_websocket/websocket_communication_protocol.py. The module and class docstrings state:
"Security enforcement (WSS or localhost only)" "Enforces security by requiring WSS or localhost connections" "Security validation of connection URLs"
There is no such validation in the code. _get_connection(), the only connection path (used by register_manual, call_tool, and call_tool_streaming), calls:
ws = await session.ws_connect(call_template.url, headers=headers, ...) # line 197
with no scheme or host check. Any URL in a WebSocketCallTemplate connects, including ws://169.254.169.254/, ws://127.0.0.1:<internal-port>/, or any internal hostname.
Credential exposure. Both plugins build connection headers in _prepare_headers(), which attaches the configured auth: ApiKeyAuth as a header, BasicAuth as an Authorization: Basic header, and OAuth2Auth as an Authorization: Bearer token. When the bypass is used to force a plain-HTTP or plain-WS connection to an attacker-resolved host, those credentials are sent to the attacker.
This is the threat model CVE-2026-44661 already established: a UTCP client ingests tool manuals, and a malicious…
- Record
GHSA-ppx3-28rw-8fpf- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-vg9f-q4xh-62r4
2.1
Duplicate Advisory: utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-ppx3-28rw-8fpf. This link is maintained to preserve external references.
Original Description
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
- Record
GHSA-vg9f-q4xh-62r4- CVSS 4.0
- 2.1 Low
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
2.1
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
- Record
CVE-2026-12210- CVSS 4.0
- 2.1 Low
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - CVSS 2.0
- 6.5 Medium
AV:N/AC:L/Au:S/C:P/I:P/A:P - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-ppx3-28rw-8fpf
4.7
utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Summary
The fix for CVE-2026-44661 (commit 5b16e43) added the ensure_secure_url() / is_secure_url() helpers and wired them into the three HTTP-family plugins, but it did not reach the GraphQL or WebSocket plugins. The GraphQL plugin (utcp-gql) still uses the startswith prefix check that the fix explicitly replaced, so http://127.0.0.1.attacker.example and http://localhost.evil.com pass it. The WebSocket plugin (utcp-websocket) performs no URL validation at all, even though its own docstrings state it enforces "WSS or localhost only." Both plugins reach the same SSRF that CVE-2026-44661 was filed for, and because both attach the call template's configured auth headers to the outbound connection, the SSRF can also leak API keys and OAuth tokens to an attacker-controlled host.
Details
In the CVE-2026-44661 fix (commit 5b16e43 ("fix(http): block SSRF via attacker-controlled OpenAPI servers[0].url")), two things in it pointed at sibling issues. The commit message says the change is "replacing the duplicated prefix check", and the new utcp_http/_security.py docstring names the exact bug:
URLs whose hostname starts with
localhost/127.0.0.1but isn't actually loopback (e.g.http://localhost.evil.com,http://127.0.0.1.attacker.example). The earlierstartswithcheck let these through.
The word "duplicated" says the vulnerable check existed in more than one place. The fix only updated the three HTTP-family plugins (http, streamable_http, sse). The other communication-protocol plugins were also inspected.
GraphQL plugin (utcp-gql). plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py still has the pre-fix check at line 43:
def _enforce_https_or_localhost(self, url: str) -> None:
if not (
url.startswith("https://")
or url.startswith("http://localhost")
or url.startswith("http://127.0.0.1")
):
raise ValueError("Security error: URL must use HTTPS or start with ...")
It is called on manual_call_template.url in register_manual (line 102) and on tool_call_template.url in call_tool (line 181). The URL then goes into AIOHTTPTransport(url=...) and a live GraphQL request.
"http://127.0.0.1.attacker.example/graphql".startswith("http://127.0.0.1") is True, so the check passes. If the attacker controls DNS for attacker.example, that hostname resolves to any address they choose, including 169.254.169.254, 127.0.0.1, or an internal 192.168.x.x host, and the GraphQL client sends a plain-HTTP request there. http://localhost.evil.com/graphql behaves the same way. This is the exact prefix bypass CVE-2026-44661 was filed for.
WebSocket plugin (utcp-websocket). plugins/communication_protocols/websocket/src/utcp_websocket/websocket_communication_protocol.py. The module and class docstrings state:
"Security enforcement (WSS or localhost only)" "Enforces security by requiring WSS or localhost connections" "Security validation of connection URLs"
There is no such validation in the code. _get_connection(), the only connection path (used by register_manual, call_tool, and call_tool_streaming), calls:
ws = await session.ws_connect(call_template.url, headers=headers, ...) # line 197
with no scheme or host check. Any URL in a WebSocketCallTemplate connects, including ws://169.254.169.254/, ws://127.0.0.1:<internal-port>/, or any internal hostname.
Credential exposure. Both plugins build connection headers in _prepare_headers(), which attaches the configured auth: ApiKeyAuth as a header, BasicAuth as an Authorization: Basic header, and OAuth2Auth as an Authorization: Bearer token. When the bypass is used to force a plain-HTTP or plain-WS connection to an attacker-resolved host, those credentials are sent to the attacker.
This is the threat model CVE-2026-44661 already established: a UTCP client ingests tool manuals, and a malicious…
- Record
GHSA-ppx3-28rw-8fpf- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-vg9f-q4xh-62r4
2.1
Duplicate Advisory: utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-ppx3-28rw-8fpf. This link is maintained to preserve external references.
Original Description
A vulnerability was detected in universal-tool-calling-protocol python-utcp 1.1.0. This affects an unknown function of the component utcp-gql/utcp-websocket. Performing a manipulation results in server-side request forgery. The attack can be initiated remotely. The exploit is now public and may be used. The vendor was contacted early about this disclosure but did not respond in any way.
- Record
GHSA-vg9f-q4xh-62r4- CVSS 4.0
- 2.1 Low
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 6.3 Medium
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVPYSEC-2026-3931
4.7
utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Summary
The fix for CVE-2026-44661 (commit 5b16e43) added the ensure_secure_url() / is_secure_url() helpers and wired them into the three HTTP-family plugins, but it did not reach the GraphQL or WebSocket plugins. The GraphQL plugin (utcp-gql) still uses the startswith prefix check that the fix explicitly replaced, so http://127.0.0.1.attacker.example and http://localhost.evil.com pass it. The WebSocket plugin (utcp-websocket) performs no URL validation at all, even though its own docstrings state it enforces "WSS or localhost only." Both plugins reach the same SSRF that CVE-2026-44661 was filed for, and because both attach the call template's configured auth headers to the outbound connection, the SSRF can also leak API keys and OAuth tokens to an attacker-controlled host.
Details
In the CVE-2026-44661 fix (commit 5b16e43 ("fix(http): block SSRF via attacker-controlled OpenAPI servers[0].url")), two things in it pointed at sibling issues. The commit message says the change is "replacing the duplicated prefix check", and the new utcp_http/_security.py docstring names the exact bug:
URLs whose hostname starts with
localhost/127.0.0.1but isn't actually loopback (e.g.http://localhost.evil.com,http://127.0.0.1.attacker.example). The earlierstartswithcheck let these through.
The word "duplicated" says the vulnerable check existed in more than one place. The fix only updated the three HTTP-family plugins (http, streamable_http, sse). The other communication-protocol plugins were also inspected.
GraphQL plugin (utcp-gql). plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py still has the pre-fix check at line 43:
def _enforce_https_or_localhost(self, url: str) -> None:
if not (
url.startswith("https://")
or url.startswith("http://localhost")
or url.startswith("http://127.0.0.1")
):
raise ValueError("Security error: URL must use HTTPS or start with ...")
It is called on manual_call_template.url in register_manual (line 102) and on tool_call_template.url in call_tool (line 181). The URL then goes into AIOHTTPTransport(url=...) and a live GraphQL request.
"http://127.0.0.1.attacker.example/graphql".startswith("http://127.0.0.1") is True, so the check passes. If the attacker controls DNS for attacker.example, that hostname resolves to any address they choose, including 169.254.169.254, 127.0.0.1, or an internal 192.168.x.x host, and the GraphQL client sends a plain-HTTP request there. http://localhost.evil.com/graphql behaves the same way. This is the exact prefix bypass CVE-2026-44661 was filed for.
WebSocket plugin (utcp-websocket). plugins/communication_protocols/websocket/src/utcp_websocket/websocket_communication_protocol.py. The module and class docstrings state:
"Security enforcement (WSS or localhost only)" "Enforces security by requiring WSS or localhost connections" "Security validation of connection URLs"
There is no such validation in the code. _get_connection(), the only connection path (used by register_manual, call_tool, and call_tool_streaming), calls:
ws = await session.ws_connect(call_template.url, headers=headers, ...) # line 197
with no scheme or host check. Any URL in a WebSocketCallTemplate connects, including ws://169.254.169.254/, ws://127.0.0.1:<internal-port>/, or any internal hostname.
Credential exposure. Both plugins build connection headers in _prepare_headers(), which attaches the configured auth: ApiKeyAuth as a header, BasicAuth as an Authorization: Basic header, and OAuth2Auth as an Authorization: Bearer token. When the bypass is used to force a plain-HTTP or plain-WS connection to an attacker-resolved host, those credentials are sent to the attacker.
This is the threat model CVE-2026-44661 already established: a UTCP client ingests tool manuals, and a malicious…
- Record
PYSEC-2026-3931- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
OSVPYSEC-2026-3932
4.7
utcp-gql SSRF: CVE-2026-44661 fix not applied to the GraphQL and WebSocket plugins
Summary
The fix for CVE-2026-44661 (commit 5b16e43) added the ensure_secure_url() / is_secure_url() helpers and wired them into the three HTTP-family plugins, but it did not reach the GraphQL or WebSocket plugins. The GraphQL plugin (utcp-gql) still uses the startswith prefix check that the fix explicitly replaced, so http://127.0.0.1.attacker.example and http://localhost.evil.com pass it. The WebSocket plugin (utcp-websocket) performs no URL validation at all, even though its own docstrings state it enforces "WSS or localhost only." Both plugins reach the same SSRF that CVE-2026-44661 was filed for, and because both attach the call template's configured auth headers to the outbound connection, the SSRF can also leak API keys and OAuth tokens to an attacker-controlled host.
Details
In the CVE-2026-44661 fix (commit 5b16e43 ("fix(http): block SSRF via attacker-controlled OpenAPI servers[0].url")), two things in it pointed at sibling issues. The commit message says the change is "replacing the duplicated prefix check", and the new utcp_http/_security.py docstring names the exact bug:
URLs whose hostname starts with
localhost/127.0.0.1but isn't actually loopback (e.g.http://localhost.evil.com,http://127.0.0.1.attacker.example). The earlierstartswithcheck let these through.
The word "duplicated" says the vulnerable check existed in more than one place. The fix only updated the three HTTP-family plugins (http, streamable_http, sse). The other communication-protocol plugins were also inspected.
GraphQL plugin (utcp-gql). plugins/communication_protocols/gql/src/utcp_gql/gql_communication_protocol.py still has the pre-fix check at line 43:
def _enforce_https_or_localhost(self, url: str) -> None:
if not (
url.startswith("https://")
or url.startswith("http://localhost")
or url.startswith("http://127.0.0.1")
):
raise ValueError("Security error: URL must use HTTPS or start with ...")
It is called on manual_call_template.url in register_manual (line 102) and on tool_call_template.url in call_tool (line 181). The URL then goes into AIOHTTPTransport(url=...) and a live GraphQL request.
"http://127.0.0.1.attacker.example/graphql".startswith("http://127.0.0.1") is True, so the check passes. If the attacker controls DNS for attacker.example, that hostname resolves to any address they choose, including 169.254.169.254, 127.0.0.1, or an internal 192.168.x.x host, and the GraphQL client sends a plain-HTTP request there. http://localhost.evil.com/graphql behaves the same way. This is the exact prefix bypass CVE-2026-44661 was filed for.
WebSocket plugin (utcp-websocket). plugins/communication_protocols/websocket/src/utcp_websocket/websocket_communication_protocol.py. The module and class docstrings state:
"Security enforcement (WSS or localhost only)" "Enforces security by requiring WSS or localhost connections" "Security validation of connection URLs"
There is no such validation in the code. _get_connection(), the only connection path (used by register_manual, call_tool, and call_tool_streaming), calls:
ws = await session.ws_connect(call_template.url, headers=headers, ...) # line 197
with no scheme or host check. Any URL in a WebSocketCallTemplate connects, including ws://169.254.169.254/, ws://127.0.0.1:<internal-port>/, or any internal hostname.
Credential exposure. Both plugins build connection headers in _prepare_headers(), which attaches the configured auth: ApiKeyAuth as a header, BasicAuth as an Authorization: Basic header, and OAuth2Auth as an Authorization: Bearer token. When the bypass is used to force a plain-HTTP or plain-WS connection to an attacker-resolved host, those credentials are sent to the attacker.
This is the threat model CVE-2026-44661 already established: a UTCP client ingests tool manuals, and a malicious…
- Record
PYSEC-2026-3932- CVSS 3.1
- 4.7 Medium
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
utcp-gqlPyPI |
through 1.1.0 | 1.1.1 |
utcp-websocketPyPI |
through 1.1.0 | 1.1.1 |
utcp-gqlPyPI |
before 1.1.1, plus listed versions 1.0.21.1.0 |
1.1.1 |
utcp-websocketPyPI |
before 1.1.1, plus listed versions 1.0.01.1.0 |
1.1.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-12210 is a medium vulnerability (CVSS 5.3) in Universal-tool-calling-protocol Python-utcp, published June 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.23%, the 13th percentile. Fixed in utcp-gql 1.1.1.
References
vuldb.com/vuln/370852
vuldb.com/vuln/370852/cti
vuldb.com/cve/CVE-2026-12210
vuldb.com/submit/832542
github.com/universal-tool-calling-protocol/python-utcp/issues/86
github.com/gola-leya/cve_submit/issues/1
Show 8 moreShow less
github.com/universal-tool-calling-protocol/python-utcp
github.com/universal-tool-calling-protocol/python-utcp/security/advisories/GHSA-ppx3-28rw-8fpf
github.com/universal-tool-calling-protocol/python-utcp/commit/fc3268e2a62e1181f91a63faf0a9bcee7639db29
nvd.nist.gov/vuln/detail/CVE-2026-12210
github.com/advisories/GHSA-ppx3-28rw-8fpf
github.com/advisories/GHSA-vg9f-q4xh-62r4
pypi.org/project/utcp-gql
pypi.org/project/utcp-websocket
Left out here: the rest of 4 source descriptions. The JSON record has them.