From e25b6ec6e60d22dcd9073542d10aa5a0d3e26ce1 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 21 Oct 2026 17:12:54 -0400 Subject: [PATCH 1/2] security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705/S7044) Resolve and validate command-line path arguments before use, enforce HTTPS at download sinks, and confirm path containment. Hardens local codegen/fetch tools against path-traversal and command-argument-injection findings. --- scripts/fetch-pdf-test-fonts.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/fetch-pdf-test-fonts.py b/scripts/fetch-pdf-test-fonts.py index 821a5e74..99ce3946 100644 --- a/scripts/fetch-pdf-test-fonts.py +++ b/scripts/fetch-pdf-test-fonts.py @@ -11,6 +11,8 @@ import urllib.request def fetch(url): + if not url.startswith("https://"): + raise ValueError("refusing to fetch a non-HTTPS URL") for attempt in range(3): try: with urllib.request.urlopen(url, timeout=60) as response: @@ -42,7 +44,9 @@ def main(): name, url, digest = fixture["name"], fixture["source"], fixture["sha256"] if Path(name).name != name or name in ("", ".", "..") or not url.startswith("https://"): raise ValueError("invalid fixture name or source") - path = destination / name + path = (destination / name).resolve() + if path.parent != destination: + raise ValueError(f"fixture path escapes destination: {name}") if path.is_file() and hashlib.sha256(path.read_bytes()).hexdigest() == digest: print(f"Verified {name}") continue From 7a6d60a285bc721ee330792111698f412ac5cf23 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Thu, 22 Oct 2026 19:17:00 -0400 Subject: [PATCH 2/2] security: enforce HTTPS redirects and atomic write in fetch-pdf-test-fonts.py --- scripts/fetch-pdf-test-fonts.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/fetch-pdf-test-fonts.py b/scripts/fetch-pdf-test-fonts.py index 99ce3946..5da6d9cd 100644 --- a/scripts/fetch-pdf-test-fonts.py +++ b/scripts/fetch-pdf-test-fonts.py @@ -4,14 +4,27 @@ import argparse import hashlib import json +import os from pathlib import Path +import tempfile import time import urllib.error +import urllib.parse import urllib.request +class _HttpsOnlyRedirectHandler(urllib.request.HTTPRedirectHandler): + def redirect_request(self, req, fp, code, msg, headers, newurl): + if urllib.parse.urlsplit(newurl).scheme != "https": + raise ValueError("refusing to follow a non-HTTPS redirect") + return super().redirect_request(req, fp, code, msg, headers, newurl) + + +urllib.request.install_opener(urllib.request.build_opener(_HttpsOnlyRedirectHandler)) + + def fetch(url): - if not url.startswith("https://"): + if urllib.parse.urlsplit(url).scheme != "https": raise ValueError("refusing to fetch a non-HTTPS URL") for attempt in range(3): try: @@ -53,7 +66,17 @@ def main(): data = fetch(url) if len(data) > 16 * 1024 * 1024 or hashlib.sha256(data).hexdigest() != digest: raise ValueError(f"fixture size or SHA-256 mismatch: {name}") - path.write_bytes(data) + fd, tmp = tempfile.mkstemp(dir=destination, prefix=f".{name}.", suffix=".tmp") + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data) + os.replace(tmp, destination / name) + except BaseException: + try: + os.unlink(tmp) + except OSError: + pass + raise print(f"Fetched and verified {name}")