Appearance
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
| Range | Description |
|---|---|
127.0.0.0/8 | Loopback |
10.0.0.0/8 | Private (RFC1918) |
172.16.0.0/12 | Private (RFC1918) |
192.168.0.0/16 | Private (RFC1918) |
169.254.0.0/16 | Link-local (cloud metadata) |
0.0.0.0/8 | "This" network |
::1 | IPv6 loopback |
fe80::/10 | IPv6 link-local |
fc00::/7 | IPv6 unique local |
::ffff:x.x.x.x | IPv4-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.comWildcard support: *.example.com matches sub.example.com and deep.sub.example.com.
Allowlisted hosts bypass DNS validation entirely.
Protected Endpoints
| File | What it protects |
|---|---|
src/auth/drivers/oauth2.ts | OAuth2 token exchange + userinfo fetch |
src/auth/drivers/openid.ts | OIDC discovery + token exchange + userinfo |
src/modules/notify/providers/mattermost.ts | Mattermost webhook delivery |
Error Handling
When a request is blocked:
SSRFBlockedErroris thrown (HTTP 403, codeSSRF_BLOCKED)- A
SSRF_BLOCKEDsecurity event is emitted (severity:critical) - 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
| Variable | Type | Default | Description |
|---|---|---|---|
SSRF_PROTECTION_ENABLED | boolean | true | Master toggle |
SSRF_ALLOWED_HOSTS | string | '' | 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
| File | Purpose |
|---|---|
src/security/ssrf/safe-fetch.ts | safeFetch() + SSRFBlockedError |
src/security/ssrf/ip-validator.ts | isPrivateIP() + isAllowedHost() |
src/security/ssrf/dns-cache.ts | DNS resolution cache |