Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 38 additions & 32 deletions .github/workflows/release-dependency-license-strix-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,12 @@ on:
required: false
type: string
default: ""
build_artifact_name:
description: Caller artifact holding the exact wheel and sdist to publish.
distribution_set_artifact_id:
description: Immutable same-run reproducibility record artifact ID containing the complete distribution set manifest.
required: true
type: string
build_artifact_id:
description: Immutable upload-artifact ID from this caller run.
required: true
type: string
build_artifact_digest:
description: Expected sha256-prefixed artifact digest from the producer upload.
distribution_set_artifact_digest:
description: Expected sha256-prefixed reproducibility record artifact digest from the producer upload.
required: true
type: string
wheel_filename:
Expand Down Expand Up @@ -188,7 +184,7 @@ jobs:
with:
repository: ContextualWisdomLab/.github
# Reviewed helper revision; intentionally distinct from workflow revision.
ref: 00c6551183cca101cfc97c43656a17cc2491c1b4
ref: 7cb4be4c5cfef406fae065eb4697018fe956b18c
path: trusted-gate
persist-credentials: false
# The whole scripts/ci tree, not an enumerated file list: the trusted
Expand All @@ -208,17 +204,18 @@ jobs:
CALLER_WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
expected=00c6551183cca101cfc97c43656a17cc2491c1b4
expected=7cb4be4c5cfef406fae065eb4697018fe956b18c
test "$(git -C "$HELPER_ROOT" rev-parse HEAD)" = "$expected"
origin="$(git -C "$HELPER_ROOT" remote get-url origin)"
case "$origin" in
https://github.com/ContextualWisdomLab/.github|https://github.com/ContextualWisdomLab/.github.git) ;;
*) echo "Foreign helper repository" >&2; exit 1 ;;
esac
test "$(git -C "$HELPER_ROOT" rev-parse HEAD:scripts/ci)" = bf26d3eefdb71fe79b855d941ffb46eb432b2f76
test "$(git -C "$HELPER_ROOT" rev-parse HEAD:scripts/ci)" = 30720a6a86fc0ee7f836bd7905fc62e7122e0b6d
test "$(git -C "$HELPER_ROOT" rev-parse HEAD:requirements-strix-ci-hashes.txt)" = 9e705850b5ce53c7fe836bc3df3a18771151e3f6
git -C "$HELPER_ROOT" diff --exit-code HEAD -- scripts/ci requirements-strix-ci-hashes.txt
test -f "$HELPER_ROOT/scripts/ci/release_dependency_gate.py"
test -f "$HELPER_ROOT/scripts/ci/verify_release_distribution_set.py"
test -f "$HELPER_ROOT/scripts/ci/verify_exact_artifact_sbom_handoff.py"
test -f "$HELPER_ROOT/requirements-strix-ci-hashes.txt"
printf 'helper_repository=ContextualWisdomLab/.github helper_sha=%s caller_workflow_sha=%s\n' "$expected" "$CALLER_WORKFLOW_SHA"
Expand Down Expand Up @@ -257,34 +254,43 @@ jobs:
with:
python-version: "3.13"

- name: Verify immutable same-run build artifact metadata
- name: List the current run and attempt artifacts
env:
GH_TOKEN: ${{ github.token }}
SOURCE_REPOSITORY: ${{ inputs.source_repository }}
ARTIFACT_ID: ${{ inputs.build_artifact_id }}
ARTIFACT_NAME: ${{ inputs.build_artifact_name }}
ARTIFACT_DIGEST: ${{ inputs.build_artifact_digest }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
# Reuse the exact-artifact attestation's same-run metadata boundary.
test "$SOURCE_REPOSITORY" = "$GITHUB_REPOSITORY"
[[ "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]
[[ "$ARTIFACT_DIGEST" =~ ^sha256:[0-9a-f]{64}$ ]]
artifact_json="$(gh api "/repos/${SOURCE_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")"
jq -e \
--arg id "$ARTIFACT_ID" \
--arg name "$ARTIFACT_NAME" \
--arg digest "$ARTIFACT_DIGEST" \
--argjson run_id "$GITHUB_RUN_ID" \
'(.id | tostring) == $id and .name == $name and .digest == $digest and .workflow_run.id == $run_id and .expired == false' \
<<<"$artifact_json" >/dev/null
gh api --paginate "repos/${SOURCE_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
--jq '.artifacts[]' > "${RUNNER_TEMP}/release-artifacts.jsonl"
gh api "repos/${SOURCE_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}" \
> "${RUNNER_TEMP}/release-attempt.json"

- name: Download the exact distributions the caller intends to publish
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ inputs.build_artifact_id }}
digest-mismatch: error
path: release-distributions
- name: Verify every immutable distribution before dependency capture
env:
GH_TOKEN: ${{ github.token }}
SOURCE_REPOSITORY: ${{ inputs.source_repository }}
SOURCE_SHA: ${{ inputs.source_sha }}
CONTROL_SHA: ${{ github.sha }}
RECORD_ID: ${{ inputs.distribution_set_artifact_id }}
RECORD_DIGEST: ${{ inputs.distribution_set_artifact_digest }}
WHEEL_FILENAME: ${{ inputs.wheel_filename }}
SDIST_FILENAME: ${{ inputs.sdist_filename }}
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
python3 -I trusted-gate/scripts/ci/verify_release_distribution_set.py \
--repository "$SOURCE_REPOSITORY" \
--source-sha "$SOURCE_SHA" \
--control-sha "$CONTROL_SHA" \
--run-id "$GITHUB_RUN_ID" \
--run-attempt "$GITHUB_RUN_ATTEMPT" \
--record-artifact-id "$RECORD_ID" \
--record-artifact-digest "$RECORD_DIGEST" \
--wheel-filename "$WHEEL_FILENAME" \
--sdist-filename "$SDIST_FILENAME" \
--metadata "${RUNNER_TEMP}/release-artifacts.jsonl" \
--attempt "${RUNNER_TEMP}/release-attempt.json" \
--output release-distributions

- name: Collect the release closure without installing or executing it
env:
Expand Down
57 changes: 0 additions & 57 deletions tests/test_release_build_artifact_identity.py

This file was deleted.

28 changes: 23 additions & 5 deletions tests/test_release_dependency_gate_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,14 @@ def test_every_action_is_pinned_to_the_same_commits_as_attestation() -> None:
"""The gate and the attestation it feeds materialize identical trusted actions."""
workflow = _workflow_text()
attestation = _ATTESTATION.read_text(encoding="utf-8")
for pin in (_HARDEN_RUNNER_PIN, _CHECKOUT_PIN, _UPLOAD_ARTIFACT_PIN, _DOWNLOAD_ARTIFACT_PIN):
for pin in (_HARDEN_RUNNER_PIN, _CHECKOUT_PIN, _UPLOAD_ARTIFACT_PIN):
assert pin in workflow
assert pin in attestation
assert _DOWNLOAD_ARTIFACT_PIN in attestation
assert _SETUP_PYTHON_PIN in workflow
references = re.findall(r"(?m)^ +uses: (.+)$", workflow)
# Eight: harden-runner, two checkouts, setup-python, download-artifact, and
# three uploads (sealed evidence, the licence report, the gate report).
assert len(references) == 8
# Seven: harden-runner, two checkouts, setup-python, and three uploads.
assert len(references) == 7
for reference in references:
assert re.match(r"^[^@]+@[0-9a-f]{40} # ", reference), reference

Expand All @@ -124,7 +124,7 @@ def test_trusted_gate_is_materialized_from_this_repository_at_its_pinned_sha() -
"""The decision code is the base repository's, never the caller's tree."""
workflow = _workflow_text()
assert "repository: ContextualWisdomLab/.github" in workflow
assert "ref: 00c6551183cca101cfc97c43656a17cc2491c1b4" in workflow
assert "ref: 7cb4be4c5cfef406fae065eb4697018fe956b18c" in workflow
assert "path: trusted-gate" in workflow
assert "persist-credentials: false" in workflow
# The whole scripts/ci tree, because the trusted Strix gate, the
Expand Down Expand Up @@ -167,6 +167,7 @@ def test_step_order_captures_then_strixes_then_gates_then_seals() -> None:
"Materialize immutable trusted gate",
"Validate the exact release identity before anything else runs",
"Check out the exact release head",
"Verify every immutable distribution before dependency capture",
"Collect the release closure without installing or executing it",
"Assemble per-dependency evidence and isolated synthetic fixtures",
"Refuse a denied or unverifiable licence before any credential exists",
Expand All @@ -183,6 +184,23 @@ def test_step_order_captures_then_strixes_then_gates_then_seals() -> None:
assert positions == sorted(positions), "gate steps are out of order"


def test_complete_distribution_set_is_required_and_verified_before_strix() -> None:
workflow = _workflow_text()
inputs = workflow.split(" inputs:\n", 1)[1].split(" secrets:\n", 1)[0]
for name in ("distribution_set_artifact_id", "distribution_set_artifact_digest"):
assert re.search(rf"(?m)^ {name}:\n(?: .*\n)*? required: true$", inputs)
assert "build_artifact_id" not in inputs
verifier = "python3 -I trusted-gate/scripts/ci/verify_release_distribution_set.py"
assert verifier in workflow
assert workflow.index(verifier) < workflow.index("release_dependency_gate.py prescreen")
assert workflow.index(verifier) < workflow.index("secrets.BYTEZ_API_KEY")
assert "--record-artifact-id \"$RECORD_ID\"" in workflow
assert "--record-artifact-digest \"$RECORD_DIGEST\"" in workflow
assert "--run-attempt \"$GITHUB_RUN_ATTEMPT\"" in workflow
assert "--wheel-filename \"$WHEEL_FILENAME\"" in workflow
assert "--sdist-filename \"$SDIST_FILENAME\"" in workflow


def test_the_licence_decision_precedes_every_credential_and_model_step() -> None:
"""A denied licence is refused before a provider secret is read at all.

Expand Down
41 changes: 27 additions & 14 deletions tests/test_release_fixed_helper_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
import pytest

ROOT = Path(__file__).resolve().parents[1]
PIN = "00c6551183cca101cfc97c43656a17cc2491c1b4"
CASES = [("release-dependency-license-strix-gate.yml", "trusted-gate"),
("exact-artifact-sbom-attestation.yml", "trusted-intake"),
("exact-artifact-sbom-attestation.yml", "trusted-signer")]
LEGACY_PIN = "00c6551183cca101cfc97c43656a17cc2491c1b4"
EXACT_SET_PIN = "7cb4be4c5cfef406fae065eb4697018fe956b18c"
CASES = [
("release-dependency-license-strix-gate.yml", "trusted-gate",
EXACT_SET_PIN, "30720a6a86fc0ee7f836bd7905fc62e7122e0b6d"),
("exact-artifact-sbom-attestation.yml", "trusted-intake",
LEGACY_PIN, "bf26d3eefdb71fe79b855d941ffb46eb432b2f76"),
("exact-artifact-sbom-attestation.yml", "trusted-signer",
LEGACY_PIN, "bf26d3eefdb71fe79b855d941ffb46eb432b2f76"),
]


def _parts(filename, destination):
Expand All @@ -22,34 +28,40 @@ def _parts(filename, destination):
return checkout, "\n".join(line[10:] for line in script.splitlines())


@pytest.mark.parametrize("filename,destination", CASES)
def test_literal_source_pin_and_sibling_scope(filename, destination):
@pytest.mark.parametrize("filename,destination,pin,tree", CASES)
def test_literal_source_pin_and_sibling_scope(filename, destination, pin, tree):
checkout, script = _parts(filename, destination)
assert f"ref: {PIN}" in checkout
assert f"ref: {pin}" in checkout
assert "repository: ContextualWisdomLab/.github" in checkout
assert "${{" not in checkout
assert f"expected={PIN}" in script
assert f"expected={pin}" in script
assert f"rev-parse HEAD:scripts/ci)\" = {tree}" in script
text = (ROOT / ".github/workflows" / filename).read_text()
following = text.split(f" path: {destination}\n", 1)[1]
scope = following.split(" - name: Verify fixed helper checkout identity", 1)[0]
assert "scripts/ci/" in scope and "requirements-strix-ci-hashes.txt" in scope


@pytest.mark.parametrize("filename,destination", CASES)
@pytest.mark.parametrize("filename,destination,pin,tree", CASES)
@pytest.mark.parametrize("case", ["ok", "caller_changed", "foreign", "missing", "pin", "tree", "dirty", "file"])
def test_actual_guard_rejects_bad_source(tmp_path, filename, destination, case):
def test_actual_guard_rejects_bad_source(tmp_path, filename, destination, pin, tree, case):
_, script = _parts(filename, destination)
helper = tmp_path / destination
(helper / "scripts/ci").mkdir(parents=True)
for name in ("scripts/ci/release_dependency_gate.py", "scripts/ci/verify_exact_artifact_sbom_handoff.py", "requirements-strix-ci-hashes.txt"):
for name in (
"scripts/ci/release_dependency_gate.py",
"scripts/ci/verify_release_distribution_set.py",
"scripts/ci/verify_exact_artifact_sbom_handoff.py",
"requirements-strix-ci-hashes.txt",
):
if not (case == "file" and name.endswith("release_dependency_gate.py")):
(helper / name).write_text("inert fixture")
fake = '''git() {
if [ "$CASE" = missing ]; then return 128; fi
case "$*" in
*"rev-parse HEAD:scripts/ci") [ "$CASE" = tree ] && echo bad || echo bf26d3eefdb71fe79b855d941ffb46eb432b2f76 ;;
*"rev-parse HEAD:scripts/ci") [ "$CASE" = tree ] && echo bad || echo "$EXPECTED_TREE" ;;
*"rev-parse HEAD:requirements-strix-ci-hashes.txt") echo 9e705850b5ce53c7fe836bc3df3a18771151e3f6 ;;
*"rev-parse HEAD") [ "$CASE" = pin ] && echo bad || echo 00c6551183cca101cfc97c43656a17cc2491c1b4 ;;
*"rev-parse HEAD") [ "$CASE" = pin ] && echo bad || echo "$EXPECTED_PIN" ;;
*"remote get-url origin") [ "$CASE" = foreign ] && echo https://github.com/caller/repo || echo https://github.com/ContextualWisdomLab/.github ;;
*"diff --exit-code"*) [ "$CASE" != dirty ] ;;
*) return 99 ;;
Expand All @@ -58,8 +70,9 @@ def test_actual_guard_rejects_bad_source(tmp_path, filename, destination, case):
'''
result = subprocess.run(["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", fake + script],
env={**os.environ, "HELPER_ROOT": str(helper), "CASE": case,
"EXPECTED_PIN": pin, "EXPECTED_TREE": tree,
"CALLER_WORKFLOW_SHA": ("b" if case == "caller_changed" else "a") * 40},
capture_output=True, text=True)
assert (result.returncode == 0) is (case in ("ok", "caller_changed")), result.stderr
if result.returncode == 0:
assert f"helper_sha={PIN}" in result.stdout
assert f"helper_sha={pin}" in result.stdout
42 changes: 42 additions & 0 deletions tests/test_verify_release_distribution_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import hashlib
import io
import json
import os
import subprocess
import sys
import zipfile
from pathlib import Path

Expand Down Expand Up @@ -109,6 +112,45 @@ def test_verifies_all_thirteen_immutable_artifact_archives(tmp_path: Path) -> No
assert _sha((tmp_path / "dist" / row["file"]).read_bytes()) == row["sha256"]


def test_cli_downloads_the_exact_ids_before_exposing_files(tmp_path: Path) -> None:
case = _case()
archives = tmp_path / "archives"
archives.mkdir()
for artifact_id, data in case["archives"].items():
(archives / f"{artifact_id}.zip").write_bytes(data)
metadata = tmp_path / "metadata.jsonl"
metadata.write_text("".join(json.dumps(item) + "\n" for item in case["metadata"]))
attempt = tmp_path / "attempt.json"
attempt.write_text(json.dumps(case["attempt"]))
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
gh = bin_dir / "gh"
gh.write_text(
"#!/usr/bin/env python3\n"
"import os, pathlib, sys\n"
"path = sys.argv[2]\n"
"sys.stdout.buffer.write((pathlib.Path(os.environ['FAKE_ARCHIVES']) / (path.split('/')[-2] + '.zip')).read_bytes())\n"
)
gh.chmod(0o755)
script = Path(__file__).resolve().parents[1] / "scripts/ci/verify_release_distribution_set.py"
record_digest = next(item["digest"] for item in case["metadata"] if item["name"] == "reproducibility-record")
result = subprocess.run(
[sys.executable, "-I", str(script), "--repository", "owner/repo",
"--source-sha", SOURCE, "--control-sha", CONTROL,
"--run-id", str(RUN), "--run-attempt", str(ATTEMPT),
"--record-artifact-id", "14", "--record-artifact-digest", record_digest,
"--wheel-filename", "pkg-1.2.3-1.whl", "--sdist-filename", "pkg-1.2.3.tar.gz",
"--metadata", str(metadata), "--attempt", str(attempt),
"--output", str(tmp_path / "dist")],
env={**os.environ, "PATH": f"{bin_dir}:{os.environ['PATH']}",
"FAKE_ARCHIVES": str(archives)},
capture_output=True, text=True,
)
assert result.returncode == 0, result.stderr
assert len(json.loads(result.stdout)["verified_distributions"]) == 13
assert len(list((tmp_path / "dist").iterdir())) == 13


def test_refuses_forged_missing_stale_and_tampered_sets(tmp_path: Path) -> None:
def missing(case):
case["manifest"]["distributions"].pop()
Expand Down
Loading