Summary
When subject-checksums points at a checksums file with LF line endings and the job runs on a Windows runner, the parser treats the entire file as a single record. Only the first digest is attested, and every other file listed is silently left unattested — the action still reports success.
Root cause
src/subject.ts splits the checksums file with the platform line ending:
const records: string[] = checksums.split(os.EOL).filter(Boolean)
On Windows, os.EOL is "\r\n", so an LF-only file never splits. The whole file becomes one record; record.indexOf(' ') then takes the first 64 hex chars as the digest (which passes HEX_STRING_RE), and everything after the first space — including the newline and all subsequent lines — becomes the subject name.
LF-only checksums files are the common case, not an edge case: GNU sha256sum/shasum emit LF, files checked into Git are typically LF-normalized, and scripts that generate checksums with explicit LF (for cross-platform verifiability) do too.
Observed in the wild
Real occurrence: EvilHumphrey/LegendCTL run 28701603914 on windows-2025, with a two-line LF SHA256SUMS.txt in shasum binary format:
63970f12… *ZDUltimateLegend-v2.5.0-Setup.exe
42ecf3a9… *ZDUltimateLegend-v2.5.0-windows.zip
The resulting attestation bundle contained ONE subject:
name: 'ZDUltimateLegend-v2.5.0-Setup.exe\n42ecf3a9… *ZDUltimateLegend-v2.5.0-windows.zip\n'
digest: 63970f12…
The zip had no attestation; gh attestation verify <zip> --repo … failed with HTTP 404 for end users. The step itself succeeded, so nothing flagged the gap at build time.
Suggested fix
Split on any line ending instead of the platform one:
const records: string[] = checksums.split(/\r?\n/).filter(Boolean)
Optionally, also reject subject names containing \n as a defense-in-depth guard — a multi-line "filename" is never valid, and the guard would turn this silent partial attestation into a loud failure.
Workaround
Use subject-path with explicit paths/globs to the artifacts instead of subject-checksums.
Summary
When
subject-checksumspoints at a checksums file with LF line endings and the job runs on a Windows runner, the parser treats the entire file as a single record. Only the first digest is attested, and every other file listed is silently left unattested — the action still reports success.Root cause
src/subject.tssplits the checksums file with the platform line ending:On Windows,
os.EOLis"\r\n", so an LF-only file never splits. The whole file becomes one record;record.indexOf(' ')then takes the first 64 hex chars as the digest (which passesHEX_STRING_RE), and everything after the first space — including the newline and all subsequent lines — becomes the subject name.LF-only checksums files are the common case, not an edge case: GNU
sha256sum/shasumemit LF, files checked into Git are typically LF-normalized, and scripts that generate checksums with explicit LF (for cross-platform verifiability) do too.Observed in the wild
Real occurrence: EvilHumphrey/LegendCTL run 28701603914 on
windows-2025, with a two-line LF SHA256SUMS.txt in shasum binary format:The resulting attestation bundle contained ONE subject:
The zip had no attestation;
gh attestation verify <zip> --repo …failed with HTTP 404 for end users. The step itself succeeded, so nothing flagged the gap at build time.Suggested fix
Split on any line ending instead of the platform one:
Optionally, also reject subject names containing
\nas a defense-in-depth guard — a multi-line "filename" is never valid, and the guard would turn this silent partial attestation into a loud failure.Workaround
Use
subject-pathwith explicit paths/globs to the artifacts instead ofsubject-checksums.