f875a98acf
40 tests cubriendo: - file: deny-by-default, path traversal, symlink escape, prefix confusion - ssh: allowlist, blocklist, sintaxis shell (pipes, subshells, redirects) - http: SSRF (loopback, IPs privadas, link-local, metadata), dominios - matrix: room authorization allowlist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
package http
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateDomain_EmptyAllowed(t *testing.T) {
|
|
if err := validateDomain("example.com", nil); err != nil {
|
|
t.Fatalf("empty list should allow all: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateDomain_Allowed(t *testing.T) {
|
|
if err := validateDomain("api.example.com", []string{"api.example.com"}); err != nil {
|
|
t.Fatalf("should be allowed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateDomain_Denied(t *testing.T) {
|
|
if err := validateDomain("evil.com", []string{"api.example.com"}); err == nil {
|
|
t.Fatal("should be denied")
|
|
}
|
|
}
|
|
|
|
func TestValidateDomain_CaseInsensitive(t *testing.T) {
|
|
if err := validateDomain("API.Example.COM", []string{"api.example.com"}); err != nil {
|
|
t.Fatalf("should be case-insensitive: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_Localhost(t *testing.T) {
|
|
if err := rejectInternalHost("localhost"); err == nil {
|
|
t.Fatal("localhost should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_Loopback(t *testing.T) {
|
|
if err := rejectInternalHost("127.0.0.1"); err == nil {
|
|
t.Fatal("loopback should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_IPv6Loopback(t *testing.T) {
|
|
if err := rejectInternalHost("::1"); err == nil {
|
|
t.Fatal("IPv6 loopback should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_PrivateA(t *testing.T) {
|
|
if err := rejectInternalHost("10.0.0.1"); err == nil {
|
|
t.Fatal("10.x should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_PrivateB(t *testing.T) {
|
|
if err := rejectInternalHost("172.16.0.1"); err == nil {
|
|
t.Fatal("172.16.x should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_PrivateC(t *testing.T) {
|
|
if err := rejectInternalHost("192.168.1.1"); err == nil {
|
|
t.Fatal("192.168.x should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_LinkLocal(t *testing.T) {
|
|
if err := rejectInternalHost("169.254.1.1"); err == nil {
|
|
t.Fatal("link-local should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_Metadata(t *testing.T) {
|
|
if err := rejectInternalHost("169.254.169.254"); err == nil {
|
|
t.Fatal("metadata IP should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestRejectInternalHost_PublicIP(t *testing.T) {
|
|
if err := rejectInternalHost("8.8.8.8"); err != nil {
|
|
t.Fatalf("public IP should be allowed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestIsPrivateIP(t *testing.T) {
|
|
cases := []struct {
|
|
ip string
|
|
want bool
|
|
}{
|
|
{"127.0.0.1", true},
|
|
{"10.0.0.1", true},
|
|
{"172.16.0.1", true},
|
|
{"192.168.0.1", true},
|
|
{"169.254.169.254", true},
|
|
{"8.8.8.8", false},
|
|
{"1.1.1.1", false},
|
|
}
|
|
for _, c := range cases {
|
|
ip := net.ParseIP(c.ip)
|
|
got := isPrivateIP(ip)
|
|
if got != c.want {
|
|
t.Errorf("isPrivateIP(%s) = %v, want %v", c.ip, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_Valid(t *testing.T) {
|
|
if err := validateURL("https://example.com/api", nil); err != nil {
|
|
t.Fatalf("public URL should pass: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_InternalIP(t *testing.T) {
|
|
if err := validateURL("http://127.0.0.1:8080/admin", nil); err == nil {
|
|
t.Fatal("internal IP in URL should be blocked")
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_NoHost(t *testing.T) {
|
|
if err := validateURL("file:///etc/passwd", nil); err == nil {
|
|
t.Fatal("URL with no host should be rejected")
|
|
}
|
|
}
|