Skip to content

Commit d99190e

Browse files
bitterpanda63claude
andcommitted
Add NormalizeHostname helper; use it in TrustedHosts
Create helpers/net/NormalizeHostname.java (lowercase, strip trailing dot, IDN via java.net.IDN) to give SSRF code a canonical hostname form. Replace the inline normalization in TrustedHosts.isTrustedHostname with a call to this helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 19738b3 commit d99190e

3 files changed

Lines changed: 67 additions & 7 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.aikido.agent_api.helpers.net;
2+
3+
import java.net.IDN;
4+
5+
public final class NormalizeHostname {
6+
private NormalizeHostname() {}
7+
8+
/**
9+
* Canonicalizes a hostname for consistent comparison: lowercases, strips a
10+
* trailing dot (FQDN form returned by some DNS resolvers), and converts
11+
* Punycode labels to Unicode via IDN.
12+
*/
13+
public static String normalize(String hostname) {
14+
if (hostname == null || hostname.isEmpty()) {
15+
return hostname;
16+
}
17+
String lower = hostname.toLowerCase();
18+
String noDot = lower.endsWith(".") ? lower.substring(0, lower.length() - 1) : lower;
19+
try {
20+
return IDN.toUnicode(noDot);
21+
} catch (Exception e) {
22+
return noDot;
23+
}
24+
}
25+
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package dev.aikido.agent_api.vulnerabilities.ssrf.imds;
22

3+
import dev.aikido.agent_api.helpers.net.NormalizeHostname;
34
import java.util.Arrays;
4-
import java.util.HashSet;
5-
import java.util.Set;
65

76
public final class TrustedHosts {
87
private TrustedHosts() {}
@@ -13,10 +12,6 @@ private TrustedHosts() {}
1312

1413
/** Checks if this hostname is trusted */
1514
public static boolean isTrustedHostname(String hostname) {
16-
String normalized = hostname.toLowerCase();
17-
if (normalized.endsWith(".")) {
18-
normalized = normalized.substring(0, normalized.length() - 1);
19-
}
20-
return Arrays.asList(trustedHosts).contains(normalized);
15+
return Arrays.asList(trustedHosts).contains(NormalizeHostname.normalize(hostname));
2116
}
2217
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package helpers.net;
2+
3+
import dev.aikido.agent_api.helpers.net.NormalizeHostname;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
9+
class NormalizeHostnameTest {
10+
11+
@Test
12+
void testNullAndEmpty() {
13+
assertNull(NormalizeHostname.normalize(null));
14+
assertEquals("", NormalizeHostname.normalize(""));
15+
}
16+
17+
@Test
18+
void testLowercases() {
19+
assertEquals("example.com", NormalizeHostname.normalize("EXAMPLE.COM"));
20+
assertEquals("metadata.google.internal", NormalizeHostname.normalize("METADATA.GOOGLE.INTERNAL"));
21+
}
22+
23+
@Test
24+
void testStripsTrailingDot() {
25+
assertEquals("example.com", NormalizeHostname.normalize("example.com."));
26+
assertEquals("metadata.google.internal", NormalizeHostname.normalize("metadata.google.internal."));
27+
assertEquals("metadata.goog", NormalizeHostname.normalize("metadata.goog."));
28+
}
29+
30+
@Test
31+
void testStripsTrailingDotAndLowercases() {
32+
assertEquals("metadata.google.internal", NormalizeHostname.normalize("METADATA.GOOGLE.INTERNAL."));
33+
}
34+
35+
@Test
36+
void testPlainHostnameUnchanged() {
37+
assertEquals("example.com", NormalizeHostname.normalize("example.com"));
38+
assertEquals("localhost", NormalizeHostname.normalize("localhost"));
39+
}
40+
}

0 commit comments

Comments
 (0)