Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 154 additions & 39 deletions testing/backend/unit/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import pytest
import socket
import ipaddress
from backend.secuscan import validation as validation_module
from backend.secuscan.config import settings
from backend.secuscan.validation import (
validate_target, validate_port, validate_port_range, validate_url,
Expand All @@ -19,17 +17,58 @@ def test_validate_target():
# Safe mode restrictions
assert validate_target("8.8.8.8", safe_mode=True)[0] is False # Public IP blocked in safe mode
assert validate_target("military.mil", safe_mode=True)[0] is False # Blocked TLD
assert validate_target("example.gov", safe_mode=True)[0] is False

# Invalid targets
assert validate_target("10.0.0.0/24")[0] is True # Private CIDR ranges are allowed in safe mode
assert validate_target("not!a!valid!hostname")[0] is False

def test_validate_target_safe_mode_blocks_public_hostname(monkeypatch):
def fake_getaddrinfo(_host, *_args, **_kwargs):
return [(socket.AF_INET, None, None, None, ("8.8.8.8", 0))]
def test_validate_target_public_ip_allowed_without_safe_mode():
ok, msg = validate_target(
"8.8.8.8",
safe_mode=False
)

assert ok is True
assert msg == ""

def test_validate_target_safe_mode_rejects_unresolved_hostname(monkeypatch):
"""
Mutation guard:
Safe mode must fail closed if DNS returns no addresses.
"""

def fake_getaddrinfo(*args, **kwargs):
return []

monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
assert validate_target("example.com", safe_mode=True)[0] is False

ok, msg = validate_target(
"unknown.example",
safe_mode=True
)

assert ok is False
assert "did not resolve" in msg.lower()

def test_validate_target_safe_mode_blocks_link_local():
ok, msg = validate_target(
"169.254.1.10",
safe_mode=True
)

assert ok is False
assert "blocked network" in msg.lower()


def test_validate_target_safe_mode_blocks_multicast():
ok, msg = validate_target(
"224.0.0.1",
safe_mode=True
)

assert ok is False
assert "blocked network" in msg.lower()

def test_validate_target_safe_mode_blocks_multi_record_when_any_public(monkeypatch):
"""If any A/AAAA record is public, safe-mode must fail closed."""
Expand All @@ -42,55 +81,132 @@ def fake_getaddrinfo(_host, *_args, **_kwargs):
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
assert validate_target("multirecord.example", safe_mode=True)[0] is False

def test_validate_target_safe_mode_blocks_dns_rebinding_union(monkeypatch):
"""Rebinding/round-robin: validate_target resolves twice and validates the union."""
calls = {"n": 0}
def test_validate_target_blocks_loopback_when_disabled(monkeypatch):
monkeypatch.setattr(settings, "allow_loopback_scans", False)

def fake_getaddrinfo(_host, *_args, **_kwargs):
calls["n"] += 1
if calls["n"] == 1:
return [(socket.AF_INET, None, None, None, ("192.168.1.10", 0))]
return [(socket.AF_INET, None, None, None, ("8.8.8.8", 0))]
ok, msg = validate_target(
"127.0.0.1",
safe_mode=False
)

monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
ok, _msg = validate_target("rebind.example", safe_mode=True)
assert ok is False
assert calls["n"] >= 2
assert "loopback" in msg.lower()

def test_validate_target_safe_mode_blocks_url_ip_literal():
assert validate_target("http://8.8.8.8", safe_mode=True)[0] is False
def test_validate_target_safe_mode_allows_private_hostname(monkeypatch):

def test_validate_target_ipv4_with_ipv6_allowed_network_does_not_crash(monkeypatch):
monkeypatch.setattr(settings, "allowed_networks", ["fc00::/7"])
ok, msg = validate_target("127.0.0.1", safe_mode=True)
def fake_getaddrinfo(*args, **kwargs):
return [
(
socket.AF_INET,
None,
None,
None,
("192.168.1.20", 0)
)
]

monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)

ok, msg = validate_target(
"internal.example",
safe_mode=True
)

assert ok is True
assert msg == ""

def test_validate_target_safe_mode_blocks_public_hostname(monkeypatch):

def fake_getaddrinfo(*args, **kwargs):
return [
(
socket.AF_INET,
None,
None,
None,
("8.8.8.8", 0)
)
]

monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)

ok, msg = validate_target(
"public.example",
safe_mode=True
)

assert ok is False
assert msg == "Target not within allowed networks in safe mode (SecuScan Guardrail)"
assert "public" in msg.lower()

def test_validate_target_safe_mode_blocks_link_local_hostname(monkeypatch):

def test_validate_target_ipv6_with_ipv4_allowed_network_does_not_crash(monkeypatch):
monkeypatch.setattr(settings, "allowed_networks", ["127.0.0.0/8"])
ok, msg = validate_target("::1", safe_mode=True)
def fake_getaddrinfo(*args, **kwargs):
return [
(
socket.AF_INET,
None,
None,
None,
("169.254.10.10", 0)
)
]

monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)

ok, msg = validate_target(
"linklocal.example",
safe_mode=True
)

assert ok is False
assert msg == "Public IPs/networks not allowed in safe mode (SecuScan Guardrail)"
assert "blocked network" in msg.lower()

def test_validate_target_hostname_respects_allowed_networks(monkeypatch):

def test_validate_target_mixed_allowed_networks_uses_later_same_version_entry(monkeypatch):
monkeypatch.setattr(settings, "allowed_networks", ["fc00::/7", "127.0.0.0/8"])
ok, msg = validate_target("127.0.0.1", safe_mode=True)
monkeypatch.setattr(
settings,
"allowed_networks",
["192.168.1.0/24"]
)

assert ok is True
assert msg == ""
def fake_getaddrinfo(*args, **kwargs):
return [
(
socket.AF_INET,
None,
None,
None,
("10.0.0.5", 0)
)
]

def test_validate_target_mixed_allowed_networks_uses_later_same_version_ipv6_entry(monkeypatch):
monkeypatch.setattr(validation_module, "ALLOWED_PRIVATE", [ipaddress.ip_network("fc00::/7")])
monkeypatch.setattr(settings, "allowed_networks", ["127.0.0.0/8", "fc00::/7"])
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)

ok, msg = validate_target("fd00::1", safe_mode=True)
ok, msg = validate_target(
"internal.example",
safe_mode=True
)

assert ok is True
assert msg == ""
assert ok is False
assert "allowed networks" in msg.lower()

def test_validate_target_safe_mode_blocks_url_ip_literal():
assert validate_target("http://8.8.8.8", safe_mode=True)[0] is False

def test_validate_target_safe_mode_rejects_outside_allowed_network(monkeypatch):
monkeypatch.setattr(
settings,
"allowed_networks",
["192.168.1.0/24"]
)

ok, msg = validate_target(
"10.0.0.5",
safe_mode=True
)

assert ok is False
assert "allowed networks" in msg.lower()

def test_validate_port():
assert validate_port(80) == (True, "")
Expand Down Expand Up @@ -314,7 +430,6 @@ def test_validate_command_network_egress_log_only(monkeypatch):
assert ok is False
assert "network policy" in err.lower()


def test_validate_command_network_egress_ignores_malformed_urls():
"""Malformed URLs without a valid hostname should be ignored."""

Expand Down
Loading