Metadata discovery turns an MCP client into an unauthenticated fetch primitive
A researcher found the SSRF guard sitting on the credential-submission side of an MCP auth SDK while the .well-known discovery fetch ran unchecked from the public entry point, which is the wrong end of the flow to protect.
The Engineer · Build desk

What happened
- A researcher reviewing AI tooling reported a server-side request forgery in the OAuth metadata discovery path of an SDK that handles authentication for MCP servers.
- The same codebase already carried a URL guard, assertSafeOAuthEndpoint delegating to validateDownloadUrl, but it had been applied only where credentials are submitted.
- From the public auth() API down through discoverOAuthProtectedResourceMetadata, discoverMetadataWithFallback and tryMetadataDiscovery to fetch, the researcher found no validation at any step.
Why it matters
- What it limits constraint Any client that resolves a remote name before authentication finishes has to be treated as an unauthenticated fetch primitive, which pushes the real control to network egress policy rather than a library helper.
- Who has to decide decision Teams whose MCP server URLs arrive from config, a database row or an end user now have to decide whether those connections egress through a resolving proxy, because the SDK's own guard does not sit on the discovery path.
- Who's exposed exposure The blast radius is set by where the agent process runs, not by what the agent was asked to do, so an app with an attached IAM role inherits the risk of every URL it was ever configured to discover.
- What doesn't add up contradiction The demonstration ends at a request arriving on localhost while the impact section reaches instance-metadata credential theft, and that gap is the difference between a blind forgery and an exfiltration you have to disclose.
Discovery runs before trust exists. That is what makes this a structural bug rather than a careless one. An OAuth client has to ask a server where its endpoints live before any token exists and before any issuer has been verified, so the only authority behind that first request is the network position of the process making it. The guard this SDK does have was applied to the part of the flow where credentials are submitted [7]. By then the requests have already gone out.
The chain is short enough to read in one sitting: `auth()` calls `discoverOAuthProtectedResourceMetadata()`, which calls `discoverMetadataWithFallback()`, which calls `tryMetadataDiscovery()`, which calls `fetch()` [4]. Four call boundaries, none of them validating [13]. The check that exists elsewhere is `assertSafeOAuthEndpoint()`, which delegates to `validateDownloadUrl()` [2]. That name is the interesting part: a URL guard written for downloads and later borrowed for OAuth endpoints. Borrowed validation gets applied where it was pasted, not where the data flows, which is the researcher's stated reason for reviewing this class of tooling in the first place [12].
On evidence strength, be precise about what was shown. A mock server on localhost received the SDK's standard OAuth discovery requests, headers and all, after the SDK was pointed at it through its public API [6]. That demonstrates request delivery to `127.0.0.1`. The writeup's other targets, the AWS metadata service at `169.254.169.254`, Redis on 6379, Elasticsearch on 9200 [8], and the credential-theft outcome it describes for instances with an attached IAM role [15], are consequences argued from that primitive rather than captured artifacts [14]. For instance metadata to become stolen credentials, the response body or an error carrying it has to be observable by whoever chose the URL, and that step is not in the writeup [14]. Blind SSRF is still a forged request originating inside your network; the distinction only changes which severity you file it under.
Three things have to hold for the finding to transfer to your deployment. The `serverUrl` has to be influenced by data you do not control, which the writeup notes can be a config file, a database row, or a user-supplied value [11]. The process has to sit somewhere with internal services worth reaching [15]. And nothing between the process and those services can be filtering egress. If your MCP client only ever dials a hardcoded host through a proxy that resolves and allowlists, this is a library defect and not your incident.
The awkward part is that the writeup does not name the SDK, describing it only as a fairly popular AI SDK for MCP authentication [10], so there is no version to diff and no advisory to read. The portable check is a shape rather than a version number: a public API that accepts a URL, a `fetch()` reachable from it, and a guard applied at some of the call sites downstream. The attacker in this model needs no credentials and no privileges [9], which means the interesting question about any agent framework you run is not whether it validates URLs, but whether it validates them at every point where a remote name turns into a socket.
What to watch
- Whether the SDK gets named and a patched version published, since the writeup supplies neither.
- Whether the fix lands as a guard at every fetch site or a single wrapper around the whole discovery chain.
- Whether MCP client guidance starts requiring pre-auth discovery fetches to pass an allowlist before the socket opens.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence45
- Adoption
- Not scored
- Hype gap+28
- Incentives55
- Confidence42
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
A researcher doing security reviews of AI-related tooling found a server-side request forgery in the OAuth metadata discovery path of an SDK that handles authentication for MCP servers.
- [2]
The SDK codebase contains a function assertSafeOAuthEndpoint(endpointUrl: URL) that calls validateDownloadUrl(endpointUrl.href).
- [3]
A separate function responsible for discovering OAuth metadata also fetches URLs during the OAuth flow, and it does not call validateDownloadUrl.
- [4]
The call chain runs from the public auth() API to discoverOAuthProtectedResourceMetadata(), then discoverMetadataWithFallback(), then tryMetadataDiscovery(), then fetch(), with no validation anywhere in that chain.
- [5]
The URL that gets fetched comes from the serverUrl the developer supplies when setting up the MCP connection, and the SDK begins making HTTP requests to it to discover OAuth metadata.
- [6]
The researcher wrote a mock server listening on localhost, pointed the SDK at it through the public API with a serverUrl that validation should have rejected, and the mock server received the standard OAuth discovery requests; the SDK connected to 127.0.0.1 with headers identifying itself.
- [7]
The developers had applied URL validation only to the part of the flow where credentials are submitted, and missed the metadata discovery path entirely.
- [8]
The writeup lists reachable targets for an attacker-influenced serverUrl: 169.254.169.254, the AWS metadata service that can leak IAM credentials; 127.0.0.1:6379, a local Redis instance; 127.0.0.1:9200, Elasticsearch; and any other internal service on the network.
- [9]
The writeup states the attacker needs no special privileges and does not need to authenticate.
- [10]
The writeup does not name the SDK, describing it only as a fairly popular AI SDK that handles authentication for MCP servers, MCP being a protocol that lets AI applications connect to external tools and data sources.
- [11]
The writeup notes the server URL may come from a configuration file, a database, or a user-supplied value in the developer's application, and says which one does not matter because the SDK trusts the URL completely.
- [12]
The researcher says the pace of development in AI tooling means security takes a back seat, features ship fast, validation logic gets copy-pasted and corners get cut.
- [13]
Four function-call boundaries separate the public auth() entry point from the raw fetch(), and none of them validates the URL.
- [14]
The strongest demonstrated artifact in the writeup is inbound request receipt at a localhost mock server; no retrieved instance-metadata response or captured credential is shown, so credential theft is asserted as a consequence rather than demonstrated.
- [15]
According to the writeup, the SDK sends HTTP requests to those addresses from within whatever environment it is running in, which is credential theft where a cloud deployment has an attached IAM role and internal reconnaissance on a corporate network.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toHow I Found an SSRF in an AI SDK's OAuth Metadata Discovery
1 article · August 29, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.
Topics
- Server-Side Request ForgeryFollow
- Cloud Metadata ExposureFollow
- OAuth Metadata DiscoveryFollow
- Model Context Protocol SecurityFollow