From eae1b4fa6cfa2e5dd0ab2abe57b356206a2b643f Mon Sep 17 00:00:00 2001 From: Gus Brodman Date: Fri, 26 Dec 2025 12:00:19 -0500 Subject: [PATCH] Add more strict hostname validation on host:check flows We do most of these on host create already so we should also do them on host checks. The only added change is the character validation (our existing hostnames all match these). --- .../flows/domain/DomainFlowUtils.java | 2 +- .../registry/flows/host/HostCheckFlow.java | 1 + .../registry/flows/host/HostFlowUtils.java | 17 +++ .../flows/host/HostCheckFlowTest.java | 34 ++++++ .../flows/host/HostCreateFlowTest.java | 4 +- .../flows/host/HostUpdateFlowTest.java | 4 +- .../rdap/RdapNameserverActionTest.java | 4 +- .../registry/flows/host/host_check_50.xml | 100 +++++++++--------- .../flows/host/host_check_generic.xml | 11 ++ 9 files changed, 121 insertions(+), 56 deletions(-) create mode 100644 core/src/test/resources/google/registry/flows/host/host_check_generic.xml diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 63edeb3f3d6..c945cdb4f14 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -218,7 +218,7 @@ public static InternetDomainName validateDomainName(String name) throws EppExcep return domainName; } - private static void validateFirstLabel(String firstLabel) throws EppException { + public static void validateFirstLabel(String firstLabel) throws EppException { if (firstLabel.length() > MAX_LABEL_SIZE) { throw new DomainLabelTooLongException(); } diff --git a/core/src/main/java/google/registry/flows/host/HostCheckFlow.java b/core/src/main/java/google/registry/flows/host/HostCheckFlow.java index dc00a363b74..c11e47d5c33 100644 --- a/core/src/main/java/google/registry/flows/host/HostCheckFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostCheckFlow.java @@ -65,6 +65,7 @@ public EppResponse run() throws EppException { ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet(); ImmutableList.Builder checks = new ImmutableList.Builder<>(); for (String hostname : hostnames) { + HostFlowUtils.validateHostName(hostname); boolean unused = !existingIds.contains(hostname); checks.add(HostCheck.create(unused, hostname, unused ? null : "In use")); } diff --git a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java index 5652a9a60b1..1c1d32f8c39 100644 --- a/core/src/main/java/google/registry/flows/host/HostFlowUtils.java +++ b/core/src/main/java/google/registry/flows/host/HostFlowUtils.java @@ -14,12 +14,14 @@ package google.registry.flows.host; +import static google.registry.flows.domain.DomainFlowUtils.validateFirstLabel; import static google.registry.model.EppResourceUtils.isActive; import static google.registry.model.tld.Tlds.findTldForName; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static java.util.stream.Collectors.joining; import com.google.common.base.Ascii; +import com.google.common.base.CharMatcher; import com.google.common.net.InternetDomainName; import google.registry.flows.EppException; import google.registry.flows.EppException.AuthorizationErrorException; @@ -38,6 +40,10 @@ /** Static utility functions for host flows. */ public class HostFlowUtils { + /** Validator for ASCII lowercase letters, digits, and "-_", allowing "." as a separator */ + private static final CharMatcher HOST_NAME_ALLOWED_CHARS = + CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('0', '9').or(CharMatcher.anyOf("-._"))); + /** Checks that a host name is valid. */ public static InternetDomainName validateHostName(String name) throws EppException { checkArgumentNotNull(name, "Must specify host name to validate"); @@ -53,6 +59,9 @@ public static InternetDomainName validateHostName(String name) throws EppExcepti if (!name.equals(hostNamePunyCoded)) { throw new HostNameNotPunyCodedException(hostNamePunyCoded); } + if (!HOST_NAME_ALLOWED_CHARS.matchesAllOf(name)) { + throw new BadHostNameCharacterException(); + } InternetDomainName hostName = InternetDomainName.from(name); if (!name.equals(hostName.toString())) { throw new HostNameNotNormalizedException(hostName.toString()); @@ -71,6 +80,7 @@ public static InternetDomainName validateHostName(String name) throws EppExcepti if (hostName.parts().size() < effectiveTld.parts().size() + 2) { throw new HostNameTooShallowException(); } + validateFirstLabel(hostName.parts().getFirst()); return hostName; } catch (IllegalArgumentException e) { throw new InvalidHostNameException(); @@ -180,4 +190,11 @@ public HostNameNotNormalizedException(String expectedHostName) { String.format("Host names must be in normalized format; expected %s", expectedHostName)); } } + + /** Host names can only contain a-z, 0-9, '.', '_', and '-'. */ + static class BadHostNameCharacterException extends ParameterValueSyntaxErrorException { + public BadHostNameCharacterException() { + super("Host names can only contain a-z, 0-9, '.', '_', and '-'"); + } + } } diff --git a/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java b/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java index ca1d69426fe..4d8bc24d07f 100644 --- a/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostCheckFlowTest.java @@ -20,7 +20,9 @@ import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.google.common.collect.ImmutableMap; import google.registry.flows.EppException; +import google.registry.flows.EppException.ParameterValueSyntaxErrorException; import google.registry.flows.FlowUtils.NotLoggedInException; import google.registry.flows.ResourceCheckFlowTestCase; import google.registry.flows.exceptions.TooManyResourceChecksException; @@ -95,4 +97,36 @@ void testIcannActivityReportField_getsLogged() throws Exception { runFlow(); assertIcannReportingActivityFieldLogged("srs-host-check"); } + + @Test + void testFailure_dotHost() throws Exception { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", ".host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_dashHost() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "-host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_underscoreHost() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "_host")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } + + @Test + void testFailure_hostDash() { + setEppInput("host_check_generic.xml", ImmutableMap.of("HOSTNAME", "host-")); + assertAboutEppExceptions() + .that(assertThrows(ParameterValueSyntaxErrorException.class, this::runFlow)) + .marshalsToXml(); + } } diff --git a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java index 8297737e232..f466db2ec07 100644 --- a/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/host/HostCreateFlowTest.java @@ -39,12 +39,12 @@ import google.registry.flows.exceptions.ResourceCreateContentionException; import google.registry.flows.host.HostCreateFlow.SubordinateHostMustHaveIpException; import google.registry.flows.host.HostCreateFlow.UnexpectedExternalHostIpException; +import google.registry.flows.host.HostFlowUtils.BadHostNameCharacterException; import google.registry.flows.host.HostFlowUtils.HostNameNotLowerCaseException; import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException; import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException; import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; -import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainInPendingDeleteException; import google.registry.model.ForeignKeyUtils; @@ -286,7 +286,7 @@ private void doFailingHostNameTest(String hostName, Class - www1.tld - www2.tld - www3.tld - www4.tld - www5.tld - www6.tld - www7.tld - www8.tld - www9.tld - www10.tld - www11.tld - www12.tld - www13.tld - www14.tld - www15.tld - www16.tld - www17.tld - www18.tld - www19.tld - www20.tld - www21.tld - www22.tld - www23.tld - www24.tld - www25.tld - www26.tld - www27.tld - www28.tld - www29.tld - www30.tld - www31.tld - www32.tld - www33.tld - www34.tld - www35.tld - www36.tld - www37.tld - www38.tld - www39.tld - www40.tld - www41.tld - www42.tld - www43.tld - www44.tld - www45.tld - www46.tld - www47.tld - www48.tld - www49.tld - www50.tld + ns1.www1.tld + ns1.www2.tld + ns1.www3.tld + ns1.www4.tld + ns1.www5.tld + ns1.www6.tld + ns1.www7.tld + ns1.www8.tld + ns1.www9.tld + ns1.www10.tld + ns1.www11.tld + ns1.www12.tld + ns1.www13.tld + ns1.www14.tld + ns1.www15.tld + ns1.www16.tld + ns1.www17.tld + ns1.www18.tld + ns1.www19.tld + ns1.www20.tld + ns1.www21.tld + ns1.www22.tld + ns1.www23.tld + ns1.www24.tld + ns1.www25.tld + ns1.www26.tld + ns1.www27.tld + ns1.www28.tld + ns1.www29.tld + ns1.www30.tld + ns1.www31.tld + ns1.www32.tld + ns1.www33.tld + ns1.www34.tld + ns1.www35.tld + ns1.www36.tld + ns1.www37.tld + ns1.www38.tld + ns1.www39.tld + ns1.www40.tld + ns1.www41.tld + ns1.www42.tld + ns1.www43.tld + ns1.www44.tld + ns1.www45.tld + ns1.www46.tld + ns1.www47.tld + ns1.www48.tld + ns1.www49.tld + ns1.www50.tld ABC-12345 diff --git a/core/src/test/resources/google/registry/flows/host/host_check_generic.xml b/core/src/test/resources/google/registry/flows/host/host_check_generic.xml new file mode 100644 index 00000000000..4418fd34315 --- /dev/null +++ b/core/src/test/resources/google/registry/flows/host/host_check_generic.xml @@ -0,0 +1,11 @@ + + + + + %HOSTNAME% + + + ABC-12345 + +