diff --git a/workspace-server/internal/handlers/registry.go b/workspace-server/internal/handlers/registry.go index 19ca8006..e5be5553 100644 --- a/workspace-server/internal/handlers/registry.go +++ b/workspace-server/internal/handlers/registry.go @@ -147,12 +147,14 @@ func validateAgentURL(rawURL string) error { // ranges. CGNAT (RFC-6598) is never used for VPC subnets on any cloud // provider. IPv4 multicast is never a unicast endpoint. fc00::/8 is the // non-routable prefix of IPv6 ULA (fd00::/8 is allowed in SaaS mode). + // RFC 3849: 2001:db8::/32 is the IPv6 documentation prefix. {"192.0.2.0/24", "TEST-NET-1 documentation range (RFC-5737)"}, {"198.51.100.0/24", "TEST-NET-2 documentation range (RFC-5737)"}, {"203.0.113.0/24", "TEST-NET-3 documentation range (RFC-5737)"}, {"100.64.0.0/10", "carrier-grade NAT address (RFC-6598)"}, {"224.0.0.0/4", "IPv4 multicast address"}, {"fc00::/8", "IPv6 ULA non-routable prefix (fc00::/8)"}, + {"2001:db8::/32", "IPv6 documentation address (RFC-3849 reserved)"}, } if !saasMode() { blockedRanges = append(blockedRanges, diff --git a/workspace-server/internal/handlers/registry_test.go b/workspace-server/internal/handlers/registry_test.go index a9ebc025..62c9e984 100644 --- a/workspace-server/internal/handlers/registry_test.go +++ b/workspace-server/internal/handlers/registry_test.go @@ -540,6 +540,21 @@ func TestValidateAgentURL(t *testing.T) { {"blocked IPv6 loopback [::1]", "http://[::1]:8080", true}, {"blocked IPv6 link-local [fe80::1]", "http://[fe80::1]:8080", true}, {"blocked IPv6 ULA [fd00::1]", "http://[fd00::1]:8080", true}, + + // ── Must be rejected: RFC 5737 TEST-NET reserved ranges ───────────── + // These addresses are reserved for documentation and example code. + // No production agent has a legitimate reason to use them. + {"blocked TEST-NET-1 192.0.2.x", "http://192.0.2.1:8080", true}, + {"blocked TEST-NET-1 192.0.2.254", "http://192.0.2.254:9000", true}, + {"blocked TEST-NET-2 198.51.100.x", "http://198.51.100.1:8080", true}, + {"blocked TEST-NET-2 198.51.100.99", "http://198.51.100.99:8000", true}, + {"blocked TEST-NET-3 203.0.113.x", "http://203.0.113.1:8080", true}, + {"blocked TEST-NET-3 203.0.113.254", "http://203.0.113.254:9000", true}, + + // ── Must be rejected: RFC 3849 IPv6 documentation prefix ──────────── + {"blocked IPv6 documentation 2001:db8::1", "http://[2001:db8::1]:8080", true}, + {"blocked IPv6 documentation 2001:db8::ffff", "http://[2001:db8::ffff]:8000", true}, + // IPv4-mapped IPv6 for a blocked range must also be rejected. // Go normalises ::ffff:169.254.x.x to IPv4 via To4(), so the existing // 169.254.0.0/16 entry catches it without a dedicated rule.