From be953491858826eece9925ffa56c52aaea24ed62 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 30 Mar 2026 18:33:11 +0200 Subject: [PATCH] Reuse IMAGE_REGISTRY_REGEX for docker_image validation (#6667) * Reuse IMAGE_REGISTRY_REGEX for docker_image validation Replace the monolithic regex in docker_image validator with a function-based approach that reuses get_registry_from_image() from docker.utils for robust registry detection. This properly handles domains, IPv4/IPv6 addresses, ports, and localhost while still rejecting tags (managed separately by the add-on system). Co-Authored-By: Claude Opus 4.6 (1M context) * Address review feedback: reorder checks for efficiency Check falsy value before isinstance, and empty path before tag check, as suggested in PR review. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- supervisor/validate.py | 44 +++++++++++++++++++++++++++++++++++++++--- tests/test_validate.py | 9 +++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/supervisor/validate.py b/supervisor/validate.py index ed41c4944..da870be22 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -50,6 +50,7 @@ from .const import ( LogLevel, UpdateChannel, ) +from .docker.utils import get_registry_from_image from .utils.validate import validate_timezone # Move to store.validate when addons_repository config removed @@ -60,9 +61,46 @@ RE_REGISTRY = re.compile(r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$") # pylint: disable=invalid-name network_port = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) wait_boot = vol.All(vol.Coerce(int), vol.Range(min=1, max=60)) -docker_image = vol.Match( - r"^([a-z0-9][a-z0-9.\-]*(:[0-9]+)?/)*?([a-z0-9{][a-z0-9.\-_{}]*/)*?([a-z0-9{][a-z0-9.\-_{}]*)$" -) +# Path component pattern for Docker image names (supports {arch}/{machine} templates) +_RE_IMAGE_PATH_COMPONENT = re.compile(r"^[a-z0-9{][a-z0-9.\-_{}]*$") + + +def docker_image(image: str) -> str: + """Validate a Docker image name without tag. + + Tags are not allowed as the version/tag is managed separately. + Uses IMAGE_REGISTRY_REGEX from docker.utils for robust registry detection. + """ + if not image or not isinstance(image, str): + raise vol.Invalid(f"Expected a non-empty string for docker image, got: {image}") + + # Extract registry if present (handles domains, IPv4/IPv6, ports, localhost) + registry = get_registry_from_image(image) + if registry: + # Registry must be lowercase + if registry != registry.lower(): + raise vol.Invalid(f"Docker image registry must be lowercase: {image}") + path = image[len(registry) + 1 :] # Remove "registry/" prefix + else: + path = image + + if not path: + raise vol.Invalid(f"Docker image has no name: {image}") + + # Tags are not allowed - version is managed separately by the add-on system + if ":" in path: + raise vol.Invalid(f"Docker image must not contain a tag: {image}") + + # Validate each path component (org/name) + for component in path.split("/"): + if not _RE_IMAGE_PATH_COMPONENT.match(component): + raise vol.Invalid( + f"Invalid Docker image path component '{component}' in: {image}" + ) + + return image + + uuid_match = vol.Match(r"^[0-9a-f]{32}$") sha256 = vol.Match(r"^[0-9a-f]{64}$") token = vol.Match(r"^[0-9a-f]{32,256}$") diff --git a/tests/test_validate.py b/tests/test_validate.py index 881373cc8..336657eec 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -25,14 +25,23 @@ IMAGE_NAME_GOOD = [ "homeassistant/amd64-homeassistant", "ttl.sh/homeassistant", "myreg.local:8080/homeassistant", + "localhost/myimage", + "localhost:5000/myimage", + "127.0.0.1/myimage", + "127.0.0.1:5000/org/myimage", + "[::1]:5000/myimage", + "dockeruser/nice-app-1.2", + "ghcr.io/blakeblackshear/frigate", ] IMAGE_NAME_BAD = [ "ghcr.io/home-assistant/homeassistant:123", + "ghcr.io/blakeblackshear/frigate:stable-rocm", ".ghcr.io/home-assistant/homeassistant", "HOMEASSISTANT/homeassistant", "homeassistant/HOMEASSISTANT", "homeassistant/_homeassistant", "homeassistant/-homeassistant", + "GHCR.IO/home-assistant/homeassistant", ]