Skip to content
Merged
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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ and a batch of table and layout fixes.
built from that source":
`gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip -R donislawdev/BeanNetworkTester`.
A checksum proves the file matches the release page; this proves the page came out of this
repository's workflow, from a specific commit. It covers the SBOM too. The proof now ships
**as a file** as well, `BeanNetworkTester-vX.Y.Z.sigstore.json`, so `--bundle` checks the
archive from a mirror, or with no network at all.
repository's workflow, from a specific commit. The proof also ships **as a file**,
`BeanNetworkTester-vX.Y.Z.sigstore.json`, so the archive can be checked against evidence that
travelled with it. The README gives the exact command - it needs `--repo` and
`--predicate-type` as well as `--bundle`.

- **Every release now carries an SBOM, signed against the download.** A standard SPDX file listing
each third-party component with its version, licence and source - the same list `--license`
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1489,15 +1489,19 @@ A checksum proves the file matches what the release page says. This proves the r
was produced by this repository's own workflow, from a specific commit, on a GitHub-hosted runner.
The same command also verifies the SBOM that ships beside the archive.

That command asks GitHub. The proof also ships **as a file**, `BeanNetworkTester-vX.Y.Z.sigstore.json`,
so you can check the archive without one:
That command asks GitHub which attestations exist. The proof also ships **as a file**,
`BeanNetworkTester-vX.Y.Z.sigstore.json`, so you can check the archive against evidence that
travelled with it:

```bash
gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip --bundle BeanNetworkTester-v0.5.0.sigstore.json
gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip --bundle BeanNetworkTester-v0.5.0.sigstore.json --repo donislawdev/BeanNetworkTester --predicate-type https://spdx.dev/Document/v2.3
```

Useful if you got the files from a mirror, or from a machine that cannot reach the API - the
evidence travelled with the download instead of living somewhere you have to trust separately.
Both extra flags are needed and neither is decoration. `--repo` names the repository the
signing identity has to match. `--predicate-type` is there because this bundle is the **SBOM**
attestation, and `gh` looks for a build-provenance one unless you say otherwise - without it you
get "no attestations found with predicate type", which is the tool being precise rather than
broken. When the file is good it prints nothing and exits 0. Change one byte and it exits 1.

Three statements, and it is worth knowing they answer different questions. The **signature** says
who stands behind the file. The **bundle above** binds this exact archive to its bill of materials,
Expand Down
14 changes: 9 additions & 5 deletions README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -1352,15 +1352,19 @@ Suma kontrolna dowodzi, 偶e plik zgadza si臋 z tym, co m贸wi strona wydania. To
strona wydania powsta艂a z workflow tego repozytorium, z konkretnego commita, na maszynie GitHuba.
Tym samym poleceniem sprawdzisz te偶 SBOM, kt贸ry jedzie obok archiwum.

To polecenie pyta GitHuba. Dow贸d jedzie te偶 **jako plik**, `BeanNetworkTester-vX.Y.Z.sigstore.json`,
wi臋c archiwum sprawdzisz bez pytania kogokolwiek:
To polecenie pyta GitHuba, jakie atestacje istniej膮. Dow贸d jedzie te偶 **jako plik**,
`BeanNetworkTester-vX.Y.Z.sigstore.json`, wi臋c archiwum sprawdzisz wobec dowodu, kt贸ry
przyjecha艂 razem z nim:

```bash
gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip --bundle BeanNetworkTester-v0.5.0.sigstore.json
gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip --bundle BeanNetworkTester-v0.5.0.sigstore.json --repo donislawdev/BeanNetworkTester --predicate-type https://spdx.dev/Document/v2.3
```

Przydaje si臋, gdy pliki masz z kopii lustrzanej albo na maszynie bez dost臋pu do API - dow贸d
przyjecha艂 razem z pobranym plikiem, zamiast le偶e膰 w miejscu, kt贸remu trzeba osobno ufa膰.
Obie dodatkowe flagi s膮 potrzebne i 偶adna nie jest ozdob膮. `--repo` m贸wi, z jakim repozytorium
ma si臋 zgadza膰 to偶samo艣膰 podpisuj膮ca. `--predicate-type` jest dlatego, 偶e ten bundle to atestacja
**SBOM-u**, a `gh` domy艣lnie szuka atestacji prowenancji - bez tej flagi dostaniesz 鈥瀗o attestations
found with predicate type", czyli narz臋dzie jest precyzyjne, a nie zepsute. Przy dobrym pliku nic
nie wypisuje i ko艅czy si臋 zerem. Zmie艅 jeden bajt, a sko艅czy si臋 jedynk膮.

