Lambda Outbound Proxies: Egress Allowlists That Block SSRF Paths

Lambda Outbound Proxies: Egress Allowlists That Block SSRF Paths
23 views
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_PROXY at runtime; block raw sockets via seccomp where feasible. Pair with Agent Tool Allowlists, Secret-Aware Context Filters, and Secure AI Sandboxes.

Lambda Outbound Proxies: production guidance

text
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
typescript
// 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

yaml
# 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

typescript
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_PROXY injected; redirects disabled for untrusted URLs
  • [ ] Deny logs include function identity
  • [ ] Periodic probe tests expect deny on metadata IP

Related reading

Last updated on September 11, 2026 Related: SSRF Hardening in Node: Block Metadata and DNS Rebinding on Fetches; Day 62: SSRF and Tool Allowlists; Hidden AWS Lambda Cold Start Fix — Zero Cost, Zero Latency.

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

1 Comment

Leave a comment

No account needed. Name and email are optional.