Cloud

169.254.169.254: the address your outbound code must never reach

One GET to a link-local address can return credentials that outlive your incident response. It is reachable from anywhere inside the instance.

October 21, 2026·7 min read·by Gurutva Murdia

Every major cloud runs a metadata service on a link-local address inside the instance. It exists so your code can ask "what role am I?" without credentials — which is precisely why an attacker with the ability to make one HTTP request wants to reach it.

  • AWS: 169.254.169.254 (and fd00:ec2::254). IAM role credentials, user-data, instance identity.
  • GCP: metadata.google.internal / 169.254.169.254, with a Metadata-Flavor: Google header requirement. Service account tokens.
  • Azure: 169.254.169.254 with Metadata: true. Managed identity tokens.
  • Kubernetes: the projected service-account token on disk, plus cloud metadata above it in managed clusters.

Why "we block localhost" misses it

169.254.0.0/16 is link-local, not loopback, so a filter written around 127.0.0.0/8 and the literal string "localhost" walks straight past it. It is also inside the range that a permissive CIDR check written by hand tends to miss, because it is neither private nor public in the RFC 1918 sense.

If your product takes a URL from a user, a webhook payload or a model's tool call, that URL is an instruction about where to send a request from inside your network. The metadata service turns that instruction into credentials.

IMDSv2 helps, and is not a complete answer

AWS introduced IMDSv2, which requires a PUT to fetch a session token before any GET. A naive SSRF that can only issue GETs fails against it, and setting the hop limit to 1 blocks the token from being relayed through containers onto the host network. Both are worth enabling — but they raise the bar rather than closing the door: an attacker who can issue arbitrary methods, or who reaches the service from the instance itself, is unaffected.

aws: require a token, and stop it leaving the box
aws ec2 modify-instance-metadata-options \
  --instance-id i-0123456789abcdef0 \
  --http-tokens required \
  --http-put-response-hop-limit 1 \
  --http-endpoint enabled

Defence in depth, cheapest layer first

  • Network: deny egress from application subnets to 169.254.169.254 except from the components that genuinely need it. This is free and it holds even when your code is wrong.
  • Identity: prefer short-lived credentials from a workload identity mechanism over instance roles where the platform offers both.
  • Application: validate resolved addresses, not hostnames, and fail closed on ambiguous DNS (the code in the SSRF post).
  • Architecture: move the outbound call off the box that holds credentials. A relay cannot reach your metadata service because it is not in your network — that is the structural version of the same fix.

That last point is the one teams underrate. Every application-level filter is a bug away from being bypassed; a call that leaves your network entirely removes the reachability the attack depends on. It trades a little control for a smaller blast radius, and for outbound calls to third parties the trade is usually worth it.

A short audit you can run this week

  • Grep for every place user or model input reaches an HTTP client.
  • Confirm each one validates after DNS, refuses mixed answers, and does not follow redirects.
  • Check that egress to link-local is denied from your application tier.
  • Confirm IMDSv2 is required and the hop limit is 1, not 2.
  • Make sure a refused target is logged — an attempt to reach metadata is the signal you want, and silence is the failure mode.

The guard described here is the product.

Backfire validates every target after DNS, refuses private and link-local addresses, never follows a redirect, and writes one record per attempt. 5,000 free trips, no card and no subscription.

Create an account

Questions about this

Keep reading

Try it with 5,000 free trips.

No card, no subscription, and the balance never expires.