From 55f91f887a47c55447d37a471da4d6e1426f31fb Mon Sep 17 00:00:00 2001 From: "eric.quintero@trailofbits.com" Date: Tue, 2 Jun 2026 16:26:25 +0000 Subject: [PATCH 1/2] Upload Windows SBOM sidecars --- tests/test_windows_merge_upload.py | 69 +++++++++++++++++++++++++++++ windows-release/merge-and-upload.py | 15 ++++++- 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/test_windows_merge_upload.py diff --git a/tests/test_windows_merge_upload.py b/tests/test_windows_merge_upload.py new file mode 100644 index 00000000..59f915d4 --- /dev/null +++ b/tests/test_windows_merge_upload.py @@ -0,0 +1,69 @@ +import importlib.util +import json +from pathlib import Path +from typing import Any + +import pytest + + +def load_merge_upload_module(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Any: + for name in ( + "INDEX_FILE", + "LOCAL_INDEX", + "MAKECAT", + "MANIFEST_FILE", + "NO_UPLOAD", + "PLINK", + "PSCP", + "SIGN_COMMAND", + "UPLOAD_HOST", + "UPLOAD_HOST_KEY", + "UPLOAD_KEYFILE", + "UPLOAD_PATH_PREFIX", + "UPLOAD_URL_PREFIX", + "UPLOAD_USER", + ): + monkeypatch.delenv(name, raising=False) + + monkeypatch.chdir(tmp_path) + script = Path(__file__).parents[1] / "windows-release" / "merge-and-upload.py" + spec = importlib.util.spec_from_file_location("merge_and_upload_for_test", script) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + + with pytest.raises(SystemExit) as exc_info: + spec.loader.exec_module(module) + + assert exc_info.value.code == 1 + return module + + +def test_calculate_uploads_uses_full_artifact_sbom_name( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + module = load_merge_upload_module(monkeypatch, tmp_path) + artifact = tmp_path / "python-3.14.0-amd64.exe" + sbom = tmp_path / "python-3.14.0-amd64.exe.spdx.json" + artifact.write_bytes(b"installer") + sbom.write_text("{}", encoding="utf-8") + (tmp_path / "__install__.amd64.json").write_text( + json.dumps( + { + "url": ( + "https://www.python.org/ftp/python/3.14.0/python-3.14.0-amd64.exe" + ) + } + ), + encoding="utf-8", + ) + + uploads = list(module.calculate_uploads()) + + assert len(uploads) == 1 + _, _, _, upload_sbom, sbom_dest = uploads[0] + assert upload_sbom == sbom + assert ( + sbom_dest + == "/srv/www.python.org/ftp/python/3.14.0/python-3.14.0-amd64.exe.spdx.json" + ) diff --git a/windows-release/merge-and-upload.py b/windows-release/merge-and-upload.py index 5e00a6d1..3cb83c2f 100644 --- a/windows-release/merge-and-upload.py +++ b/windows-release/merge-and-upload.py @@ -150,6 +150,17 @@ def validate_new_installs(installs): print("WARNING: Duplicate id fields:", *sorted(set(ids))) +def sbom_path_for(src): + return src.parent / f"{src.name}.spdx.json" + + +def sibling_upload_path(dest, filename): + destdir, separator, _ = dest.rpartition("/") + if not separator: + return filename + return f"{destdir}/{filename}" + + def purge(url): if not UPLOAD_HOST or NO_UPLOAD: print("Skipping purge of", url, "because UPLOAD_HOST is missing") @@ -176,8 +187,8 @@ def calculate_uploads(): dest = url2path(i["url"]) if LOCAL_INDEX: i["url"] = str(src.relative_to(Path.cwd())).replace("\\", "/") - sbom = src.with_suffix(".spdx.json") - sbom_dest = dest.rpartition("/")[0] + sbom.name + sbom = sbom_path_for(src) + sbom_dest = sibling_upload_path(dest, sbom.name) if not sbom.is_file(): sbom = None sbom_dest = None From 1f7f39dfc95bb0962b614b30648e81b713f96b31 Mon Sep 17 00:00:00 2001 From: "eric.quintero@trailofbits.com" Date: Mon, 22 Jun 2026 20:02:01 +0000 Subject: [PATCH 2/2] Simplify Windows SBOM sidecar lookup --- tests/test_windows_merge_upload.py | 69 ----------------------------- windows-release/merge-and-upload.py | 15 +------ 2 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 tests/test_windows_merge_upload.py diff --git a/tests/test_windows_merge_upload.py b/tests/test_windows_merge_upload.py deleted file mode 100644 index 59f915d4..00000000 --- a/tests/test_windows_merge_upload.py +++ /dev/null @@ -1,69 +0,0 @@ -import importlib.util -import json -from pathlib import Path -from typing import Any - -import pytest - - -def load_merge_upload_module(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Any: - for name in ( - "INDEX_FILE", - "LOCAL_INDEX", - "MAKECAT", - "MANIFEST_FILE", - "NO_UPLOAD", - "PLINK", - "PSCP", - "SIGN_COMMAND", - "UPLOAD_HOST", - "UPLOAD_HOST_KEY", - "UPLOAD_KEYFILE", - "UPLOAD_PATH_PREFIX", - "UPLOAD_URL_PREFIX", - "UPLOAD_USER", - ): - monkeypatch.delenv(name, raising=False) - - monkeypatch.chdir(tmp_path) - script = Path(__file__).parents[1] / "windows-release" / "merge-and-upload.py" - spec = importlib.util.spec_from_file_location("merge_and_upload_for_test", script) - assert spec is not None - assert spec.loader is not None - module = importlib.util.module_from_spec(spec) - - with pytest.raises(SystemExit) as exc_info: - spec.loader.exec_module(module) - - assert exc_info.value.code == 1 - return module - - -def test_calculate_uploads_uses_full_artifact_sbom_name( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path -) -> None: - module = load_merge_upload_module(monkeypatch, tmp_path) - artifact = tmp_path / "python-3.14.0-amd64.exe" - sbom = tmp_path / "python-3.14.0-amd64.exe.spdx.json" - artifact.write_bytes(b"installer") - sbom.write_text("{}", encoding="utf-8") - (tmp_path / "__install__.amd64.json").write_text( - json.dumps( - { - "url": ( - "https://www.python.org/ftp/python/3.14.0/python-3.14.0-amd64.exe" - ) - } - ), - encoding="utf-8", - ) - - uploads = list(module.calculate_uploads()) - - assert len(uploads) == 1 - _, _, _, upload_sbom, sbom_dest = uploads[0] - assert upload_sbom == sbom - assert ( - sbom_dest - == "/srv/www.python.org/ftp/python/3.14.0/python-3.14.0-amd64.exe.spdx.json" - ) diff --git a/windows-release/merge-and-upload.py b/windows-release/merge-and-upload.py index 3cb83c2f..9c9c0a74 100644 --- a/windows-release/merge-and-upload.py +++ b/windows-release/merge-and-upload.py @@ -150,17 +150,6 @@ def validate_new_installs(installs): print("WARNING: Duplicate id fields:", *sorted(set(ids))) -def sbom_path_for(src): - return src.parent / f"{src.name}.spdx.json" - - -def sibling_upload_path(dest, filename): - destdir, separator, _ = dest.rpartition("/") - if not separator: - return filename - return f"{destdir}/{filename}" - - def purge(url): if not UPLOAD_HOST or NO_UPLOAD: print("Skipping purge of", url, "because UPLOAD_HOST is missing") @@ -187,8 +176,8 @@ def calculate_uploads(): dest = url2path(i["url"]) if LOCAL_INDEX: i["url"] = str(src.relative_to(Path.cwd())).replace("\\", "/") - sbom = sbom_path_for(src) - sbom_dest = sibling_upload_path(dest, sbom.name) + sbom = src.with_name(f"{src.name}.spdx.json") + sbom_dest = f"{dest}.spdx.json" if not sbom.is_file(): sbom = None sbom_dest = None