SSRF from a Lambda is boring until it hits 169.254.169.254, an internal Redis, or a cloud metadata flavor you forgot. The unfair advantage is forcing all egress through a centralized proxy with an explicit domain allowlist — application code cannot dial arbitrary IPs even if a prompt injection or open redirect tells it to.
⚡ TL;DR: VPC Lambdas with no NAT to the world; only routes to an egress proxy (or VPC endpoints + proxy). Proxy enforces HTTPS allowlist hosts; deny link-local, ULA, and RFC1918 by default. Inject
HTTPS_PROXYat runtime; block raw sockets via seccomp where feasible. Pair with Agent Tool Allowlists, Secret-Aware Context Filters, and Secure AI Sandboxes.
Network topology that makes bypass hard
Lambda ENI -> private subnets
NO default route to NAT for app subnets
Routes: VPC endpoints (S3/STS/Secrets) + proxy TG
Proxy (egress VPCs) -> NAT -> Internet
Allowlist: api.stripe.com, api.github.com, ...
Deny: 169.254.0.0/16, 10.0.0.0/8, 127.0.0.0/8, metadata hostnames
// GOOD: force undici/node through proxy
process.env.HTTPS_PROXY = process.env.EGRESS_PROXY_URL;
process.env.NO_PROXY = "169.254.169.254,localhost,10.0.0.0/8"; // still deny at proxy
// BAD: app constructs http.request to user-supplied URL with no allowlist
import http from "node:http";
export function fetchUserUrl(url: string) {
return http.get(url); // SSRF classic
}
✅ Central proxy allowlist is source of truth.
❌ Per-function ad-hoc if (!url.startsWith("https://api.")) only.
Proxy policy sketch
# illustrative Envoy/RBAC-ish policy
allow_hosts:
- api.stripe.com
- hooks.slack.com
- bedrock-runtime.us-east-1.amazonaws.com
deny_cidrs:
- 169.254.0.0/16
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 127.0.0.0/8
require:
- tls: true
- methods: [GET, POST, PUT]
Log every deny with source function name (from mTLS client cert or SigV4 to proxy). That telemetry feeds incident response when an agent tool starts probing.
Application hardening on top of network controls
import { Agent, fetch } from "undici";
const ALLOW = new Set(["api.stripe.com", "hooks.slack.com"]);
export async function safeFetch(input: string, init?: RequestInit) {
const u = new URL(input);
if (u.protocol !== "https:") throw new Error("https_only");
if (!ALLOW.has(u.hostname)) throw new Error(`host_not_allowed:${u.hostname}`);
if (u.hostname.endsWith(".internal")) throw new Error("internal_denied");
return fetch(u, {
...init,
dispatcher: new Agent({ connect: { timeout: 5_000 } }),
redirect: "error", // open redirects are SSRF helpers
} as any);
}
Defense in depth: network deny + app allowlist + no redirects. For coding agents, tool URLs must pass the same gate — see Agent Tool Allowlists.
Closing checklist
- [ ] App subnets lack direct NAT; egress only via proxy or VPC endpoints
- [ ] Proxy deny list covers link-local and RFC1918
- [ ] Domain allowlist managed as code with review
- [ ]
HTTPS_PROXYinjected; redirects disabled for untrusted URLs - [ ] Deny logs include function identity
- [ ] Periodic probe tests expect deny on metadata IP
Related reading
- Agent Tool Allowlists: Least Privilege for Filesystem and Shell Access
- Secret-Aware Context Filters: Stop AI Editors From Shipping Keys
- Secure AI Sandboxes: Ephemeral ECS Tasks for Agent Tool Execution
- AI Coding in VPC: Private Bedrock Endpoints and Secret Hygiene
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: SSRF Hardening in Node: Block Metadata and DNS Rebinding on Fetches - CheatCoders