How I Found an SSRF in an AI SDK's OAuth Metadata Discovery A security researcher discovered a server-side request forgery vulnerability in an AI SDK's OAuth metadata discovery process. The SDK, which handles authentication for MCP servers, failed to validate URLs in the metadata discovery path, allowing an attacker to control the serverUrl and make the SDK fetch internal addresses. The researcher demonstrated the flaw by pointing the SDK at a local mock server, which received requests without validation. Some bugs announce themselves. You're reading through a codebase and the vulnerability practically waves at you from the screen. This was not one of those bugs. This one required me to ask a question that I almost didn't ask. And that question led down a rabbit hole that ended with a server-side request forgery in a fairly popular AI SDK. I've been doing security reviews of AI-related tooling lately. Partly because it's obviously where the industry is heading, and partly because the pace of development in that space means security often takes a back seat. New features ship fast. Validation logic gets copy-pasted. Corners get cut. The target this time was an SDK that handles authentication for something called MCP servers. If you're not familiar, MCP is a protocol that lets AI applications connect to external tools and data sources. Think of it as a standardized way for an AI assistant to talk to your calendar, your database, or your internal APIs. The auth flow I was looking at involves OAuth. The SDK needs to discover where the OAuth endpoints live, so it makes a request to a well-known URL on the server and parses the metadata from the response. Pretty standard. Here's where my curiosity kicked in. I was reading through the OAuth implementation when I noticed something that seemed off. There was a function that validated URLs before fetching them. It was doing the right thing: function assertSafeOAuthEndpoint endpointUrl: URL : void { // ... some checks ... validateDownloadUrl endpointUrl.href ; } Good. That's the pattern I want to see. Someone thought about SSRF here and added a guard. But then I kept scrolling. And I found another function that fetches URLs during the OAuth flow. It was responsible for discovering OAuth metadata—the part where the SDK asks the server "hey, where are your OAuth endpoints?" That function did not call validateDownloadUrl . I stared at it for a minute. Was that intentional? Did the metadata discovery path not need validation for some reason I wasn't seeing? I pulled up the call chain. The public auth API eventually calls discoverOAuthProtectedResourceMetadata , which calls discoverMetadataWithFallback , which calls tryMetadataDiscovery , which calls fetch . No validation anywhere in that chain. And here's the thing: the URL being fetched comes from the user. When a developer sets up their MCP connection, they provide a serverUrl . The SDK takes that URL and starts making HTTP requests to it to discover OAuth metadata. If an attacker can control that serverUrl —which is the entire threat model of "connecting to an untrusted MCP server"—then the SDK will happily fetch whatever URL you give it. Including internal addresses. I didn't trust my initial read. So I built a test. I wrote a small mock server that would listen on localhost and log any incoming requests. Then I pointed the SDK at it using its public API, passing in a serverUrl that should have been rejected if any validation existed. The result: my mock server received the requests. The SDK connected to 127.0.0.1 without hesitation. It sent the standard OAuth discovery requests to my local service, complete with headers identifying itself. That's when I realized this was actually a problem. The validateDownloadUrl function existed in the codebase. The developers clearly knew they needed URL validation for OAuth endpoints. But they'd only applied it to one part of the flow—the part where credentials are submitted—and missed the metadata discovery path entirely. Let me paint the picture. A developer builds an application using this AI SDK. They configure it to connect to an MCP server. Maybe that server URL comes from a configuration file, a database, or even from a user-supplied value in their app. It doesn't really matter how. What matters is that the SDK trusts that URL completely. An attacker who can influence that serverUrl can point it at: 169.254.169.254 — the AWS metadata service, which can leak IAM credentials 127.0.0.1:6379 — a local Redis instance 127.0.0.1:9200 — ElasticsearchThe SDK will send HTTP requests to those addresses from within whatever environment it's running in. If the application is deployed in a cloud environment with an attached IAM role, that's credential theft. If it's on a corporate network, that's internal reconnaissance. And the attacker doesn't need any special privileges. They don't need to authenticate. They just need to convince a developer to connect to a malicious MCP server, which is exactly the scenario the OAuth flow is supposed to handle safely. While I was digging into this, I found something else that made it worse. The fetch function used in the metadata discovery path had redirect: 'follow' enabled by default. That means even if someone added URL validation to the initial request, an attacker could potentially bypass it by having the initial URL redirect to an internal address. The SDK would follow the redirect and end up at the internal service anyway. This is a classic SSRF pattern: validate the first URL, forget that redirects can take you anywhere. The presence of redirect: 'follow' meant that fixing the missing validation on the initial URL wouldn't even be sufficient—the redirect behavior needed attention too. I reported the issue through the proper channels. The fix involved two parts: adding URL validation to the metadata discovery path, and addressing the redirect-follow behavior. Simple in concept. But the fact that both layers existed—missing validation on one path, and redirect-follow on another—meant that a naive fix would have left the vulnerability half-open. That's actually a lesson I keep learning in security research: the first bug you find is rarely the whole story. There's usually a second layer if you keep digging. Consistency in security controls is hard. The developers had a validation function. They used it in one place. They didn't use it in another place that needed it just as much. This happens all the time, especially in codebases that are growing quickly. The security control wasn't missing—it was just incomplete. Redirects are SSRF's best friend. If your URL validation doesn't account for redirects, it's not real validation. Fetch APIs follow redirects silently. An attacker who can control the response from a validated URL can redirect you anywhere. Untrusted server URLs are an SSRF waiting to happen. If your application fetches URLs that a user or external party can influence, you need to think carefully about where those requests might end up. This is especially true in cloud environments where internal addresses can expose sensitive credentials. Test your assumptions. I didn't just read the code and assume the missing validation was exploitable. I built a quick proof of concept that demonstrated the SDK making requests to a local service. If you're reporting a security issue, concrete evidence is worth a lot more than "I noticed something in the code." I can't share specifics about the SDK or the vendor's response—that's between me and them for now. But the pattern here is worth understanding because it's everywhere. SSRF via missing URL validation in OAuth flows. SSRF via redirect-follow. These aren't exotic bugs. They're the result of a simple oversight in a complex flow, hidden in the gap between two functions that were supposed to do the same thing but only one of them did. If you're building anything that involves fetching URLs based on user input, go look at every fetch call in your codebase right now. Not just the obvious ones. Every single one. Ask yourself: could this URL ever be influenced by someone other than the developer? If the answer is yes, you might have an SSRF. And if you're on the security research side, the next time you're reading through a codebase and something feels slightly off—a validation function used in one place but not another—don't just note it and move on. Dig in. That inconsistency might be the thread that unravels the whole thing.