Skip to content

SSRF Protection

safeFetch() is a drop-in replacement for fetch() that validates all outbound HTTP requests against private IP ranges and DNS rebinding attacks.

How It Works

Key behaviors:

  • Manual redirect following — each redirect hop is validated (up to 5 hops)
  • DNS resolution before connect — prevents DNS rebinding where first lookup returns public IP, second returns private
  • DNS cache — 5-minute TTL, prevents repeated lookups for the same host

Blocked IP Ranges

RangeDescription
127.0.0.0/8Loopback
10.0.0.0/8Private (RFC1918)
172.16.0.0/12Private (RFC1918)
192.168.0.0/16Private (RFC1918)
169.254.0.0/16Link-local (cloud metadata)
0.0.0.0/8"This" network
::1IPv6 loopback
fe80::/10IPv6 link-local
fc00::/7IPv6 unique local
::ffff:x.x.x.xIPv4-mapped IPv6 (checks underlying IPv4)

Allowlist

Set SSRF_ALLOWED_HOSTS to a comma-separated list of trusted hosts:

env
SSRF_ALLOWED_HOSTS=oauth.provider.com,*.trusted-domain.com

Wildcard support: *.example.com matches sub.example.com and deep.sub.example.com.

Allowlisted hosts bypass DNS validation entirely.

Protected Endpoints

FileWhat it protects
src/auth/drivers/oauth2.tsOAuth2 token exchange + userinfo fetch
src/auth/drivers/openid.tsOIDC discovery + token exchange + userinfo
src/modules/notify/providers/mattermost.tsMattermost webhook delivery

Error Handling

When a request is blocked:

  1. SSRFBlockedError is thrown (HTTP 403, code SSRF_BLOCKED)
  2. A SSRF_BLOCKED security event is emitted (severity: critical)
  3. The blocked URL and reason are logged via pino
json
{
  "errors": [{
    "message": "SSRF blocked: DNS resolved to private IP: 10.0.0.1 (https://evil.com/redirect)",
    "extensions": { "code": "SSRF_BLOCKED" }
  }]
}

Configuration

VariableTypeDefaultDescription
SSRF_PROTECTION_ENABLEDbooleantrueMaster toggle
SSRF_ALLOWED_HOSTSstring''Comma-separated allowlist

When SSRF_PROTECTION_ENABLED=false, safeFetch() delegates directly to native fetch().

Docker / Kubernetes Note

Internal hostnames (e.g., postgres, redis, backend) in Docker/K8s networks resolve to private IPs and will be blocked. This is intentional for user-controlled URLs (OAuth callbacks, webhooks from admin settings).

For future system-level internal calls (e.g., webhook engine calling internal services), safeFetch will support an allowInternal option to bypass private IP checks on trusted system calls.

Source Files

FilePurpose
src/security/ssrf/safe-fetch.tssafeFetch() + SSRFBlockedError
src/security/ssrf/ip-validator.tsisPrivateIP() + isAllowedHost()
src/security/ssrf/dns-cache.tsDNS resolution cache

ODP Internal API Documentation