mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-14 14:54:06 +00:00
fix(security): normalize IPv6-mapped IPv4 in loopback check, add tests
- Apply _normalize_addr in _is_allowed_loopback_target so ::ffff:127.0.0.1 is correctly identified as loopback - Add test for contains_internal_url with IPv6-mapped addresses - Add test for whitelist + IPv6-mapped CGNAT interaction
This commit is contained in:
parent
13dec9d2c2
commit
288146315e
@ -149,7 +149,7 @@ def _is_allowed_loopback_target(
|
|||||||
hostname: str,
|
hostname: str,
|
||||||
addrs: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
|
addrs: list[ipaddress.IPv4Address | ipaddress.IPv6Address],
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if not addrs or not all(addr.is_loopback for addr in addrs):
|
if not addrs or not all(_normalize_addr(addr).is_loopback for addr in addrs):
|
||||||
return False
|
return False
|
||||||
normalized = hostname.rstrip(".").lower()
|
normalized = hostname.rstrip(".").lower()
|
||||||
if normalized == "localhost":
|
if normalized == "localhost":
|
||||||
|
|||||||
@ -7,7 +7,11 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from nanobot.security.network import configure_ssrf_whitelist, contains_internal_url, validate_url_target
|
from nanobot.security.network import (
|
||||||
|
configure_ssrf_whitelist,
|
||||||
|
contains_internal_url,
|
||||||
|
validate_url_target,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _fake_resolve(host: str, results: list[str]):
|
def _fake_resolve(host: str, results: list[str]):
|
||||||
@ -155,6 +159,12 @@ def test_loopback_exception_rejects_metadata():
|
|||||||
assert contains_internal_url("curl http://169.254.169.254/latest/meta-data/", allow_loopback=True)
|
assert contains_internal_url("curl http://169.254.169.254/latest/meta-data/", allow_loopback=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_detects_ipv6_mapped_loopback():
|
||||||
|
"""contains_internal_url must catch IPv6-mapped loopback in shell commands."""
|
||||||
|
with patch("nanobot.security.network.socket.getaddrinfo", _fake_resolve_v6("evil.com", ["::ffff:127.0.0.1"])):
|
||||||
|
assert contains_internal_url("curl http://evil.com/secret")
|
||||||
|
|
||||||
|
|
||||||
def test_allows_normal_curl():
|
def test_allows_normal_curl():
|
||||||
with patch("nanobot.security.network.socket.getaddrinfo", _fake_resolve("example.com", ["93.184.216.34"])):
|
with patch("nanobot.security.network.socket.getaddrinfo", _fake_resolve("example.com", ["93.184.216.34"])):
|
||||||
assert not contains_internal_url("curl https://example.com/api/data")
|
assert not contains_internal_url("curl https://example.com/api/data")
|
||||||
@ -206,3 +216,14 @@ def test_whitelist_invalid_cidr_ignored():
|
|||||||
assert ok
|
assert ok
|
||||||
finally:
|
finally:
|
||||||
configure_ssrf_whitelist([])
|
configure_ssrf_whitelist([])
|
||||||
|
|
||||||
|
|
||||||
|
def test_whitelist_allows_ipv6_mapped_cgnat():
|
||||||
|
"""Whitelist must work when DNS returns IPv6-mapped CGNAT address."""
|
||||||
|
configure_ssrf_whitelist(["100.64.0.0/10"])
|
||||||
|
try:
|
||||||
|
with patch("nanobot.security.network.socket.getaddrinfo", _fake_resolve_v6("ts.local", ["::ffff:100.100.1.1"])):
|
||||||
|
ok, err = validate_url_target("http://ts.local/api")
|
||||||
|
assert ok, f"Whitelisted IPv6-mapped CGNAT should be allowed, got: {err}"
|
||||||
|
finally:
|
||||||
|
configure_ssrf_whitelist([])
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user