Oct 2, 2026·6 min read·5 visits
The rmcp OAuth client blindly fetches absolute URLs from the resource_metadata parameter in the WWW-Authenticate header of an MCP server response. Attackers can exploit this to perform Server-Side Request Forgery (SSRF), targeting loopback networks, private IP spaces, or cloud metadata endpoints like 169.254.169.254.
A Server-Side Request Forgery (SSRF) vulnerability exists in the rmcp OAuth client, which is part of the Model Context Protocol (MCP) Rust SDK. The vulnerability arises from insecure processing of the resource_metadata parameter in WWW-Authenticate headers returned by a malicious or compromised MCP server. The client parses and fetches absolute URLs from this header without validation of scheme, origin, or network routing, allowing remote attackers to initiate HTTP GET requests to local network interfaces, RFC 1918 private subnets, or cloud metadata endpoints.
The Model Context Protocol (MCP) is an open-standard architecture designed to facilitate secure communication and context exchange between local clients, large language models, and external data services. Within the Rust ecosystem, the rmcp crate provides the key transport layer and OAuth client capabilities to handle server discovery and credential validation routines. This library exposes an authentication-level attack surface during the initial protocol-level handshake sequence.
When a client attempts to connect to an MCP server, it must negotiate access and validate any authentication parameters supplied by the remote endpoint. If the server requires authorization, it initiates an HTTP challenge by returning an HTTP 401 Unauthorized status. The response contains standard WWW-Authenticate header records containing parameters that direct the client to authorization endpoints.
This architecture exposes a trust-boundary vulnerability during the parsing of these challenge records. An attacker who controls or compromises an upstream MCP server can craft response headers to alter the target endpoint configuration. Because the client processes these parameters without validating the destination host, the server can coerce the client application into issuing arbitrary outbound HTTP queries.
The root cause of this vulnerability lies within the header extraction logic implemented in crates/rmcp/src/transport/auth.rs. When parsing the WWW-Authenticate challenge, the client extracts configured variables using the helper function extract_www_authenticate_params(). This function parses string slices to find matching tokens such as the resource_metadata= key value.
Upon isolating the value string, the parser attempts to convert the slice directly into a standard structured format using Url::parse(). If this conversion yields a valid absolute URI containing a scheme prefix such as http:// or https://, the library accepts it directly. The parsed URL is assigned to params.resource_metadata_url without checking if it shares the same host, port, or scheme as the connected MCP server.
After parameter assignment, the library initiates an automated network request to fetch the metadata resource. This execution path invokes the internal reqwest HTTP client inside the fetch_resource_metadata_from_url() function. The client dispatches an HTTP GET request to the parsed address without verifying whether the target resolves to restricted loopbacks, private networks, or internal metadata paths.
An inspection of the vulnerable implementation in crates/rmcp/src/transport/auth.rs shows that absolute URLs were stored directly without validating the target origin:
// Vulnerable Code Path (Pre-v2.0.0)
let resource_key = "resource_metadata=";
while let Some(pos) = header_lowercase[search_offset..].find(resource_key) {
let global_pos = search_offset + pos + resource_key.len();
let value_slice = &header[global_pos..];
if let Some((value, consumed)) = Self::parse_next_header_value(value_slice) {
// Unvalidated absolute URLs are accepted directly
if let Ok(url) = Url::parse(&value) {
params.resource_metadata_url = Some(url);
break;
}
// Base URL fallback is only used if absolute parsing fails
if let Ok(url) = base_url.join(&value) {
params.resource_metadata_url = Some(url);
break;
}
}
}To mitigate this vulnerability, the fix in version 2.0.0 introduces a strict origin-matching check. The updated parser implements is_same_origin() to ensure that candidate metadata URLs match the core scheme, host, and port of the trusted MCP base URL:
// Patched Same-Origin Check (v2.0.0)
fn is_same_origin(base: &Url, candidate: &Url) -> bool {
base.scheme() == candidate.scheme()
&& base
.host_str()
.zip(candidate.host_str())
.is_some_and(|(base, candidate)| base.eq_ignore_ascii_case(candidate))
&& base.port_or_known_default() == candidate.port_or_known_default()
}
// Applying the protection during extraction
if let Ok(url) = Url::parse(&value) {
if Self::is_same_origin(base_url, &url) {
params.resource_metadata_url = Some(url);
break;
} else {
warn!("rejecting resource metadata URL because it is not same-origin");
}
}Additionally, the patch implements network boundary controls, including private network filters (is_disallowed_metadata_ipv4/ipv6), hostname blocklists for cloud metadata providers, and manual redirect handling. The updated code disables automatic redirects in the HTTP engine by using OAuthHttpRedirectPolicy::Stop and manually verifies that subsequent redirections remain within the origin boundaries.
To execute this attack, an adversary must establish a malicious MCP server and convince a vulnerable client application to connect to it. Alternatively, an attacker can compromise a legitimate server and modify its challenge headers. Once the client initiates a connection, the malicious server issues a crafted HTTP 401 Unauthorized challenge.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="http://169.254.169.254/latest/meta-data/"The client extracts the resource metadata target from the WWW-Authenticate header, parsing it as an absolute URL pointing to the link-local IP of the cloud environment. The client then initiates an outbound HTTP GET query to the target IP, passing the protocol version header.
In environments like AWS, GCP, or Azure, this GET query targets internal systems or local services. Depending on how the application handles the response, the attacker can leverage the request flow to map the internal network infrastructure or retrieve instance credentials.
Although the primary vulnerability in resource_metadata_url is mitigated, the cross-origin authorization discovery pathway in version 2.0.0 remains vulnerable to a DNS resolution bypass. The patched library implements the function is_disallowed_metadata_host() to evaluate if cross-origin endpoints point to restricted addresses.
This logic evaluates host strings against a static blocklist of names like localhost or metadata. If the host string is a domain name rather than a raw IP, it bypasses the IP range check function, which returns false and allows the URL configuration to pass.
match host.parse::<IpAddr>() {
Ok(IpAddr::V4(addr)) => Self::is_disallowed_metadata_ipv4(addr),
Ok(IpAddr::V6(addr)) => Self::is_disallowed_metadata_ipv6(addr),
Err(_) => false, // Default fallback allows the domain name configuration
}This pattern allows attackers to bypass controls using DNS pointer redirection. An attacker can map a custom public domain name to a loopback address or a private network address. Because the client library evaluates the host string before DNS resolution occurs, it accepts the configuration, allowing the HTTP client to resolve and access the private target during request dispatch.
The primary remediation for this vulnerability is to upgrade the rmcp dependency to version 2.0.0 or newer within the Rust Cargo.toml manifest file.
[dependencies]
rmcp = "2.0.0"Security teams can identify vulnerable instances within their code repositories by executing the cargo audit command, which reviews the dependency tree against the advisory database. Organizations can also use security scanning rules to verify if application clients remain vulnerable to absolute URL challenges:
id: rmcp-oauth-ssrf-detection
info:
name: Model Context Protocol rmcp Client SSRF Detection
author: Security Research Team
severity: medium
classification:
cwe-id: CWE-918
http:
- raw:
- |
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", resource_metadata="http://{{interactsh-url}}/.well-known/oauth-protected-resource"
matchers:
- type: word
part: interactsh_protocol
words:
- "http"To counter DNS bypasses, network administrators should implement system-level egress firewall rules. Blocking traffic from the client container to AWS metadata ranges and RFC 1918 subnets prevents exploitation regardless of domain mapping techniques.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
rmcp modelcontextprotocol | < 2.0.0 | 2.0.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-918 |
| Attack Vector | Network |
| CVSS Score | 6.3 |
| Exploit Status | poc |
| CISA KEV Status | Not Listed |
| Impact | Information Disclosure / Server-Side Request Forgery |
The web server receives a URL or similar request from an upstream server or client and retrieves the resource without sufficiently validating the destination, allowing the attacker to coerce the server into sending requests to unintended locations.
A critical Broken Object Level Authorization (BOLA) vulnerability was identified in Trigger.dev before version v4.5.2. An authenticated attacker could trigger a run replay and supply an arbitrary target environmentId belonging to a completely different tenant. Because the server failed to validate whether the target environment belonged to the same project or organization as the source run, it would execute the task within the victim's environment, resulting in unauthorized cross-tenant write operations and remote task execution.
A critical Server-Side Request Forgery (SSRF) vulnerability in Trigger.dev prior to version 4.5.2 allows authenticated organization members to configure webhook alert channels with unvalidated target URLs. This can lead to internal network scanning and cloud metadata extraction.
A module allowlist bypass vulnerability (CVE-2026-92945 / GHSA-7q3f-wx44-378m) was identified in the vm2 sandboxing library prior to version 3.11.7. This flaw permits unauthenticated or untrusted code running within the sandbox environment to bypass explicit module restrictions. When the transitive resolution option is disabled, the system fails to validate file system path boundaries, allowing prefix-sharing sibling directories to be resolved and loaded, thereby escaping intended sandbox restrictions.
CVE-2026-92941 is a critical sandbox-escape and trust-manipulation vulnerability in the vm2 library (versions 3.11.3 to 3.11.6). This security flaw allows untrusted code executing within a NodeVM sandbox environment to compromise the global TLS trust store of the host Node.js process. By leveraging a design flaw where the host's native 'tls.setDefaultCACertificates' can be executed via a proxy wrapper, combined with a bridge unwrapping bypass in the 'url' module, an attacker can modify the process-wide default root Certificate Authorities. Consequently, all subsequent outbound TLS/HTTPS clients running on the host thread are forced to trust attacker-signed certificates, facilitating transparent Man-in-the-Middle (MitM) attacks. The vulnerability was resolved in version 3.11.7 of vm2 by introducing built-in member-level sanitization before applying read-only proxy wrappers.
A critical engine-level reachability failure in Node.js 26 running V8 14.6 allows attackers to escape the vm2 sandbox environment. When consecutive prototype properties are modified using sequential assignments, a V8 optimization bug fails to invalidate the PromiseThenLookupChain protector. By calling Promise.prototype.finally, the attacker bypasses the vm2 wrappers, hijacks the promise reaction using a custom constructor, triggers a calibrated stack overflow to capture a host-realm RangeError, and executes arbitrary shell commands on the host.
A critical sandbox escape vulnerability in the vm2 library allows sandboxed JavaScript code to bypass containment and execute arbitrary native code on the host process. This occurs when the host's builtin crypto module is exposed to the NodeVM environment. Although vm2 implements a read-only proxy layer to restrict direct modifications to host properties, it does not prevent invocation of host-level functions. By calling the crypto.setEngine API with a path to a malicious native library on disk, an attacker can trigger OpenSSL's dynamic module loader. The host's operating system loader immediately runs the dynamic library's initializers/constructors before verifying engine compatibility, leading to remote code execution in the context of the host process.