Skip to content

Fix SSRF blocklist missing the 10.0.0.0/8 private range#2435

Open
Osamaali313 wants to merge 1 commit into
huggingface:mainfrom
Osamaali313:fix/ssrf-block-10-8
Open

Fix SSRF blocklist missing the 10.0.0.0/8 private range#2435
Osamaali313 wants to merge 1 commit into
huggingface:mainfrom
Osamaali313:fix/ssrf-block-10-8

Conversation

@Osamaali313

Copy link
Copy Markdown

Problem

UNSAFE_IPV4_SUBNETS in src/lib/server/urlSafety.ts is the sole data behind isUnsafeIp(), which backs the entire server-side SSRF boundary:

  • isValidUrl() — validates IP-literal hostnames.
  • assertSafeIp() → the ssrfSafeAgent custom DNS lookup — validates the resolved IP at connect time (the anti-DNS-rebinding guard).

The list includes loopback (127/8), link-local (169.254/16), CGNAT (100.64/10), and two of the three RFC1918 private blocks (172.16/12, 192.168/16) — but omits the largest one, 10.0.0.0/8:

const UNSAFE_IPV4_SUBNETS = [
	"0.0.0.0/8",
	"100.64.0.0/10",
	"127.0.0.0/8",
	"169.254.0.0/16",
	"172.16.0.0/12",
	"192.168.0.0/16",
].map((s) => new Address4(s));

Because isValidUrl requires https:, a literal https://10.0.0.1/... passes; and independently, any hostname that resolves to 10.x.x.x passes the DNS guard. Either way the server can be induced to reach internal 10.0.0.0/8 hosts (cloud metadata/VPC services, internal APIs, admin panels) — SSRF via the public file-fetch proxy (api/fetch-url) and MCP URL handling (runMcpFlow, clientPool, mcp/tools, mcp/health).

Evidence (sibling proves intent)

The client-side private-IP validator (src/lib/utils/mcpValidation.ts) lists 10\. first:

const ipv4Regex = /^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|0\.0\.0\.0|169\.254\.)/;

Both validators are meant to cover the same private space; the real security boundary (urlSafety.ts) is the one that dropped 10/8.

Fix

Add 10.0.0.0/8 to the blocklist (also covers IPv4-mapped IPv6 ::ffff:10.x.x.x, since the same list is reused there). No behavior change for legitimate public URLs.

Reproduction

Faithful CIDR reimplementation of isUnsafeIp's IPv4 path:

address current → unsafe? fixed → unsafe?
10.0.0.1 false true
10.255.255.254 false true
172.16.0.1 true true
192.168.1.1 true true
8.8.8.8 (public) false false

`UNSAFE_IPV4_SUBNETS` in `urlSafety.ts` is the sole data behind
`isUnsafeIp()`, which backs the whole SSRF boundary (`isValidUrl` for
IP-literal hosts and the `ssrfSafeAgent` DNS `lookup` that validates the
resolved IP at connect time). It lists loopback, link-local, CGNAT, and two
of the three RFC1918 blocks (`172.16/12`, `192.168/16`) — but omits the
largest one, `10.0.0.0/8`.

As a result `https://10.0.0.1/...` (IP literal) passes `isValidUrl`, and any
hostname that resolves to `10.x.x.x` passes the DNS guard, so the server can
be made to reach internal `10.0.0.0/8` hosts via the file-fetch proxy
(`api/fetch-url`) and MCP URL handling — SSRF into the most common cloud/VPC
private range.

The client-side sibling validator (`utils/mcpValidation.ts`) already lists
`10\.` first in its private-IP regex, confirming `10/8` is meant to be
blocked. Add `10.0.0.0/8` to the list (this also covers IPv4-mapped IPv6,
since the same list is reused for `::ffff:10.x.x.x`). No behavior change for
legitimate public URLs.
Copilot AI review requested due to automatic review settings July 17, 2026 19:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants