From a0e4964b46394cd3797591d43d63a6e56d0585e2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 1 Sep 2026 00:35:33 +0200 Subject: [PATCH] fix: verify fetched source revisions from lock Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663 --- .forgejo/workflows/publish-image.yaml | 2 -- Containerfile | 2 -- tests/test_publication.py | 50 +++++++++++++++++++++++++++ tools/build_site.py | 24 +++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/.forgejo/workflows/publish-image.yaml b/.forgejo/workflows/publish-image.yaml index 4011105..024e460 100644 --- a/.forgejo/workflows/publish-image.yaml +++ b/.forgejo/workflows/publish-image.yaml @@ -87,7 +87,6 @@ jobs: --policy-revision "${REF}" \ --token-env FORGEJO_SOURCE_TOKEN \ --token-origin "${FORGEJO_ORIGIN}" - NETKINGDOM_REVISION="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["repositories"]["net-kingdom"]["revision"])')" SOURCE_SET_DIGEST="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["source_set_digest"])')" docker cp "${PYTHON_CONTAINER}:/workspace/_sources" "${BUILD_CONTEXT}/" docker rm -f "${PYTHON_CONTAINER}" @@ -102,7 +101,6 @@ jobs: docker build \ --file "${BUILD_CONTEXT}/Containerfile" \ --build-arg "VCS_REVISION=${REF}" \ - --build-arg "NETKINGDOM_REVISION=${NETKINGDOM_REVISION}" \ --build-arg "SOURCE_SET_DIGEST=${SOURCE_SET_DIGEST}" \ --tag "${IMAGE}:${SOURCE_TAG}" \ --tag "${IMAGE}:${REVISION_TAG}" \ diff --git a/Containerfile b/Containerfile index b038fab..c13925a 100644 --- a/Containerfile +++ b/Containerfile @@ -19,9 +19,7 @@ FROM runtime-base AS local-artifact COPY --chown=101:101 build/ /usr/share/nginx/html/ FROM docker.io/library/python@sha256:d09d15e60962ca365d1cd544a48773bac9d33f2fb1b00f2aa0deec78ade7dc31 AS release-builder -ARG NETKINGDOM_REVISION ARG SOURCE_SET_DIGEST -ENV POLICY_NEXUS_SOURCE_REVISION_NET_KINGDOM=$NETKINGDOM_REVISION ENV POLICY_NEXUS_SOURCE_ROOT=/workspace/_sources WORKDIR /workspace/policy-nexus COPY . /workspace/policy-nexus diff --git a/tests/test_publication.py b/tests/test_publication.py index 7a33ca8..77077c9 100644 --- a/tests/test_publication.py +++ b/tests/test_publication.py @@ -85,6 +85,56 @@ class PublicationTest(unittest.TestCase): with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"): build_site._source_revision(repo, source) + def test_archive_build_uses_fetched_source_lock_revision(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + repo = root / "the-custodian" + source = repo / "canon/example.md" + source.parent.mkdir(parents=True) + source.write_text("example", encoding="utf-8") + revision = "b" * 40 + (root / "source-lock.json").write_text( + json.dumps( + { + "schema_version": 1, + "repositories": { + "the-custodian": {"revision": revision} + }, + } + ), + encoding="utf-8", + ) + + with mock.patch.dict( + os.environ, {"POLICY_NEXUS_SOURCE_ROOT": str(root)}, clear=False + ): + self.assertEqual(revision, build_site._source_revision(repo, source)) + + def test_archive_build_rejects_invalid_fetched_source_lock_revision(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + repo = root / "the-custodian" + source = repo / "canon/example.md" + source.parent.mkdir(parents=True) + source.write_text("example", encoding="utf-8") + (root / "source-lock.json").write_text( + json.dumps( + { + "schema_version": 1, + "repositories": { + "the-custodian": {"revision": "sha256:" + "b" * 64} + }, + } + ), + encoding="utf-8", + ) + + with mock.patch.dict( + os.environ, {"POLICY_NEXUS_SOURCE_ROOT": str(root)}, clear=False + ): + with self.assertRaisesRegex(ValueError, "clean 40-hex Git commit"): + build_site._source_revision(repo, source) + def test_manifest_builds_index_current_revision_and_legacy_alias(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) diff --git a/tools/build_site.py b/tools/build_site.py index ccbe396..f449875 100644 --- a/tools/build_site.py +++ b/tools/build_site.py @@ -45,6 +45,30 @@ def _source_revision(repo: Path, source: Path) -> str: f"{revision_env} must be a clean 40-hex Git commit, got {supplied_revision!r}" ) return supplied_revision + source_root = os.environ.get("POLICY_NEXUS_SOURCE_ROOT", "") + if source_root: + root = Path(source_root).resolve() + try: + repo.relative_to(root) + except ValueError: + pass + else: + lock_path = root / "source-lock.json" + try: + lock = json.loads(lock_path.read_text(encoding="utf-8")) + locked_revision = lock["repositories"][repo.name]["revision"] + except (KeyError, OSError, TypeError, json.JSONDecodeError) as exc: + raise ValueError( + f"{repo.name}: source revision is missing from {lock_path}" + ) from exc + if not isinstance(locked_revision, str) or not CLEAN_GIT_REVISION.fullmatch( + locked_revision + ): + raise ValueError( + f"{repo.name}: locked source revision must be a clean 40-hex Git commit, " + f"got {locked_revision!r}" + ) + return locked_revision try: head = subprocess.run( ["git", "-C", str(repo), "rev-parse", "HEAD"],