diff --git a/pkg/tools/integration/web_test.go b/pkg/tools/integration/web_test.go index 8c53f152..c69f734f 100644 --- a/pkg/tools/integration/web_test.go +++ b/pkg/tools/integration/web_test.go @@ -845,6 +845,25 @@ func TestWebFetch_Blocks6to4WithPrivateEmbed(t *testing.T) { } } +// TestWebFetch_BlocksISATAPWithPrivateEmbed verifies ISATAP with private embedded IPv4 is blocked +func TestWebFetch_BlocksISATAPWithPrivateEmbed(t *testing.T) { + tool, err := NewWebFetchTool(50000, format, testFetchLimit) + if err != nil { + t.Fatalf("Failed to create web fetch tool: %v", err) + } + // 2001:db8:1234::5efe:127.0.0.1 embeds 127.0.0.1 + result := tool.Execute(context.Background(), map[string]any{ + "url": "http://[2001:db8:1234::5efe:127.0.0.1]:0", + }) + + if !result.IsError { + t.Error("expected error for ISATAP with private embedded IPv4, got success") + } + if !strings.Contains(result.ForLLM, "private or local network hosts is not allowed") { + t.Fatalf("expected private-host guard rejection, got %q", result.ForLLM) + } +} + // TestWebFetch_Allows6to4WithPublicEmbed verifies 6to4 with public embedded IPv4 is NOT blocked func TestWebFetch_Allows6to4WithPublicEmbed(t *testing.T) { tool, err := NewWebFetchTool(50000, format, testFetchLimit) @@ -981,6 +1000,12 @@ func TestIsPrivateOrRestrictedIP_Table(t *testing.T) { {"2002:7f00:0001::1", true, "6to4 with embedded 127.x (private)"}, {"2002:0a00:0001::1", true, "6to4 with embedded 10.0.0.1 (private)"}, {"2002:0801:0101::1", false, "6to4 with embedded 8.1.1.1 (public)"}, + {"2001:db8:1234::5efe:127.0.0.1", true, "ISATAP with embedded 127.0.0.1 (private)"}, + {"2001:db8:1234::5efe:10.0.0.1", true, "ISATAP with embedded 10.0.0.1 (private)"}, + {"2001:db8:1234::5efe:8.8.8.8", false, "ISATAP with embedded 8.8.8.8 (public)"}, + {"2001:db8:1234:0:0200:5efe:127.0.0.1", true, "ISATAP 0200 with embedded 127.0.0.1 (private)"}, + {"2001:db8:1234:0:0200:5efe:10.0.0.1", true, "ISATAP 0200 with embedded 10.0.0.1 (private)"}, + {"2001:db8:1234:0:0200:5efe:8.8.8.8", false, "ISATAP 0200 with embedded 8.8.8.8 (public)"}, {"2001:0000:4136:e378:8000:63bf:f5ff:fffe", true, "Teredo with client 10.0.0.1 (private)"}, {"2001:0000:4136:e378:8000:63bf:f7f6:fefe", false, "Teredo with client 8.9.1.1 (public)"}, {"2607:f8b0:4004:800::200e", false, "public IPv6 (Google)"}, diff --git a/pkg/utils/http_guard.go b/pkg/utils/http_guard.go index bf80e811..4889b891 100644 --- a/pkg/utils/http_guard.go +++ b/pkg/utils/http_guard.go @@ -285,6 +285,13 @@ func IsPrivateOrRestrictedIP(ip net.IP) bool { client := net.IPv4(ip[12]^0xff, ip[13]^0xff, ip[14]^0xff, ip[15]^0xff) return IsPrivateOrRestrictedIP(client) } + // ISATAP interface identifiers embed an IPv4 address behind either + // 00:00:5e:fe or 02:00:5e:fe. + if ((ip[8] == 0x00 && ip[9] == 0x00) || (ip[8] == 0x02 && ip[9] == 0x00)) && + ip[10] == 0x5e && ip[11] == 0xfe { + embedded := net.IPv4(ip[12], ip[13], ip[14], ip[15]) + return IsPrivateOrRestrictedIP(embedded) + } } return false diff --git a/pkg/utils/http_guard_test.go b/pkg/utils/http_guard_test.go index 754994d0..e547a866 100644 --- a/pkg/utils/http_guard_test.go +++ b/pkg/utils/http_guard_test.go @@ -178,6 +178,12 @@ func TestIsPrivateOrRestrictedIP_Table(t *testing.T) { {"fc00::1", true}, {"2002:7f00:0001::1", true}, {"2002:0801:0101::1", false}, + {"2001:db8:1234::5efe:127.0.0.1", true}, + {"2001:db8:1234::5efe:10.0.0.1", true}, + {"2001:db8:1234::5efe:8.8.8.8", false}, + {"2001:db8:1234:0:0200:5efe:127.0.0.1", true}, + {"2001:db8:1234:0:0200:5efe:10.0.0.1", true}, + {"2001:db8:1234:0:0200:5efe:8.8.8.8", false}, {"2001:0000:4136:e378:8000:63bf:f5ff:fffe", true}, {"2607:f8b0:4004:800::200e", false}, }