Skip to content

Commit 5f241aa

Browse files
bitterpanda63claude
andcommitted
Reuse normalize_hostname helper in is_trusted_hostname; expand helper
Expand normalize_hostname (sinks/socket) to also lowercase and strip trailing dots so it is a proper canonical form for all hostname comparisons. Use it in is_trusted_hostname instead of inline .lower() .rstrip("."). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5b44cc6 commit 5f241aa

3 files changed

Lines changed: 20 additions & 14 deletions

File tree

aikido_zen/sinks/socket/normalize_hostname.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ def normalize_hostname(hostname):
22
if not hostname or not isinstance(hostname, str):
33
return hostname
44

5-
result = hostname
6-
try:
7-
# Check if hostname contains punycode (starts with xn--)
8-
if hostname.startswith("xn--"):
9-
result = hostname.encode("ascii").decode("idna")
5+
# Lowercase and strip trailing dot (DNS resolvers may return FQDNs like "example.com.")
6+
result = hostname.lower().rstrip(".")
107

8+
try:
9+
# Decode Punycode if the hostname starts with xn--
10+
if result.startswith("xn--"):
11+
result = result.encode("ascii").decode("idna")
1112
return result
1213
except (UnicodeError, LookupError):
13-
# If decoding fails, return original hostname
14-
return hostname
14+
return result

aikido_zen/sinks/socket/normalize_hostname_test.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ def test_normalize_hostname_punycode_subdomain():
5555
assert result == "müller.example.com"
5656

5757

58-
def test_normalize_hostname_mixed_case():
59-
"""Test that case is preserved in non-punycode hostnames"""
60-
assert normalize_hostname("Example.COM") == "Example.COM"
61-
assert normalize_hostname("MixedCase.Example.com") == "MixedCase.Example.com"
58+
def test_normalize_hostname_lowercases():
59+
"""Test that hostnames are lowercased"""
60+
assert normalize_hostname("Example.COM") == "example.com"
61+
assert normalize_hostname("MixedCase.Example.com") == "mixedcase.example.com"
6262

6363

6464
def test_normalize_hostname_non_string_input():
@@ -94,7 +94,12 @@ def test_normalize_hostname_punycode_not_starting_with_xn():
9494

9595
def test_normalize_hostname_punycode_error_handling():
9696
"""Test error handling for malformed punycode"""
97-
# This should return the original string if decoding fails
9897
result = normalize_hostname("xn--invalid-punycode")
99-
# Should either return the original or a decoded version if valid
10098
assert isinstance(result, str)
99+
100+
101+
def test_normalize_hostname_trailing_dot():
102+
"""Test that trailing dots (FQDN form from DNS resolvers) are stripped"""
103+
assert normalize_hostname("example.com.") == "example.com"
104+
assert normalize_hostname("metadata.google.internal.") == "metadata.google.internal"
105+
assert normalize_hostname("metadata.goog.") == "metadata.goog"

aikido_zen/vulnerabilities/ssrf/imds.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
from aikido_zen.helpers.ip_matcher import IPMatcher
7+
from aikido_zen.sinks.socket.normalize_hostname import normalize_hostname
78

89
imds_addresses = IPMatcher(
910
[
@@ -29,7 +30,7 @@ def is_trusted_hostname(hostname):
2930
"""
3031
If the hostname is a trusted host (like metadata.goog), there was no spoofing of hostnames, so it's not an attack
3132
"""
32-
return hostname.lower().rstrip(".") in trusted_hosts
33+
return normalize_hostname(hostname) in trusted_hosts
3334

3435

3536
def resolves_to_imds_ip(resolved_ip_addresses, hostname):

0 commit comments

Comments
 (0)