runward

RW™ · V0.36.1

Docs · Décisions · ADR-0048

ADR-0048.

the release carries verifiable proof, and the builder paths we refuse

Date: 2026-08-11 Status: accepted (ratified 2026-08-11 — see Ratification)

Context

Until 2026-08-11 a GitHub release of runward carried the SBOM and nothing else. The provenance existed — npm audit signatures returns it, and deps.dev relays verified: true for every version since 0.15.0 — but it lived on the npm registry, and a GitHub release is where people look. The project's own OpenSSF Scorecard read the release assets and answered, verbatim, "Project has not signed or included provenance with any releases": the public scorecard contradicted the project's strongest claim, across at least three releases.

Fixing the attachment (0.33.3) surfaced two deeper findings, both established at source level against ossf/scorecard v5.5.0 (commit c395761, cloned and read 2026-08-11):

  1. The score is a filename convention. The Signed-Releases check tests strings.HasSuffix on asset names over the last five releases — signatures are .asc, .minisig, .sig, .sign, .sigstore, .sigstore.json (8 points), provenance is .intoto.jsonl alone (10 points), floor(sum / releases-with-assets) — and never downloads an asset (probes/releasesAreSigned/impl.go:43,69, probes/releasesHaveProvenance/impl.go:43,70, checks/evaluation/signed_releases.go:111-152, clients/githubrepo/releases.go:78-81). No cryptographic verification exists anywhere in the tool; its own documentation says "The check does not verify the signatures". A zero-byte file with the right name scores the same as a real bundle. The check's releasesHaveVerifiedProvenance probe is experimental, belongs to no check, and itself only relays a boolean deps.dev takes from the npm registry.
  2. The verifier GitHub hands to everyone could not verify the artifact GitHub hosts. With no flag, gh attestation verify runward-0.33.3.tgz --repo stranxik/runward answered HTTP 404, measured 2026-08-11: gh filters on the provenance predicate by default and the GitHub attestation store held only the SBOM attestation (predicate https://cyclonedx.org/bom) for that digest — the build provenance lived on npm alone, which gh does not read.

An enterprise that trusts the score is therefore trusting a naming convention. What it should be handed instead is a replayable verification, which is what this ADR wires.

Decision

1. Every release attaches the tarball and two attestation bundles, under locked names. runward-X.Y.Z.intoto.jsonl is the SBOM attestation; runward-X.Y.Z.provenance.intoto.jsonl is the build provenance, emitted by actions/attest-build-provenance against the same tarball that is published. A missing bundle reds the release (test -s). Three files suffice for a fully offline verification: the tarball, a bundle, and a pre-fetched trusted root (docs/verifying-a-release.md).

2. The asset names never change to .sigstore.json. That suffix is classed as a signature (8 points) where .intoto.jsonl is provenance (10) — probes/releasesAreSigned/impl.go:43 at tag v5.5.0. A well-intentioned rename would silently downgrade the release's category. The score is a side effect and is never the goal; the goal is verifiability from the release page alone. This clause exists so nobody "improves" the naming.

3. slsa-github-generator is refused, not merely unchosen. Three reasons, each sufficient: its repository carries an official non-maintenance notice (merged to main 2026-08-07, PR #4515); its Node.js builder never left beta; and its publish path uses a long-lived NPM_TOKEN, which would regress this project from OIDC trusted publishing — the one property of the chain praised in every audit of it — to exactly the durable secret the posture guard forbids (test/unit/regulated-posture.test.js).

4. The isolated-builder path is deferred, with named preconditions — not refused. GitHub documents one mechanism for the build level above the current one: moving the build into a reusable workflow, verifiable by a reader with gh attestation verify --signer-workflow. The gain is real and the effort bounded (1 to 2 maintainer-days), but it is the only gesture of this family that touches a publish chain that demonstrably works, so it waits for: a canary release; byte-for-byte reproducibility re-proven on that canary before any public sentence; and the no-overclaim guard question settled first, because the admissible citation currently passes the guard only through the dot in "v1.0", which is an accident of a regex and not a decision.

5. The four releases in the current Scorecard window are backfilled, under a hash rule. Their attestations are real and were produced at build time — they live in the GitHub attestation store and on npm; only the attachment to the GitHub release is missing, and v0.33.2 through v0.32.0 do not even carry their tarball. Per release: the npm tarball, hash-matched against the attestation subject, plus the SBOM bundle restored via gh attestation download; a dated line in the release notes says when the assets were attached (the Sigstore timestamps independently date the attestations to the original release day). On any hash mismatch: abstain and report. An attachment that cannot be hash-verified is exactly the hollow proof the rest of this ADR refuses.

Consequences

  • A reader verifies a release offline from three files, with the verifier identity pinned; the procedure, its expected outputs and its negative controls are in docs/verifying-a-release.md, including the paragraph on what none of it proves (the signature establishes who attested these bytes, never that the code is sound).
  • .github/workflows/verify-release.yml replays that procedure against the published artifacts after every release, negative control included. It is executable documentation, never a substitute for the reader running the commands themselves.
  • Scorecard's Signed-Releases will converge upward as a side effect of the window filling with compliant releases. No sentence anywhere presents that number as a verification.
  • No SLSA level is claimed for runward anywhere, in any phrasing; what is written is descriptive and cited (the guard's SLSA level asserted without an assessment rule stays the ceiling).

Ratification

Ratified 2026-08-11 by the maintainer, on the investigation of the same day: three axes (the check at source level, the enterprise verification path, the provenance chain), each counter-expertised with re-execution of every reported command; all three held.

Reevaluation trigger (mandatory, dated)

Trigger set on: 2026-11-05.

The decision is wrong and must be revisited if any holds: a consumer reports that the published proof (provenance + attested SBOM) does not answer the question they actually had to answer, and names which one; a release ships whose artifacts a third party could not re-verify from the published instructions alone; or the attestation formats used here are superseded by one the ecosystem's verifiers read instead.

Watched via: the release verification workflow (verify-release.yml) and any consumer report.

← Tous les ADR