Trzy o艣wiadczenia, i warto wiedzie膰, 偶e odpowiadaj膮 na r贸偶ne pytania. **Podpis** m贸wi, kto za tym
plikiem stoi. **Powy偶szy bundle** wi膮偶e dok艂adnie to archiwum z jego list膮 sk艂adnik贸w i powstaje
Expand Down
10 changes: 9 additions & 1 deletion tests/test_cli_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@

from fakes import ROOT, check

IGNORE = {"--help", "--noconfirm", "--noconsole", "--onefile", "--uac-admin"}
# Flags the READMEs mention that belong to OTHER programs, so the parser will never
# have them. PyInstaller's are here because the build section explains what the spec
# does and does not pass; `gh`'s are here because the verification section spells out
# the exact command a user runs against a release, and that command needs three flags
# (measured on v0.5.0-rc.2 - without them `gh` refuses, or looks for the wrong kind of
# attestation). Keep this list short and keep knowing whose each flag is: its job is to
# stop a real typo in OUR flags hiding behind somebody else's.
IGNORE = {"--help", "--noconfirm", "--noconsole", "--onefile", "--uac-admin",
"--bundle", "--repo", "--predicate-type"}
READMES = ("README.md", "README.pl.md")


Expand Down
10 changes: 10 additions & 0 deletions tests/test_mutation_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,16 @@
"new": r' return term.rstrip("\\")',
"test": "test_a_term_may_not_end_in_an_escape_that_swallows_the_separator",
},
{
# The command in the README stops matching what we attest, and every user who
# follows it gets an error. Nothing here runs `gh`, so only this pairing can
# notice.
"label": "release: the documented verify command loses its predicate type",
"file": "README.md",
"old": " --predicate-type https://spdx.dev/Document/v2.3",
"new": "",
"test": "test_the_documented_verify_command_matches_what_we_actually_attest",
},
{
# The escaping removed as "noise" - and the tool that writes our supply-chain
# hashes can again be pointed at a different PyPI endpoint by a `?` in a
Expand Down
38 changes: 38 additions & 0 deletions tests/test_version_and_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,3 +859,41 @@ def test_the_release_workflow_grants_write_on_the_job_not_the_whole_file():
for scope in ("contents: write", "id-token: write", "attestations: write"):
check(f"the release job still asks for {scope}",
f" {scope}" in body, "(a six-space indent is the job's own block)")


def test_the_documented_verify_command_matches_what_we_actually_attest():
"""The README hands users a command. It has to be the command that works.

馃敶 Paid for on 2026-08-19, on a real release: the first version of that line was
`gh attestation verify <zip> --bundle <bundle>` and it fails twice over. `gh`
refuses without `--repo` or `--owner`, and then looks for a build-provenance
attestation and reports "no attestations found with predicate type" - because the
bundle we publish is the SBOM attestation, made after signing over the bytes a
user downloads. Both are the tool being precise; the documentation was wrong.

So this pins the two halves together: the predicate type the README tells people
to ask for must be the one `attest-release.yml` actually produces. Change the
workflow to attest something else and the README's command starts failing for
every user, silently, because nothing here runs `gh`.
"""
workflow = os.path.join(ROOT, ".github", "workflows", "attest-release.yml")
with open(workflow, encoding="utf-8") as handle:
yaml_body = handle.read()
# An SBOM attestation is what `sbom-path` makes, and its predicate type is SPDX.
makes_sbom = "sbom-path:" in yaml_body
check("the attestation workflow still makes an SBOM attestation", makes_sbom,
"(if this changed, the predicate type in both READMEs has to change with it)")

for readme in ("README.md", "README.pl.md"):
with open(os.path.join(ROOT, readme), encoding="utf-8") as handle:
text = handle.read()
line = [ln for ln in text.splitlines() if "gh attestation verify" in ln
and "--bundle" in ln]
check(f"{readme} documents the offline verify command", bool(line))
command = line[0] if line else ""
check(f"{readme}: it names the repository", "--repo " in command, f"({command[:120]})")
check(f"{readme}: it names the predicate type",
"--predicate-type " in command, f"({command[:120]})")
if makes_sbom:
check(f"{readme}: the predicate type is the SPDX one the workflow makes",
"https://spdx.dev/Document/v2.3" in command, f"({command[:160]})")
Loading