published-artifact-gate #125
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: published-artifact-gate | |
| # Fundament F2 — hermetic Published-Artifact gate + reproducibility, live from 3.3.1. | |
| # | |
| # Built early (with 3.3.1) so every later release is hermetic + reproducible with NO repackaging of | |
| # prior releases. Three legs: | |
| # 1. reproducible-sdist — the sdist is byte-identical across two builds (proven, not asserted). | |
| # 2. hermetic-cleanroom — install the BUILT sdist into a FRESH venv (not this checkout) and prove the | |
| # PUBLISHED bytes pass the offline demo + example verify + the tamper matrix. This is what a reader | |
| # installs; a red run means the shipped artifact is broken independently of the editable checkout. | |
| # 3. reusable-attest-dryrun — exercise the SLSA-L3 reusable signing workflow's build+determinism half | |
| # on every PR (dry_run: no throwaway attestations), so it is live-tested, not a paper anchor. | |
| # | |
| # Actions pinned to full commit SHAs, matching ci.yml / release.yml supply-chain hardening. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| schedule: | |
| - cron: "31 5 * * *" | |
| permissions: | |
| contents: read | |
| jobs: | |
| reproducible-sdist: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install the build frontend | |
| run: python -m pip install --upgrade build | |
| - name: Two sdist builds must be byte-identical (deterministic packaging) | |
| run: python scripts/build_reproducible.py --check | |
| hermetic-cleanroom: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install the build frontend | |
| run: python -m pip install --upgrade build | |
| - name: Build the reproducible sdist | |
| run: python scripts/build_reproducible.py --outdir dist | |
| - name: Fresh venv, install the BUILT sdist (cleanroom — the installed package, NOT this checkout) | |
| run: | | |
| python -m venv /tmp/clean | |
| /tmp/clean/bin/pip install --quiet "$(ls dist/*.tar.gz)[eval]" | |
| /tmp/clean/bin/proofbundle --help >/dev/null | |
| - name: Published bytes — demo must exit 0 (honest OK, six tampers caught, sample swap caught) | |
| # `demo` emits + verifies + tampers entirely from the installed package (no checkout src on | |
| # path), so this exercises the SHIPPED bytes, not the editable tree. | |
| run: /tmp/clean/bin/proofbundle demo | |
| - name: Published bytes — emit a fresh receipt, verify it round-trips, then a tamper MUST fail | |
| run: | | |
| printf 'hermetic cleanroom payload' > /tmp/payload.txt | |
| /tmp/clean/bin/proofbundle emit --payload-file /tmp/payload.txt \ | |
| --out /tmp/pub_bundle.json --new-key /tmp/pub.key | |
| /tmp/clean/bin/proofbundle verify /tmp/pub_bundle.json | |
| python - <<'PY' | |
| import json, base64 | |
| b = json.load(open("/tmp/pub_bundle.json")) | |
| raw = base64.b64decode(b["payload_b64"]) | |
| b["payload_b64"] = base64.b64encode(raw[:-1] + bytes([raw[-1] ^ 0x01])).decode() | |
| json.dump(b, open("/tmp/pub_tampered.json", "w")) | |
| PY | |
| if /tmp/clean/bin/proofbundle verify /tmp/pub_tampered.json; then | |
| echo "::error::tampered receipt verified OK — the published artifact is broken"; exit 1 | |
| fi | |
| echo "tampered receipt correctly FAILED on the published artifact" | |
| - name: Published bytes — the shipped test suite runs green from the EXTRACTED sdist (PKG-01 + L6-01) | |
| # The documented invariant is "pip install proofbundle[eval,test] && pytest is green" from an | |
| # extracted sdist. Enforce it: install the [test] extra (the deps needed to RUN the shipped tests — | |
| # hypothesis/PyYAML/jsonschema/sd-jwt), extract the sdist tree and run its tests from OUTSIDE a git | |
| # checkout. Two things this pins: (1) a repo-context test that reads a pruned artifact (e.g. | |
| # audit_artifacts/findings_register_361.json) is conftest-skipped, never a silent failure; (2) the | |
| # optional-dep modules degrade to a clean module-level skip on a bare [eval] install rather than a | |
| # collection error (L6-01). Stops the invariant from drifting false again. | |
| run: | | |
| /tmp/clean/bin/pip install --quiet "$(ls dist/*.tar.gz)[test]" | |
| mkdir -p /tmp/sdisttree | |
| tar -xzf "$(ls dist/*.tar.gz)" -C /tmp/sdisttree --strip-components=1 | |
| cd /tmp/sdisttree | |
| test ! -e audit_artifacts/findings_register_361.json # confirm it really is pruned from the sdist | |
| /tmp/clean/bin/python -m pytest tests/ -q -p no:cacheprovider | |
| reusable-attest-dryrun: | |
| # Live-exercise the SLSA-L3 reusable workflow's build+determinism half (no attestation on PRs). | |
| # Fork-PRs get no id-token:write, so don't start the job there (fork-pr-isolation line). | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| # P3 / PR #102 (integrated into 3.6.1): a called reusable workflow may not exceed the caller's | |
| # permissions; reusable-build-attest's build-attest job declares id-token:write + attestations:write, so | |
| # grant them to THIS job (the workflow-wide `contents: read` stays unchanged for the other jobs). Without | |
| # this the run is refused at startup (the observed daily "Startup failure"), because permissions are | |
| # validated at job level before any step runs and dry_run: true does not bypass it. | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| uses: ./.github/workflows/reusable-build-attest.yml | |
| with: | |
| dry_run: true |