Skip to content

subject-checksums: LF-only checksums file on a Windows runner parses as ONE subject; remaining files silently unattested #440

Description

@EvilHumphrey

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions