From e655a7a4e43d0c06617011acc7707d2e9d60428f Mon Sep 17 00:00:00 2001 From: jubnl Date: Sun, 3 May 2026 16:43:39 +0200 Subject: [PATCH] fix(ssrf): let .local/.internal hostnames pass to IP-level checks The pre-DNS hostname block was redundant: any .local/.internal host that resolves to a private IP is already gated by isPrivateNetwork + ALLOW_INTERNAL_NETWORK, and any that resolves to loopback/link-local is caught by isAlwaysBlocked unconditionally. Dropping the hostname pre-check means Docker/LAN deployments can reach services on .local hostnames (e.g. immich.local) with ALLOW_INTERNAL_NETWORK=true, while loopback and link-local IPs (including 169.254.169.254) remain hard-blocked with no override. Reverts the isAlwaysBlocked guard loosening from 9a08368. --- server/src/utils/ssrfGuard.ts | 6 +----- wiki/Internal-Network-Access.md | 17 +++++++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/server/src/utils/ssrfGuard.ts b/server/src/utils/ssrfGuard.ts index 74dc0cab..f9b4255f 100644 --- a/server/src/utils/ssrfGuard.ts +++ b/server/src/utils/ssrfGuard.ts @@ -66,10 +66,6 @@ export async function checkSsrf(rawUrl: string, bypassInternalIpAllowed: boolean const hostname = url.hostname.toLowerCase(); - if (isInternalHostname(hostname) && hostname !== 'localhost' && !ALLOW_INTERNAL_NETWORK) { - return { allowed: false, isPrivate: false, error: 'Requests to .local/.internal domains are not allowed' }; - } - // Resolve hostname to IP let resolvedIp: string; try { @@ -79,7 +75,7 @@ export async function checkSsrf(rawUrl: string, bypassInternalIpAllowed: boolean return { allowed: false, isPrivate: false, error: 'Could not resolve hostname' }; } - if (isAlwaysBlocked(resolvedIp) && !ALLOW_INTERNAL_NETWORK) { + if (isAlwaysBlocked(resolvedIp)) { return { allowed: false, isPrivate: true, diff --git a/wiki/Internal-Network-Access.md b/wiki/Internal-Network-Access.md index 59d246c1..981ae63d 100644 --- a/wiki/Internal-Network-Access.md +++ b/wiki/Internal-Network-Access.md @@ -6,27 +6,32 @@ TREK makes outbound HTTP requests when you configure integrations such as Immich All outbound requests go through an SSRF guard (`ssrfGuard.ts`). The guard resolves the hostname to an IP address before allowing the connection and blocks addresses in private ranges. -## Blocked unless `ALLOW_INTERNAL_NETWORK=true` +## Always blocked (no override possible) -All of the following are blocked by default and can be permitted by setting `ALLOW_INTERNAL_NETWORK=true`: +These ranges are blocked regardless of any setting: -| Range / Hostname | Description | +| Range | Description | |---|---| | `127.0.0.0/8`, `::1` | Loopback | | `0.0.0.0/8` | Unspecified | | `169.254.0.0/16`, `fe80::/10` | Link-local / cloud metadata endpoints | | `::ffff:127.x.x.x`, `::ffff:169.254.x.x` | IPv4-mapped loopback and link-local | + +## Blocked unless `ALLOW_INTERNAL_NETWORK=true` + +| Range / Hostname | Description | +|---|---| | `10.0.0.0/8` | RFC-1918 private | | `172.16.0.0/12` | RFC-1918 private | | `192.168.0.0/16` | RFC-1918 private | | `100.64.0.0/10` | CGNAT / Tailscale shared address space | | `fc00::/7` | IPv6 ULA | | IPv4-mapped RFC-1918 variants | e.g. `::ffff:10.x`, `::ffff:192.168.x` | -| `*.local`, `*.internal` hostnames | mDNS / internal DNS suffixes | +| `*.local`, `*.internal` hostnames | mDNS / internal DNS suffixes (e.g. Docker service names, LAN hosts) | -The hostname `localhost` is not blocked at the hostname stage but resolves to `127.0.0.1`, which falls under the loopback rule above. +The hostname `localhost` is not blocked at the hostname stage, but it resolves to `127.0.0.1` which is caught by the loopback rule above and is therefore always blocked. -> **Warning:** `ALLOW_INTERNAL_NETWORK=true` also permits loopback and link-local addresses, including `169.254.169.254` (cloud instance metadata). Do **not** set this flag on a cloud-hosted TREK instance. +`*.local` and `*.internal` hostnames are permitted when `ALLOW_INTERNAL_NETWORK=true` — the guard still resolves them to an IP and enforces all IP-level rules, so any such hostname that resolves to a loopback or link-local address remains blocked regardless. ## When to enable