Merge pull request #3143 from lc6464/fix/isatap-ssrf-guard

fix(web): block private IPv4 embeds in ISATAP literals
This commit is contained in:
Mauro
2026-06-26 22:59:41 +02:00
committed by GitHub
3 changed files with 38 additions and 0 deletions
+25
View File
@@ -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)"},
+7
View File
@@ -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
+6
View File
@@ -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},
}