Connector egress guard (SSRF defense)
Parent: connector
Once an owner can upload an arbitrary OpenAPI connector, the target URL is attacker-influenceable — a classic SSRF surface (point it at 169.254.169.254 or an internal service). The defense is two-layer:
- Assembly time (static): every spec's
servers[].urland OAuthtokenUrlis checked; loopback / RFC1918 private / ULA / link-local targets are rejected before the connector is ever usable. - Run time (dynamic): a custom dialer re-checks on every DNS resolution that the resolved IPs are public, and blocks 3xx redirects to private addresses — defeating DNS-rebinding (where a hostname resolves public at check time, private at call time). An allow-list (
CONNECTOR_EGRESS_ALLOW) opens specific hostnames for e2e/mocks.
The two-axis contrast (worth holding onto)
The two plugin axes get opposite isolation, because their needs are opposite:
| axis | needs network? | isolation |
|---|---|---|
| mcp-capability-plugins / sandbox-js-hardening | no | caged off the network (--unshare-net) |
| OpenAPI connector | yes (calls SaaS) | let out, but the exit is filtered (egress guard) |
Caged-in vs filtered-exit: when untrusted code must reach out, you can't isolate by removing the network — you isolate by controlling the egress. Same instinct (untrusted code reaches the host only through a narrow, declared channel), dual mechanism.