diff --git a/scripts/validate_candidate.py b/scripts/validate_candidate.py index da6e505..db80982 100644 --- a/scripts/validate_candidate.py +++ b/scripts/validate_candidate.py @@ -74,6 +74,10 @@ def handle_starttag(self, tag, attrs): self.links.extend(value for key, value in attrs if key in {"href", "src"} and value) +# Raster-only: SVG and other data: types can carry script or markup, so they stay rejected. +SAFE_INLINE_IMAGE = re.compile(r"(?i)image/(?:png|jpeg|gif|webp);base64,[A-Za-z0-9+/=\s]*\Z") + + def validate_links_and_privacy(): checked = 0 files = list((ROOT / "site").rglob("*.html")) @@ -106,6 +110,8 @@ def validate_links_and_privacy(): continue for value in links: url = urlsplit(html.unescape(value)) + if url.scheme == "data" and SAFE_INLINE_IMAGE.match(url.path): + continue # Inline raster image (e.g. the standalone Floorplan's icon); nothing to fetch. if url.scheme or url.netloc: if url.scheme not in {"http", "https"} or not url.hostname or url.username or url.password: raise ValueError("Unsafe public link in " + str(path.relative_to(ROOT))) diff --git a/tests/test_distribution.py b/tests/test_distribution.py index a4b793e..c92d1b4 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -10,7 +10,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) from build_candidate import ROOT, NAME, render, build -from validate_candidate import archive_members, extract, validate_links_and_privacy +from validate_candidate import SAFE_INLINE_IMAGE, archive_members, extract, validate_links_and_privacy def malicious(entries): @@ -74,6 +74,13 @@ def test_new_build_cannot_overwrite_candidate(self): def test_public_safe_links_and_payload(self): self.assertGreater(validate_links_and_privacy()["local_links_checked"], 0) + def test_only_inline_raster_images_pass_the_link_check(self): + self.assertTrue(SAFE_INLINE_IMAGE.match("image/png;base64,iVBORw0KGgo=")) + self.assertTrue(SAFE_INLINE_IMAGE.match("image/webp;base64,UklGRg==")) + for path in ("image/svg+xml;base64,PHN2Zz4=", "text/html;base64,PHNjcmlwdD4=", + "image/png,