Skip to content

ci(release): move to actions/attest, and let the SBOM name the tool that built the release - #127

Merged
donislawdev merged 3 commits into
masterfrom
ci/attest-action
Aug 12, 2026
Merged

ci(release): move to actions/attest, and let the SBOM name the tool that built the release#127
donislawdev merged 3 commits into
masterfrom
ci/attest-action

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

Two changes to the release plumbing, both prompted by the v0.5.0-rc.1 run - the first time the attestation step had ever executed.

The action is deprecated

That run logged actions/attest-sbom has been deprecated, please use actions/attest instead. The action's own README says the same: deprecated, kept working as a wrapper over actions/attest, and "all of the existing action inputs are compatible".

Checked against actions/attest's action.yml rather than taken on trust: sbom-path is there with the same meaning, and supplying it is what makes the run an SBOM attestation instead of build provenance. Both inputs therefore stay exactly as they were, so this is a change of name and pinned SHA (v4.2.2, read from the API on 2026-08-12).

🔴 artifact-metadata: write is deliberately not added, and the comment now says why - because it is exactly the kind of thing a later reader "fixes" by granting it. That permission exists to create the artifact STORAGE RECORD, and create-storage-record requires push-to-registry, which defaults to false and is unused here. It is a valid permission key, so adding it would parse. It would just grant something we do not need, against the line standing right above it.

What a tag has to confirm: that the emitted predicate type stays https://spdx.dev/Document/v2.3. That is inferred from the wrapper relationship, not measured. The failure mode is cheap - the attestation step runs before the release is created, so a failure publishes nothing at all.

The SBOM could not name the tool that built it

v0.5.0-rc.1's SBOM said NOASSERTION for PyInstaller (bootloader). That was correct rather than broken: the entry carries no module, component_rows() falls back to "bundled", and the generator deliberately maps bundled to empty instead of pretending it is a version. Nothing was asking. The result was a bill of materials that could not name a component which ships a piece of itself - the bootloader - inside the executable, under its own licence.

It is now read through importlib.metadata, which does not import the package: the answer is identical, and asking would otherwise drag a build tool into --license on any machine that has it. What makes the answer honest is where it is asked - tools/sbom.py runs in the release job minutes after PyInstaller froze the archive, so the installed distribution is the one that made it.

Inside the shipped executable there is nothing to ask, and the fallback stays "bundled" rather than "-". The bootloader really is in there, and - is this module's word for "not present here"; the first attempt returned it and would have made the binary's own --license report a component as missing. So --license is unchanged for users, and the SBOM keeps saying NOASSERTION there.

Measured on the real bundle before choosing this scope, rather than guessed:

component measurement outcome
PyInstaller importlib.metadata resolves it now named
Microsoft C Runtime VCRUNTIME140.dll carries FileVersion 14.42.34438.0 left alone - reading a DLL version resource needs ctypes prototypes, a native-prototype guard and a non-Windows fallback
libffi libffi-8.dll carries no version resource at all stays NOASSERTION, which is the honest answer rather than a guess

This closes half of the PyInstaller backlog item: what built the release is now reported. Pinning which version builds it is untouched and remains a separate decision - the two are independent.

Verified

python -m pytest tests - 1121 passed - and smoke_gui.py OK, after the change to legal.py; the workflow edit was additionally parsed with yaml.safe_load, since a syntax error there would only surface at tag time.

Two guards in tests/test_sbom.py, both proved by mutation and registered: one for the version being reported, one for absence not becoming a guess. Generated locally, the SBOM now reads PyInstaller (bootloader) 6.21.0 while libffi and the Microsoft C Runtime stay NOASSERTION.

donislawdev and others added 3 commits August 12, 2026 20:45
The v0.5.0-rc.1 run - the first time this step ever executed - logged that
actions/attest-sbom is deprecated. Its README says the same: deprecated in
favour of actions/attest, kept working as a wrapper over it, and "all of the
existing action inputs are compatible".

Verified against actions/attest's own action.yml rather than taken on trust:
sbom-path is there with the same meaning, and providing it is what makes this an
SBOM attestation rather than build provenance. Both inputs stay as they were, so
this is a change of name and pinned SHA.

artifact-metadata: write is deliberately NOT added, and the comment now says
why: it is needed for the artifact storage record, which requires
push-to-registry, which defaults to false and is unused here. Granting a
permission we do not need would contradict the line above it.

What a tag will have to confirm: that the emitted predicate type stays
https://spdx.dev/Document/v2.3. The step runs before the release is created, so
a failure publishes nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The row said NOASSERTION, and that was CORRECT rather than broken: the entry
carries no module, component_rows() falls back to "bundled", and the generator
deliberately maps "bundled" to empty rather than pretending it is a version.
Nothing was asking. So the SBOM could not name a component that ships a piece of
itself - the bootloader - inside the executable, under its own licence.

It is read through importlib.metadata rather than by importing the package: the
answer is identical, and asking would otherwise drag a build tool into --license
on any machine that has it. What makes the answer honest is WHERE it is asked -
tools/sbom.py runs in the release job minutes after PyInstaller froze the
archive, so the installed distribution is the one that made it.

Inside the shipped executable there is nothing to ask, and the fallback stays
"bundled" rather than "-": the bootloader really is in there, and "-" is this
module's word for "not present here". So the binary's own --license output does
not change, and the SBOM keeps saying NOASSERTION there.

Measured on the real bundle before choosing this scope: VCRUNTIME140.dll does
carry a file version and could be read the same way, but that needs ctypes
prototypes, a native-prototype guard and a non-Windows fallback. libffi-8.dll
carries no version resource at all, so that row stays NOASSERTION honestly.

This closes half of the PyInstaller item in the backlog: what built the release
is now reported. Pinning which version builds it is untouched and stays a
separate decision.

Guards: two in tests/test_sbom.py, both proved by mutation and registered - one
for the version being reported, one for absence not becoming a guess.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tools/sbom.py -o -` does not mean stdout - it wrote a file literally named
`-` in the project root, and `git add -A` swept it into the previous commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit 0a8e012 into master Aug 12, 2026
8 checks passed
@donislawdev
donislawdev deleted the ci/attest-action branch August 12, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant