Skip to content

SSRF and Agentic AI Gone Wrong: The Login Handshake Was Never the Problem

7 min read Fullmakt Team

  • ssrf
  • agents
  • authentication
  • credentials
  • governance
  • traceability

An AI agent authenticates cleanly, holds a properly scoped credential, and makes exactly the API call it was asked to make. Nothing about the request looks wrong — until you look at where it actually connects. A URL that was supposed to point at a public webhook resolves, at the moment the socket opens, to 169.254.169.254 — the cloud metadata endpoint that hands out your instance’s own IAM credentials to anything that asks. The agent didn’t get hacked and it didn’t lie about its identity. It just followed a destination nobody checked. That’s server-side request forgery (SSRF), and it’s one of the more overlooked ways “agentic actions” becomes “agentic actions gone wrong.”

Why agents make SSRF worse, not just present

SSRF isn’t new — it’s a decade-old class of bug in any server that fetches a user-supplied URL on someone else’s behalf. What’s new is how often AI agents now sit in that exact position by design. An agent that’s allowed to call webhooks, fetch a page to summarize, hit an API endpoint named in a config variable, or follow a link found in a document it was processing is, structurally, a URL-fetching proxy with a model deciding the URL. Every one of those inputs — a tool argument, a variable value, a link inside untrusted content the agent just read — is a place an attacker (or just a misconfiguration) can substitute an internal address for the one the agent’s task actually needed.

The blast radius is also bigger than the classic case. A traditional SSRF bug exposes one endpoint. An agent with SSRF exposure sits behind a credential — often several, resolved per call — so a redirected request doesn’t just read whatever the internal service returns. It can hand that response back to the model as tool output, which the agent then acts on, chaining a network bug into the same kind of unvetted-content problem we’ve written about with MCP tool poisoning: the agent now has “instructions” from an internal service nobody meant it to reach.

Why the login handshake doesn’t catch this

This is the part that trips teams up: SSRF isn’t an authentication failure, and no amount of hardening the handshake fixes it. OAuth, MCP’s connection auth, and A2A’s agent-to-agent handshake all answer is this agent who it claims to be. SSRF happens entirely on the other side of that question, inside a call the agent was fully authorized to make, to a destination the handshake never inspects. A perfectly authenticated agent with a perfectly scoped credential can still SSRF itself, because “scoped to this task” describes what data the credential can touch, not which network address the underlying HTTP call is allowed to reach.

It’s also not a check you can do once, up front, and call finished. A URL can look public when it’s first validated and still resolve somewhere else by the time the connection actually opens — DNS TTLs are cheap, and an attacker who controls a domain can flip its answer between “looks safe” and “127.0.0.1” in seconds. A guard that only parses the URL string or does a pre-flight DNS lookup misses exactly that case. It has to hold at the moment the socket connects, and it has to hold again on every redirect a response sends back, because a 302 from an otherwise-fine host is a completely ordinary way to end up somewhere internal.

Where the guard actually has to live

That puts the fix at a specific, narrow layer: not the model, not the prompt, not the login flow — the outbound connection itself, checked at connect time against the resolved IP, not the hostname string. The address ranges that matter are the predictable ones: loopback, the RFC 1918 private ranges, the link-local block that includes every major cloud’s metadata address, and carrier-grade NAT and multicast ranges on top. None of that is exotic; it’s the same list every cloud provider’s own SSRF guidance publishes. The part that’s easy to get wrong is when it’s enforced — string-match the hostname early and an attacker just needs a domain that resolves differently later; enforce it at the TCP connection, re-checked on every hop, and the timing trick stops working.

The business case: the guard sits where the call actually happens

Fullmakt brokers every outbound call an agent makes — resolving the credential, evaluating policy, and proxying the request — which puts it in the one place that sees the real destination IP at the moment the connection opens, not just the URL string an agent or a tool argument handed it. That’s the same chokepoint argument we’ve made about scoped credentials and tamper-proof audit logging: a broker already sitting between every agent and every API doesn’t need new infrastructure to add a control, it needs to use the vantage point it already has.

  • Connect-time enforcement, not URL parsing. The destination is resolved and validated as an IP address right before the socket opens, so a hostname that looks public at review time but resolves internally later is still blocked — closing the DNS-rebinding gap that a one-time URL check leaves open.
  • Every redirect hop re-checked. A 302 to an internal address doesn’t get a pass because the original host was fine; the same guard runs again for each hop on the same connection.
  • Policy and credential scope, evaluated separately from network reach. Whether a call is allowed for this agent and whether its destination is safe to connect to are two different checks, enforced independently, so narrowing one doesn’t quietly substitute for the other.
  • A traceable record either way. Whether a call is denied by policy or refused by the network guard, it’s logged with who, what, and why — an SSRF attempt shows up as an auditable event, not a silent connection that never happened.
  • One control point, every protocol. The same guard covers requests made through MCP tool calls and A2A-triggered actions alike, so adding a new integration doesn’t mean re-implementing network-layer safety for it.

FAQ

What is SSRF in the context of AI agents? Server-side request forgery is when a server — here, the broker making a call on an agent’s behalf — is tricked into connecting to a destination its operator never intended, often an internal service or a cloud metadata endpoint, because the target URL came from a tool argument, a variable, or content the agent processed rather than from a trusted source.

Doesn’t OAuth or the A2A handshake already prevent this? No. Those prove the agent’s identity at connection time. SSRF happens inside a call the agent was already authorized to make — the handshake has no opinion on which network address that call actually reaches.

Why isn’t checking the URL once, up front, enough? A hostname can resolve to a public address during an early check and to an internal one by the time the connection opens — a DNS TTL is enough time for that to change. The check has to run at the moment the socket connects, using the resolved IP, and again on every redirect hop.

Is this the same problem as MCP tool poisoning or prompt injection? It’s a related but distinct failure mode. Tool poisoning and prompt injection steer an agent’s decision. SSRF is a network-layer gap — even a correctly-decided, fully-authorized call can still land somewhere unintended if nothing validates the destination at connect time.

What’s the single most useful control against agentic SSRF? Enforce the block at TCP-connect time against the resolved IP address, not against the URL string, and re-check on every redirect. Anything checked earlier or less often can be timed around.

A well-authenticated agent making an authorized call to the wrong address is still an incident — the login handshake did its job and the outcome was still bad. Closing that gap doesn’t mean a stronger handshake; it means a guard at the one layer that actually knows where the connection is going, checked at the moment it matters.