mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-23 10:42:00 +00:00
test(url-safety): cover IPv6 scope-ID strip + fail-closed in URL guards
Follow-up to the salvaged #25961 fix: regression tests asserting that scope-bearing IPv6 addresses (fe80::1%eth0, ::1%lo) are blocked by is_safe_url after the scope is stripped, that a still-unparseable address fails closed, and that a scoped IPv4-mapped IMDS address is caught by the always-blocked floor.
This commit is contained in:
parent
ed966696eb
commit
87ab373381
1 changed files with 34 additions and 0 deletions
|
|
@ -164,6 +164,31 @@ class TestIsSafeUrl:
|
|||
]):
|
||||
assert is_safe_url("http://[::ffff:169.254.169.254]/") is False
|
||||
|
||||
def test_ipv6_scope_id_link_local_blocked(self):
|
||||
"""fe80::1%eth0 — a scope-ID-bearing link-local address must not bypass
|
||||
the guard. ``ipaddress.ip_address`` rejects the ``%scope`` suffix, so
|
||||
the scope must be stripped before the block check rather than skipped.
|
||||
"""
|
||||
with patch("socket.getaddrinfo", return_value=[
|
||||
(10, 1, 6, "", ("fe80::1%eth0", 0, 0, 0)),
|
||||
]):
|
||||
assert is_safe_url("http://[fe80::1%eth0]/") is False
|
||||
|
||||
def test_ipv6_scope_id_loopback_blocked(self):
|
||||
"""::1%lo — scoped IPv6 loopback must still be blocked."""
|
||||
with patch("socket.getaddrinfo", return_value=[
|
||||
(10, 1, 6, "", ("::1%lo", 0, 0, 0)),
|
||||
]):
|
||||
assert is_safe_url("http://[::1%lo]/") is False
|
||||
|
||||
def test_unparseable_ip_after_scope_strip_fails_closed(self):
|
||||
"""An address that is still unparseable after stripping the scope ID
|
||||
must fail closed (block), not be silently skipped."""
|
||||
with patch("socket.getaddrinfo", return_value=[
|
||||
(10, 1, 6, "", ("not-an-ip%garbage", 0, 0, 0)),
|
||||
]):
|
||||
assert is_safe_url("http://example.invalid/") is False
|
||||
|
||||
def test_unspecified_address_blocked(self):
|
||||
"""0.0.0.0 — unspecified address, can bind to all interfaces."""
|
||||
with patch("socket.getaddrinfo", return_value=[
|
||||
|
|
@ -492,6 +517,15 @@ class TestIsAlwaysBlockedUrl:
|
|||
]):
|
||||
assert is_always_blocked_url("http://attacker-controlled.example.com/") is True
|
||||
|
||||
def test_scope_id_imds_in_floor_blocked(self):
|
||||
"""A scope-ID suffix on an IPv4-mapped IMDS address resolving in the
|
||||
always-blocked floor must be caught after the scope is stripped, not
|
||||
skipped as unparseable."""
|
||||
with patch("socket.getaddrinfo", return_value=[
|
||||
(10, 1, 6, "", ("::ffff:169.254.169.254%eth0", 0, 0, 0)),
|
||||
]):
|
||||
assert is_always_blocked_url("http://attacker-controlled.example.com/") is True
|
||||
|
||||
# -- Things the floor must NOT block ----------------------------------------
|
||||
|
||||
def test_public_url_not_blocked(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue