diff --git a/.github/workflows/AGENTS.md b/.github/workflows/AGENTS.md new file mode 100644 index 0000000..07cd07c --- /dev/null +++ b/.github/workflows/AGENTS.md @@ -0,0 +1,50 @@ +# CI/Workflow Knowledge Base + +## OVERVIEW + +GitHub Actions workflows for lint, autoformat, dependency review, OSV scanning, and OpenSSF +Scorecard. All workflows are hardened by default; the authoritative security policy lives in +`windlasstech/.github`. + +## STRUCTURE + +```text +.github/workflows/ +├── autofix.yml # autoformat PRs/pushes with Prettier, golangci-lint fmt, shfmt +├── dependency-review.yml # PR/merge-group dependency review gate +├── lint.yml # markdownlint, actionlint, golangci-lint +├── osv-scanner.yml # scheduled + PR/push vulnerability scanning +└── scorecard.yml # OpenSSF Scorecard on branch-protection/main/schedule events +``` + +## WHERE TO LOOK + +| Task | File | Notes | +| -------------------------- | ----------------------- | ------------------------------------------------------ | +| Required code-quality gate | `lint.yml` | Runs on PR and push to `main`. | +| Autoformat a PR | `autofix.yml` | Pushes formatter fixes via `autofix.ci`. | +| Dependency review | `dependency-review.yml` | Ignores docs/markdown-only changes. | +| Vulnerability scanning | `osv-scanner.yml` | Reusable org workflow; runs on schedule too. | +| Supply-chain scorecard | `scorecard.yml` | Reusable org workflow. | +| Local hook equivalents | `lefthook.yml` | Pre-commit formatters → linters; commit-msg DCO check. | + +## CONVENTIONS + +- **SHA-pinning**: every third-party action is pinned to a full SHA with a comment tag. +- **Hardened runner**: every job starts with `step-security/harden-runner` in + `egress-policy: audit`. +- **Minimal permissions**: top-level `permissions: {}` with job-level elevation only where required. +- **Reusable org workflows**: security workflows (`dependency-review.yml`, `osv-scanner.yml`, + `scorecard.yml`) call `windlasstech/.github` workflows on the main branch. +- **No secrets**: initial workflows avoid publishing credentials; see ADRs for trusted publishing + plans. +- **Go formatting**: use `golangci-lint fmt`/`golangci-lint run`; there is no standalone `gofmt` CI + step. + +## ANTI-PATTERNS + +- Do not use floating tags for third-party actions (e.g., `@v3`). Always pin a SHA. +- Do not add `contents: write` to a job that does not need it. +- Do not add a workflow that bypasses `windlasstech/.github` dependency-review or OSV scanner gates. +- Do not reference reusable workflows by mutable branch or tag; use the org’s main branch refs. +- Do not add long-lived cloud credentials; prefer OIDC where elevation is required. diff --git a/AGENTS.md b/AGENTS.md index 6a6fc32..ceb1146 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,8 @@ # PROJECT KNOWLEDGE BASE -- **Generated:** 12026-06-28 -- **Commit:** 7c6fcbe -- **Branch:** setup/initial-tooling +- **Generated:** 12026-07-07 +- **Commit:** e40a91e +- **Branch:** docs/adr-npm-and-release-asset-profiles ## OVERVIEW @@ -16,6 +16,7 @@ the Go implementation tree has not been added yet. ├── AGENTS.md # this file ├── README.md / README.ko.md ├── docs/decisions/ # architecture ADRs (see docs/decisions/AGENTS.md) +├── .github/workflows/ # CI/CD workflows (see .github/workflows/AGENTS.md) ├── .golangci.yml # Go format/lint policy ├── lefthook.yml # git hooks ├── mise.toml / mise.lock # pinned runtimes and tools @@ -27,8 +28,9 @@ the Go implementation tree has not been added yet. | Task | Location | Notes | | ----------------------- | ---------------------------------------------------------- | ---------------------------------------- | -| Why a decision was made | `docs/decisions/` | MADR 4.0.0 ADRs, numbered `0000`–`0012`. | +| Why a decision was made | `docs/decisions/` | MADR 4.0.0 ADRs, numbered `0000`–`0053`. | | Bootstrap / dev setup | `README.md`, `mise.toml` | `mise install` + `pnpm install`. | +| CI / workflow security | `.github/workflows/` | See `.github/workflows/AGENTS.md`. | | Lint/format policy | `.golangci.yml`, `.prettierrc`, `.markdownlint-cli2.jsonc` | Go, Markdown, shell. | | Git hooks / DCO | `lefthook.yml` | Commit-msg `Signed-off-by:` check. | | Dependency security | `pnpm-workspace.yaml` | Cooldown, trust policy, frozen lockfile. | diff --git a/docs/decisions/0021-use-profile-specific-buildtype-uris.md b/docs/decisions/0021-use-profile-specific-buildtype-uris.md index 0dc2d2b..d04f584 100644 --- a/docs/decisions/0021-use-profile-specific-buildtype-uris.md +++ b/docs/decisions/0021-use-profile-specific-buildtype-uris.md @@ -1,7 +1,7 @@ --- parent: Decisions nav_order: 21 -status: accepted +status: superseded by ADR-0042 date: 12026-06-29 decision-makers: Yunseo Kim --- diff --git a/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md b/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md new file mode 100644 index 0000000..955f6f0 --- /dev/null +++ b/docs/decisions/0029-use-windlass-generated-slsa-provenance-for-npm-publish.md @@ -0,0 +1,268 @@ +--- +parent: Decisions +nav_order: 29 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Use Windlass-Generated SLSA Provenance for npm Publish + +## Context and Problem Statement + +ADR 0002 chose an extensible trusted reusable workflow foundation, ADR 0003 refined it into a thin +core with profile-owned reusable workflows, ADR 0013 scoped the initial JS/TS profile to npm package +releases, ADR 0024 selected OIDC trusted publishing without npm publish secrets, and ADR 0028 chose +SHA-pinned reusable workflow builder identity. + +The remaining provenance publication decision is which attestation should be canonical for the +production JS/TS npm package SLSA3 profile. npm trusted publishing can automatically generate npm +provenance for supported public packages from supported public repositories. npm publish also +accepts an externally generated provenance bundle through `--provenance-file`. GitHub artifact +attestations can generate SLSA build provenance through `actions/attest`, but their primary +distribution and verification surface is GitHub's artifact attestation system rather than npm +package publication. + +SLSA v1.2 Build L3 expects provenance to be generated or verified by the trusted build platform's +control plane, with the verifier checking the provenance signature, artifact subject digest, +`predicateType`, trusted `builder.id`, `buildType`, and expected `externalParameters`. For this +project, the trusted builder identity, build type schema, external parameter schema, package subject +rules, and verification contract are Windlass-owned. The next decision is whether canonical npm +package provenance should be generated by npm's trusted-publishing automation, by the Windlass +reusable workflow and submitted to npm, or by a separate GitHub artifact attestation path. + +Should the production JS/TS npm package SLSA3 profile treat npm-generated provenance, Windlass- +generated SLSA provenance, GitHub artifact attestations, or a combination as the canonical +provenance and verification surface? + +## Decision Drivers + +- Keep canonical provenance generation inside the trusted reusable workflow boundary selected by ADR + 0002 and ADR 0003. +- Emit SLSA build provenance using `predicateType: https://slsa.dev/provenance/v1`. +- Preserve ADR 0028's SHA-based `builder.id` as the machine trust anchor for production builds. +- Preserve ADR 0021's Windlass-owned `buildType` URI and profile-specific `externalParameters` + schema as verifier-relevant contract, not merely documentation. +- Keep npm trusted publishing as the publish authentication mechanism selected by ADR 0024 while + avoiding long-lived npm publish secrets. +- Publish provenance through npm's package ecosystem when possible so package consumers can discover + provenance through npm registry tooling and package-version metadata. +- Avoid presenting two unrelated canonical provenance records for the same npm package tarball. +- Keep GitHub artifact attestations available as a future or secondary distribution path without + making them the canonical npm package provenance surface. + +## Considered Options + +- Generate canonical SLSA provenance in the Windlass reusable workflow and submit it to npm with + `npm publish --provenance-file`. +- Use npm trusted-publishing automatic provenance as the canonical provenance. +- Use npm trusted-publishing automatic provenance as canonical and emit Windlass or GitHub + attestations as secondary evidence. +- Use GitHub artifact attestations as canonical and do not publish canonical provenance through npm. +- Publish without provenance and rely only on package registry integrity metadata. + +## Decision Outcome + +Chosen option: "Generate canonical SLSA provenance in the Windlass reusable workflow and submit it +to npm with `npm publish --provenance-file`", because the production profile's SLSA Build +L3-oriented trust contract is Windlass-owned and must be verifiable through Windlass `builder.id`, +`buildType`, and `externalParameters` rather than through an npm-generated default predicate shape. + +The production JS/TS npm package SLSA3 profile should generate the canonical SLSA provenance v1 +predicate inside the Windlass reusable workflow. The provenance should use: + +- `_type: https://in-toto.io/Statement/v1`; +- `predicateType: https://slsa.dev/provenance/v1`; +- `subject` entries that identify the published npm package artifact and its verified tarball digest + values; +- `buildDefinition.buildType` set to + `https://github.com/windlasstech/slsa-builder/buildtypes/js-ts-npm-package/v1`; +- complete verifier-relevant `buildDefinition.externalParameters` as defined by the profile build + type specification; +- `runDetails.builder.id` set to the SHA-based reusable workflow identity selected by ADR 0028; +- `runDetails.builder.version`, `builderDependencies`, `metadata`, `resolvedDependencies`, and + byproducts as defined by the profile architecture specification. + +The profile should sign the provenance as a Sigstore DSSE bundle suitable for npm's +`provenance-file` publish path. The profile should publish the package with +`npm publish --provenance-file=`, or the equivalent npm configuration that submits the same +external provenance bundle. The profile should not rely on npm trusted publishing's automatic +provenance as the canonical SLSA provenance for this builder mode. + +The profile should continue to use npm trusted publishing and GitHub Actions OIDC for publish +authentication as decided in ADR 0024. In this decision, npm trusted publishing authenticates the +registry mutation; Windlass-generated SLSA provenance describes and authenticates the trusted build. + +The profile should fail rather than silently falling back to npm automatic provenance, token-based +publish, unsigned provenance, local-only provenance, or a GitHub-only attestation when the +production SLSA3 profile is expected to publish a package. A materially different provenance +publication mode should use a later ADR and, if its trust properties differ, a distinct builder +identity. + +Canonical verification for a package published by this profile should require all of the following: + +- the npm package name, version, registry package URL, tarball name, and tarball digest identify the + published package version expected by the verifier; +- the downloaded or registry-resolved tarball digest matches the provenance `subject` digest; +- the provenance envelope signature verifies against the trusted Sigstore root and expected GitHub + Actions signer identity for the Windlass reusable workflow; +- `predicateType` is exactly `https://slsa.dev/provenance/v1`; +- `runDetails.builder.id` matches the SHA-based Windlass reusable workflow identity trusted for the + release, as defined by ADR 0028 and the signed release metadata policy; +- `buildDefinition.buildType` is exactly + `https://github.com/windlasstech/slsa-builder/buildtypes/js-ts-npm-package/v1`; +- all `buildDefinition.externalParameters` match the selected build type specification and verifier + expectations, with unexpected fields rejected unless explicitly allowed by that specification; +- source repository, source ref, resolved source commit, package directory, publish intent, package + manager selection, runtime policy, and release event context match the profile's expected values; +- npm registry provenance metadata links the published package version to the submitted Windlass + provenance bundle. + +Unsigned workflow outputs such as `package-tarball-sha256` and `package-tarball-sha512` are useful +release handles, but they are not substitutes for signed provenance verification. + +### Consequences + +- Good, because the canonical provenance is generated within the Windlass trusted reusable workflow + boundary rather than by caller-controlled steps. +- Good, because Windlass `builder.id`, `buildType`, and `externalParameters` become enforceable + verifier contract fields. +- Good, because the provenance can use SLSA build provenance v1 from the beginning instead of + inheriting older npm or upstream builder predicate shapes. +- Good, because npm remains the package ecosystem distribution surface for provenance, aligning with + SLSA guidance that package registries should distribute provenance alongside artifacts when they + can. +- Good, because npm trusted publishing still removes long-lived npm write tokens from the production + profile. +- Good, because GitHub artifact attestations remain available for future release asset, SBOM, + monitor, or secondary evidence workflows without becoming the canonical npm package provenance. +- Neutral, because npm automatic provenance may still be useful for simpler npm workflows outside + the Windlass SLSA3 profile. +- Bad, because Windlass must implement, sign, test, publish, and document its own provenance bundle + generation and verification behavior. +- Bad, because the profile must keep npm `provenance-file` compatibility under test as npm CLI and + registry behavior evolves. +- Bad, because consumers need Windlass-specific verifier policy in addition to generic npm + provenance awareness. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, release documentation, and verifier guidance define: + +- Windlass-generated SLSA build provenance v1 as the canonical provenance for the production + profile; +- `npm publish --provenance-file` or an equivalent external provenance-file configuration as the npm + submission path; +- no reliance on npm trusted-publishing automatic provenance as the canonical production provenance; +- the exact provenance subject naming and `sha256`/`sha512` digest encoding for the published npm + package tarball; +- the complete `buildType`, `externalParameters`, `internalParameters`, `resolvedDependencies`, + `builder.id`, `builder.version`, `builderDependencies`, invocation metadata, and byproducts + schema; +- Sigstore DSSE bundle generation and signing requirements; +- npm trusted publishing requirements for publish authentication, separate from provenance + generation; +- failure behavior when the provenance bundle cannot be generated, signed, submitted, linked by npm, + or later verified; +- verifier checks for signature, subject digest, `predicateType`, SHA-based `builder.id`, Windlass + `buildType`, complete `externalParameters`, source identity, package identity, runtime policy, and + npm registry linkage; +- documentation that GitHub artifact attestations and workflow outputs are secondary evidence unless + a later ADR makes them canonical for a distinct profile or builder mode. + +Implementation review should verify that production publishes cannot silently downgrade to npm +automatic provenance, token-based publish, unsigned provenance, local-only provenance, or +GitHub-only attestations under the same production SLSA3 builder identity. + +## Pros and Cons of the Options + +### Generate Windlass provenance and submit it with `npm publish --provenance-file` + +The reusable workflow generates the SLSA v1 provenance predicate, signs it as a Sigstore DSSE +bundle, and submits that bundle to npm during publish. + +- Good, because canonical provenance is controlled by the same trusted reusable workflow boundary + that claims the SLSA3 builder identity. +- Good, because it lets Windlass define and verify a profile-specific `buildType` and + `externalParameters` schema. +- Good, because it keeps provenance distribution attached to the npm package version. +- Good, because it matches the SLSA requirement that the trusted build platform generate or verify + provenance fields. +- Bad, because implementation and compatibility responsibility belongs to Windlass. +- Bad, because npm's external provenance-file behavior must be treated as part of the production + compatibility surface. + +### Use npm automatic provenance as canonical + +npm trusted publishing automatically generates and publishes provenance when supported conditions +are met. + +- Good, because it follows npm's simplest supported trusted-publishing path. +- Good, because npm CLI and registry tooling understand the resulting provenance by default. +- Good, because implementation effort is small. +- Bad, because npm controls the predicate shape and builder identity semantics. +- Bad, because Windlass cannot make its `buildType` and `externalParameters` schema the canonical + verifier contract. +- Bad, because this weakens the distinction between an npm trusted-publishing workflow and a + Windlass SLSA3 reusable builder. + +### Use npm automatic provenance as canonical and emit secondary attestations + +npm provenance remains the primary package provenance while Windlass or GitHub also emits separate +attestations for audit or defense in depth. + +- Good, because it combines npm registry UX with extra evidence. +- Good, because GitHub artifact attestations can support repository-side audit and monitoring. +- Neutral, because this may be useful for future secondary evidence or SBOM workflows. +- Bad, because consumers must understand which attestation is authoritative. +- Bad, because divergent subjects, digests, builder IDs, or build types can create confusing or + contradictory verification results. + +### Use GitHub artifact attestations as canonical + +The workflow uses GitHub artifact attestations, for example through `actions/attest`, as the primary +provenance and does not submit canonical provenance through npm. + +- Good, because GitHub provides maintained attestation generation, storage, and + `gh attestation verify` tooling. +- Good, because this may be a strong fit for future GitHub Release asset, container, or SBOM + profiles. +- Bad, because npm package consumers do not get the canonical provenance through the npm registry + package-version surface. +- Bad, because it does not use npm's package ecosystem provenance distribution path for the initial + npm package profile. + +### Publish without provenance + +The profile publishes the npm package with registry integrity metadata but without SLSA provenance. + +- Good, because implementation is minimal. +- Bad, because it does not satisfy the project's SLSA builder purpose. +- Bad, because consumers cannot verify the package against trusted builder, build type, source, and + external parameter expectations. +- Bad, because it conflicts with the production SLSA3 profile name and prior ADR direction. + +## More Information + +Reference points considered: + +- SLSA v1.2 Build Provenance defines `predicateType: https://slsa.dev/provenance/v1`, separates + `builder.id` from `buildType`, and requires `externalParameters` to capture the external build + interface. +- SLSA v1.2 Build Requirements require Build L3 provenance to be strongly resistant to tenant + forgery and generated or verified by the trusted control plane. +- SLSA v1.2 Verifying Artifacts recommends verifying provenance signature, subject digest, + `predicateType`, trusted `builder.id`, `buildType`, and expected `externalParameters`. +- SLSA v1.2 Distributing Provenance prefers package registries distributing provenance alongside + artifacts when the ecosystem supports it. +- npm trusted publishing uses OIDC for publish authentication and can automatically generate npm + provenance for supported public packages from supported public repositories. +- npm publish supports `provenance-file` for submitting an externally generated provenance bundle + and treats it as mutually exclusive with automatic `provenance` generation. +- GitHub artifact attestations and `actions/attest` can generate signed SLSA provenance + attestations, but their primary discovery and verification path is GitHub's attestation API and + `gh attestation verify`. +- SLSA GitHub Generator's Node.js builder is a relevant precedent for a reusable workflow that + builds npm package tarballs, generates provenance, and publishes with an externally supplied + provenance file. diff --git a/docs/decisions/0030-accept-registry-url-while-guaranteeing-only-npmjs-semantics.md b/docs/decisions/0030-accept-registry-url-while-guaranteeing-only-npmjs-semantics.md new file mode 100644 index 0000000..93c3864 --- /dev/null +++ b/docs/decisions/0030-accept-registry-url-while-guaranteeing-only-npmjs-semantics.md @@ -0,0 +1,236 @@ +--- +parent: Decisions +nav_order: 30 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Accept Registry URL While Guaranteeing Only npmjs Semantics + +## Context and Problem Statement + +ADR 0013 scoped the initial JS/TS profile to npm package releases, ADR 0024 selected OIDC trusted +publishing without publish secrets, and ADR 0029 selected Windlass-generated SLSA provenance +submitted with `npm publish --provenance-file` as the canonical production provenance path. + +The remaining registry-scope question is how the profile should treat its npm registry input. The +npm CLI can publish to the public npm registry by default, and it can also be configured with a +different registry URL or a scope-specific registry. However, npm trusted publishing, npm registry +provenance linkage, and `provenance-file` behavior are ecosystem features whose production semantics +are documented for npmjs.com, not as a portable guarantee for every npm-compatible registry API. + +The profile therefore needs to decide whether it should hard-code `registry.npmjs.org`, accept any +npm-compatible registry URL, support token fallback for registries that do not implement OIDC, or +split registry families into separate profiles. The choice affects the public `workflow_call` input +contract, the provenance subject and registry linkage rules, verifier expectations, and whether the +same production SLSA3 builder identity would accidentally imply support for registries whose +authentication and provenance behavior Windlass has not validated. + +Should the production JS/TS npm package SLSA3 profile accept configurable registry URLs, and if so, +which registry behaviors does Windlass guarantee? + +## Decision Drivers + +- Preserve ADR 0024's no-publish-secret invariant and avoid token fallback in the production SLSA3 + profile. +- Preserve ADR 0029's canonical `npm publish --provenance-file` submission path and npm registry + provenance linkage expectations. +- Avoid claiming production compatibility for npm-compatible registries whose OIDC authentication, + provenance-file acceptance, digest semantics, or provenance discovery behavior Windlass has not + validated. +- Keep the workflow API flexible enough for users to test compatible registries without requiring a + new input shape later. +- Make `https://registry.npmjs.org/` the only registry with guaranteed production semantics for the + initial profile. +- Keep non-npm package registries, including JSR, outside the initial npm package profile while + leaving room for future dedicated profiles. +- Avoid overloading one `builder.id` and `buildType` with materially different package ecosystem + semantics. + +## Considered Options + +- Accept a configurable npm registry URL, guarantee only npmjs production semantics, and never fall + back to token-based publishing. +- Require `https://registry.npmjs.org/` and reject every other registry URL. +- Accept arbitrary npm-compatible registry URLs and claim production support for all of them. +- Accept arbitrary npm-compatible registry URLs and use token fallback for registries without OIDC + trusted publishing. +- Add JSR and other non-npm registries to the initial JS/TS package profile. + +## Decision Outcome + +Chosen option: "Accept a configurable npm registry URL, guarantee only npmjs production semantics, +and never fall back to token-based publishing", because this preserves the no-secret production +security model while avoiding an unnecessary hard block on users who want to validate npm-compatible +custom registries themselves. + +The production JS/TS npm package SLSA3 profile should expose or preserve a registry URL input for +npm publishing. The officially guaranteed production target for that input is +`https://registry.npmjs.org/`. The profile may attempt to publish to another npm-compatible registry +URL when a caller explicitly configures one, but that configuration is outside Windlass's guaranteed +production compatibility surface unless a later ADR and specification add support for that registry +class. + +The profile must not use `NPM_TOKEN`, `NODE_AUTH_TOKEN`, OTP, or any other publish-capable token as +a fallback when a non-npmjs registry cannot complete OIDC trusted publishing, external +provenance-file submission, or provenance linkage. A custom registry path must either complete the +same tokenless publish and provenance submission flow or fail clearly. + +Documentation for the registry URL input should state that custom npm-compatible registries are +unsupported-but-not-blocked. Users who choose such registries are responsible for validating that +the registry supports all required semantics for their use case, including: + +- OIDC or equivalent tokenless publish authentication compatible with the profile's + no-publish-secret contract; +- `npm publish --provenance-file` or equivalent external provenance bundle submission behavior; +- stable npm package identity, version, tarball URL, and digest semantics compatible with the + profile's provenance subject rules; +- discoverable registry metadata that can link the package version to the submitted provenance + bundle; +- verifier-accessible package and provenance data after publication. + +Windlass verifier guidance for the initial production profile should define normative registry +linkage checks for npmjs.com. For non-npmjs npm-compatible registries, verifier behavior is +registry-specific unless a later ADR defines a supported registry class and its verification +contract. + +JSR and other non-npm package registries are out of scope for the initial JS/TS npm package profile. +They may be supported later through separate profiles, build types, provenance subject rules, +publication flows, and verifier policies. Their lower priority for the initial implementation should +not be interpreted as a rejection of future support; it only keeps the first production profile +focused on npm package publication. + +### Consequences + +- Good, because the profile remains flexible for callers that want to experiment with npm-compatible + custom registries. +- Good, because npmjs.com remains the only officially guaranteed production registry target for the + initial profile. +- Good, because the no-publish-secret and no-token-fallback invariant from ADR 0024 remains intact. +- Good, because Windlass avoids claiming SLSA3 registry compatibility for ecosystems it has not + specified or validated. +- Good, because custom registry behavior can be added later through a narrower support contract + rather than by weakening the initial profile. +- Good, because JSR and other non-npm registries can receive dedicated profiles instead of being + forced into an npm-shaped build type. +- Neutral, because users may still see a `registry-url` input and assume broader support unless + documentation is explicit. +- Bad, because custom registry users must validate compatibility themselves before relying on the + profile. +- Bad, because implementation and documentation must distinguish "accepted input" from "guaranteed + production support". + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, release documentation, and verifier guidance define: + +- a registry URL input or equivalent npm registry configuration surface; +- `https://registry.npmjs.org/` as the only officially guaranteed production registry target; +- no token-based publish fallback for any registry URL in the production SLSA3 profile; +- clear failure behavior when OIDC trusted publishing, external provenance-file submission, package + digest resolution, or provenance linkage is unavailable; +- documentation that custom npm-compatible registries are unsupported-but-not-blocked and require + user validation; +- normative npmjs.com registry linkage checks for package identity, tarball digest, and provenance + metadata; +- registry-specific verifier guidance as future work for any custom registry class that becomes + officially supported; +- JSR and other non-npm registries as out of scope for the initial npm profile and candidates for + later dedicated profiles. + +Implementation review should verify that selecting a custom registry cannot make the production +profile use publish tokens, silently omit provenance, silently switch to npm automatic provenance, +skip provenance registry linkage checks without documenting the weaker support level, or claim npmjs +verification semantics for a registry that Windlass has not specified. + +## Pros and Cons of the Options + +### Accept registry URL, guarantee only npmjs, and forbid token fallback + +The profile accepts a configurable npm registry URL, officially supports npmjs.com production +semantics, and requires all registry choices to use the same no-publish-secret production flow. + +- Good, because it keeps the workflow input flexible without over-claiming compatibility. +- Good, because npmjs.com remains the documented and tested production path. +- Good, because security properties stay stable across registry choices: no publish secret and no + token fallback. +- Good, because custom registry users can test their registry-specific behavior without Windlass + blessing it as supported. +- Bad, because documentation must be precise to avoid support ambiguity. +- Bad, because custom registries may fail at publish or verification time if they do not implement + compatible OIDC, provenance-file, or metadata behavior. + +### Require npmjs and reject every other registry URL + +The profile hard-codes or validates the registry URL to `https://registry.npmjs.org/`. + +- Good, because the support boundary is unambiguous. +- Good, because verifier semantics can be fully npmjs-specific. +- Good, because unsupported custom registry failures happen early. +- Bad, because users cannot experiment with registries that may already support compatible behavior. +- Bad, because a future custom-registry profile may need to introduce a new API surface that could + have been preserved safely from the start. + +### Accept arbitrary npm-compatible registries as officially supported + +The profile treats any npm-compatible registry URL as part of the production support contract. + +- Good, because it maximizes apparent compatibility. +- Good, because it aligns with npm CLI's general ability to target alternate registries. +- Bad, because npm-compatible package APIs do not imply compatible OIDC trusted publishing, + provenance-file submission, provenance storage, or verifier discovery semantics. +- Bad, because Windlass would be claiming support for behavior it has not specified, tested, or + secured. +- Bad, because registry-specific behavior could weaken or confuse the same `builder.id` and + `buildType` trust contract. + +### Accept custom registries with token fallback + +The profile publishes to npmjs with OIDC but falls back to publish tokens for registries that do not +support tokenless publishing. + +- Good, because it matches many existing private registry workflows. +- Good, because it would make custom registries more likely to work operationally. +- Bad, because long-lived publish credentials re-enter the production workflow boundary. +- Bad, because it conflicts with ADR 0024's OIDC-only publish authentication decision. +- Bad, because token and OIDC publish modes have materially different security properties under the + same production profile unless split by a later ADR and distinct identity. + +### Add JSR and other non-npm registries to the initial profile + +The initial JS/TS package profile supports npm plus other JavaScript/TypeScript package registries. + +- Good, because it acknowledges the broader JS/TS package ecosystem. +- Good, because JSR and similar registries may have modern provenance or OIDC-oriented publishing + models that deserve support. +- Bad, because non-npm registries have different package identity, publication, provenance, and + verifier semantics. +- Bad, because combining multiple registry families would enlarge the initial profile and dilute its + npm-specific `buildType` contract. +- Bad, because npm remains the higher-priority initial ecosystem for this project. + +## More Information + +This decision follows ADR 0024's publish authentication decision and ADR 0029's provenance +submission decision. It decides only registry URL scope for the initial production JS/TS npm package +profile. It does not decide private dependency registry authentication, staged publishing, +lower-assurance token-based profiles, registry mirrors used only for dependency installation, or +future JSR support. + +Reference points considered: + +- npm publish defaults to the public npm registry but can be configured with another registry URL or + scope-specific registry. +- npm trusted publishing is documented for npmjs.com package settings and supported cloud CI/CD + providers, with OIDC replacing long-lived publish tokens. +- npm provenance and trusted publishing have public package, public repository, supported CI, and + registry-specific limitations. +- npm publish supports `provenance-file` for submitting an externally generated provenance bundle + and treats it as mutually exclusive with automatic `provenance` generation. +- Windlass organization policy prefers OIDC over long-lived secrets and targets signed provenance + for production releases. +- SLSA verification requires checking artifact subject digest, provenance signature, + `predicateType`, trusted `builder.id`, `buildType`, and expected external parameters; registry + linkage is ecosystem-specific evidence that must be specified before it can be guaranteed. diff --git a/docs/decisions/0031-use-sigstore-signed-in-toto-release-manifest.md b/docs/decisions/0031-use-sigstore-signed-in-toto-release-manifest.md new file mode 100644 index 0000000..ef1f0a4 --- /dev/null +++ b/docs/decisions/0031-use-sigstore-signed-in-toto-release-manifest.md @@ -0,0 +1,292 @@ +--- +parent: Decisions +nav_order: 31 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Use Sigstore-Signed in-toto Release Manifest + +## Context and Problem Statement + +ADR 0028 selected full commit SHA pins as the production reusable workflow reference form and +required signed release metadata that maps each human-readable release version to the exact workflow +commit SHA and SHA-based `builder.id` values. It intentionally left the metadata format and +signature or attestation mechanism open. + +SLSA v1.2 verification expects verifiers to compare provenance against preconfigured roots of trust, +including recognized signing identities, trusted `builder.id` values, and the maximum SLSA Build +level assigned to those builders. For this project, the release metadata is not the package build +provenance itself. Instead, it is producer-defined expectation metadata that tells a verifier which +Windlass release version corresponds to which immutable reusable workflow commit and SHA-based +builder identities. + +The project needs a release metadata format that is machine-readable, signed, compatible with the +Sigstore and in-toto ecosystem used elsewhere in the profile, easy to distribute with GitHub +Releases, and precise enough for verifier trust decisions. It should also leave room to adopt a more +complete update-metadata system later if release metadata distribution grows beyond a simple signed +manifest model. + +Should Windlass release metadata use a custom signed manifest, SLSA provenance, Git tag signatures, +repository-committed metadata, TUF-style metadata, verifier-embedded allowlists, or another trust +root mechanism? + +## Decision Drivers + +- Satisfy ADR 0028's requirement for signed version-to-SHA metadata without reintroducing mutable + tag refs as the primary machine trust input. +- Align with SLSA v1.2's verifier root-of-trust model: trusted signer identity, trusted + `builder.id`, expected `buildType`, and expected external parameters. +- Use a signing mechanism that supports transparency logs and avoids long-lived signing keys in + release workflows. +- Keep release metadata separate from SLSA build provenance while using a familiar in-toto + attestation envelope. +- Let verifiers make deterministic SHA-based trust decisions without querying current GitHub tag + state as the primary check. +- Keep the initial metadata system small enough for a 1-person organization and early profile + implementation. +- Preserve a path to stronger TUF-style metadata if rollback, freeze, threshold-signing, delegation, + or metadata-mirroring needs become material. + +## Considered Options + +- Publish a Windlass-defined JSON release manifest wrapped as an in-toto Statement and signed as a + Sigstore DSSE bundle. +- Treat the release metadata file as a build artifact and publish SLSA provenance for that artifact. +- Store the version-to-SHA mapping only in GPG-signed annotated Git tag messages. +- Commit release metadata files into the repository and trust signed commits plus protected + branches. +- Use TUF-style role-separated metadata with root, targets, snapshot, and timestamp roles. +- Embed the version-to-SHA allowlist directly in verifier releases or verifier configuration. +- Publish unsigned or checksum-only release metadata. + +## Decision Outcome + +Chosen option: "Publish a Windlass-defined JSON release manifest wrapped as an in-toto Statement and +signed as a Sigstore DSSE bundle", because it gives the verifier authenticated, machine-readable +version-to-SHA expectations while staying aligned with the Sigstore/in-toto trust model already used +for package provenance. + +Each Windlass release that publishes reusable profile workflows should publish a release metadata +manifest as a GitHub Release asset. The manifest should use a Windlass-defined JSON schema carried +in an in-toto Statement. The Statement should use a Windlass-owned release metadata predicate type, +for example: + +```text +https://github.com/windlasstech/slsa-builder/predicate/release-manifest/v1 +``` + +The Statement should be signed as a Sigstore DSSE bundle by the Windlass release workflow. The +bundle is the canonical machine-verifiable release metadata artifact. Plain JSON copies, checksums, +release notes, and Git tag messages may help humans discover the mapping, but they are not +substitutes for the signed DSSE bundle. + +The release manifest predicate should identify at least: + +- the manifest schema version; +- the Windlass release version, such as `vX.Y.Z`; +- the source repository URL; +- the release tag ref; +- the release commit SHA; +- each released profile name; +- each profile workflow path; +- the exact full workflow commit SHA production consumers should pin; +- each SHA-based `builder.id` value trusted for the release; +- each profile `buildType` URI associated with that builder identity; +- the metadata generation time and release workflow identity fields needed by verifier diagnostics. + +The in-toto Statement `subject` should identify the canonical release manifest artifact by digest, +or an equivalent stable release metadata artifact whose digest lets verifiers detect tampering. The +predicate itself should carry the version-to-SHA and builder mapping. The profile architecture +specification may refine exact filenames, digest algorithms, JSON field names, and canonicalization +rules. + +Verifier trust roots for this metadata should include: + +- the configured Sigstore trust root, such as the Sigstore public-good Fulcio/Rekor root unless a + later ADR selects a different root; +- the expected GitHub Actions OIDC signer identity for the Windlass release workflow; +- the Windlass release metadata predicate type and schema version; +- the `github.com/windlasstech/slsa-builder` repository identity; +- the signed and protected release tag policy as release-integrity evidence; +- the allowed profile workflow paths, SHA-based `builder.id` shapes, and `buildType` URIs. + +The verifier should trust a SHA-based `builder.id` through this metadata only when the DSSE +signature verifies against the configured Sigstore root, the signing certificate identity matches +the expected Windlass release workflow identity, the Statement and predicate use the expected types +and schema version, and the metadata maps the requested release version to the exact workflow SHA +and `builder.id` observed in package provenance. + +Git signed annotated tags remain required release-integrity evidence and human discovery handles, +but they are not the canonical machine-readable version-to-SHA trust root. Verifiers should not need +to resolve current GitHub tag state before making the primary machine trust decision, although they +may optionally check tag signatures and tag-to-commit consistency as defense in depth. + +TUF-style metadata is not adopted for the initial release metadata mechanism. The project should +reconsider TUF-style role-separated metadata if at least one of the following becomes true: + +- Windlass maintains multiple release channels, repositories, profile families, or delegated + metadata authorities that need independent signing roles; +- there are multiple trusted maintainers or automated signers that can provide meaningful threshold + signing for root or targets metadata; +- verifier clients need automatic metadata refresh with strong rollback and freeze attack defenses; +- release metadata is mirrored outside GitHub Releases or served through a registry-like metadata + repository; +- key rotation, metadata expiry, timestamping, or delegated ownership becomes difficult to manage + with a single signed manifest per release; +- consumers require a long-lived update framework rather than per-release metadata verification. + +### Consequences + +- Good, because the release metadata is machine-readable and directly supports ADR 0028's + version-to-SHA trust model. +- Good, because Sigstore keyless signing avoids introducing long-lived release metadata signing keys + into the workflow. +- Good, because DSSE and in-toto are consistent with the project's provenance signing direction. +- Good, because verifiers can validate release metadata without trusting mutable Git tag state as + the primary machine input. +- Good, because a Windlass-owned predicate can represent release-to-builder expectations without + overloading SLSA build provenance semantics. +- Good, because Git signed tags remain useful release integrity evidence without becoming the + primary executable reference or metadata format. +- Neutral, because the manifest is Windlass-specific and requires a dedicated verifier policy. +- Bad, because Windlass must define and version a JSON schema, predicate type, file naming policy, + and verifier support. +- Bad, because this does not provide TUF's built-in rollback, freeze, delegation, threshold-signing, + or metadata-expiry model. + +### Confirmation + +This decision is confirmed when the release process, profile architecture specification, +documentation, and verifier define: + +- the Windlass release manifest predicate type and schema version; +- the exact release manifest JSON fields and canonical field semantics; +- the in-toto Statement subject rules and digest algorithms; +- Sigstore DSSE bundle generation by the Windlass release workflow; +- GitHub Release asset names and distribution expectations; +- verifier trust root configuration for Sigstore root, signer identity, repository, predicate type, + schema version, workflow path, `builder.id`, and `buildType` values; +- verifier behavior for mapping a human release version to a full workflow SHA and SHA-based + `builder.id`; +- optional defense-in-depth checks for signed tag consistency; +- documentation of the criteria that would trigger reconsideration of TUF-style metadata. + +Implementation review should verify that package provenance trust does not depend on unsigned +release notes, mutable GitHub tag lookup, repository-committed metadata alone, checksum-only files, +or verifier-embedded allowlists when a signed Windlass release manifest is expected. + +## Pros and Cons of the Options + +### Publish a Sigstore-signed in-toto release manifest + +Windlass defines a JSON release manifest predicate, wraps it in an in-toto Statement, signs the +Statement as a Sigstore DSSE bundle, and publishes the bundle with the GitHub Release. + +- Good, because it authenticates the release version to workflow SHA and `builder.id` mapping needed + by ADR 0028. +- Good, because it fits SLSA's expectation-based verification model without pretending the mapping + is package build provenance. +- Good, because it uses keyless signing, transparency logging, and an attestation envelope already + familiar to SLSA tooling. +- Good, because distribution through GitHub Releases is simple for humans and automation. +- Bad, because Windlass must maintain a custom predicate and verifier policy. +- Bad, because a single per-release manifest is weaker than TUF for update metadata lifecycle + management. + +### Publish SLSA provenance for the release metadata artifact + +The release metadata file is treated as an artifact and receives SLSA build provenance from the +release workflow. + +- Good, because it reuses the standard SLSA provenance predicate and existing provenance tooling. +- Good, because it can prove how the metadata file was produced. +- Bad, because the important payload is not how the metadata was built; it is the producer-defined + expectation mapping inside the metadata. +- Bad, because verifiers still need a Windlass-specific schema to interpret the version-to-SHA + mapping. +- Bad, because this can confuse release expectation metadata with package build provenance. + +### Store the mapping in GPG-signed annotated tags + +The release tag message contains the workflow SHA and builder IDs, and verifiers trust the signed +tag. + +- Good, because release tags are already required to be signed and protected by organization policy. +- Good, because humans can inspect tag messages with standard Git tooling. +- Bad, because tag messages are a poor machine-readable schema and schema evolution surface. +- Bad, because verifiers need Git tag object and signature verification support. +- Bad, because it risks making tag state feel like the primary trust root again, contrary to ADR + 0028's SHA-based machine trust decision. + +### Commit release metadata into the repository + +Release metadata files live in the repository and are protected by signed commits, protected +branches, and signed release tags. + +- Good, because changes are visible in normal source review and history. +- Good, because repository files are easy to browse and diff. +- Bad, because the metadata can be changed after a release unless additional immutability rules are + enforced and verified. +- Bad, because verification can become dependent on current repository or GitHub API state. +- Bad, because repository-committed metadata does not by itself create a per-release signed artifact + distributed with the release. + +### Use TUF-style metadata + +Windlass adopts role-separated metadata such as root, targets, snapshot, and timestamp metadata with +versioning, expiry, threshold signatures, and delegation. + +- Good, because TUF directly addresses rollback, freeze, key rotation, delegation, and threshold + signing for update metadata. +- Good, because it scales well to many release channels, signers, and mirrored metadata stores. +- Bad, because it is operationally heavy for the initial project and a 1-person organization. +- Bad, because threshold-signing benefits are limited if one maintainer controls all signing roles. +- Bad, because verifier and release automation complexity would grow before the project has a clear + need for TUF's stronger lifecycle guarantees. + +### Embed the allowlist in verifier releases or configuration + +The verifier binary or its local configuration contains trusted version-to-SHA mappings. + +- Good, because offline verification is simple once the verifier is updated. +- Good, because no separate metadata download path is needed. +- Bad, because verifier releases become coupled to builder releases. +- Bad, because third-party verifiers and consumers need a separate trust distribution channel. +- Bad, because metadata fixes or new builder releases require verifier updates or config changes. + +### Publish unsigned or checksum-only metadata + +Windlass publishes JSON metadata plus checksums without an authenticated attestation envelope. + +- Good, because it is easy to implement. +- Good, because checksums catch accidental corruption when delivered over a trusted channel. +- Bad, because checksums alone do not identify an authorized signer or trusted release workflow. +- Bad, because this does not satisfy ADR 0028's signed metadata requirement. +- Bad, because verifiers would need to place excessive trust in the release asset distribution + channel. + +## More Information + +This decision follows ADR 0028 and decides only the release metadata format and trust root for +mapping Windlass release versions to SHA-based reusable workflow builder identities. It does not +decide package provenance format, npm provenance submission, registry scope, release-note format, +changelog format, or final verifier command-line UX. + +Reference points considered: + +- SLSA v1.2 verification recommends verifying provenance envelope signatures, artifact subjects, + `predicateType`, trusted `builder.id`, `buildType`, and expected `externalParameters` against + configured roots of trust. +- SLSA v1.2 describes verifier roots of trust as mappings from recognized signing identities and + `builder.id` values to trusted SLSA Build levels. +- SLSA v1.2 distinguishes package build provenance from producer-defined expectations used by + verifiers. +- SLSA v1.2 Build L2 and L3 require signed provenance and stronger protection of provenance + generation, but they do not prescribe this project's release version metadata format. +- Windlass security policy requires signed release tags, signed commits on protected branches, + OIDC-preferred workflows, SHA-pinned workflow references, and signed release provenance wherever + feasible. +- ADR 0028 already made the primary machine trust decision SHA-based and required signed metadata to + preserve human-readable release versions. diff --git a/docs/decisions/0032-constrain-manual-dispatch-releases-to-version-tags.md b/docs/decisions/0032-constrain-manual-dispatch-releases-to-version-tags.md new file mode 100644 index 0000000..b51447a --- /dev/null +++ b/docs/decisions/0032-constrain-manual-dispatch-releases-to-version-tags.md @@ -0,0 +1,239 @@ +--- +parent: Decisions +nav_order: 32 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Constrain Manual Dispatch Releases to Version Tags + +## Context and Problem Statement + +ADR 0026 selected tag-push caller workflows plus constrained `workflow_dispatch` as the supported +production release caller patterns for the initial JS/TS npm package SLSA3 profile. It intentionally +left the exact manual-dispatch constraints open, including whether the selected ref must be a tag, +how the selected ref relates to the package version, and how reruns differ from new releases. + +ADR 0028 later required production consumers to execute the Windlass reusable workflow by full +commit SHA and to use SHA-based `builder.id` values. That decision does not weaken the caller +repository's release-ref requirements: package provenance still needs a clear source ref, release +intent, and external-parameter contract for downstream verification. + +Manual dispatch is operationally useful for recovering from transient runner, registry, or trusted +publishing failures. It is also risky if treated as an arbitrary release button, because a +maintainer could accidentally publish from a moving branch ref, provide a version input that +disagrees with the package metadata, or rerun a workflow after npm has already accepted an immutable +package version. + +Should production `workflow_dispatch` publishes require a tag ref, require the tag name to match the +package version, and define rerun semantics before npm publish? + +## Decision Drivers + +- Preserve ADR 0026's constrained manual dispatch support without broadening manual dispatch into an + arbitrary branch-based release mechanism. +- Keep package releases tied to immutable, protected, signed release tags rather than moving branch + refs or free-form version inputs. +- Preserve SLSA Build L3 expectations that externally controlled release inputs and invocation + context are complete and verifier-meaningful. +- Align with npm's immutable package name/version model: the same package version cannot be + republished as a new release. +- Distinguish a retry of the same release tag from a new package release. +- Ensure provenance can distinguish separate build invocations through GitHub run identity and run + attempt values. +- Avoid giving the production workflow permission or responsibility to create release tags. + +## Considered Options + +- Require manual dispatch to run on a tag ref, require `v${package.json.version}` tag matching, and + treat reruns or repeated dispatches as distinct invocations for the same release tag. +- Require manual dispatch to run on a tag ref, but only warn when the tag and package version + differ. +- Allow manual dispatch from branch refs with a required version input. +- Allow manual dispatch from branch refs and let the workflow create the release tag. +- Allow manual dispatch only for retry after a pre-publish failure. +- Remove manual dispatch support and allow tag-push releases only. + +## Decision Outcome + +Chosen option: "Require manual dispatch to run on a tag ref, require `v${package.json.version}` tag +matching, and treat reruns or repeated dispatches as distinct invocations for the same release tag", +because it preserves a practical manual recovery path while keeping production release identity tied +to immutable Git tags and npm package versions. + +The production JS/TS npm package SLSA3 profile should accept `workflow_dispatch` only when the +selected ref is a tag. The called workflow should fail before package packing, provenance +generation, or npm publish when `github.event_name == "workflow_dispatch"` and +`github.ref_type != "tag"`. Branch refs, default-branch manual runs, arbitrary commit-SHA dispatches +without a tag ref, and manual version inputs are unsupported for production publish under this +profile. + +The selected tag name must match the package version using the initial profile's release tag +convention: + +```text +v${package.json.version} +``` + +For example, tag `v1.2.3` must publish package version `1.2.3`, and tag `v1.2.3-beta.1` must publish +package version `1.2.3-beta.1`. The workflow should read the packed package metadata, not only the +working-tree `package.json`, when checking the final publish version. A mismatch between the +selected tag and the package version should fail clearly. + +Manual dispatch is a supported manual retry or approved release path for an existing release tag. It +is not a tag-creation path. The production profile should not create, move, sign, or delete release +tags. Tag creation, tag signing, protected tag policy, and release approval happen outside this +reusable workflow before the publish attempt. + +GitHub reruns and repeated manual dispatches for the same tag are distinct build invocations for the +same release tag, not new releases. Provenance and workflow outputs should distinguish invocations +with the GitHub run id and run attempt where available. The profile should include or derive a build +invocation identifier equivalent to: + +```text +${github.run_id}-${github.run_attempt} +``` + +The profile should fail clearly when the target npm package name and version are already published +in the selected registry. It should not silently skip publish, overwrite provenance, treat the rerun +as a successful new release, or attempt to republish the immutable npm version. If a previous +attempt failed before registry mutation, a rerun or repeated manual dispatch may publish the same +tag and package version. If the registry already contains that package version, verification or +inspection is the correct operation, not another production publish. + +### Consequences + +- Good, because manual dispatch keeps a supported operational recovery path without weakening the + release-ref boundary. +- Good, because package version, Git tag, and provenance source ref remain aligned. +- Good, because branch refs and free-form version inputs cannot become production release sources in + the initial SLSA3 profile. +- Good, because reruns are represented as distinct build invocations instead of ambiguous duplicate + releases. +- Good, because the production publish workflow does not need `contents: write` permission to create + or move tags. +- Neutral, because callers still need repository-level protections such as signed/protected tags and + protected environments where appropriate. +- Bad, because users must create the release tag before manual dispatch. +- Bad, because custom release tag conventions are out of scope until a later ADR or profile revision + changes the tag/version contract. +- Bad, because checking already-published package versions requires registry interaction and careful + error reporting. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, documentation, and verifier guidance define: + +- `workflow_dispatch` production publishes as tag-ref-only manual retries or approved release paths; +- failure before pack, provenance generation, or publish when manual dispatch is not running on a + tag ref; +- the initial release tag convention `v${package.json.version}`; +- package-version checks against the packed artifact metadata; +- failure behavior for tag/package-version mismatches; +- no workflow-created, moved, signed, or deleted release tags in the production publish profile; +- provenance or byproduct fields that include event name, ref, ref type, ref SHA, run id, run + attempt, actor, and triggering actor where available; +- build invocation identity that distinguishes reruns, such as + `${github.run_id}-${github.run_attempt}`; +- clear failure when the package name/version is already published; +- documentation that verification or inspection, not republish, is the correct path after a package + version has already been accepted by the registry. + +Implementation review should verify that branch-based manual dispatch, arbitrary version inputs, +workflow-created tags, and already-published package versions cannot proceed to production npm +publish through the initial SLSA3 profile. + +## Pros and Cons of the Options + +### Require tag ref, require tag/version match, and define reruns as same-release invocations + +Manual dispatch is allowed only when the selected ref is a tag whose name equals +`v${package.json.version}`. Reruns and repeated dispatches produce distinct build invocations for +the same release tag. + +- Good, because this preserves tag-push release semantics while allowing manual recovery. +- Good, because verifier expectations can rely on a stable tag-to-package-version relationship. +- Good, because npm's immutable version model is respected. +- Good, because run id and run attempt can uniquely identify each build invocation. +- Bad, because it reduces flexibility for projects with nonstandard tag naming. +- Bad, because manual dispatch cannot create a release from a branch without prior tag creation. + +### Require tag ref but only warn on tag/version mismatch + +Manual dispatch must use a tag ref, but the workflow only warns when the tag and package version do +not match. + +- Good, because it supports projects whose Git tags and package versions intentionally differ. +- Good, because it is easier to adopt during migration. +- Bad, because accidental mismatches can still publish a wrong package version. +- Bad, because release notes, npm version, Git tag, and verifier expectations can diverge. +- Bad, because a warning is too weak for a production registry mutation in the SLSA3 profile. + +### Allow branch refs with a required version input + +Manual dispatch may run from a branch ref when the caller provides an explicit version input. + +- Good, because the GitHub UI flow is convenient. +- Good, because maintainers can initiate releases before creating tags. +- Bad, because branch refs are moving release sources. +- Bad, because version input becomes another externally controlled parameter that can disagree with + package metadata. +- Bad, because tag protection and signed-tag release integrity can be bypassed. + +### Allow branch refs and let the workflow create the tag + +Manual dispatch runs on a branch, derives or accepts a version, creates the release tag, then +publishes. + +- Good, because it can provide a one-button release workflow. +- Good, because tag creation and publish can be ordered by automation. +- Bad, because the publish workflow needs source-control write permissions. +- Bad, because tag signing, protected tags, release approval, and source mutation become coupled to + the package publish path. +- Bad, because this gives the reusable workflow a broader trust boundary than registry publishing. + +### Allow manual dispatch only after a pre-publish failure + +Manual dispatch is accepted only when it can prove an earlier attempt failed before registry +mutation. + +- Good, because this is the narrowest retry semantics. +- Good, because it minimizes duplicate release attempts. +- Bad, because proving exact prior failure state across GitHub Actions and the registry is brittle. +- Bad, because registry/network failures can leave ambiguous publish status. +- Bad, because the workflow still needs a simple already-published-version failure rule. + +### Remove manual dispatch and support tag-push only + +Only pushed tags can publish production npm packages. + +- Good, because the release trigger model is simplest. +- Good, because verifier expectations and documentation are concise. +- Good, because manual misuse risk is minimized. +- Bad, because maintainers lack a supported recovery path for transient release infrastructure + failures. +- Bad, because users may create unsupported wrapper workflows to recover from failures. + +## More Information + +This decision follows ADR 0026 and decides the remaining manual-dispatch constraints for the initial +production JS/TS npm package profile. It does not change the reusable workflow entrypoint, the +SHA-pinned builder identity requirement from ADR 0028, the release manifest decision from ADR 0031, +npm trusted publishing requirements, registry scope, or future non-publish validation workflows. + +Reference points considered: + +- SLSA v1.2 Build Requirements require a consistent build process and complete, trustworthy + provenance generation for the chosen build level. +- SLSA v1.2 Verifying Artifacts recommends checking trusted `builder.id`, `buildType`, and expected + `externalParameters` against verifier expectations. +- SLSA GitHub Generator's Node.js builder documents `workflow_dispatch` as a supported trigger while + showing release-oriented examples that guard on tag refs. +- SLSA GitHub Generator's Node.js provenance records GitHub event context such as event name, ref, + ref type, run id, and run attempt, and uses run id plus run attempt to distinguish reruns. +- GitHub manual `workflow_dispatch` runs can be started against a selected ref, so a production + release profile must define which selected refs are valid. +- npm package versions are immutable after publication, so rerunning a successful publish should not + be treated as a new production release. diff --git a/docs/decisions/0033-run-build-script-only-when-declared.md b/docs/decisions/0033-run-build-script-only-when-declared.md new file mode 100644 index 0000000..a374f69 --- /dev/null +++ b/docs/decisions/0033-run-build-script-only-when-declared.md @@ -0,0 +1,200 @@ +--- +parent: Decisions +nav_order: 33 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Run Build Script Only When Declared + +## Context and Problem Statement + +ADR 0014 selected npm, pnpm, and Yarn as the supported package managers for install, build, and pack +stages in the initial JS/TS npm package profile. ADR 0015 through ADR 0017 defined package-manager +selection, Corepack usage, and exact version enforcement. ADR 0019 selected source metadata +validation followed by packed artifact inspection, and ADR 0023 rejected arbitrary install, build, +publish, environment, step, service, and default inputs for the production SLSA3 profile. + +The remaining build-stage contract needs to define what the reusable workflow does between +dependency installation and package packing. In particular, npm packages vary widely: some require a +build script before packing, while others are source-only, type-only, configuration-only, or rely on +package manager lifecycle scripts that run during pack. Requiring every package to define +`scripts.build` would make release intent explicit but would reject many valid npm packages. +Skipping a build script entirely would improve compatibility but would make it easier to publish +packages that forgot to run their expected build. + +Should the initial production JS/TS npm package profile require a build script, skip build entirely, +or run a declared build script while treating an absent build script as an intentional no-op? + +## Decision Drivers + +- Preserve the production SLSA3 profile's small public input surface and avoid caller-provided + arbitrary commands. +- Keep install, build, and pack behavior deterministic and package-manager selected from repository + metadata rather than workflow inputs. +- Support common npm packages that do not need a build step before packing. +- Run a package's declared build step when the package declares one. +- Make the build-step result visible to provenance and verifier policy without making the builder a + package quality linter. +- Avoid silently expanding the trusted workflow boundary into monorepo orchestration, task-runner + selection, or caller-defined shell execution. +- Keep future stricter modes possible through a later ADR or distinct builder identity. + +## Considered Options + +- Use fixed package-manager commands and require `scripts.build`. +- Use fixed package-manager commands, run `scripts.build` when present, and treat an absent build + script as a successful no-op. +- Use fixed package-manager commands and require an explicit package metadata marker when no build + script is present. +- Allow a caller-provided build command. +- Omit a separate build stage and rely only on package-manager pack or publish lifecycle behavior. + +## Decision Outcome + +Chosen option: "Use fixed package-manager commands, run `scripts.build` when present, and treat an +absent build script as a successful no-op", because it preserves the trusted workflow boundary while +supporting valid npm packages that do not require a build step. + +The initial production JS/TS npm package profile should use builder-defined commands for install, +build, and pack stages. The concrete command forms for each supported package manager belong in the +JS/TS npm package profile architecture specification, but the public contract should follow these +rules: + +- the selected package manager is determined by ADR 0015 through ADR 0017; +- dependency installation uses the selected package manager's frozen or immutable install behavior; +- the build stage checks the selected source package manifest for `scripts.build`; +- if `scripts.build` is present, the workflow runs the selected package manager's normal `build` + script command for the selected package; +- if `scripts.build` is absent, the build stage succeeds as an explicit no-op; +- package packing then uses the selected package manager's pack behavior, and packed artifact + inspection remains authoritative as decided by ADR 0019; +- final registry publishing continues to use `npm publish` as decided by ADR 0013 and ADR 0029. + +The production profile should not expose `install-command`, `build-command`, `pack-command`, +`publish-command`, task-runner, arbitrary shell, arbitrary environment, arbitrary step, service, or +defaults inputs. Packages that need Turbo, Nx, Changesets, Make, or another project-specific build +orchestration layer should invoke that orchestration through their declared `scripts.build` rather +than through a reusable workflow command override. + +The workflow should record whether the build script was present, whether it was executed or skipped, +and the selected package manager used for the build stage in logs and verifier-relevant provenance +or byproduct fields where appropriate. An absent build script should be represented as "skipped +because absent" or an equivalent explicit value, not hidden as an unobservable implementation +detail. + +This decision does not guarantee that the package's consumer surface is correct. It does not +semantically validate `exports`, `main`, `bin`, TypeScript declarations, generated files, or package +quality beyond the metadata and packed artifact checks selected by ADR 0019. Repositories that +require tests, type checks, linting, or stricter release validation should run those checks before +calling the production publish profile, or use a future validation profile if one is defined. + +### Consequences + +- Good, because packages that declare a build script get a real build stage before pack. +- Good, because valid packages without a build script are not forced to add a meaningless script. +- Good, because caller workflows cannot inject arbitrary trusted build commands through workflow + inputs. +- Good, because monorepo-specific build tools can still be used behind the package's own + `scripts.build` contract. +- Good, because provenance can distinguish an executed build from an intentionally skipped build. +- Neutral, because packages that require tests or type checks must enforce those outside this + publish profile unless a later validation profile is added. +- Bad, because the builder cannot detect every accidental omission of `scripts.build`. +- Bad, because some repositories may need to add or adjust `scripts.build` to fit the fixed profile + contract. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, documentation, and verifier guidance define: + +- exact install commands for npm, pnpm, and Yarn using frozen or immutable dependency installation; +- exact build script invocation commands for npm, pnpm, and Yarn when `scripts.build` is present; +- explicit no-op success behavior when `scripts.build` is absent; +- exact pack commands for npm, pnpm, and Yarn; +- stage ordering of install, optional build, pack, packed artifact inspection, provenance + generation, and publish; +- provenance or byproduct fields that identify build script presence, execution or skip status, + selected package manager, and selected package-manager version; +- documentation that package tests, type checks, linting, and consumer-surface validation are + outside this publish profile unless encoded in `scripts.build` or provided by another workflow; +- rejection of arbitrary command, environment, step, service, task-runner, and default inputs in the + production SLSA3 profile. + +Implementation review should verify that the production workflow cannot be made to execute +caller-provided install, build, pack, or publish commands through workflow inputs, and that an +absent `scripts.build` is observable as an explicit skipped build rather than being silently +conflated with an executed build. + +## Pros and Cons of the Options + +### Use fixed commands and require `scripts.build` + +The profile runs fixed package-manager install, build, and pack commands, and fails when the +selected package does not declare `scripts.build`. + +- Good, because every release artifact has an explicit build command. +- Good, because a missing expected build script fails before publish. +- Good, because verifier guidance can treat build execution as mandatory. +- Bad, because valid no-build npm packages need dummy scripts or cannot use the initial profile. +- Bad, because the profile becomes a stronger package-shape gate than npm itself. + +### Use fixed commands, run build when present, and no-op when absent + +The profile runs fixed package-manager install and pack commands. It runs the selected package's +normal build script only when `scripts.build` is present; otherwise the build stage succeeds as an +explicit no-op. + +- Good, because it follows common npm package practice without exposing arbitrary commands. +- Good, because build execution remains package-owned through `scripts.build`. +- Good, because no-build packages remain supported. +- Good, because provenance can record whether build was executed or skipped. +- Bad, because a package that forgot to declare `scripts.build` may still publish. + +### Require an explicit no-build package metadata marker + +The profile runs `scripts.build` when present and fails without a build script unless the package +declares a Windlass-specific no-build marker. + +- Good, because a skipped build is explicitly declared by the package author. +- Good, because accidental build omission is less likely than with a default no-op. +- Bad, because it introduces Windlass-specific package metadata into npm packages. +- Bad, because the initial profile and verifier schema become more complex. +- Bad, because it may surprise maintainers whose package already has valid npm semantics. + +### Allow a caller-provided build command + +The reusable workflow accepts a workflow input such as `build-command` and executes it before pack. + +- Good, because it can support complex monorepo, task-runner, and migration workflows. +- Good, because callers can preserve existing release commands more easily. +- Bad, because caller-controlled command execution weakens the production trusted workflow boundary. +- Bad, because verifier-relevant behavior becomes much harder to constrain. +- Bad, because it conflicts with the current small input surface and no arbitrary command decisions. + +### Omit a separate build stage + +The profile only installs dependencies and packs the package, relying on package-manager lifecycle +scripts such as `prepack`, `prepare`, or equivalent pack behavior. + +- Good, because it stays close to native package-manager pack behavior. +- Good, because the reusable workflow has one fewer explicit stage. +- Bad, because build intent is less visible in provenance and documentation. +- Bad, because packages that conventionally use `scripts.build` before pack would not be built + unless they also wire build behavior into lifecycle scripts. +- Bad, because package-manager lifecycle differences become more important to the trusted profile + contract. + +## More Information + +This decision follows ADR 0014 through ADR 0019 and ADR 0023. It decides the build-stage policy for +the initial production JS/TS npm package profile only. It does not change package-manager selection, +Corepack enforcement, packed artifact validation, publish authentication, provenance generation, +registry scope, release trigger policy, or SHA-pinned builder identity. + +The architecture specification should still define the exact npm, pnpm, and Yarn command lines, +including frozen install flags, workspace package invocation behavior, pack output parsing, +lifecycle script expectations, and failure handling for unsupported package-manager versions or +package layouts. diff --git a/docs/decisions/0034-do-not-support-private-dependency-credentials-in-initial-profile.md b/docs/decisions/0034-do-not-support-private-dependency-credentials-in-initial-profile.md new file mode 100644 index 0000000..6267517 --- /dev/null +++ b/docs/decisions/0034-do-not-support-private-dependency-credentials-in-initial-profile.md @@ -0,0 +1,237 @@ +--- +parent: Decisions +nav_order: 34 +status: accepted +date: 12026-07-05 +decision-makers: Yunseo Kim +--- + +# Do Not Support Private Dependency Credentials in the Initial Profile + +## Context and Problem Statement + +ADR 0024 selected npm trusted publishing with GitHub Actions OIDC and no npm publish secret for the +initial production JS/TS npm package SLSA3 profile. That decision intentionally left private +dependency installation credentials undecided. npm trusted publishing authenticates `npm publish`; +it does not authenticate `npm install`, `npm ci`, `pnpm install`, or `yarn install` when those +commands need to fetch private packages. + +The npm ecosystem commonly handles private dependency installation in CI with read-only granular +access tokens. npm documentation recommends trusted publishing for publish operations, while also +recommending read-only granular tokens when CI needs to install private npm packages. SLSA v1.2 does +not require hermetic builds, and dependency fetching during a build is allowed. However, the +production SLSA3 builder identity selected for this project has a small, verifier-oriented public +contract: workflow inputs are intentionally constrained, publish tokens are forbidden, and modes +with materially different security properties require distinct builder identities. + +Allowing dependency-fetch credentials inside the initial reusable workflow would add a +secret-bearing mode to the same production profile. Even if the credential is intended to be +read-only, install-time lifecycle scripts and package-manager behavior can observe the install +environment. The workflow also cannot prove every caller-provided token is read-only, short-lived, +registry-scoped, or unable to publish. Private dependency usage can also make downstream verifier +checks less reproducible because dependency metadata or tarballs may not be publicly resolvable. + +Should the initial production JS/TS npm package SLSA3 profile support read-only private dependency +credentials, reject them entirely, or split private-dependency support into a future distinct +profile or builder mode? + +## Decision Drivers + +- Preserve ADR 0024's no-publish-secret invariant and avoid confusing dependency-fetch credentials + with publish credentials. +- Keep the initial production profile's `workflow_call.secrets` contract empty. +- Preserve a small and easily verifiable `externalParameters` and secret surface for the initial + SLSA3 builder identity. +- Avoid exposing long-lived or caller-controlled credentials to install-time package lifecycle + scripts in the initial production profile. +- Keep private dependency and private registry semantics out of the first npmjs-focused production + compatibility surface. +- Align with SLSA v1.2's guidance that `externalParameters` should be complete and minimized, and + that different security modes should be distinguishable through builder identity. +- Leave a deliberate future path for read-only dependency credential support under a distinct trust + contract if needed. + +## Considered Options + +- Do not support private dependency credentials in the initial production profile, and define a + future path for a distinct credential-bearing profile or mode. +- Allow one optional read-only npm dependency token secret in the initial production profile. +- Allow registry- or scope-specific read-only credential maps for npm-compatible registries. +- Support only OIDC or brokered short-lived credentials for dependency reads. +- Allow `secrets: inherit` or arbitrary environment passthrough for dependency installation. + +## Decision Outcome + +Chosen option: "Do not support private dependency credentials in the initial production profile, and +define a future path for a distinct credential-bearing profile or mode", because it keeps the first +production SLSA3 builder identity small, tokenless, and straightforward to verify while leaving room +for a separately specified private-dependency mode later. + +The initial production JS/TS npm package SLSA3 profile should not define any `workflow_call.secrets` +for dependency installation. It should not accept `npm-read-token`, `NPM_READ_TOKEN`, `NPM_TOKEN`, +`NODE_AUTH_TOKEN`, GitHub Packages tokens, registry tokens, cloud credentials, `.npmrc` secret +contents, `.yarnrc.yml` secret contents, or equivalent dependency-fetch credentials through the +production reusable workflow contract. + +The profile should not support `secrets: inherit`, arbitrary environment passthrough, arbitrary +registry credential maps, or caller-provided package-manager auth configuration as part of the +initial production SLSA3 profile. If the selected package's install step requires private dependency +authentication, the workflow should fail clearly rather than prompting users to pass a token, +falling back to a publish token, or silently changing the builder security mode. + +This decision does not claim that private dependency credentials are incompatible with SLSA in +general. SLSA v1.2 does not require hermetic builds, and read-only dependency credentials can be a +reasonable CI practice when they are scoped, protected, and documented. The decision is narrower: +the initial Windlass production JS/TS npm package profile will not include that secret-bearing +behavior under the same builder identity. + +A future ADR may add private dependency support through a separate reusable workflow, separate mode, +or revised profile contract. If the future mode exposes dependency-fetch credentials to install-time +commands, it should use a distinct builder identity when its security properties, verifier +expectations, or secret exposure differ materially from the initial tokenless profile. That future +decision should define at least: + +- which registries and package managers are supported; +- whether credentials are long-lived read-only tokens, OIDC-exchanged short-lived tokens, or + brokered credentials; +- how credential names avoid confusion with publish credentials; +- how credentials are scoped to install only and removed before build, pack, provenance generation, + and publish stages where possible; +- what credential mode, registry scope, and dependency-fetch policy are recorded in provenance or + byproducts without recording secret values; +- how verifier policy can accept or reject private-dependency credential mode; +- how package-manager lifecycle scripts, logs, caches, and artifact handoff avoid leaking secrets. + +### Consequences + +- Good, because the initial production profile keeps an empty `workflow_call.secrets` surface. +- Good, because dependency-fetch credentials cannot be confused with publish credentials in the + initial reusable workflow contract. +- Good, because install-time lifecycle scripts cannot access caller-provided registry tokens through + this profile. +- Good, because verifier expectations for the first builder identity remain smaller and easier to + document. +- Good, because future private-dependency support can receive its own ADR, tests, documentation, and + possibly distinct builder identity. +- Neutral, because SLSA itself permits non-hermetic dependency fetching and best-effort dependency + recording; this decision is a product-scope constraint rather than a SLSA prohibition. +- Bad, because packages that require private dependency authentication cannot use the initial + production profile without changing their dependency model. +- Bad, because some users may need to wait for a future credential-bearing profile or publish + through a less constrained workflow outside the initial SLSA3 profile. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, documentation, and verifier guidance define: + +- no `workflow_call.secrets` entries for dependency installation credentials; +- no accepted `npm-read-token`, `NPM_READ_TOKEN`, `NPM_TOKEN`, `NODE_AUTH_TOKEN`, GitHub Packages + token, registry token, cloud credential, `.npmrc` secret, `.yarnrc.yml` secret, or equivalent + dependency-fetch credential in the production profile; +- no `secrets: inherit`, arbitrary environment passthrough, or arbitrary registry credential map in + caller examples or supported workflow contract; +- clear failure behavior when dependency installation requires credentials not available through the + selected public dependency configuration; +- documentation that private dependency installation is unsupported in the initial production + profile and may require a future distinct profile or mode; +- verifier guidance that does not treat private dependency credential behavior as part of the + initial tokenless `buildType` contract. + +Implementation review should verify that dependency installation cannot receive caller-provided +secrets through the production SLSA3 workflow API and that no implementation path falls back to an +npm publish token or inherited secret for installing private dependencies. + +## Pros and Cons of the Options + +### Do not support private dependency credentials in the initial production profile + +The production reusable workflow exposes no dependency credential secrets. Packages whose install +stage requires private dependency authentication fail clearly under the initial profile. + +- Good, because the secret surface stays empty and easy to audit. +- Good, because the initial profile remains aligned with ADR 0024's tokenless publish direction. +- Good, because verifier policy does not need to reason about secret-bearing dependency-fetch modes. +- Good, because a future credential-bearing mode can be isolated under a distinct trust contract. +- Bad, because private-dependency packages are outside the first production compatibility surface. +- Bad, because adoption is harder for organizations with internal npm packages. + +### Allow one optional read-only npm dependency token secret + +The profile accepts a single optional secret intended only for installing private npm dependencies. + +- Good, because it matches npm's documented CI guidance for private dependencies. +- Good, because many real npm packages could adopt the profile without changing dependency hosting. +- Good, because publish authentication can remain OIDC-only with no publish token fallback. +- Bad, because install-time scripts and package-manager behavior can observe the token environment. +- Bad, because the workflow cannot fully prove the token is read-only, short-lived, or narrowly + scoped. +- Bad, because the same builder identity would cover both tokenless and secret-bearing install + behavior unless split by a later decision. + +### Allow registry- or scope-specific read-only credential maps + +The profile supports multiple private registries or scopes through explicit credential mappings. + +- Good, because it fits enterprise npm, GitHub Packages, Artifactory, Verdaccio, and mixed-scope + dependency layouts. +- Good, because registry-specific policy can be represented more precisely than a single token. +- Bad, because initial public API and verifier expectations become much larger. +- Bad, because custom registry semantics are outside ADR 0030's guaranteed npmjs production surface. +- Bad, because multiple credentials increase leakage and misconfiguration risks. + +### Support only OIDC or brokered short-lived dependency credentials + +The profile supports private dependency reads only through OIDC exchange or a credential broker +rather than long-lived read tokens. + +- Good, because it follows the same short-lived credential direction as trusted publishing. +- Good, because it can reduce long-lived secret exposure if registry support exists. +- Bad, because npm private dependency install does not have the same simple trusted-publishing OIDC + path as `npm publish`. +- Bad, because registry support and broker design would require a larger separate architecture + decision. +- Bad, because it is too broad for the initial profile's first production specification. + +### Allow `secrets: inherit` or arbitrary environment passthrough + +The caller can pass all secrets or arbitrary environment variables into the reusable workflow so its +normal install commands can access whatever credentials the caller uses. + +- Good, because it is highly flexible for existing workflows. +- Good, because it can support almost any private dependency setup. +- Bad, because the workflow cannot enumerate or verify all external influences on the build. +- Bad, because publish tokens, cloud tokens, and unrelated secrets may enter the build environment. +- Bad, because this conflicts with the small input and secret surface required for the initial + production SLSA3 profile. + +## More Information + +This decision follows ADR 0024 and decides only private dependency installation credentials for the +initial production JS/TS npm package profile. It does not change publish authentication, +Windlass-generated provenance, supported package managers, package-manager install command policy, +registry URL scope, release trigger policy, or SHA-pinned builder identity. + +Reference points considered: + +- SLSA v1.2 Build Requirements require security best practices for secrets and require SLSA Build L3 + provenance to be strongly resistant to tenant forgery. Secret material used for provenance + authenticity must not be accessible to user-defined build steps. +- SLSA v1.2 Build Requirements do not require hermetic builds. Dependency fetching is allowed, and + resolved dependency completeness is best effort at Build L3. +- SLSA v1.2 Build Provenance defines `externalParameters` as the externally controlled build + interface, requires those parameters to be complete at Build L3, and recommends minimizing their + size and complexity for verifier usability. +- SLSA v1.2 Build Provenance says modes with materially different security attributes or SLSA Build + levels must have different `builder.id` values and should have different signer identities. +- npm trusted publishing documentation recommends trusted publishing for publish operations while + recommending read-only granular access tokens when CI needs to install private npm packages. +- npm documentation notes that trusted publishing applies to publish operations; private dependency + install commands still require traditional authentication when private packages are used. +- GitHub Actions documentation notes that secrets are not automatically passed to reusable + workflows, that callers can pass named secrets or use `secrets: inherit`, and that commit SHA + references are safest for reusable workflows. +- The SLSA GitHub Generator Node.js builder exposes inputs such as `run-scripts` but does not define + a dependency-read token secret on its reusable workflow. Its publish examples pass publish tokens + in a separate publishing path rather than making private dependency credentials part of the + builder contract. diff --git a/docs/decisions/0035-use-actions-attest-as-initial-sigstore-signing-adapter.md b/docs/decisions/0035-use-actions-attest-as-initial-sigstore-signing-adapter.md new file mode 100644 index 0000000..d79ccd0 --- /dev/null +++ b/docs/decisions/0035-use-actions-attest-as-initial-sigstore-signing-adapter.md @@ -0,0 +1,233 @@ +--- +parent: Decisions +nav_order: 35 +status: accepted +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use actions/attest as the Initial Sigstore Signing Adapter + +## Context and Problem Statement + +ADR 0029 selected Windlass-generated SLSA provenance as the canonical provenance for the initial +production JS/TS npm package SLSA3 profile. That provenance must be signed as a Sigstore DSSE bundle +that can be submitted through npm's external `provenance-file` path. ADR 0029 intentionally left the +implementation boundary for signing open. + +The signing adapter choice affects the trusted core dependency set, GitHub Actions permissions, SLSA +Build L3 signing-key isolation story, verifier compatibility, and the long-term path for a Go +implementation. The project needs a production-friendly initial adapter without foreclosing a later +move to an in-process Go Sigstore implementation. + +Should the initial production profile use GitHub `actions/attest`, the `cosign` CLI, `sigstore-go`, +npm automatic provenance, or a custom Sigstore protocol implementation to sign Windlass-generated +SLSA provenance? + +## Decision Drivers + +- Preserve ADR 0029's Windlass-generated SLSA provenance as the canonical provenance record. +- Produce a Sigstore DSSE bundle using short-lived GitHub Actions OIDC identity rather than a + long-lived signing key. +- Keep signing-key material inaccessible to caller-controlled build steps, as required for SLSA + Build L3 provenance unforgeability. +- Minimize initial implementation risk while the repository is still defining architecture + specifications. +- Use a signing path compatible with GitHub reusable workflow signer identity verification. +- Keep workflow permissions explicit and narrowly scoped to the signing job. +- Avoid making npm automatic provenance the canonical builder provenance for the Windlass profile. +- Leave a clear future path toward a Go-native `sigstore-go` signing adapter when the trusted core + is ready to own that complexity. + +## Considered Options + +- Use GitHub `actions/attest` as the initial Sigstore signing adapter, with a future migration path + toward `sigstore-go`. +- Use `sigstore-go` directly in the initial Go trusted core. +- Invoke the `cosign` CLI from the reusable workflow. +- Rely on npm trusted publishing or `npm publish --provenance` automatic provenance. +- Implement the Sigstore protocol directly without a maintained client or action. + +## Decision Outcome + +Chosen option: "Use GitHub `actions/attest` as the initial Sigstore signing adapter, with a future +migration path toward `sigstore-go`", because it gives the initial GitHub Actions reusable workflow +a well-supported OIDC and Sigstore bundle path while deferring Go-native signing implementation work +until the trusted core has enough specification and test coverage to own it safely. + +The initial production JS/TS npm package SLSA3 profile should treat `actions/attest` as the signing +adapter for Windlass-generated SLSA provenance. Windlass remains responsible for generating and +validating the SLSA provenance statement selected by ADR 0029, including `subject`, `predicateType`, +`buildType`, `externalParameters`, `builder.id`, and other profile-defined fields. `actions/attest` +is responsible only for creating the Sigstore-signed attestation bundle and, when configured, +uploading it to GitHub's attestation storage. + +The reusable workflow should pin `actions/attest` to a full commit SHA, not a mutable tag. The +signing job should grant only the permissions required for the selected signing and storage +behavior: + +- `contents: read` for repository access; +- `id-token: write` for GitHub Actions OIDC token minting; +- `attestations: write` for GitHub artifact attestation persistence when that storage path is used; +- `artifact-metadata: write` only when linked artifact storage metadata is intentionally created. + +The production profile should not grant signing permissions to caller-controlled install, build, +pack, or publish-script steps. If signing and build execution are split across jobs, artifacts and +provenance inputs handed to the signing job must be authenticated by the trusted workflow design +described in the architecture specification. The signing adapter must not accept caller-provided +signing keys, long-lived Sigstore credentials, arbitrary OIDC tokens, or npm publish tokens as +provenance-signing credentials for the initial profile. + +Verifier guidance for the initial profile should include GitHub artifact attestation verification +where applicable, including reusable workflow signer identity checks such as `--signer-workflow` or +`--signer-repo`. npm package verification remains governed by ADR 0029's canonical package +provenance requirements: the verifier must check the Sigstore signature, expected GitHub Actions +signer identity, SLSA predicate type, Windlass `builder.id`, Windlass `buildType`, expected +`externalParameters`, and published package tarball digest. + +Long term, the project should prefer a `sigstore-go` adapter when the Go trusted core is ready to +implement and test Sigstore signing directly. A future migration ADR should define how to preserve +or intentionally change signer identity, bundle format, verifier behavior, npm `provenance-file` +compatibility, GitHub attestation storage behavior, and workflow permissions. If the migration +materially changes security properties, verifier expectations, or `builder.id` meaning, it should +use a distinct builder identity. + +### Consequences + +- Good, because the initial production profile gets a maintained GitHub-native Sigstore signing + path. +- Good, because the workflow can use short-lived GitHub Actions OIDC identity instead of managing a + long-lived signing key. +- Good, because GitHub artifact attestation verification can validate reusable workflow signer + identity directly. +- Good, because `actions/attest` keeps Fulcio, Rekor, timestamp, bundle, and GitHub attestation API + integration out of the first Go implementation milestone. +- Good, because a later `sigstore-go` implementation remains aligned with ADR 0004's Go trusted core + direction. +- Neutral, because GitHub artifact attestations may be secondary evidence for npm package releases + unless a later ADR changes the canonical distribution model. +- Bad, because `actions/attest` becomes part of the initial trusted core dependency set and must be + SHA-pinned, monitored, and updated deliberately. +- Bad, because the initial signing path is GitHub-specific and does not by itself support GitHub + Enterprise Server or non-GitHub build platforms. +- Bad, because a later `sigstore-go` migration may require verifier documentation and compatibility + testing even if the emitted bundle shape stays compatible. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +implementation, workflow documentation, and verifier guidance define: + +- `actions/attest` as the initial Sigstore signing adapter for Windlass-generated SLSA provenance; +- full-SHA pinning for the `actions/attest` action reference; +- job-level permissions limited to the required `contents: read`, `id-token: write`, + `attestations: write`, and optional `artifact-metadata: write` permissions; +- no caller-provided signing keys, Sigstore credentials, arbitrary OIDC tokens, npm publish tokens, + or inherited secrets for provenance signing; +- the exact handoff boundary between provenance statement generation, subject digest verification, + signing, npm `provenance-file` submission, and optional GitHub attestation upload; +- verifier guidance for expected GitHub Actions signer identity, Windlass `builder.id`, Windlass + `buildType`, SLSA predicate type, `externalParameters`, and package tarball digest; +- a documented future path for a `sigstore-go` adapter without requiring that implementation in the + initial profile. + +Implementation review should verify that signing permissions are not available to caller-controlled +build steps and that production releases cannot silently fall back to npm automatic provenance, +unsigned provenance, long-lived key signing, or a different signer identity under the same +production builder identity. + +## Pros and Cons of the Options + +### Use GitHub `actions/attest` as the initial signing adapter + +The reusable workflow uses a SHA-pinned `actions/attest` step to sign the Windlass-generated SLSA +statement with GitHub Actions OIDC-backed Sigstore identity and to produce a Sigstore bundle. + +- Good, because it is a GitHub-native path with first-party support for GitHub artifact + attestations. +- Good, because it uses short-lived OIDC identity and avoids local signing-key management. +- Good, because it provides a practical initial path for DSSE bundle generation before the Go + trusted core implements Sigstore signing directly. +- Good, because `gh attestation verify` supports reusable workflow signer checks. +- Bad, because it adds a pinned third-party action dependency to the trusted builder boundary. +- Bad, because its storage and verification conveniences are GitHub-specific. + +### Use `sigstore-go` directly in the initial Go trusted core + +The Go implementation calls Sigstore libraries directly to create and verify Sigstore bundles. + +- Good, because it aligns with ADR 0004's Go trusted core direction. +- Good, because it gives Windlass direct control over signing, bundle generation, error handling, + and tests. +- Good, because it can reduce workflow shell/action glue over time. +- Bad, because it requires the initial implementation to own OIDC acquisition, Fulcio/Rekor/TSA/TUF + behavior, failure handling, and compatibility testing immediately. +- Bad, because GitHub attestation storage and linked artifact metadata integration would still need + separate design. + +### Invoke the `cosign` CLI from the reusable workflow + +The workflow installs or vendors `cosign` and signs the provenance through `cosign attest-blob` or +an equivalent keyless command. + +- Good, because `cosign` is the widely recognized Sigstore CLI and supports keyless signing, + bundles, and broad verification workflows. +- Good, because it is less tied to GitHub artifact attestation storage than `actions/attest`. +- Bad, because installing, pinning, and verifying a CLI binary adds operational and supply-chain + surface. +- Bad, because `cosign` has a broad container- and registry-oriented feature set beyond the initial + npm package profile's needs. +- Bad, because GitHub artifact attestation API integration would be separate. + +### Rely on npm automatic provenance + +npm trusted publishing or `npm publish --provenance` creates npm ecosystem provenance automatically +instead of Windlass signing its own canonical provenance. + +- Good, because it is npm ecosystem-native and naturally discoverable through npm tooling. +- Good, because trusted publishing can generate provenance without an npm publish token. +- Bad, because it conflicts with ADR 0029's Windlass-generated canonical provenance decision. +- Bad, because it does not make Windlass `builder.id`, `buildType`, and `externalParameters` the + canonical verifier contract. +- Bad, because it would couple the production profile's SLSA semantics to npm's generated predicate + shape rather than the Windlass build type specification. + +### Implement the Sigstore protocol directly + +The project implements Fulcio, Rekor, timestamp, bundle, and trust-root interactions without using a +maintained Sigstore client or GitHub action. + +- Good, because it gives maximum implementation control. +- Bad, because it duplicates security-sensitive client behavior that maintained Sigstore tools + already provide. +- Bad, because direct protocol implementation increases compatibility, maintenance, and + vulnerability response burden. +- Bad, because it is unnecessary for the initial profile and would slow specification-driven + development. + +## More Information + +This decision follows ADR 0029 and decides only the initial implementation adapter for signing +Windlass-generated SLSA provenance. It does not change the canonical provenance contents, package +manager policy, npm trusted publishing authentication decision, private dependency credential +policy, or SHA-pinned reusable workflow builder identity. + +Reference points considered: + +- SLSA v1.2 Build Requirements require authentic provenance at Build L2 and unforgeable provenance + at Build L3. Secret material used to authenticate provenance must not be accessible to + user-defined build steps. +- SLSA v1.2 Build Provenance separates signer identity from `builder.id` and requires consumers to + accept only expected signer-builder pairs. +- GitHub `actions/attest` generates signed in-toto attestations with Sigstore-issued short-lived + certificates, writes Sigstore bundle JSON, and can upload attestations to GitHub's attestation + API. +- GitHub `actions/attest-build-provenance` v4 is a wrapper around `actions/attest`; new + implementations should use `actions/attest` directly. +- GitHub `gh attestation verify` documents reusable workflow verification through signer workflow or + signer repository checks and warns that predicate contents can be falsified if the workflow + context is compromised. +- `sigstore-go` is production-ready and provides Go APIs for signing and verifying Sigstore bundles, + but adopting it directly requires the Windlass trusted core to own more Sigstore behavior. +- `cosign` remains a stable Sigstore CLI with keyless signing, bundles, and attestation support, but + its CLI installation and broad feature surface are larger than the initial adapter needs. diff --git a/docs/decisions/0036-use-three-job-digest-verified-publish-graph.md b/docs/decisions/0036-use-three-job-digest-verified-publish-graph.md new file mode 100644 index 0000000..1d206ab --- /dev/null +++ b/docs/decisions/0036-use-three-job-digest-verified-publish-graph.md @@ -0,0 +1,269 @@ +--- +parent: Decisions +nav_order: 36 +status: accepted +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use a Three-Job Digest-Verified Publish Graph + +## Context and Problem Statement + +ADR 0029 selected Windlass-generated SLSA provenance as the canonical npm package provenance, ADR +0035 selected `actions/attest` as the initial Sigstore signing adapter, and ADR 0024 selected OIDC +trusted publishing without npm publish secrets. Together, those decisions require a workflow graph +that builds a package, signs provenance for the exact packed artifact, and mutates the npm registry +only after the signed provenance and package artifact have been checked. + +The open boundary is how the initial production JS/TS npm package SLSA3 reusable workflow should +split build, provenance-signing, and publish responsibilities across GitHub Actions jobs. The split +must preserve SLSA Build L3 signing-key isolation, keep caller-controlled package scripts away from +signing and publish permissions, and make the publish step verify the exact tarball and signed +provenance bundle it submits to npm. + +Should the initial production profile use one job, two jobs, three jobs, four jobs, or leave publish +to the caller after a build-and-sign reusable workflow? + +## Decision Drivers + +- Keep caller-controlled install, build, pack, and package lifecycle scripts away from provenance + signing permissions and npm registry mutation capability. +- Preserve SLSA v1.2 Build L3's requirement that provenance authentication material is not + accessible to user-defined build steps. +- Make each cross-job artifact handoff digest-verified rather than name- or path-trusted. +- Require the publish job to verify both the packed artifact and signed provenance bundle before npm + registry mutation. +- Keep Windlass-generated SLSA provenance, not unsigned workflow outputs, as the source of truth for + verifier policy. +- Keep `actions/attest` signing permissions scoped to a signing job, as selected by ADR 0035. +- Keep npm trusted publishing authentication separate from provenance generation and signing. +- Follow the SLSA GitHub Generator precedent of reusable workflow isolation, job-level VM isolation, + and hash-protected artifact handoff. +- Define a job graph that is precise enough for architecture specifications and implementation + tests. + +## Considered Options + +- Use a three-job digest-verified graph: `build` → `provenance-sign` → `publish`. +- Use one job for build, provenance signing, and publish. +- Use two jobs: `build` → `publish-sign`. +- Use four jobs: `build` → `provenance-generate` → `sign` → `publish`. +- Build and sign inside the reusable workflow, then leave publish to the caller workflow. + +## Decision Outcome + +Chosen option: "Use a three-job digest-verified graph: `build` → `provenance-sign` → `publish`", +because it gives the initial production profile clear job isolation, minimal permission scopes, and +a publish-time verification gate for both the package tarball and signed provenance bundle before +npm registry mutation. + +The initial production JS/TS npm package SLSA3 reusable workflow should define three primary jobs: + +```text +build -> provenance-sign -> publish +``` + +The `build` job should run the caller-selected package build contract, produce the packed npm +tarball, validate package metadata from the packed artifact, calculate the package tarball `sha256` +and `sha512` digests, upload the tarball as a workflow artifact, and expose only the package +identity and digest handles needed by later jobs and public workflow outputs. + +The `provenance-sign` job should download the package tarball artifact, recalculate its digest, +compare it with the `build` job's expected digest outputs, generate the Windlass SLSA provenance v1 +statement for exactly that tarball, verify that the statement subject matches the tarball digest, +sign the statement through the SHA-pinned `actions/attest` adapter selected by ADR 0035, and upload +the resulting signed Sigstore bundle as a workflow artifact. The signing job should not have npm +publish credentials or npm registry mutation authority. + +The `publish` job should download the package tarball and signed provenance bundle artifacts, +recalculate both artifact digests, verify that those digests match the handoff values produced by +the previous jobs, verify the signed provenance bundle against the expected Sigstore root and GitHub +Actions reusable workflow signer identity, extract and verify the SLSA statement expectations, and +only then run `npm publish --provenance-file ` or the equivalent +npm external provenance-file submission. The publish job should fail before registry mutation if any +digest, signature, signer identity, package identity, subject, `predicateType`, `builder.id`, +`buildType`, `externalParameters`, source identity, package-directory, or runtime-policy check +fails. + +The initial job permission boundary should be: + +- `build`: `contents: read`; no `id-token: write`; no `attestations: write`; no npm publish + authentication; no publish secrets. +- `provenance-sign`: `contents: read`, `id-token: write`, `attestations: write` when GitHub + attestation storage is used, and optional `artifact-metadata: write` only when linked artifact + metadata is intentionally created; no npm publish authentication; no registry mutation authority. +- `publish`: `contents: read` and only the OIDC or registry permissions required for npm trusted + publishing; no `attestations: write` unless a later ADR adds post-publish attestation behavior. + +Cross-job handoff should use immutable GitHub workflow artifacts and trusted job outputs as handles. +The architecture specification should define exact artifact names or IDs, digest encodings, digest +algorithms, download locations, and failure behavior. Jobs must not trust artifact names, paths, +workflow outputs, or logs as substitutes for recalculating digests and verifying the signed +provenance bundle. Unsigned public workflow outputs such as package identity and tarball digest +remain release handles only; they are not substitutes for signed provenance verification. + +After publishing, the `publish` job should resolve npm registry metadata for the published package +version when possible and verify that the registry-visible package identity, tarball URL or name, +integrity or digest metadata, and provenance linkage correspond to the verified tarball and +submitted Windlass provenance bundle. The architecture specification may refine which npm registry +metadata is mandatory versus best-effort when npm registry behavior or tooling support changes. + +### Consequences + +- Good, because package build scripts cannot access provenance signing or npm publish authority. +- Good, because `actions/attest` permissions are scoped to the signing job rather than the build + job. +- Good, because npm registry mutation is gated by digest checks and signed provenance verification. +- Good, because the job graph mirrors SLSA GitHub Generator's isolated-job and hash-protected + handoff model while keeping Windlass-specific provenance semantics. +- Good, because architecture specifications can assign clear inputs, outputs, permissions, and + failure behavior to each job. +- Good, because the future `sigstore-go` migration path can replace signing internals without + collapsing the build/publish boundary. +- Neutral, because the publish job performs verifier-like checks inside the producer workflow; final + consumers should still verify published artifacts independently. +- Bad, because the workflow has more jobs, handoff artifacts, and verification code than a one- or + two-job design. +- Bad, because Windlass must maintain tooling to verify its own signed bundle before publish. +- Bad, because artifact handoff implementation must be tested carefully to avoid name/path + confusion, stale artifacts, digest encoding drift, or accidental fallback behavior. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- exactly three primary production jobs named or equivalent to `build`, `provenance-sign`, and + `publish`; +- the job-level permissions and absence of signing or publish authority from caller-controlled build + steps; +- package tarball artifact upload, download, digest calculation, and digest comparison behavior; +- signed provenance bundle artifact upload, download, digest calculation, and digest comparison + behavior; +- Windlass SLSA statement generation and subject digest verification before signing; +- Sigstore bundle verification in the publish job before `npm publish`; +- publish-time checks for signer identity, `predicateType`, `builder.id`, `buildType`, + `externalParameters`, source identity, package identity, package directory, runtime policy, and + tarball digest; +- `npm publish --provenance-file` or equivalent external provenance-file submission using only the + verified tarball and verified signed bundle paths; +- post-publish npm registry metadata checks and documented best-effort versus mandatory behavior; +- hard-failure behavior for missing artifacts, digest mismatch, signature mismatch, unexpected + provenance fields, unsupported registry metadata, and any attempted fallback to npm automatic + provenance, unsigned provenance, token-based publish, or caller-managed publish. + +Implementation review should verify that no path can publish a package under the production SLSA3 +builder identity unless the package tarball and signed Windlass provenance bundle have both passed +the publish job's verification gate. + +## Pros and Cons of the Options + +### Use a three-job digest-verified graph + +The reusable workflow separates package creation, provenance signing, and npm registry mutation into +`build`, `provenance-sign`, and `publish` jobs. Each cross-job artifact handoff is checked by +digest, and the publish job verifies the signed provenance before publishing. + +- Good, because it creates the clearest permission and trust boundary for the initial profile. +- Good, because build scripts cannot access signing credentials or publish authority. +- Good, because the publish job can behave like a producer-side verifier before registry mutation. +- Good, because it follows the SLSA GitHub Generator pattern of job isolation plus hash-protected + artifact exchange. +- Good, because it keeps Windlass-generated provenance, not workflow outputs, as the verifier source + of truth. +- Bad, because it requires more implementation and testing than simpler job graphs. +- Bad, because bundle verification logic must exist before publish, not only in downstream verifier + tooling. + +### Use one job for build, signing, and publish + +One job checks out source, runs install/build/pack, generates and signs provenance, and publishes to +npm. + +- Good, because it is simple and avoids artifact upload/download handoff. +- Good, because local paths are easy to reason about during an early prototype. +- Bad, because caller-controlled package scripts share a runner environment with signing and publish + permissions. +- Bad, because it weakens the SLSA Build L3 signing-key isolation story. +- Bad, because it conflicts with the digest-verified isolated handoff direction. + +### Use two jobs: `build` then `publish-sign` + +The build job creates the tarball, and a second job verifies the tarball, signs provenance, and +publishes. + +- Good, because build scripts are isolated from signing and publish permissions. +- Good, because it is simpler than a three-job graph. +- Good, because the publish-capable job can verify the tarball before signing and publishing. +- Bad, because provenance signing and registry mutation share one job boundary. +- Bad, because publish does not consume a previously signed bundle through an explicit verification + handoff. +- Bad, because it is less precise for specifying publish-time verification of signed provenance. + +### Use four jobs: `build`, `provenance-generate`, `sign`, and `publish` + +The workflow separates unsigned provenance statement generation from Sigstore signing. + +- Good, because the adapter boundary between statement generation and signing is explicit. +- Good, because replacing `actions/attest` with `sigstore-go` later may be mechanically simpler. +- Good, because unsigned statement validation can happen before signing. +- Bad, because it creates another cross-job artifact handoff and more digest state to verify. +- Bad, because unsigned provenance statement artifacts require their own integrity and + path-confusion controls. +- Bad, because it is too much ceremony for the initial production profile. + +### Build and sign in the reusable workflow, then leave publish to the caller + +The trusted reusable workflow returns package and provenance artifacts; the caller workflow +downloads them and performs npm publish. + +- Good, because it resembles SLSA GitHub Generator Node.js custom publishing examples. +- Good, because callers can customize publishing workflows. +- Bad, because the production profile would not control the registry mutation path. +- Bad, because callers could publish without verifying the signed provenance bundle or could submit + a different provenance file. +- Bad, because it conflicts with ADR 0029's production profile requirement to submit Windlass + canonical provenance through npm's external provenance-file path. +- Bad, because publish failures and downgrade behavior would become caller-specific rather than + profile-specified. + +## More Information + +This decision follows ADR 0024, ADR 0025, ADR 0029, ADR 0034, and ADR 0035. It decides the initial +production workflow job graph and artifact handoff boundary only. It does not change package manager +selection, private dependency credential support, canonical provenance contents, signing adapter +choice, public workflow outputs, or signed release metadata. + +Reference points considered: + +- SLSA v1.2 Build Requirements require authentic provenance at Build L2 and unforgeable provenance + at Build L3. Secret material used to authenticate provenance must not be accessible to + user-defined build steps, and builds must run in isolated, ephemeral environments. +- SLSA v1.2 Build Provenance defines `subject`, `builder.id`, `buildType`, `externalParameters`, + `internalParameters`, `resolvedDependencies`, and verifier-relevant provenance semantics. It also + separates signer identity from builder identity and requires consumers to accept only expected + signer-builder pairs. +- SLSA v1.2 Verifying Artifacts recommends checking the provenance envelope signature, subject + digest, `predicateType`, trusted builder identity, `buildType`, and `externalParameters`; + unrecognized external parameters should generally fail verification. +- The SLSA GitHub Generator technical design uses reusable workflows to prevent caller workflow + interference, GitHub-hosted job isolation as the VM boundary, GitHub artifacts for larger job + handoff data, and job outputs as a trusted channel for hashes and handles. +- The SLSA GitHub Generator Node.js builder produces package and provenance artifacts, exposes + artifact names and SHA-256 values, and shows publish flows that securely download the package and + provenance before running `npm publish --provenance-file`. +- The SLSA GitHub Generator `secure-package-download`, `secure-attestations-download`, and + `nodejs/publish` actions use artifact names plus expected SHA-256 values as a package/provenance + handoff model for publishing. +- GitHub `actions/attest` can create Sigstore bundle JSON, supports custom predicates, exposes a + `bundle-path` output, and requires `id-token: write` plus attestation permissions only where the + signing/storage action runs. +- GitHub `actions/upload-artifact` v4 and later make uploaded artifacts immutable, expose artifact + IDs, URLs, and SHA-256 digests, and reject accidental same-name mutation patterns. +- GitHub `actions/download-artifact` supports downloading by name or artifact ID and has digest + mismatch handling, but Windlass should still recalculate and compare package and bundle digests as + part of its own trust boundary. +- npm provenance documentation supports publishing packages with provenance from cloud-hosted CI and + third-party publish tooling that supplies an external provenance file, while npm trusted + publishing can separately authenticate publish operations without long-lived npm tokens. diff --git a/docs/decisions/0037-define-initial-verification-deliverables.md b/docs/decisions/0037-define-initial-verification-deliverables.md new file mode 100644 index 0000000..61d2e6d --- /dev/null +++ b/docs/decisions/0037-define-initial-verification-deliverables.md @@ -0,0 +1,264 @@ +--- +parent: Decisions +nav_order: 37 +status: accepted +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Define Initial Verification Deliverables + +## Context and Problem Statement + +ADR 0028 selected a SHA-pinned reusable workflow `builder.id`, ADR 0029 selected Windlass-generated +SLSA provenance as the canonical npm package provenance, ADR 0031 selected a Sigstore-signed release +manifest, ADR 0035 selected `actions/attest` as the initial signing adapter, and ADR 0036 selected a +three-job graph with publish-time verification before npm registry mutation. + +Those decisions require verifier behavior, but they do not yet define what verifier-facing artifacts +Windlass must ship in the initial production JS/TS npm package SLSA3 profile. The open question is +whether the first release must include a standalone consumer verifier CLI, only document verifier +policy, rely on ecosystem tools, or provide a smaller set of policy and conformance deliverables +that can support a later standalone verifier without committing to one too early. + +What verification deliverables should the initial production profile ship? + +## Decision Drivers + +- Preserve SLSA v1.2's expectation that provenance is useful only when checked against trusted roots + and package-specific expectations. +- Ensure npm registry mutation is blocked unless the package tarball and signed Windlass provenance + bundle pass producer-side verification. +- Avoid making a premature public CLI/API commitment before the Go trusted core owns Sigstore bundle + verification and Windlass policy evaluation. +- Keep consumer guidance explicit enough to verify Windlass-produced artifacts using existing tools + where possible. +- Make full-SHA builder identity, release manifest, package identity, source identity, and runtime + policy expectations testable. +- Avoid delegating Windlass-specific SLSA policy entirely to generic ecosystem tools that do not + know the profile's required `builder.id`, `buildType`, `externalParameters`, and package-directory + semantics. +- Preserve a clear future path for a standalone verifier CLI or upstream verifier integration. + +## Considered Options + +- Ship a standalone verifier consumer CLI in the initial release. +- Ship only documented verifier policy. +- Implement only the publish job's producer-side verification gate. +- Ship producer-side publish verification, documented verifier policy, fixtures, and reference + commands, but no standalone consumer verifier CLI. +- Ship a thin wrapper around `gh attestation verify`. +- Make `slsa-verifier` compatibility the official initial verifier deliverable. + +## Decision Outcome + +Chosen option: "Ship producer-side publish verification, documented verifier policy, fixtures, and +reference commands, but no standalone consumer verifier CLI", because this gives the first +production profile concrete verification behavior and conformance evidence without locking Windlass +into a consumer verifier interface before the trusted Go implementation and Sigstore policy surface +are ready. + +The initial production JS/TS npm package SLSA3 profile should include these verification +deliverables: + +- a producer-side verification gate in the `publish` job selected by ADR 0036; +- an architecture specification section or document that defines Windlass verifier policy; +- conformance fixtures for accepted and rejected package/provenance/bundle combinations; +- reference commands and guidance for existing tools such as `gh attestation verify`, + `npm audit signatures`, and, where compatible, `slsa-verifier`; +- explicit non-goal language that the initial profile does not ship a standalone consumer verifier + CLI. + +The `publish` job remains in scope as production logic, not merely documentation. It should verify +the package tarball digest, signed bundle digest, Sigstore signer identity, SLSA statement subject, +`predicateType`, `builder.id`, `buildType`, `externalParameters`, source identity, package identity, +package version, package directory, runtime policy, and release manifest expectations before running +`npm publish --provenance-file` or an equivalent external provenance-file submission. It should fail +closed before registry mutation if any required check fails. + +The documented verifier policy should explain which expectations are enforced by Windlass before +publish, which expectations consumers should independently verify after publication, and which +checks generic ecosystem tools do and do not cover. It should describe expected roots of trust and +identity bindings in terms of the Sigstore root, GitHub Actions reusable workflow signer identity, +SHA-pinned Windlass `builder.id`, SLSA `predicateType`, SLSA `buildType`, external parameters, +package identity, source identity, and release manifest linkage. + +The conformance fixture set should include at least one valid package tarball and signed provenance +bundle shape, plus invalid cases for digest mismatch, signature or signer mismatch, wrong +`predicateType`, wrong `builder.id`, wrong `buildType`, unrecognized or mismatched +`externalParameters`, source identity mismatch, package identity or version mismatch, +package-directory mismatch, runtime-policy mismatch, and release manifest mismatch. Fixtures may be +synthetic until the implementation can generate real signed bundles, but their expected pass/fail +semantics must match the architecture specification. + +The reference guidance may show existing tools as partial verifier building blocks. +`gh attestation verify` can verify GitHub/Sigstore attestation signature material, predicate type, +signer workflow, source ref, and source digest, and can emit JSON for additional policy checks. +`npm audit signatures` can report registry signatures and npm-recognized attestations for installed +packages, but it is not a complete Windlass policy verifier. `slsa-verifier` may be documented as an +ecosystem verifier or future compatibility target only where it can express the profile's required +expectations. + +Windlass should add a standalone verifier CLI only after a later ADR decides the command scope, +input model, offline/online Sigstore behavior, release manifest policy, error taxonomy, and +compatibility responsibilities. Upstream `slsa-verifier` support may also be pursued later, but the +initial profile should not make its release criteria depend on external verifier support for +Windlass-specific policy. + +### Consequences + +- Good, because initial production publishing cannot proceed without producer-side verification. +- Good, because consumers receive explicit policy guidance instead of implicit trust in unsigned + workflow outputs. +- Good, because fixtures make verifier semantics testable before a public verifier CLI exists. +- Good, because the future standalone verifier can reuse the same policy and fixture corpus. +- Good, because `gh attestation verify`, `npm audit signatures`, and `slsa-verifier` are positioned + accurately as useful tools with specific limits. +- Good, because Windlass avoids prematurely owning a consumer CLI surface for Sigstore verification, + npm registry lookup, release manifest policy, and error compatibility. +- Neutral, because the publish job performs verifier-like checks inside the producer workflow while + consumers still need independent verification for defense in depth. +- Bad, because users do not get a single verify command in the initial release. +- Bad, because reference commands cannot fully enforce Windlass-specific policy without additional + manual or scripted checks. +- Bad, because fixture design must be kept aligned with the future implementation to avoid stale + policy examples. + +### Confirmation + +This decision is confirmed when the initial JS/TS npm package profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- the `publish` job's producer-side verification gate before npm registry mutation; +- exact pass/fail checks for tarball digest, signed bundle digest, Sigstore signature, signer + identity, `subject`, `predicateType`, `builder.id`, `buildType`, `externalParameters`, source + identity, package identity, package version, package directory, runtime policy, and release + manifest linkage; +- documented roots of trust and identity bindings for the initial Windlass production builder; +- documented consumer-side verification expectations and the limits of npm registry verification; +- reference commands for `gh attestation verify`, `npm audit signatures`, and any supported + `slsa-verifier` compatibility path; +- conformance fixtures that include both accepted and rejected verification cases; +- hard-failure behavior for every invalid fixture category; +- an explicit statement that a standalone consumer verifier CLI is not part of the initial profile; +- a future-decision note for standalone verifier CLI or upstream verifier integration. + +Implementation review should verify that no documentation or workflow output implies that generic +tool success alone is equivalent to complete Windlass policy verification unless all +Windlass-specific expectations have also been checked. + +## Pros and Cons of the Options + +### Ship a standalone verifier consumer CLI + +Windlass would include a first-party command such as `windlass verify npm-package` that consumes a +package tarball, signed provenance bundle, release manifest, and expected policy inputs. + +- Good, because it gives users the clearest consumer-side verification UX. +- Good, because Windlass-specific policy can be enforced by one tool. +- Good, because full-SHA `builder.id`, release manifest, and profile-specific expectations can be + checked without generic-tool gaps. +- Bad, because it expands the initial trusted implementation surface significantly. +- Bad, because Windlass would immediately own Sigstore root handling, online/offline bundle + verification, registry lookup, error taxonomy, and CLI compatibility. +- Bad, because a premature verifier interface could become difficult to change after consumers + automate it. + +### Ship only documented verifier policy + +Windlass would define what consumers should verify, but would not implement publish-time +verification or provide fixtures and reference commands. + +- Good, because it is the smallest documentation-only scope. +- Good, because SLSA expectation fields can be described before implementation. +- Bad, because it would not satisfy ADR 0036's publish-time verification gate. +- Bad, because users would have no concrete examples or conformance evidence. +- Bad, because policy could drift from implementation without fixtures. + +### Implement only the publish job's producer-side verification gate + +Windlass would verify package and provenance artifacts before publish but would not document +consumer policy or provide reusable fixtures. + +- Good, because it protects npm registry mutation from bad package/provenance combinations. +- Good, because the checks live in the workflow path that must be correct for release. +- Bad, because it does not explain what downstream consumers should verify. +- Bad, because internal verification behavior would be harder to audit without documented policy and + fixtures. +- Bad, because producer-side verification does not cover registry compromise or download-time + threats. + +### Ship publish verification, documented policy, fixtures, and reference commands + +Windlass includes the producer-side verification gate, policy documentation, conformance fixtures, +and examples using existing tools, but does not ship a standalone consumer verifier CLI. + +- Good, because it balances initial security value with implementation scope. +- Good, because it makes verifier semantics testable before public CLI stabilization. +- Good, because it supports defense-in-depth guidance without overclaiming generic verifier + coverage. +- Good, because it creates a stable base for a later verify command. +- Bad, because users must combine reference tools and policy guidance rather than run one Windlass + verifier command. +- Bad, because Windlass must clearly communicate which tool results are partial. + +### Ship a thin wrapper around `gh attestation verify` + +Windlass would provide a small command or action that shells out to, or directly mirrors, +`gh attestation verify`. + +- Good, because GitHub CLI already verifies GitHub/Sigstore attestation identity and predicate type. +- Good, because it aligns with the initial `actions/attest` signing adapter. +- Bad, because it would still need extra Windlass policy checks for `builder.id`, `buildType`, + `externalParameters`, release manifest, and npm package semantics. +- Bad, because a wrapper could look complete while only enforcing part of the policy. +- Bad, because it would inherit GitHub CLI versioning and output-shape compatibility concerns. + +### Make `slsa-verifier` compatibility the official initial verifier deliverable + +Windlass would treat existing `slsa-verifier` support as the initial verification surface, or block +release on upstream compatibility. + +- Good, because `slsa-verifier` is the main SLSA ecosystem verifier precedent. +- Good, because it already supports source, builder, package, and provenance checks for several + builders, including experimental npm package verification. +- Bad, because npm package support is still experimental. +- Bad, because Windlass-specific profile policy may not be expressible without upstream changes. +- Bad, because initial Windlass release criteria would depend on external support for the profile's + exact builder identity and policy semantics. + +## More Information + +This decision follows ADR 0028, ADR 0029, ADR 0031, ADR 0035, and ADR 0036. It decides initial +verification deliverables only. It does not change the canonical provenance format, signing adapter, +job graph, release manifest decision, or future option to provide a standalone verifier. + +Reference points considered: + +- SLSA v1.2 Verifying Artifacts recommends checking the provenance envelope signature, artifact + subject digest, `predicateType`, trusted builder identity, `buildType`, and `externalParameters`; + unrecognized external parameters should generally fail verification. +- SLSA v1.2 describes package ecosystem, consumer, and monitor verification as valid verification + locations, with package upload-time verification useful but not a replacement for consumer-side + verification against registry or download-time compromise. +- SLSA v1.2 Build Requirements require authentic provenance at Build L2 and unforgeable provenance + at Build L3, including protection of provenance authentication material from user-defined build + steps. +- The SLSA GitHub Generator technical design treats the provenance verifier as part of the trusted + computing base and describes verification of Fulcio/Sigstore identity, subject digest, builder + identity, source identity, and policy fields. +- The SLSA GitHub Generator Node.js builder documents `npm audit signatures` and `slsa-verifier` as + verification paths while also using secure package/provenance download and hash checks during + publish. +- `slsa-verifier` provides established consumer-side SLSA verification UX and policy flags, but npm + package verification is documented as experimental and cannot be assumed to cover all + Windlass-specific policy without compatibility work. +- GitHub `gh attestation verify` can verify artifact attestations, predicate type, signer workflow, + source ref, and source digest, and can emit JSON for further policy checks; GitHub warns that + predicate contents need trusted-builder context and additional policy care. +- GitHub `actions/attest` emits Sigstore bundle JSON and is verifiable with GitHub attestation + tooling, but generic attestation success does not by itself prove Windlass package, release + manifest, or profile policy expectations. +- npm provenance documentation exposes `npm audit signatures` for installed package registry + signatures and attestations, but npm provenance does not guarantee absence of malicious code and + is not a complete Windlass policy verifier. diff --git a/docs/decisions/0038-use-windlass-generated-provenance-with-actions-attest-for-release-assets.md b/docs/decisions/0038-use-windlass-generated-provenance-with-actions-attest-for-release-assets.md new file mode 100644 index 0000000..7e43c9d --- /dev/null +++ b/docs/decisions/0038-use-windlass-generated-provenance-with-actions-attest-for-release-assets.md @@ -0,0 +1,251 @@ +--- +parent: Decisions +nav_order: 38 +status: superseded by ADR-0049 +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use Windlass-Generated Provenance with actions/attest for GitHub Release Assets + +## Context and Problem Statement + +ADR 0013 scoped the initial JS/TS profile to npm package releases and deferred GitHub Release assets +to a future composable profile. The initial release scope now needs to include a GitHub Release +asset profile as a sibling profile rather than waiting for a later release. The first architectural +choice for that profile is whether it should be a thin reusable workflow wrapper around GitHub +`actions/attest` default provenance behavior, or whether Windlass should directly implement the +profile's provenance and signing behavior in Go or another trusted runtime. + +GitHub Release assets differ from npm packages. They are distributed by the source repository +hosting provider rather than by npm, may be protected by GitHub immutable releases, and can be +verified with GitHub release and artifact-attestation tooling. However, SLSA v1.2 still treats +provenance as artifact-bound build metadata that should identify the output artifact by digest, +describe how it was produced, and be generated or verified by the trusted build platform for Build +L3. GitHub's artifact attestation documentation also distinguishes direct artifact attestations, +which provide SLSA Build Level 2 by themselves, from reusable-workflow-based builds that can provide +the additional isolation needed for Build Level 3. + +Should the initial GitHub Release asset profile rely on `actions/attest` default provenance as a +wrapper, generate Windlass-owned SLSA provenance in the Go trusted core and use `actions/attest` +only as a signing and storage adapter, implement Sigstore signing directly in Go from the start, or +rely on GitHub immutable release attestations instead of a separate build provenance profile? + +## Decision Drivers + +- Preserve ADR 0002 and ADR 0003's trusted reusable workflow and profile-owned workflow boundary. +- Keep the GitHub Release asset profile aligned with SLSA v1.2 Build L3 expectations for + unforgeable, artifact-bound provenance. +- Reuse ADR 0004's Go trusted core direction for profile-specific provenance, subject, digest, and + policy logic. +- Use a Windlass-owned `builder.id`, `buildType`, and complete `externalParameters` schema rather + than inheriting a generic GitHub Actions workflow build type as the production verifier contract. +- Avoid giving caller-controlled build steps the ability to inject or alter provenance contents + other than allowed artifact subject information. +- Avoid implementing Fulcio, Rekor, timestamp, Sigstore bundle, and GitHub attestations API behavior + directly before the trusted core has enough specification and fixture coverage. +- Keep GitHub immutable releases and release attestations available as release-integrity evidence + without treating them as a substitute for artifact-bound build provenance. +- Preserve a future migration path toward a Go-native `sigstore-go` signing adapter if the project + later needs to own signing internals directly. + +## Considered Options + +- Use `actions/attest` default provenance as a thin reusable workflow wrapper. +- Generate Windlass SLSA provenance in Go and use `actions/attest` as the signing and storage + adapter. +- Implement provenance generation, Sigstore signing, and GitHub attestation persistence directly in + Go for the initial profile. +- Rely on GitHub immutable release attestations and release verification instead of a separate + artifact-bound SLSA provenance profile. + +## Decision Outcome + +Chosen option: "Generate Windlass SLSA provenance in Go and use `actions/attest` as the signing and +storage adapter", because it gives the GitHub Release asset profile a Windlass-owned SLSA verifier +contract while still using GitHub's maintained Sigstore and artifact-attestation integration for the +initial signing path. + +The initial GitHub Release asset profile should generate canonical SLSA build provenance v1 inside +Windlass trusted logic. The trusted implementation, expected to be Go by default under ADR 0004, +should own at least: + +- release asset subject naming and digest normalization; +- the profile-specific `buildType` URI and schema; +- complete verifier-relevant `buildDefinition.externalParameters`; +- `runDetails.builder.id` using the SHA-pinned reusable workflow identity model from ADR 0028; +- source repository, source ref, resolved source commit, release tag, asset path or name, runtime + policy, and release event context fields; +- validation that the provenance subject identifies exactly the release asset artifact being signed + and later uploaded or verified. + +The profile should invoke a full-SHA-pinned `actions/attest` step in custom attestation mode to sign +the Windlass-generated SLSA predicate and, when configured, upload the resulting Sigstore bundle to +GitHub's attestation storage. `actions/attest` should not be used in its default provenance mode as +the canonical production provenance generator for this profile. In this profile, `actions/attest` is +an adapter for Sigstore bundle generation and GitHub attestation storage, not the owner of the +Windlass release asset build type semantics. + +GitHub immutable releases, release attestations, `gh release verify`, and `gh release verify-asset` +should be treated as complementary release-integrity and asset-immutability mechanisms. They may be +required or recommended by the profile's release process, but they do not replace artifact-bound +SLSA build provenance for the release asset. + +The initial profile should not implement Sigstore signing directly in Go. A later ADR may replace +the `actions/attest` adapter with `sigstore-go` or another Go-native signing path after the project +has specified bundle compatibility, signer identity, GitHub attestation storage behavior, release +asset verification policy, trust roots, and migration rules. If that migration materially changes +security properties, verifier expectations, or `builder.id` meaning, it should use a distinct +builder identity or explicitly document compatibility. + +### Consequences + +- Good, because the release asset profile can define a Windlass-owned `buildType` and + `externalParameters` schema instead of inheriting a generic GitHub Actions provenance contract. +- Good, because SLSA predicate generation, subject rules, digest handling, and release asset policy + stay in the Go trusted core direction selected by ADR 0004. +- Good, because `actions/attest` still provides a maintained GitHub-native Sigstore signing and + attestation-storage path for the initial release. +- Good, because reusable workflow signer checks through GitHub artifact attestation tooling remain + available. +- Good, because GitHub immutable release verification can complement, rather than replace, + artifact-bound SLSA provenance. +- Neutral, because the profile will need Windlass-specific verifier policy in addition to generic + `gh attestation verify` or `gh release verify` success. +- Bad, because the release asset profile must implement and test its own SLSA predicate generation, + release asset subject model, and provenance policy before shipping. +- Bad, because `actions/attest` remains a trusted, SHA-pinned GitHub-specific dependency in the + initial profile. +- Bad, because a future Go-native signing migration will require compatibility and verifier-policy + work. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- Windlass-generated SLSA build provenance v1 as the canonical provenance for GitHub Release assets; +- a profile-specific GitHub Release asset `buildType` URI and schema; +- complete `externalParameters`, `internalParameters`, `resolvedDependencies`, `builder.id`, + `builder.version`, `builderDependencies`, invocation metadata, and byproducts rules for the + release asset profile; +- release asset subject naming, digest algorithms, and digest encodings; +- full-SHA pinning for the `actions/attest` action reference; +- use of `actions/attest` custom attestation mode with + `predicate-type: https://slsa.dev/provenance/v1` or an equivalent path that signs the + Windlass-generated SLSA predicate; +- job-level permissions that keep Sigstore signing and GitHub attestation storage permissions away + from caller-controlled build steps; +- release asset upload, immutable release, release attestation, and `gh release verify` behavior as + complementary release-integrity checks rather than substitutes for SLSA provenance; +- verifier guidance for Sigstore signature, reusable workflow signer identity, SHA-based + `builder.id`, Windlass `buildType`, `externalParameters`, source identity, release tag, release + asset name, release asset digest, and release manifest linkage; +- a documented future path for replacing `actions/attest` with a Go-native Sigstore adapter without + requiring that implementation in the initial release asset profile. + +Implementation review should verify that production GitHub Release asset provenance cannot silently +fall back to `actions/attest` default provenance, unsigned release assets, release-level attestation +only, mutable release assets, long-lived signing keys, or a different signer identity under the same +production builder identity. + +## Pros and Cons of the Options + +### Use `actions/attest` default provenance as a thin reusable workflow wrapper + +The release asset profile builds or receives an artifact in a reusable workflow and invokes +`actions/attest` with only `subject-path`, `subject-digest`, or `subject-checksums`, allowing the +action to generate its default SLSA provenance predicate. + +- Good, because it is the smallest implementation and uses GitHub's official artifact attestation + action directly. +- Good, because it naturally integrates with GitHub attestations storage and + `gh attestation verify`. +- Good, because GitHub documents reusable workflows plus artifact attestations as a path toward SLSA + Build Level 3. +- Bad, because the default predicate is not the Windlass release asset build type contract. +- Bad, because Windlass would have less control over `buildType`, `externalParameters`, and + profile-specific release asset verification semantics. +- Bad, because it risks creating a release asset profile whose public verifier contract differs from + the Windlass-generated provenance model selected for the npm package profile. + +### Generate Windlass SLSA provenance in Go and use `actions/attest` as the adapter + +The trusted core creates the SLSA build provenance predicate for the release asset profile, then a +SHA-pinned `actions/attest` step signs and stores that predicate in custom attestation mode. + +- Good, because Windlass owns the release asset subject, build type, parameter, and verification + schema. +- Good, because it keeps the security-critical JSON, digest, subject, and policy logic in the Go + trusted-core direction. +- Good, because it avoids implementing Sigstore signing and GitHub attestation persistence directly + in the first release asset milestone. +- Good, because it is consistent with ADR 0035's adapter model while adapting it to a non-npm + profile. +- Neutral, because generic GitHub verification tools remain useful but incomplete without + Windlass-specific policy checks. +- Bad, because it requires a real provenance generator and conformance fixture set before the + profile can ship. +- Bad, because `actions/attest` behavior, versioning, and GitHub support boundaries must be + monitored as part of the trusted release path. + +### Implement signing and attestation persistence directly in Go + +The Go trusted core generates provenance, obtains or uses GitHub Actions OIDC identity, creates the +Sigstore bundle, and uploads or distributes the attestation without invoking `actions/attest`. + +- Good, because it gives Windlass maximum control over signing, bundle generation, error handling, + tests, and compatibility. +- Good, because it may eventually reduce the number of trusted workflow action dependencies. +- Bad, because the initial profile would immediately own Fulcio, Rekor, timestamp, bundle, + trust-root, GitHub attestations API, and private/public Sigstore behavior. +- Bad, because the larger initial trusted implementation surface increases security and maintenance + risk. +- Bad, because a direct implementation is unnecessary to decide the release asset profile's build + semantics. + +### Rely on GitHub immutable release attestations instead + +The profile depends on GitHub immutable release protections and release attestations to prove the +release tag, commit, and assets, without generating separate artifact-bound SLSA build provenance. + +- Good, because immutable releases protect release tags and assets from modification or deletion + after publication. +- Good, because GitHub release verification commands provide a simple consumer-facing integrity + check. +- Good, because release attestations can record the release tag, commit SHA, and release assets. +- Bad, because SLSA v1.2 recommends binding build attestations to artifacts, not only to releases. +- Bad, because release-level attestation does not define the Windlass build process, `buildType`, or + complete `externalParameters` needed for the profile's SLSA verifier contract. +- Bad, because this would make GitHub release integrity a substitute for build provenance rather + than defense-in-depth evidence. + +## More Information + +This decision extends ADR 0002, ADR 0003, ADR 0004, ADR 0028, ADR 0031, ADR 0035, and ADR 0037 to +the initial GitHub Release asset profile. It does not decide the full release asset profile scope, +workflow filename, public input contract, job graph, release creation policy, immutable release +requirement, asset upload behavior, or standalone verifier CLI surface. Those details belong in +follow-up ADRs or the release asset architecture specification. + +Reference points considered: + +- SLSA v1.2 Build Requirements require authentic provenance at Build L2 and unforgeable provenance + at Build L3. At Build L3, every provenance field must be generated or verified by the trusted + build platform, except permitted tenant-controlled subject information, and `externalParameters` + must be fully enumerated. +- SLSA v1.2 Build Provenance defines `predicateType: https://slsa.dev/provenance/v1`, separates + `builder.id` from signer identity, and expects build type specifications to define the schema for + `externalParameters` and `internalParameters`. +- SLSA v1.2 Distributing Provenance says attestations should be bound to artifacts rather than + releases, and recommends publishing provenance alongside source repository releases when that is + the artifact distribution surface. +- GitHub artifact attestations documentation states that artifact attestations by themselves provide + SLSA Build Level 2, while reusable workflows can provide the known build instructions and + isolation needed for Build Level 3. +- GitHub `actions/attest` supports provenance, SBOM, and custom attestation modes; custom mode + accepts `predicate-type` with `predicate` or `predicate-path`, emits Sigstore bundle JSON, and can + upload attestations to GitHub's attestations API. +- GitHub immutable releases protect tags and release assets from modification or deletion after + publication and create release attestations, but they do not replace artifact-bound SLSA build + provenance for a Windlass-owned builder profile. diff --git a/docs/decisions/0039-scope-release-asset-profile-to-one-asset-per-run.md b/docs/decisions/0039-scope-release-asset-profile-to-one-asset-per-run.md new file mode 100644 index 0000000..32195d0 --- /dev/null +++ b/docs/decisions/0039-scope-release-asset-profile-to-one-asset-per-run.md @@ -0,0 +1,247 @@ +--- +parent: Decisions +nav_order: 39 +status: accepted +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Scope the GitHub Release Asset Profile to One Asset per Run + +## Context and Problem Statement + +ADR 0038 added the initial GitHub Release asset profile direction: Windlass trusted logic generates +canonical SLSA provenance for release assets, while `actions/attest` acts as the initial Sigstore +signing and storage adapter. The next decision is the release asset profile's artifact scope and +verification unit. + +GitHub Releases commonly contain multiple downloadable files: OS-specific binaries, architecture +variants, checksum files, SBOMs, provenance sidecars, signatures, archives, and sometimes generated +source archives. SLSA v1.2 recommends binding attestations to artifacts rather than releases, while +GitHub `actions/attest` models attestation subjects as named artifacts with digests and supports +both one subject and multiple subjects per attestation. The project needs an initial scope that +keeps the production SLSA3 profile easy to verify, while still allowing full releases to contain +multiple assets through composition. + +Should the initial GitHub Release asset profile treat one profile run as one release asset, an +explicit set of release assets, a full GitHub Release, a generated archive from a directory, or a +checksum-manifest-centered asset group? + +## Decision Drivers + +- Keep the profile's SLSA subject and verifier policy artifact-bound, not release-bound. +- Make one profile run's verification unit as simple and auditable as the npm profile's + one-package-per-run decision in ADR 0018. +- Avoid partial-success and partial-retry ambiguity for multi-platform or multi-file releases. +- Keep release asset upload, duplicate-name handling, immutable release behavior, and provenance + verification straightforward for the initial profile. +- Preserve a path for releases that publish multiple OS or architecture assets, checksum files, + SBOMs, provenance sidecars, and signatures. +- Avoid treating GitHub-generated source archives as ordinary release assets in the initial build + artifact profile. +- Avoid making checksum manifests the primary SLSA subject when the real downloadable artifacts are + the files referenced by the checksum manifest. +- Keep future multi-asset orchestration separate from the low-level trusted profile contract. + +## Considered Options + +- Publish and verify one explicitly named GitHub Release asset file per profile run, with an + orchestration layer for multi-asset releases. +- Publish and verify an explicit set of GitHub Release asset files per profile run. +- Treat the full GitHub Release as the profile run's verification unit. +- Treat one generated archive from a selected directory as the profile run's release asset. +- Treat a checksum manifest plus referenced files as the profile run's verification unit. +- Include GitHub-generated source archives in the release asset profile. + +## Decision Outcome + +Chosen option: "Publish and verify one explicitly named GitHub Release asset file per profile run, +with an orchestration layer for multi-asset releases", because it keeps the trusted profile's SLSA +subject model small and artifact-bound while still allowing a higher-level release workflow to +compose complete multi-asset GitHub Releases. + +The initial GitHub Release asset profile should define one profile run as exactly one GitHub Release +asset file. That asset is the profile's primary output artifact and primary verification unit. The +profile's SLSA provenance should have exactly one release asset subject for the production path +unless a later ADR defines a multi-subject profile or mode. The release asset subject should +identify the uploaded asset by a profile-defined asset name and digest, with release tag, source +identity, and upload metadata represented in the profile's provenance parameters, dependencies, +byproducts, or verification policy as specified by the architecture documentation. + +The initial profile should not treat these as the same low-level verification unit: + +- a full GitHub Release; +- a set of OS or architecture assets; +- checksum files plus all referenced artifacts; +- SBOM files plus the artifact they describe; +- provenance sidecars plus the artifact they describe; +- detached signatures plus the artifact they sign; +- GitHub-generated source archives. + +Repositories that need to publish multiple release assets should use a higher-level orchestration +layer. That orchestration layer may call the one-asset profile once per OS or architecture artifact, +once per SBOM, once per checksum file, or once per provenance sidecar when those files are intended +to be published release assets. The orchestration layer should own cross-asset concerns such as +release creation order, draft or immutable release workflow, grouping by tag, aggregate release +notes, checksum manifest generation, SBOM set publication, provenance sidecar naming, and +whole-release user experience. It should not weaken the one-asset profile's artifact-bound SLSA +subject contract. + +Checksum files, SBOMs, provenance sidecars, and detached signatures may be published as release +assets through the same one-asset profile when the project wants those files to be independently +downloadable and independently attested. They may also be generated or referenced as byproducts of +an orchestration workflow where appropriate. The architecture specification should define when such +files are release assets, byproducts, or verification inputs; this ADR only decides that they are +not implicitly bundled into the low-level profile's verification unit for another asset. + +GitHub-generated source archives are excluded from the initial GitHub Release asset profile because +they are not uploaded release assets produced by the trusted profile. Source integrity and generated +source archive verification should be handled by source-track documentation, GitHub release +verification, or a later dedicated decision if needed. + +### Consequences + +- Good, because the profile's SLSA subject is simple: one release asset file and its digest. +- Good, because release asset upload and verification can fail closed without partial multi-asset + state handling. +- Good, because the model matches SLSA's artifact-bound provenance guidance more closely than a + full-release verification unit. +- Good, because the profile remains easy to compose for OS and architecture matrices by calling it + once per asset. +- Good, because checksum files, SBOMs, provenance sidecars, and detached signatures can still be + published and attested as first-class release assets when desired. +- Good, because a future orchestration workflow can provide a friendly whole-release abstraction + without changing the trusted asset-level profile. +- Neutral, because complete release publication will require orchestration for projects with more + than one asset. +- Bad, because users do not get a one-call multi-platform release asset workflow from the low-level + profile alone. +- Bad, because cross-asset consistency, such as "all assets in the checksum file were published and + attested", must be specified in the orchestration layer rather than in the one-asset profile. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and documentation define: + +- one explicitly named GitHub Release asset file as the unit of one profile run; +- exactly one SLSA subject for the production release asset profile path; +- release asset name, local path, digest, upload target, and GitHub Release tag handling for one + asset; +- failure behavior for missing asset, unexpected directory input, duplicate asset names, upload + mismatch, digest mismatch, and unsupported source archive inputs; +- outputs for the uploaded asset identity, asset URL or API identifier, and digest values; +- exclusion of full-release, multi-asset, checksum-manifest-centered, SBOM-bundled, + provenance-sidecar-bundled, detached-signature-bundled, and GitHub-generated source archive + verification units from the low-level profile; +- an orchestration-layer boundary for multi-OS, multi-architecture, checksum, SBOM, provenance + sidecar, detached signature, and whole-release workflows; +- documentation explaining that the orchestration layer composes multiple one-asset profile runs + rather than changing the one-asset SLSA subject contract. + +Implementation review should verify that the low-level production release asset profile cannot +silently publish, sign, or verify multiple primary release assets in one run, and that orchestration +logic does not bypass the one-asset profile's provenance, digest, signing, and upload checks for any +published release asset. + +## Pros and Cons of the Options + +### Publish and verify one explicitly named GitHub Release asset file per profile run + +Each invocation produces, signs, uploads, and verifies one named release asset file. Multi-file +releases are composed by a separate orchestration layer that invokes the profile once per asset. + +- Good, because it provides the smallest clear artifact-bound verification unit. +- Good, because one subject, one digest, one upload result, and one signed provenance bundle are + easy to explain and test. +- Good, because failed upload, duplicate asset name, or digest mismatch handling is localized to one + asset. +- Good, because OS and architecture matrices can map naturally to repeated profile invocations. +- Neutral, because multi-asset releases still require an orchestration layer. +- Bad, because it can feel lower-level than users expect from a release-publishing workflow. + +### Publish and verify an explicit set of GitHub Release asset files per profile run + +One invocation accepts a manifest, glob, or list of release asset files and creates one provenance +attestation with multiple subjects. + +- Good, because it matches `actions/attest` support for multiple subjects in a single attestation. +- Good, because a release with several platform artifacts can be published by one workflow call. +- Good, because shared release metadata appears once in the provenance predicate. +- Bad, because partial upload, partial attestation, already-existing asset, and retry behavior + become more complex. +- Bad, because all assets in the set must share one build definition and verifier expectation, which + may not be true for OS or architecture variants built differently. +- Bad, because adding or rebuilding one platform asset after the fact is harder to model cleanly. + +### Treat the full GitHub Release as the verification unit + +One invocation treats the release tag and all attached assets as the artifact being verified. + +- Good, because it matches a user's intuitive concept of "verify this release". +- Good, because it aligns with GitHub immutable release and release attestation concepts. +- Bad, because SLSA recommends artifact-bound build attestations rather than release-bound + attestations. +- Bad, because releases can contain assets from different builds, environments, and times. +- Bad, because this conflates build provenance with release integrity and orchestration policy. + +### Treat one generated archive from a selected directory as the release asset + +The profile accepts a directory and creates a tarball or zip file that becomes the single release +asset. + +- Good, because the final verification unit is still one release asset file. +- Good, because profile-owned archive generation can make file ordering, permissions, and timestamps + explicit. +- Bad, because archive format, deterministic packing, symlink behavior, permissions, timestamps, and + path normalization become part of the trusted profile contract. +- Bad, because accepting arbitrary directories can blur the boundary between build output and + release packaging. +- Bad, because this is a packaging feature, not the minimal release asset scope decision. + +### Treat a checksum manifest plus referenced files as the verification unit + +One invocation centers verification on a checksum file that names all release assets and their +digests. + +- Good, because checksum manifests are common in GitHub Releases. +- Good, because they are convenient for human and script-based download verification. +- Bad, because the checksum file is not the same artifact as the files it references. +- Bad, because the SLSA subject can become ambiguous: the manifest, the referenced files, or both. +- Bad, because synchronization between the manifest, uploaded assets, and signed provenance requires + orchestration-level policy. + +### Include GitHub-generated source archives + +The profile treats GitHub-generated source zip or tarball downloads as release assets. + +- Good, because those archives appear on GitHub Release pages and are commonly downloaded. +- Neutral, because GitHub release verification can cover release integrity around immutable + releases. +- Bad, because GitHub-generated source archives are not uploaded artifacts produced by this trusted + profile. +- Bad, because they are source snapshots or generated source distributions rather than build outputs + owned by the release asset profile. +- Bad, because mixing source archive verification into this profile would blur SLSA Build Track and + Source Track responsibilities. + +## More Information + +This decision follows ADR 0038 and decides only the initial GitHub Release asset profile's artifact +scope and verification unit. It does not decide the profile workflow filename, exact public input +names, job graph, build or pack command policy, immutable release requirement, release creation +policy, checksum manifest schema, SBOM format, sidecar naming convention, or orchestration workflow +interface. + +Reference points considered: + +- SLSA v1.2 Distributing Provenance says attestations should be bound to artifacts rather than + releases, and package ecosystems should support multiple individual attestations per release. +- SLSA v1.2 Build Provenance represents build outputs through the top-level `subject` list, with + each subject identified by digest. +- GitHub `actions/attest` models subjects as named artifacts with digest maps. It supports one + subject, multiple subjects by glob or explicit list, and subject enumeration through checksum + files, but multiple subjects are still artifact subjects rather than a full-release subject. +- GitHub immutable releases protect release tags and release assets after publication and can + produce release attestations, but that release-level mechanism remains complementary to + artifact-bound build provenance. diff --git a/docs/decisions/0040-use-github-release-asset-slsa3-workflow-entrypoint.md b/docs/decisions/0040-use-github-release-asset-slsa3-workflow-entrypoint.md new file mode 100644 index 0000000..15e8e43 --- /dev/null +++ b/docs/decisions/0040-use-github-release-asset-slsa3-workflow-entrypoint.md @@ -0,0 +1,211 @@ +--- +parent: Decisions +nav_order: 40 +status: superseded by ADR-0049 +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use `github-release-asset-slsa3.yml` as the GitHub Release Asset Workflow Entrypoint + +## Context and Problem Statement + +ADR 0002 chose an extensible trusted reusable workflow foundation, and ADR 0003 refined that +foundation into a thin shared core with profile-owned reusable workflows. ADR 0022 selected +`.github/workflows/js-ts-npm-package-slsa3.yml` as the initial JS/TS npm package profile entrypoint. +ADR 0028 later made the reusable workflow file path plus full commit SHA part of the production +`builder.id` trust anchor. ADR 0038 added the GitHub Release asset profile direction, and ADR 0039 +scoped that profile to one explicitly named GitHub Release asset per profile run. + +The GitHub Release asset profile now needs a concrete reusable workflow filename before its +architecture specification fixes `runDetails.builder.id`, caller examples, and verifier policy. +Since the workflow filename appears in both the caller's `jobs..uses` reference and the SLSA +provenance `runDetails.builder.id`, this is a verifier-visible public contract, not an internal YAML +detail. + +What reusable workflow filename and entrypoint should the initial GitHub Release asset SLSA3 profile +expose? + +## Decision Drivers + +- Make the public entrypoint identify the selected profile scope: one GitHub Release asset per run. +- Keep the workflow path aligned with ADR 0028's SHA-pinned reusable workflow `builder.id` pattern. +- Preserve consistency with ADR 0022's `-slsa3.yml` naming style for production SLSA3 + profile-owned reusable workflows. +- Make the SLSA Build L3-oriented trust mode visible in caller workflows, provenance, and verifier + policy. +- Avoid implying that the low-level profile publishes or verifies multiple release assets in one + run. +- Avoid generic artifact naming that would hide GitHub Release-specific parameters such as release + tag, asset name, upload target, immutable release behavior, and GitHub attestation storage. +- Leave room for a separate higher-level orchestration workflow for multi-asset releases. + +## Considered Options + +- Use `.github/workflows/github-release-asset-slsa3.yml`. +- Use `.github/workflows/release-asset-slsa3.yml`. +- Use `.github/workflows/github-release-assets-slsa3.yml`. +- Use `.github/workflows/generic-artifact-slsa3.yml`. +- Use `.github/workflows/github-release-asset.yml`. + +## Decision Outcome + +Chosen option: "Use `.github/workflows/github-release-asset-slsa3.yml`", because it names the GitHub +Release asset profile directly, uses singular `asset` to reinforce ADR 0039's one-asset-per-run +scope, and follows the existing production profile naming pattern selected for the JS/TS npm package +profile. + +The initial GitHub Release asset profile should expose this reusable workflow entrypoint: + +```text +.github/workflows/github-release-asset-slsa3.yml +``` + +Production callers should invoke the released workflow using a full 40-character commit SHA as +selected by ADR 0028: + +```yaml +jobs: + release-asset: + uses: windlasstech/slsa-builder/.github/workflows/github-release-asset-slsa3.yml@0123456789abcdef0123456789abcdef01234567 +``` + +The production SLSA provenance emitted by this profile should use a `builder.id` shaped like: + +```text +https://github.com/windlasstech/slsa-builder/.github/workflows/github-release-asset-slsa3.yml@0123456789abcdef0123456789abcdef01234567 +``` + +The workflow should be the public `workflow_call` entrypoint for the low-level one-asset GitHub +Release asset profile. Caller workflows own their local triggers and orchestration choices, but they +interact with this trusted profile through declared workflow inputs and secrets only. The workflow +filename decision does not decide the full input, output, secret, runner, permission, job graph, +release creation, immutable release, upload, or verification contract; those details belong in the +release asset architecture specification or follow-up ADRs. + +A higher-level multi-asset release orchestration workflow, if added later, should use a distinct +workflow filename and should compose this one-asset profile rather than sharing its `builder.id`. +Any preview, experimental, lower-assurance, baseline attestation, generic artifact, or full-release +workflow should also use a distinct filename if it has materially different security properties, +artifact scope, isolation guarantees, signing behavior, runner trust, or claimed SLSA Build level. + +### Consequences + +- Good, because the filename directly matches the profile selected in ADR 0038 and scoped in + ADR 0039. +- Good, because singular `asset` makes the one-profile-run-one-asset verification unit visible in + the builder identity. +- Good, because the `slsa3` suffix clearly marks the production SLSA Build L3-oriented reusable + workflow contract. +- Good, because the chosen name stays consistent with the existing + `.github/workflows/js-ts-npm-package-slsa3.yml` profile entrypoint pattern. +- Good, because later multi-asset orchestration, generic artifact, and lower-assurance workflows can + receive distinct filenames and builder identities. +- Neutral, because the filename is longer than `release-asset-slsa3.yml`. +- Bad, because including `github` and `slsa3` makes the entrypoint intentionally GitHub-specific and + commits the implementation to satisfying the documented SLSA3-oriented trust contract before + release. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification +and implementation define: + +- `.github/workflows/github-release-asset-slsa3.yml` as the public reusable workflow entrypoint; +- `on.workflow_call` as the supported entry mechanism for callers; +- production caller examples using + `windlasstech/slsa-builder/.github/workflows/github-release-asset-slsa3.yml@`; +- production `builder.id` values using the workflow file path plus the same full commit SHA used for + reusable workflow execution; +- exactly one explicitly named GitHub Release asset as the primary output and verification unit for + this workflow; +- separate filenames for multi-asset orchestration, preview, experimental, lower-assurance, + non-production, generic artifact, or full-release workflows; +- review checks that prevent arbitrary caller-defined steps, services, defaults, or environment from + becoming part of this trusted workflow entrypoint. + +Implementation review should verify that production release asset provenance does not emit a +different workflow filename in `builder.id` unless a later ADR changes this public entrypoint +decision. + +## Pros and Cons of the Options + +### Use `.github/workflows/github-release-asset-slsa3.yml` + +Expose the low-level release asset profile as a GitHub-specific, one-asset reusable workflow whose +name includes the SLSA3 trust mode. + +- Good, because it is the most direct match for GitHub Release asset scope. +- Good, because singular `asset` avoids implying multi-asset handling in one profile run. +- Good, because it keeps `builder.id` verifier policies readable and profile-specific. +- Good, because it matches the established `-slsa3.yml` production profile naming pattern. +- Neutral, because the name is long. +- Bad, because the entrypoint is explicitly GitHub-specific rather than reusable for arbitrary file + artifacts. + +### Use `.github/workflows/release-asset-slsa3.yml` + +Expose the profile with a shorter release-asset-focused filename. + +- Good, because it is concise and still names the release asset artifact scope. +- Good, because it includes the SLSA3 trust mode. +- Bad, because it does not identify GitHub Release as the distribution surface. +- Bad, because future release surfaces could make the name ambiguous. +- Bad, because GitHub-specific verifier parameters are less visible in `builder.id`. + +### Use `.github/workflows/github-release-assets-slsa3.yml` + +Use a plural filename to reflect that GitHub Releases commonly contain multiple assets. + +- Good, because it names the GitHub Release asset domain clearly. +- Good, because it may feel natural to users thinking about complete releases. +- Bad, because plural `assets` conflicts with ADR 0039's one-asset-per-run verification unit. +- Bad, because users may expect one invocation to publish, sign, or verify multiple release assets. +- Bad, because documentation would need to explain why a plural builder identity emits exactly one + primary SLSA subject. + +### Use `.github/workflows/generic-artifact-slsa3.yml` + +Expose the profile as a generic artifact SLSA3 workflow and treat GitHub Release upload as one use +case. + +- Good, because it appears more reusable for future artifact surfaces. +- Good, because SLSA subjects are artifact-bound and can be represented by generic name-and-digest + pairs. +- Bad, because it broadens the profile beyond ADR 0038 and ADR 0039 before the GitHub Release asset + contract is specified. +- Bad, because it hides release tag, asset name, upload target, immutable release, and GitHub + attestation storage semantics. +- Bad, because generic naming would make a future truly generic artifact profile harder to + distinguish from this GitHub-specific profile. + +### Use `.github/workflows/github-release-asset.yml` + +Expose the profile without encoding the SLSA trust mode in the filename. + +- Good, because it is shorter than the selected filename. +- Good, because it avoids putting the SLSA level directly in the workflow path. +- Bad, because it is inconsistent with the existing JS/TS npm package production entrypoint name. +- Bad, because it makes security mode separation less visible in caller workflows and `builder.id`. +- Bad, because future preview, SLSA2, baseline attestation, or non-claiming workflows could be + tempted to share the same filename and blur verifier expectations. + +## More Information + +This decision follows ADR 0038 and ADR 0039. It decides only the reusable workflow filename and +public entrypoint for the initial GitHub Release asset SLSA3 profile. It does not decide the exact +public input contract, output names, job graph, build or pack command policy, release creation +policy, immutable release requirement, asset upload behavior, checksum or SBOM handling, provenance +sidecar naming, orchestration workflow interface, or standalone verifier CLI surface. + +Reference points considered: + +- GitHub reusable workflows must live directly under `.github/workflows/`, must include + `on.workflow_call`, and are invoked by callers through + `{owner}/{repo}/.github/workflows/{filename}@{ref}`. +- ADR 0028 requires production consumers to pin reusable workflows by full commit SHA and requires + production `builder.id` values to include the same full commit SHA. +- SLSA v1.2 provenance defines `runDetails.builder.id` as the trusted build platform identity and + requires different builder IDs for modes with different security attributes or SLSA Build levels. +- ADR 0039 scopes the initial GitHub Release asset profile to exactly one explicitly named GitHub + Release asset per profile run. diff --git a/docs/decisions/0041-use-github-release-asset-buildtype-uri.md b/docs/decisions/0041-use-github-release-asset-buildtype-uri.md new file mode 100644 index 0000000..645ca13 --- /dev/null +++ b/docs/decisions/0041-use-github-release-asset-buildtype-uri.md @@ -0,0 +1,247 @@ +--- +parent: Decisions +nav_order: 41 +status: superseded by ADR-0042 +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use `github-release-asset` as the GitHub Release Asset `buildType` Profile URI + +## Context and Problem Statement + +ADR 0021 selected Windlass-owned, profile-specific, major-versioned repository-path `buildType` URIs +for profile-owned reusable workflows. ADR 0038 added the GitHub Release asset profile and requires a +profile-specific GitHub Release asset `buildType` URI and schema. ADR 0039 scoped that profile to +one explicitly named GitHub Release asset per run. ADR 0040 selected +`.github/workflows/github-release-asset-slsa3.yml` as the profile's reusable workflow entrypoint. + +The release asset build type specification will define how verifiers interpret the profile's asset +inputs, source ref, resolved source commit, artifact digest, GitHub Release linkage, and +verification rules. Because SLSA provenance uses `buildDefinition.buildType` as the schema identity +for `externalParameters`, `internalParameters`, and `resolvedDependencies`, the URI must identify +the GitHub Release asset profile semantics without confusing them with the reusable workflow path, +signing adapter, release orchestration layer, or a future generic artifact profile. + +What `buildType` URI should the initial GitHub Release asset profile emit? + +## Decision Drivers + +- Follow ADR 0021's Windlass-owned repository-path `buildType` URI convention. +- Preserve SLSA provenance v1's separation between `builder.id` as the trusted builder identity and + `buildType` as the parameterized build template and schema identity. +- Give verifiers a stable schema URI for interpreting release asset `externalParameters` and + rejecting unexpected fields. +- Make GitHub Release asset semantics first-class: release tag, asset name, local asset path, upload + target, uploaded asset identity, immutable release behavior, and GitHub attestation storage. +- Keep ADR 0039's one-asset-per-run scope visible in the profile segment. +- Avoid a generic artifact URI that hides GitHub Release-specific linkage and verification rules. +- Avoid embedding the SLSA level or workflow filename into the build type schema identity. +- Preserve a future path for a stable Windlass documentation-domain URI if documentation hosting + changes later. + +## Considered Options + +- Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1`. +- Use `https://github.com/windlasstech/slsa-builder/buildtypes/release-asset/v1`. +- Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-assets/v1`. +- Use `https://github.com/windlasstech/slsa-builder/buildtypes/generic-artifact/v1`. +- Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset-slsa3/v1`. +- Use `https://slsa-builder.windlass.dev/buildtypes/github-release-asset/v1`. + +## Decision Outcome + +Chosen option: "Use +`https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1`", because it +follows the existing Windlass build type URI convention while naming the selected GitHub Release +asset profile precisely and preserving one-asset-per-run semantics. + +The initial GitHub Release asset profile should emit this SLSA provenance build type: + +```text +https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1 +``` + +This URI identifies the release asset profile's schema and verification contract, not the workflow +filename, signing adapter, or SLSA Build level. The production reusable workflow identity selected +in ADR 0040 remains the `builder.id`, while this build type remains stable across +backward-compatible builder releases: + +```text +builder.id: https://github.com/windlasstech/slsa-builder/.github/workflows/github-release-asset-slsa3.yml@0123456789abcdef0123456789abcdef01234567 +buildType: https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1 +``` + +The build type specification for this URI should define at least: + +- the profile and artifact scope: exactly one explicitly named GitHub Release asset per production + profile run; +- supported and unsupported invocation modes; +- `externalParameters` for source repository, source ref, release tag, local asset path, release + asset name, upload intent, release policy, runtime policy, and other verifier-relevant + caller-controlled inputs; +- `internalParameters`, if any, for trusted workflow-controlled values useful for reproduction, + debugging, or incident response; +- `resolvedDependencies` rules, including source ref to resolved commit mapping; +- subject naming and digest rules for the single GitHub Release asset; +- byproduct rules for upload result, asset URL or API identifier, signed attestation bundle, release + metadata, or related diagnostic artifacts; +- GitHub Release linkage rules connecting source identity, release tag, asset name, uploaded asset + identity, and provenance subject digest; +- verifier expectations for Sigstore signature, signer identity, SHA-based `builder.id`, this + `buildType`, complete `externalParameters`, source commit, release tag, asset name, subject + digest, and GitHub Release linkage; +- rejection behavior for unknown `externalParameters` unless the specification explicitly allows + them; +- exclusion of full-release, multi-asset, checksum-manifest-centered, SBOM-bundled, + provenance-sidecar-bundled, detached-signature-bundled, and GitHub-generated source archive + semantics from this low-level build type; +- a complete example SLSA provenance predicate and version history. + +Breaking changes to required fields, field meanings, verification expectations, supported release +asset scope, or subject semantics should use a new major build type URI such as `/v2`. Backward- +compatible clarifications and optional fields may remain under `/v1` if verifiers can continue to +reject unexpected `externalParameters` according to the specification. + +### Consequences + +- Good, because the build type URI matches ADR 0021's existing convention. +- Good, because `github-release-asset` directly names the GitHub Release asset profile selected by + ADR 0038 and scoped by ADR 0039. +- Good, because singular `asset` keeps the one-primary-subject verification unit visible. +- Good, because the URI lets verifiers distinguish this profile from generic artifact, + whole-release, and multi-asset orchestration workflows. +- Good, because the URI stays independent of workflow release SHAs, signing adapter implementation, + and SLSA Build level claims. +- Neutral, because the URI is repository-hosted rather than hosted on a dedicated documentation + domain. +- Bad, because Windlass must maintain a concrete build type specification and fixtures for another + profile-specific schema. +- Bad, because generic GitHub or SLSA tooling will not understand this custom build type without + Windlass-specific policy or documentation. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1` as the + production release asset profile `buildType`; +- a repository-local specification document that explains that build type; +- complete schemas for `externalParameters`, `internalParameters`, `resolvedDependencies`, subjects, + byproducts, and release linkage; +- examples showing the distinction between SHA-based `builder.id` and this stable `buildType`; +- verifier behavior that rejects unexpected `externalParameters` unless explicitly allowed by the + build type specification; +- accepted and rejected fixture cases for source mismatch, release tag mismatch, asset name + mismatch, digest mismatch, upload mismatch, wrong `builder.id`, wrong `buildType`, unknown + parameters, and unsupported full-release or multi-asset semantics; +- versioning rules for backward-compatible clarifications and breaking schema changes; +- a future-decision boundary for moving to a stable Windlass documentation-domain URI. + +Implementation review should verify that production GitHub Release asset provenance does not reuse a +generic GitHub Actions workflow build type, SLSA GitHub Generator generic build type, generic +artifact build type, released workflow identity, or `actions/attest` default provenance build type +under this production builder identity. + +## Pros and Cons of the Options + +### Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset/v1` + +Use the existing Windlass repository-path convention with a singular GitHub Release asset profile +segment. + +- Good, because it is the most direct match for the selected profile and one-asset scope. +- Good, because it keeps GitHub Release-specific parameters first-class. +- Good, because it decouples the schema from builder release SHAs and workflow filenames. +- Good, because a later `/v2` can represent breaking release asset schema changes cleanly. +- Neutral, because URI-to-file mapping must be documented until a dedicated documentation site + exists. +- Bad, because it is intentionally GitHub Release-specific and not reusable for arbitrary artifacts. + +### Use `https://github.com/windlasstech/slsa-builder/buildtypes/release-asset/v1` + +Use a shorter profile segment that omits GitHub. + +- Good, because it is concise and still names release asset semantics. +- Good, because it might appear reusable for non-GitHub release surfaces. +- Bad, because the initial profile is tightly coupled to GitHub Release tags, assets, upload + behavior, immutable release behavior, and GitHub attestation storage. +- Bad, because future release surfaces could make the URI ambiguous. +- Bad, because GitHub-specific verifier expectations are less visible. + +### Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-assets/v1` + +Use a plural profile segment for GitHub Release assets. + +- Good, because GitHub Releases commonly contain multiple downloadable assets. +- Good, because it may feel natural to users thinking about complete release publication. +- Bad, because plural `assets` conflicts with ADR 0039's one-asset-per-run verification unit. +- Bad, because verifiers and users may infer multi-subject or whole-release semantics. +- Bad, because documentation would need to explain that the build type emits exactly one primary + release asset subject. + +### Use `https://github.com/windlasstech/slsa-builder/buildtypes/generic-artifact/v1` + +Use a broad artifact-bound build type and treat GitHub Release upload as one specialization. + +- Good, because SLSA build provenance is artifact-bound and generic file subjects are easy to model. +- Good, because it may be reusable across future file artifact profiles. +- Bad, because it broadens the profile beyond the GitHub Release asset contract selected in + ADR 0038. +- Bad, because it hides release tag, asset name, upload target, immutable release, and GitHub + attestation storage semantics. +- Bad, because it would conflict with a future truly generic artifact profile. + +### Use `https://github.com/windlasstech/slsa-builder/buildtypes/github-release-asset-slsa3/v1` + +Include the SLSA3 trust mode in the build type profile segment. + +- Good, because it mirrors the production workflow filename closely. +- Good, because it makes the SLSA Build L3-oriented claim visible in the build type URI. +- Bad, because SLSA level and trust mode are better represented by `builder.id`, signer identity, + and verifier roots of trust than by the build template schema name. +- Bad, because the same release asset schema may remain valid across future assurance modes, while + different assurance modes should use distinct builder identities. +- Bad, because it couples schema identity to the production workflow naming convention. + +### Use `https://slsa-builder.windlass.dev/buildtypes/github-release-asset/v1` + +Use a stable Windlass documentation-domain URI. + +- Good, because it provides the cleanest long-term human-readable documentation URI. +- Good, because it decouples build type identity from GitHub repository layout. +- Good, because it remains a good future direction if Windlass publishes stable versioned docs. +- Bad, because ADR 0021 already selected repository-path URIs for the current stage. +- Bad, because the project does not yet have the dedicated documentation domain, redirect policy, or + publication workflow needed to make this operational. +- Bad, because changing URI families now would require broader compatibility and transition rules. + +## More Information + +This decision applies ADR 0021's build type URI convention to the GitHub Release asset profile. It +decides only the `buildDefinition.buildType` URI and high-level scope of its specification. It does +not decide the exact public workflow input names, output names, job graph, release creation policy, +immutable release requirement, upload implementation, checksum manifest schema, SBOM format, +provenance sidecar naming, orchestration workflow interface, or standalone verifier CLI surface. + +Reference points considered: + +- SLSA v1.2 Build Provenance defines `buildDefinition.buildType` as the URI identifying how to + perform the build and interpret `externalParameters`, `internalParameters`, and + `resolvedDependencies`. +- SLSA v1.2 Build Provenance says `buildType` URIs should resolve to a human-readable specification + that includes parameter schemas, unambiguous initiation instructions, and a complete example. +- SLSA v1.2 Verifying Artifacts recommends verifying `buildType` and `externalParameters`, with + unrecognized `externalParameters` causing verification failure unless allowed by policy. +- SLSA v1.2 Distributing Provenance says attestations should be bound to artifacts rather than + releases and that source repository releases are a valid provenance distribution surface when they + publish artifacts. +- The community GitHub Actions workflow build type describes top-level workflow execution and is not + intended to describe reusable workflows, actions, or jobs. +- SLSA GitHub Generator uses project-owned build type URIs such as + `https://github.com/slsa-framework/slsa-github-generator/generic@v1`, but its generic profile does + not define Windlass GitHub Release asset semantics. +- GitHub `actions/attest` can sign custom predicates and identify subjects by path, digest, or + checksum input, but ADR 0038 treats it as a signing and storage adapter rather than the owner of + the Windlass release asset build type semantics. diff --git a/docs/decisions/0042-use-acquired-domains-for-buildtype-uris.md b/docs/decisions/0042-use-acquired-domains-for-buildtype-uris.md new file mode 100644 index 0000000..70e7dd0 --- /dev/null +++ b/docs/decisions/0042-use-acquired-domains-for-buildtype-uris.md @@ -0,0 +1,283 @@ +--- +parent: Decisions +nav_order: 42 +status: accepted +date: 12026-07-06 +decision-makers: Yunseo Kim +--- + +# Use Acquired Domains for `buildType` URIs: `buildtype.dev` as Identifier, `slsa-builder.dev` as Documentation Host + +## Context and Problem Statement + +ADR 0021 selected Windlass-owned, profile-specific, major-versioned repository-path `buildType` URIs +for profile-owned reusable workflows. ADR 0041 applied that convention to the GitHub Release asset +profile. Both decisions intentionally left open a future move to a stable Windlass documentation +domain once such infrastructure existed. + +Windlass has now acquired the relevant domains for this project and future build type namespace +work: + +- `slsa-builder.dev` – the project documentation site. +- `buildtype.dev` – the canonical namespace for `buildType` URI identifiers. +- `buildtypes.dev` – a registry, index, or alias surface for build type discovery. +- `buildtype.com` and `buildtypes.com` – defensive domains that redirect to the `.dev` surfaces. + +The project is still in the architecture and decision phase; no build type specifications, workflow +implementations, or provenance attestations have been published. Because no provenance has been +issued, there is no existing corpus to migrate, no verifier policy to update for legacy URIs, and no +need for a grace period or alias chain. Switching the `buildType` URI family now carries the lowest +possible cost and prevents a disruptive migration later. + +The remaining question is how to assign roles to the two acquired domains and what exact URI shape +the project should adopt for profile `buildType` values going forward. + +## Decision Drivers + +- Separate the stable machine-readable `buildType` identifier from the human-readable documentation + hosting location. +- Replace repository-path `buildType` URIs before any provenance is emitted, avoiding future + migration or alias maintenance. +- Keep the `buildType` identifier stable and independent of GitHub repository layout, hosting + provider, or future repository renames. +- Preserve a clear organizational and project namespace so Windlass can register additional build + types under the same identifier domain without collision. +- Ensure `buildType` URIs still resolve to human-readable specifications, as recommended by SLSA + provenance v1. +- Follow SLSA community conventions for build type URI shape (`buildtypes//v`) on + the documentation side. +- Minimize operational complexity while achieving the above goals. + +## Considered Options + +- Keep the repository-path `buildType` URIs selected in ADR 0021 and ADR 0041. +- Use `slsa-builder.dev` as both the `buildType` identifier and the documentation host. +- Use `buildtype.dev` as both the `buildType` identifier and the documentation host. +- Use `buildtype.dev` as the `buildType` identifier and `slsa-builder.dev` as the documentation + host, with redirects from the identifier domain to the documentation domain. +- Use `slsa-builder.dev` as the `buildType` identifier and `buildtype.dev` as a registry/index site. + +## Decision Outcome + +Chosen option: "Use `buildtype.dev` as the `buildType` identifier and `slsa-builder.dev` as the +documentation host, with redirects from the identifier domain to the documentation domain", because +it separates identifier stability from documentation presentation, decouples the build type +namespace from the project documentation site, and avoids migration cost by switching before any +provenance is published. + +### Canonical `buildType` URI shape + +The canonical `buildType` URI value that profile-owned reusable workflows must emit in provenance +predicates is: + +```text +https://buildtype.dev////v +``` + +For the Windlass `slsa-builder` project, the concrete URIs are: + +- JS/TS npm package profile: + + ```text + https://buildtype.dev/windlass/slsa-builder/js-ts-npm-package/v1 + ``` + +- GitHub Release asset profile: + + ```text + https://buildtype.dev/windlass/slsa-builder/github-release-asset/v1 + ``` + +The `/` segments identify the owner of the build type schema and isolate the namespace +from other Windlass projects or third parties. The `` segment identifies the artifact and +profile semantics. The `` segment identifies the major version of the build type schema. +Backward-compatible schema clarifications and additional optional fields may remain on the same +major version. Breaking changes to meanings, required fields, verification expectations, or +supported build templates must use a new major version such as `/v2`. + +### Documentation host and redirect behavior + +The human-readable build type specification for each URI is hosted on `slsa-builder.dev` at: + +```text +https://slsa-builder.dev/buildtypes//v +``` + +Concrete documentation URLs: + +```text +https://slsa-builder.dev/buildtypes/js-ts-npm-package/v1 +https://slsa-builder.dev/buildtypes/github-release-asset/v1 +``` + +Requests to `https://buildtype.dev////v` must resolve through a +permanent redirect, preferably HTTP 308 or otherwise HTTP 301, to the corresponding +`https://slsa-builder.dev/buildtypes//v` documentation page. This lets +`buildtype.dev` act as a stable, opaque identifier namespace while still satisfying the SLSA +recommendation that `buildType` URIs resolve to human-readable specifications. Temporary redirects +such as HTTP 302 or HTTP 307 should not be used for canonical build type specification resolution +because they imply that the documentation target is not stable. + +The adjacent domains are supporting surfaces, not separate `buildType` URI authorities for this +decision. `buildtypes.dev` may serve as a registry, index, or alias surface that points users and +tools to canonical `buildtype.dev` identifiers. It may redirect to `buildtype.dev` or host a hub UI +that lists known build type specifications. `buildtype.com` and `buildtypes.com` are defensive +registrations and should redirect to the corresponding `.dev` surfaces rather than emit canonical +Windlass `buildType` URIs. + +Verifiers should compare the exact canonical `buildtype.dev` URI recorded in provenance. Redirects +exist for human-readable specification discovery and ecosystem navigation; they do not make redirect +targets or alias domains equivalent `buildType` identifiers. + +### Superseded decisions + +This decision supersedes the repository-path `buildType` URI choices in: + +- ADR 0021 – Use Profile-Specific `buildType` URIs +- ADR 0041 – Use `github-release-asset` as the GitHub Release Asset `buildType` Profile URI + +Those ADRs remain valid historical context but their chosen `buildType` URI values are no longer +authoritative. The existing repository-path URIs will not be retained as aliases because no +provenance has been issued and the cost of immediate replacement is zero. + +### Consequences + +- Good, because `buildType` identifiers are now stable domain-based URIs rather than repository-path + URIs. +- Good, because the identifier namespace is decoupled from GitHub repository layout and hosting + changes. +- Good, because `buildtype.dev` can host identifiers for other Windlass projects without mixing them + into the `slsa-builder.dev` documentation site. +- Good, because adjacent domains can support registry discovery and defensive redirects without + multiplying canonical `buildType` URI authorities. +- Good, because `slsa-builder.dev` remains a clean, branded project documentation site. +- Good, because redirecting from the identifier domain preserves the SLSA expectation that + `buildType` URIs resolve to specifications. +- Good, because no provenance has been published, so the switch requires no legacy alias or + migration period. +- Neutral, because two domains must be maintained instead of one. +- Neutral, because the URI is slightly longer than a single-domain project URI. +- Bad, because the redirect from `buildtype.dev` to `slsa-builder.dev` must be kept operational for + the identifier resolution contract to hold. +- Bad, because the `/` prefix assumes Windlass will continue to use GitHub-style org + names; a different organizational structure would require revisiting the namespace design. + +### Confirmation + +This decision is confirmed when: + +- ADR 0021 and ADR 0041 are updated to `status: superseded by ADR-0042`. +- This ADR is added to `docs/decisions/` with `status: accepted`. +- Architecture specifications for the JS/TS npm package profile and GitHub Release asset profile + list the new `buildtype.dev` URIs as their `buildType` values. +- Reusable workflow implementations emit the new `buildType` URIs in production provenance. +- Verifier documentation and example policies reference the new `buildType` URIs. +- `buildtype.dev` is configured to issue permanent HTTP 308 or HTTP 301 redirects from + `////v` to the matching + `slsa-builder.dev/buildtypes//v` documentation page. +- `buildtypes.dev`, `buildtype.com`, and `buildtypes.com` are documented or configured as registry, + alias, or defensive redirect surfaces rather than canonical `buildType` URI namespaces. +- Tests assert that emitted provenance predicates use the new `buildType` URI format and do not + reference the superseded repository-path URIs. + +## Pros and Cons of the Options + +### Keep the repository-path `buildType` URIs + +Continue using `https://github.com/windlasstech/slsa-builder/buildtypes//v`. + +- Good, because no new infrastructure or redirect configuration is required. +- Good, because existing ADRs remain authoritative without change. +- Bad, because the URI is tied to GitHub repository path and ownership; a repository rename or move + would break or complicate the identifier. +- Bad, because it wastes the acquired domains and misses the opportunity to switch before any + provenance is published. +- Bad, because it conflates source hosting with build type schema ownership. + +### Use `slsa-builder.dev` as both identifier and documentation host + +Use `https://slsa-builder.dev/buildtypes//v` for both `buildType` values and +specifications. + +- Good, because it is a single domain to operate and explain. +- Good, because it strongly brands each build type with the project name. +- Good, because it matches the future-documentation-domain example in ADR 0021. +- Bad, because it does not leverage `buildtype.dev` for its intended purpose as a build type + namespace. +- Bad, because the identifier namespace is tied to one project's documentation site, making + organization-wide reuse harder. +- Bad, because a future Windlass project would need a separate domain or a subpath under + `slsa-builder.dev`, which is semantically awkward. + +### Use `buildtype.dev` as both identifier and documentation host + +Use `https://buildtype.dev////v` for both identifiers and rendered +specifications. + +- Good, because it keeps build type identifiers in a dedicated namespace. +- Good, because organizational and project scoping is clear. +- Bad, because the documentation site loses the `slsa-builder.dev` project branding and URL. +- Bad, because users following a `buildType` URI would land on a generic `buildtype.dev` page rather + than the project documentation site. +- Bad, because it does not separate the concerns of identifier stability and human-readable + documentation presentation as cleanly as the chosen option. + +### Use `buildtype.dev` as identifier and `slsa-builder.dev` as documentation host + +Use `https://buildtype.dev////v` as the canonical `buildType` +identifier and `https://slsa-builder.dev/buildtypes//v` as the human-readable +specification, with redirects from the former to the latter. + +- Good, because it cleanly separates stable identifier namespace from project documentation hosting. +- Good, because `buildtype.dev` can scale to other Windlass projects under the same namespace + convention. +- Good, because `slsa-builder.dev` remains the branded, user-facing documentation site. +- Good, because redirects preserve SLSA's expectation that `buildType` URIs resolve to + specifications. +- Good, because the switch happens before any provenance is issued, eliminating migration cost. +- Neutral, because two domains must be configured and renewed. +- Bad, because redirect availability becomes part of the build type resolution contract. + +### Use `slsa-builder.dev` as identifier and `buildtype.dev` as registry/index + +Use `https://slsa-builder.dev/buildtypes//v` as the identifier and +`https://buildtype.dev/` as a cross-project build type registry or index. + +- Good, because it brands the identifier with the project. +- Bad, because it ties the identifier to a single project documentation domain, reintroducing the + coupling the dedicated `buildtype.dev` domain was meant to avoid. +- Bad, because a registry/index role for `buildtype.dev` is speculative and not currently needed. +- Bad, because it does not use `buildtype.dev` for its natural purpose as the identifier namespace. + +## More Information + +This decision supersedes the `buildType` URI choices in ADR 0021 and ADR 0041. Those ADRs retain +their reasoning about why `buildType` should be separate from `builder.id`, why the profile segment +should name artifact semantics rather than workflow filenames, and why major-versioned schema URIs +are preferable to tag-churning identities. Only the URI authority and namespace shape change here. + +Reference points considered: + +- SLSA provenance v1 requires `buildDefinition.buildType` and recommends that the URI resolve to a + human-readable specification with parameter schemas, initiation instructions, and a complete + example. +- SLSA provenance v1 treats `buildType` as the schema identity for `externalParameters`, + `internalParameters`, and `resolvedDependencies`. +- ADR 0021 separated `builder.id` (released reusable workflow identity) from `buildType` (profile + schema identity) and explicitly noted a future move to a stable documentation domain. +- ADR 0041 applied the ADR 0021 convention to the GitHub Release asset profile and likewise left + open a future documentation-domain move. +- No provenance, verifier policy, or published specification currently references the superseded + repository-path URIs, making immediate replacement cost-free. + +Future decisions may define: + +- the exact repository-local file path for build type specifications before they are published to + `slsa-builder.dev`; +- the redirect and hosting mechanics for `buildtype.dev` (e.g., static site generator, edge redirect + rules, DNS configuration); +- the registry, index, alias, or defensive redirect behavior for `buildtypes.dev`, `buildtype.com`, + and `buildtypes.com`; +- whether future Windlass projects follow the same `https://buildtype.dev///...` + convention; +- how to version and publish build type specifications alongside project releases. diff --git a/docs/decisions/0043-upload-release-assets-to-existing-github-releases.md b/docs/decisions/0043-upload-release-assets-to-existing-github-releases.md new file mode 100644 index 0000000..cfeaffe --- /dev/null +++ b/docs/decisions/0043-upload-release-assets-to-existing-github-releases.md @@ -0,0 +1,229 @@ +--- +parent: Decisions +nav_order: 43 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Upload GitHub Release Assets to Existing Releases + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance with `actions/attest` as the initial signing +and storage adapter for GitHub Release assets. ADR 0039 scoped the low-level profile to one +explicitly named GitHub Release asset per profile run. ADR 0040 selected the reusable workflow +entrypoint for that one-asset profile. + +The remaining lifecycle policy question is how much GitHub Release state the low-level profile +should own. GitHub Releases can be created from existing tags, can create tags when the tag is +missing in some tooling flows, can be drafts or prereleases, can become immutable after publication, +and can contain existing assets with the same name. The profile needs a failure-closed policy that +preserves artifact-bound SLSA provenance without turning the one-asset profile into a whole-release +orchestrator. + +Should the initial GitHub Release asset profile create or update GitHub Releases, or should it +upload one asset only to an existing release selected by an existing tag? + +## Decision Drivers + +- Preserve ADR 0039's one-asset profile boundary and keep whole-release lifecycle concerns in an + orchestration layer. +- Keep SLSA provenance artifact-bound rather than treating a GitHub Release as the build subject. +- Avoid giving the low-level profile responsibility for creating, moving, signing, deleting, or + selecting release tags. +- Avoid GitHub tooling behavior that can create a missing tag from a default branch or selected + target commit during release creation. +- Support GitHub immutable release workflows, where the recommended flow is draft release creation, + asset upload, then publication. +- Fail clearly when the target release or asset slot is not in the expected state. +- Avoid overwriting, deleting, or replacing existing release assets under the same production + profile invocation. + +## Considered Options + +- Upload one asset to an existing GitHub Release selected by an existing tag, failing when the tag + or release is missing. +- Create the GitHub Release when it is missing, then upload the asset. +- Create a draft GitHub Release, upload the asset, and publish the release from the same profile + run. +- Allow asset overwrite or clobber behavior for existing asset names. +- Treat release creation, asset upload, and publish as one high-level orchestration workflow. + +## Decision Outcome + +Chosen option: "Upload one asset to an existing GitHub Release selected by an existing tag, failing +when the tag or release is missing", because it keeps the low-level profile focused on one +artifact-bound release asset while leaving release lifecycle, draft creation, publication, notes, +and multi-asset coordination to a higher-level orchestration layer. + +The initial GitHub Release asset profile should require both: + +- an existing Git tag matching the requested release tag; and +- an existing GitHub Release associated with that tag. + +The profile should fail before provenance signing or asset upload when either the tag or release is +missing. It should not create a tag, create a release, select a default branch target, move a +release tag, publish a draft release, update release notes, change the prerelease flag, change the +latest release marker, or otherwise mutate release lifecycle metadata. + +Draft releases are valid upload targets. This supports the immutable-release-friendly flow where an +orchestration layer creates a draft release, invokes the one-asset profile once per asset, and then +publishes the release after all required assets are attached. Published releases are also valid +upload targets when GitHub and repository policy allow adding assets after publication, but the +recommended production flow is to upload through draft releases when immutable releases are enabled +or desired. + +Prerelease GitHub Releases are valid upload targets. The low-level profile should record the +observed release state if the architecture specification makes it verifier-relevant, but it should +not decide or change whether the release is a prerelease. + +The profile should reject an existing release asset with the requested asset name. It should not +delete, overwrite, replace, or clobber an existing asset as part of the production path. If a +previous attempt uploaded the asset but later failed, the correct follow-up is verification or +manual inspection, not silent replacement under the same tag and asset name. Retrying after a +failure is supported only when the asset was not uploaded or when the caller chooses a distinct +asset name under an explicit new release policy outside this low-level profile. + +The profile's required GitHub token permissions should not exceed what is needed to read repository +contents, attest provenance, and upload the selected release asset. Any additional permissions +needed to create releases, publish drafts, edit release notes, mark prereleases, or manage tags +belong to the orchestration layer or caller workflow that performs those lifecycle operations. + +### Consequences + +- Good, because the low-level profile remains a one-asset upload and provenance contract rather than + a whole-release lifecycle manager. +- Good, because missing tags and releases fail closed instead of being created implicitly from a + default branch or caller-selected target. +- Good, because draft releases can still be used for GitHub immutable release publication flows. +- Good, because existing asset names cannot be silently replaced with different bytes or provenance. +- Good, because release notes, draft publication, prerelease flags, latest markers, and multi-asset + completeness checks stay in the orchestration layer that owns whole-release user experience. +- Neutral, because callers must create the release before invoking the low-level profile. +- Neutral, because published mutable releases may still accept new assets when repository policy + allows it. +- Bad, because a single call to the low-level profile cannot create a complete GitHub Release from + scratch. +- Bad, because callers need orchestration when they want draft creation, multiple asset uploads, and + publish as one workflow. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and documentation define: + +- a required release tag input that must identify an existing Git tag; +- failure before provenance signing or upload when the release tag does not exist; +- failure before provenance signing or upload when no GitHub Release exists for the tag; +- upload of exactly one explicitly named asset to the existing release; +- support for draft release targets without publishing them; +- support for prerelease targets without changing prerelease state; +- no tag creation, tag movement, release creation, draft publication, release note editing, + prerelease flag editing, or latest marker editing in the low-level profile; +- failure when an existing release asset already uses the requested asset name; +- no overwrite, delete, replace, or clobber behavior for existing release assets; +- documentation that draft release creation, multi-asset sequencing, immutable release publication, + release notes, and whole-release completeness checks belong to an orchestration layer. + +Implementation review should verify that the production path cannot silently create missing tags or +releases, upload to the wrong release target, publish a draft release, modify release metadata, or +replace an existing asset under the same release tag and asset name. + +## Pros and Cons of the Options + +### Upload one asset to an existing release and fail when tag or release is missing + +The caller or orchestration layer creates the tag and release first. The low-level profile validates +that both exist, uploads one named asset, signs or stores the associated provenance, and fails if +the asset name already exists. + +- Good, because it matches ADR 0039's low-level one-asset scope. +- Good, because release governance remains outside the trusted asset upload profile. +- Good, because it avoids implicit tag creation during release creation flows. +- Good, because it composes cleanly with draft releases and immutable release publication. +- Good, because duplicate asset handling is simple and failure-closed. +- Neutral, because an orchestration layer is required for a one-command whole-release workflow. +- Bad, because first-time users must create the GitHub Release before the asset profile can run. + +### Create the release when it is missing + +The profile accepts release metadata, creates the GitHub Release when the selected tag has no +release, and then uploads the asset. + +- Good, because it reduces setup for simple single-asset releases. +- Good, because the release and asset upload can happen in one profile invocation. +- Bad, because release creation brings release notes, title, target commit, latest marker, draft, + and prerelease policy into the low-level profile. +- Bad, because some tooling can create a missing tag as part of release creation unless explicitly + guarded. +- Bad, because it blurs the boundary between artifact-bound provenance and whole-release lifecycle + management. + +### Create a draft release, upload the asset, and publish it + +The profile owns the full immutable-release-friendly sequence for one release: create draft, upload +the selected asset, and publish. + +- Good, because it matches GitHub's recommended immutable release publication flow for complete + releases. +- Good, because users get a convenient single workflow for simple releases. +- Bad, because publishing a release is a whole-release decision that must know whether all required + assets, checksums, SBOMs, and notes are complete. +- Bad, because multi-asset releases would need partial-success and publish-order handling inside the + low-level one-asset profile. +- Bad, because the profile would need broader `contents: write` behavior and release metadata + inputs. + +### Allow asset overwrite or clobber behavior + +The profile deletes or replaces an existing release asset when the requested asset name already +exists. + +- Good, because retries after partial failure can be operationally convenient. +- Good, because it resembles common mutable release upload tooling that supports clobber behavior. +- Bad, because deleting the old asset before uploading the new one can lose the original asset if + the new upload fails. +- Bad, because replacing bytes under the same release tag and asset name weakens artifact identity + and consumer expectations. +- Bad, because it conflicts with immutable release behavior and SLSA's preference for immutable + attestations corresponding to artifacts. + +### Treat release creation, asset upload, and publish as one orchestration workflow + +A higher-level workflow creates or selects a release, invokes the one-asset profile for each asset, +checks whole-release completeness, and publishes the draft when appropriate. + +- Good, because it can provide the user-friendly whole-release flow that the low-level profile + avoids. +- Good, because it can own release notes, draft status, prerelease status, latest marker, checksums, + SBOMs, and multi-asset completeness checks together. +- Good, because it composes the one-asset profile without changing the artifact-bound SLSA subject + contract. +- Neutral, because this is a separate product surface with a distinct lifecycle contract. +- Bad, because it requires additional design work and possibly a distinct workflow entrypoint or + builder identity. + +## More Information + +This decision follows ADR 0038, ADR 0039, and ADR 0040. It decides only the low-level GitHub Release +asset profile's release creation and upload policy. It does not decide the future orchestration +workflow interface, release notes schema, checksum manifest policy, immutable release requirement, +release asset sidecar naming, or standalone verifier CLI behavior. + +Reference points considered: + +- SLSA v1.2 Distributing Provenance says attestations should be bound to artifacts rather than + releases, and that provenance should accompany the artifact at publish time. +- SLSA v1.2 Distributing Provenance says attestations should be immutable and should not be + overwritten later with a different attestation for the same artifact. +- GitHub CLI `gh release create` can automatically create a matching tag when one does not exist; + `--verify-tag` aborts release creation when the tag is missing. +- GitHub CLI `gh release upload --clobber` deletes existing assets before uploading replacements and + warns that the original asset is lost if the upload fails. +- GitHub immutable releases protect release tags and assets from modification or deletion after + publication, and GitHub recommends creating the release as a draft, attaching all assets, and then + publishing it. +- GitHub artifact attestations and `actions/attest` bind named artifact subjects and digests to + signed predicates; they complement GitHub release attestations but do not require the low-level + profile to own release lifecycle operations. diff --git a/docs/decisions/0044-use-three-job-release-asset-permission-boundary.md b/docs/decisions/0044-use-three-job-release-asset-permission-boundary.md new file mode 100644 index 0000000..75c9805 --- /dev/null +++ b/docs/decisions/0044-use-three-job-release-asset-permission-boundary.md @@ -0,0 +1,270 @@ +--- +parent: Decisions +nav_order: 44 +status: superseded by ADR-0049 +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Use a Three-Job Permission Boundary for GitHub Release Assets + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance with `actions/attest` as the signing and +storage adapter for GitHub Release assets. ADR 0039 scoped the profile to one explicitly named +release asset per profile run. ADR 0043 then selected an upload-only lifecycle policy: the profile +uploads one asset to an existing GitHub Release selected by an existing tag, fails when the tag or +release is missing, and never overwrites an existing asset. + +The remaining boundary is how the GitHub Actions jobs and `GITHUB_TOKEN` permissions should be +split. The profile needs to run caller-controlled build or packaging steps, sign Windlass-generated +SLSA provenance, and mutate a GitHub Release by uploading one asset. Combining those +responsibilities in a single job would put provenance signing authority and release mutation +authority in the same runner environment as user-defined build steps. + +Should the initial GitHub Release asset profile use one job, two jobs, three jobs, or leave release +upload outside the reusable profile? + +## Decision Drivers + +- Keep caller-controlled build and packaging steps away from provenance signing permissions. +- Keep caller-controlled build and packaging steps away from GitHub Release mutation authority. +- Preserve SLSA v1.2 Build L3's requirement that provenance authentication material is not + accessible to user-defined build steps. +- Keep `actions/attest` OIDC and attestation-storage permissions scoped to the job that signs + provenance. +- Keep `contents: write` release mutation authority scoped to the job that uploads the verified + release asset. +- Require release upload to verify both the asset and signed provenance bundle before mutating the + existing GitHub Release. +- Match ADR 0036's npm package precedent: build, signing, and publish or upload mutation are + separate digest-verified jobs. +- Follow Windlass workflow hardening guidance: minimal top-level permissions and job-level elevation + only where required. + +## Considered Options + +- Use a three-job digest-verified graph: `build` → `provenance-sign` → `release-upload`. +- Use one job for build, provenance signing, and release upload. +- Use two jobs: `build` → `sign-and-upload`. +- Use two jobs: `build-and-sign` → `release-upload`. +- Build and sign inside the reusable workflow, then leave release upload to the caller or + orchestration workflow. + +## Decision Outcome + +Chosen option: "Use a three-job digest-verified graph: `build` → `provenance-sign` → +`release-upload`", because it gives the initial GitHub Release asset profile clear job isolation, +minimal permission scopes, and an upload-time verification gate before GitHub Release mutation. + +The initial production GitHub Release asset SLSA3 reusable workflow should define three primary +jobs: + +```text +build -> provenance-sign -> release-upload +``` + +The `build` job should run the profile's caller-controlled build or packaging contract, produce the +single release asset file, validate that the input resolves to exactly one file, calculate the asset +digest, upload the asset as a workflow artifact, and expose only the asset identity and digest +handles needed by later jobs and public workflow outputs. + +The `provenance-sign` job should download the asset artifact, recalculate its digest, compare it +with the `build` job's expected digest outputs, generate the Windlass SLSA provenance v1 statement +for exactly that asset, verify that the statement subject matches the asset digest, sign the +statement through the SHA-pinned `actions/attest` adapter selected by ADR 0038, and upload the +resulting signed Sigstore bundle as a workflow artifact. The signing job should not have GitHub +Release mutation authority. + +The `release-upload` job should download the asset and signed provenance bundle artifacts, +recalculate both artifact digests, verify that those digests match the handoff values produced by +the previous jobs, verify the signed provenance bundle against the expected Sigstore root and GitHub +Actions reusable workflow signer identity, extract and verify the SLSA statement expectations, and +only then upload the verified asset to the existing GitHub Release selected by the requested tag. +The upload job should fail before release mutation if any digest, signature, signer identity, asset +identity, subject, `predicateType`, `builder.id`, `buildType`, `externalParameters`, source +identity, release tag, release state, or duplicate-asset check fails. + +The initial job permission boundary should be: + +- `build`: `contents: read`; no `id-token: write`; no `attestations: write`; no `contents: write`; + no release mutation authority. +- `provenance-sign`: `contents: read`, `id-token: write`, `attestations: write` when GitHub + attestation storage is used, and optional `artifact-metadata: write` only when linked artifact + metadata is intentionally created; no `contents: write`; no release mutation authority. +- `release-upload`: `contents: write`; no `id-token: write`; no `attestations: write`; no signing + authority. The job should use its write permission only for the ADR 0043-selected operation of + uploading one non-duplicate asset to an existing GitHub Release. + +The reusable workflow should set minimal top-level permissions, preferably `permissions: {}` or the +smallest read-only default that GitHub Actions permits for the implementation, and should grant the +above permissions at the job level. Caller documentation should make the required caller workflow +permissions explicit so that the called workflow can receive the needed job-level permissions +without granting broad default write access to every job. + +Cross-job handoff should use immutable GitHub workflow artifacts and trusted job outputs as handles. +The architecture specification should define exact artifact names or IDs, digest encodings, digest +algorithms, download locations, and failure behavior. Jobs must not trust artifact names, paths, +workflow outputs, or logs as substitutes for recalculating digests and verifying the signed +provenance bundle. Unsigned public workflow outputs such as uploaded asset name, asset URL, or +digest remain release handles only; they are not substitutes for signed provenance verification. + +The `release-upload` job should not create tags, create releases, publish draft releases, edit +release notes, change prerelease state, change latest-release state, delete assets, overwrite +assets, or invoke clobber behavior. Those lifecycle mutations remain outside the low-level profile +under ADR 0043. + +### Consequences + +- Good, because caller-controlled build steps cannot access provenance signing authority. +- Good, because caller-controlled build steps cannot access GitHub Release mutation authority. +- Good, because `actions/attest` OIDC and attestation permissions are scoped to the signing job. +- Good, because `contents: write` release mutation authority is scoped to the upload job. +- Good, because release upload is gated by digest checks and signed provenance verification. +- Good, because the job graph mirrors the npm package profile's ADR 0036 split while adapting the + mutation boundary from npm publish to GitHub Release asset upload. +- Good, because a future `sigstore-go` migration can replace signing internals without collapsing + the build/sign/upload boundary. +- Neutral, because the upload job performs producer-side verification; final consumers should still + verify downloaded assets independently. +- Bad, because the workflow has more jobs, handoff artifacts, and verification logic than one- or + two-job designs. +- Bad, because Windlass must maintain tooling to verify its own signed bundle before release upload. +- Bad, because artifact handoff implementation must be tested carefully to avoid name/path + confusion, stale artifacts, digest encoding drift, or accidental fallback behavior. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- exactly three primary production jobs named or equivalent to `build`, `provenance-sign`, and + `release-upload`; +- top-level workflow permissions that default to no access or minimal read-only access; +- job-level permission requirements for `build`, `provenance-sign`, and `release-upload`; +- absence of signing and release mutation authority from caller-controlled build steps; +- absence of release mutation authority from the signing job; +- absence of signing authority from the release upload job; +- release asset artifact upload, download, digest calculation, and digest comparison behavior; +- signed provenance bundle artifact upload, download, digest calculation, and digest comparison + behavior; +- Windlass SLSA statement generation and subject digest verification before signing; +- Sigstore bundle verification in the `release-upload` job before GitHub Release mutation; +- upload-time checks for signer identity, `predicateType`, `builder.id`, `buildType`, + `externalParameters`, source identity, release tag, release state, asset name, and asset digest; +- hard-failure behavior for missing artifacts, digest mismatch, signature mismatch, unexpected + provenance fields, missing tag, missing release, duplicate asset name, and any attempted fallback + to unsigned provenance, `actions/attest` default provenance, release creation, draft publication, + or asset clobbering. + +Implementation review should verify that no path can upload a GitHub Release asset under the +production SLSA3 builder identity unless the asset and signed Windlass provenance bundle have both +passed the `release-upload` job's verification gate. + +## Pros and Cons of the Options + +### Use a three-job digest-verified graph + +The reusable workflow separates asset creation, provenance signing, and GitHub Release mutation into +`build`, `provenance-sign`, and `release-upload` jobs. Each cross-job artifact handoff is checked by +digest, and the upload job verifies the signed provenance before uploading the asset. + +- Good, because it creates the clearest permission and trust boundary for the initial GitHub Release + asset profile. +- Good, because build steps cannot access signing credentials or release mutation authority. +- Good, because signing and release upload authorities are never present in the same job. +- Good, because the upload job can behave like a producer-side verifier before mutating the release. +- Good, because it follows the SLSA GitHub Generator and ADR 0036 pattern of job isolation plus + hash-protected artifact exchange. +- Good, because it keeps Windlass-generated provenance, not workflow outputs, as the verifier source + of truth. +- Bad, because it requires more implementation and testing than simpler job graphs. +- Bad, because bundle verification logic must exist before upload, not only in downstream verifier + tooling. + +### Use one job for build, signing, and release upload + +One job checks out source, runs build or packaging steps, generates and signs provenance, and +uploads the release asset. + +- Good, because it is simple and avoids artifact upload/download handoff. +- Good, because local paths are easy to reason about during an early prototype. +- Bad, because caller-controlled build steps share a runner environment with signing and release + mutation permissions. +- Bad, because it weakens the SLSA Build L3 signing-key isolation story. +- Bad, because a compromised build step could both influence provenance inputs and attempt release + mutation. + +### Use two jobs: `build` then `sign-and-upload` + +The build job creates the asset, and a second job verifies the asset, signs provenance, and uploads +the release asset. + +- Good, because build steps are isolated from signing and release mutation permissions. +- Good, because it is simpler than a three-job graph. +- Good, because the privileged job can verify the asset before signing and uploading. +- Bad, because provenance signing and GitHub Release mutation share one job boundary. +- Bad, because upload does not consume a previously signed bundle through an explicit verification + handoff. +- Bad, because a signing-adapter failure or fallback path is closer to the release mutation + boundary. + +### Use two jobs: `build-and-sign` then `release-upload` + +The first job creates the asset and signs provenance. The second job verifies the signed bundle and +uploads the asset. + +- Good, because release mutation authority is isolated from signing authority. +- Good, because the upload job can verify the signed provenance before mutation. +- Bad, because caller-controlled build steps share a runner environment with provenance signing + permissions. +- Bad, because this is a weaker fit for SLSA Build L3 signing-key isolation than the selected + three-job split. +- Bad, because it conflicts with ADR 0038's requirement to keep signing and GitHub attestation + storage permissions away from caller-controlled build steps. + +### Build and sign in the reusable workflow, then leave upload to the caller + +The trusted reusable workflow returns the release asset and signed provenance bundle; the caller or +a separate orchestration workflow downloads them and performs GitHub Release upload. + +- Good, because the low-level profile would not need `contents: write` release mutation authority. +- Good, because callers could integrate custom release upload or orchestration logic. +- Bad, because the production release asset profile would not control the release mutation path + selected by ADR 0043. +- Bad, because callers could upload without verifying the signed provenance bundle or could upload a + different asset from the one that was signed. +- Bad, because upload failures, duplicate asset behavior, and downgrade behavior would become + caller-specific rather than profile-specified. + +## More Information + +This decision follows ADR 0038, ADR 0039, ADR 0040, and ADR 0043. It decides the initial production +GitHub Release asset profile's job graph and GitHub token permission boundary only. It does not +change the release asset lifecycle policy, release creation policy, immutable release requirement, +canonical provenance contents, signing adapter choice, public workflow outputs, or future +orchestration workflow interface. + +Reference points considered: + +- SLSA v1.2 Build Requirements require authentic provenance at Build L2 and unforgeable provenance + at Build L3. Secret material used to authenticate provenance must not be accessible to + user-defined build steps, and builds must run in isolated, ephemeral environments. +- SLSA v1.2 Verifying Artifacts recommends checking the provenance envelope signature, subject + digest, `predicateType`, trusted builder identity, `buildType`, and `externalParameters`; + unrecognized external parameters should generally fail verification. +- GitHub Actions supports `permissions` at workflow and job level, and when any explicit permission + set is specified, unspecified permissions are set to `none`. +- Windlass workflow hardening guidance requires explicit minimal top-level permissions and job-level + elevation only when required. +- GitHub `actions/attest` generates Sigstore-signed in-toto attestations, supports custom predicate + mode, exposes a `bundle-path` output, and requires `id-token: write` plus `attestations: write` + only where signing and attestation storage occur. +- GitHub Release asset upload uses GitHub Release mutation authority, represented in GitHub Actions + by `contents: write`; ADR 0043 narrows that mutation to one non-duplicate asset upload on an + existing release. +- The SLSA GitHub Generator generic workflow examples use a separate provenance job with + `id-token: write` and `contents: write` when uploading provenance to releases, while the Node.js + builder examples use expected SHA-256 values and secure download actions before package publish. +- ADR 0036 selected the stricter Windlass npm pattern of separate build, signing, and mutation jobs + with digest-verified handoff, which this decision adapts to GitHub Release assets. diff --git a/docs/decisions/0045-use-release-asset-name-as-slsa-subject-name.md b/docs/decisions/0045-use-release-asset-name-as-slsa-subject-name.md new file mode 100644 index 0000000..fb4d195 --- /dev/null +++ b/docs/decisions/0045-use-release-asset-name-as-slsa-subject-name.md @@ -0,0 +1,230 @@ +--- +parent: Decisions +nav_order: 45 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Use the GitHub Release Asset Name as the SLSA Subject Name + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance for GitHub Release assets, with +`actions/attest` acting only as the initial Sigstore signing and storage adapter. ADR 0039 scoped +the initial production profile to exactly one GitHub Release asset per profile run. ADR 0043 +selected an upload-only lifecycle for one explicitly named asset on an existing GitHub Release, and +ADR 0044 selected a three-job graph where `release-upload` verifies the signed provenance bundle +before mutating the release. + +The next subject and digest semantics decision is the `subject[0].name` value in the in-toto +Statement. The profile needs a stable name that identifies the one asset the user expects to upload, +without confusing local build paths, GitHub Release URLs, GitHub API IDs, or whole-release identity +with the artifact-bound SLSA subject. + +What should the initial GitHub Release asset profile use as the SLSA subject name for the one +release asset artifact? + +## Decision Drivers + +- Keep the profile's SLSA subject artifact-bound and aligned with ADR 0039's one-asset scope. +- Match the GitHub Release asset identity that callers and consumers see on the release page. +- Avoid making workspace-local paths part of the verifier contract when the asset may be renamed for + upload. +- Avoid making mutable or provider-specific URLs the primary subject identifier. +- Avoid using GitHub API asset IDs that do not exist until after the provenance is signed. +- Keep digest verification as the cryptographic identity check, with `name` as the policy-relevant + asset identifier. +- Align with `actions/attest`, in-toto, and SLSA GitHub Generator conventions for named artifact + subjects with digest maps. + +## Considered Options + +- Use the final GitHub Release asset name as `subject[0].name`. +- Use the repository-relative local artifact path as `subject[0].name`. +- Use the GitHub Release browser or download URL as `subject[0].name`. +- Use the GitHub Release asset API ID or GraphQL node ID as `subject[0].name`. +- Use a package-url-like GitHub Release asset identifier as `subject[0].name`. + +## Decision Outcome + +Chosen option: "Use the final GitHub Release asset name as `subject[0].name`", because it matches +the one explicitly named upload target selected by ADR 0039 and ADR 0043 while keeping digest +matching as the cryptographic artifact identity. + +The initial GitHub Release asset profile should emit exactly one production SLSA subject for the +release asset: + +```json +"subject": [ + { + "name": "", + "digest": { + "sha256": "" + } + } +] +``` + +The subject name is the final GitHub Release asset name requested by the caller and accepted by the +profile for upload. It is not the local workspace path, repository-relative build output path, +workflow artifact name, GitHub Release URL, GitHub API asset ID, provenance sidecar name, or package +URL. + +The subject name should be a single release asset name, not a path. The architecture specification +should reject subject asset names that contain path separators, empty path segments, URL syntax, +query strings, fragments, control characters, or values that normalize differently across supported +platforms. The same normalized asset name should be used for duplicate-asset checks, upload, public +workflow output handles, and subject-name verification. + +The subject digest should be the SHA-256 digest of the exact bytes uploaded as the GitHub Release +asset. It should be encoded as lowercase hexadecimal in `subject[0].digest.sha256`, without a +`sha256:` prefix. The implementation may expose or use prefixed digest strings at tool boundaries +where required, such as `actions/attest` `subject-digest`, but the in-toto Statement digest map +should use the algorithm key `sha256` and the raw lowercase hex value. + +Release repository, release tag, release URL, release asset API ID, draft or prerelease state, +download URL, upload metadata, and GitHub attestation storage identifiers should not be folded into +`subject[0].name`. Those values should be represented in the profile's `externalParameters`, +`resolvedDependencies`, byproducts, upload verification policy, workflow outputs, or release +metadata fields as defined by later architecture specifications. + +Producer-side verification in the `release-upload` job should require the signed provenance subject +name to equal the expected release asset name and the recalculated release asset SHA-256 digest to +equal `subject[0].digest.sha256` before upload. Consumer-side verification should similarly check +the downloaded asset bytes against the subject digest and check the subject name against the +expected release asset name when policy cares about the release asset slot. + +### Consequences + +- Good, because the SLSA subject name matches the GitHub Release asset identity that users upload + and consumers download. +- Good, because local workspace layout does not become part of the long-term verifier contract. +- Good, because the duplicate asset-name check selected by ADR 0043 and the subject-name check use + the same asset slot identifier. +- Good, because the digest remains the cryptographic artifact identity while the name remains the + stable policy label. +- Good, because the shape aligns with `sha256sum`-style SLSA GitHub Generator subjects and + `actions/attest` named artifact subjects. +- Neutral, because release repository, tag, URL, and API ID still need explicit representation in + other provenance fields or verification policy. +- Bad, because the same asset name can exist in different repositories, releases, or tags, so + subject name alone is not globally unique. +- Bad, because callers must choose final release asset names before provenance signing. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- exactly one SLSA subject for the production one-asset release profile path; +- `subject[0].name` as the final GitHub Release asset name requested for upload; +- rejection of local paths, repository-relative paths, URLs, GitHub API IDs, package URLs, and + provenance sidecar names as subject names; +- normalization and validation rules for release asset names; +- `subject[0].digest.sha256` as the lowercase hexadecimal SHA-256 digest of the exact uploaded asset + bytes, without a `sha256:` prefix; +- tool-boundary conversion rules for any APIs that require prefixed digest strings; +- producer-side verification that the signed subject name and digest match the expected asset before + release upload; +- consumer-side verifier guidance that checks both subject digest and expected release asset name; +- separate provenance or verification-policy fields for repository, release tag, release state, + release URLs, asset API IDs, and attestation storage identifiers. + +Implementation review should verify that production provenance cannot be signed or uploaded when the +subject name describes a different release asset slot than the upload target, or when the subject +digest describes bytes different from the uploaded asset. + +## Pros and Cons of the Options + +### Use the final GitHub Release asset name + +The subject name is the basename-style GitHub Release asset name that the profile will upload, such +as `slsa-builder_0.1.0_linux_amd64.tar.gz`. + +- Good, because it matches the user-visible release asset slot. +- Good, because it aligns subject verification with duplicate asset-name checks. +- Good, because it stays stable when local workspace paths or workflow artifact names change. +- Good, because it follows the filename-style subject convention used by checksum files, + `actions/attest` subject checksums, and the SLSA GitHub Generator generic workflow. +- Neutral, because globally unique release identity must still come from repository and tag fields. +- Bad, because the asset name must be known before signing and cannot be inferred only from the + local file path. + +### Use the repository-relative local artifact path + +The subject name is a path such as `dist/slsa-builder_0.1.0_linux_amd64.tar.gz`. + +- Good, because it can directly identify the file generated by the build job. +- Good, because it resembles `actions/attest` `subject-path` usage for local binaries. +- Bad, because it binds verifier policy to workspace layout rather than release asset identity. +- Bad, because upload-time renaming would make the subject name differ from the release asset name. +- Bad, because path normalization differs across platforms and shell tools. + +### Use the GitHub Release browser or download URL + +The subject name is a URL such as +`https://github.com/windlasstech/slsa-builder/releases/download/v0.1.0/foo.tar.gz`. + +- Good, because it can be globally recognizable to humans and scripts. +- Good, because it points at the consumer download surface after release publication. +- Bad, because URL shape, redirects, repository renames, and CDN behavior are provider-specific and + mutable. +- Bad, because draft-release upload flows may not have a final consumer-facing URL at signing time. +- Bad, because in-toto ResourceDescriptor has `uri` and `downloadLocation` fields for URI semantics; + overloading `name` with a URL weakens the distinction. + +### Use the GitHub Release asset API ID or node ID + +The subject name is a GitHub REST asset ID, GraphQL node ID, or API URL for the uploaded release +asset. + +- Good, because GitHub can identify the uploaded asset object precisely after upload. +- Good, because it avoids ambiguity among same-named files in different releases. +- Bad, because the asset ID does not exist until after upload, while ADR 0044 requires provenance to + be signed and verified before release mutation. +- Bad, because it would make the subject name GitHub-API-specific and less meaningful to consumers. +- Bad, because it conflicts with the failure-closed no-overwrite upload path: the expected asset + slot must be known before creating the asset object. + +### Use a package-url-like GitHub Release asset identifier + +The subject name is a custom package URL or URI-like identifier containing repository, tag, and +asset name. + +- Good, because it could make the subject name globally scoped without relying on GitHub download + URL stability. +- Good, because it can be useful for future package-ecosystem-style verifier indexing. +- Bad, because GitHub Release asset package URL conventions are not as established as npm package or + OCI image naming conventions. +- Bad, because it duplicates repository and tag fields that should be verified through the profile's + `externalParameters` and source or release metadata. +- Bad, because it is less compatible with `actions/attest` and SLSA GitHub Generator filename-style + subject conventions. + +## More Information + +This decision follows ADR 0038, ADR 0039, ADR 0043, and ADR 0044. It decides only the initial +production GitHub Release asset subject name and SHA-256 subject digest encoding. It does not decide +the complete release asset `externalParameters` schema, release URL outputs, GitHub asset API ID +outputs, `downloadLocation` usage, provenance sidecar naming, checksum file policy, SBOM policy, or +future multi-asset orchestration model. + +Reference points considered: + +- in-toto Statement v1 requires each subject to have a digest, treats subjects as immutable, and + says subject artifacts are matched purely by digest. The `name` field can distinguish artifacts + within the subject and may be used by policy when meaningful. +- in-toto ResourceDescriptor says `name` should be a stable machine-readable identifier such as a + filename, while `uri` and `downloadLocation` carry URI semantics. +- SLSA v1.2 Build Provenance represents build outputs as the top-level in-toto Statement `subject` + list and uses digest maps keyed by algorithm names such as `sha256`. +- SLSA v1.2 Verifying Artifacts recommends verifying that the Statement subject matches the digest + of the artifact being verified before checking `predicateType`, trusted builder identity, + `buildType`, and `externalParameters`. +- GitHub `actions/attest` binds a named artifact and digest to a predicate. Its `subject-digest` + input requires the form `sha256:HEX_DIGEST` and a `subject-name`, while checksum-file subjects use + `shasum`-style lines containing a hex digest and filename. +- The SLSA GitHub Generator generic workflow accepts subjects formatted like `sha256sum` output, + where the decoded input is a SHA-256 hash followed by an artifact name, and its examples use + artifact filenames as subject names for release artifacts. diff --git a/docs/decisions/0046-keep-checksums-and-sboms-out-of-release-asset-subject-digest.md b/docs/decisions/0046-keep-checksums-and-sboms-out-of-release-asset-subject-digest.md new file mode 100644 index 0000000..84404ad --- /dev/null +++ b/docs/decisions/0046-keep-checksums-and-sboms-out-of-release-asset-subject-digest.md @@ -0,0 +1,239 @@ +--- +parent: Decisions +nav_order: 46 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Keep Checksums and SBOMs Out of the Release Asset Subject Digest + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance for GitHub Release assets. ADR 0039 scoped the +initial production profile to exactly one GitHub Release asset per profile run and explicitly kept +checksum files, SBOM files, provenance sidecars, and detached signatures out of the low-level +verification unit for another asset. ADR 0044 selected a three-job graph that recalculates asset and +bundle digests across handoffs. ADR 0045 selected the final GitHub Release asset name as +`subject[0].name` and lowercase hexadecimal SHA-256 of the uploaded asset bytes as +`subject[0].digest.sha256`. + +The remaining digest relationship question is how the selected release asset subject digest relates +to checksum files and SBOMs. GitHub Releases commonly publish checksum manifests and SBOM files next +to binaries. Those files can be useful verification aids and may themselves be release assets, but +they are not the same bytes as the primary release asset. + +Should checksum files and SBOMs extend, replace, or otherwise participate in the initial GitHub +Release asset profile's SLSA subject digest? + +## Decision Drivers + +- Keep the one-asset profile's SLSA subject digest bound to exactly the selected release asset + bytes. +- Preserve ADR 0039's separation between the low-level one-asset profile and multi-asset release + orchestration. +- Avoid treating checksum manifests or SBOM documents as substitutes for SLSA build provenance over + the artifact they describe. +- Allow checksum files, SBOMs, provenance sidecars, and detached signatures to be independently + published and attested when they are release assets. +- Keep consumer verification simple: the downloaded asset bytes must match the SLSA subject digest. +- Keep cross-asset completeness, checksum manifest generation, SBOM set publication, and + whole-release policy in the orchestration layer. +- Align with SLSA v1.2's artifact-bound provenance guidance and GitHub `actions/attest` SBOM mode, + where the artifact remains the subject and the SBOM is the predicate. + +## Considered Options + +- Keep checksum files and SBOMs out of the release asset subject digest. +- Add checksum file and SBOM digests to the primary release asset subject digest map. +- Include the release asset, checksum file, and SBOM as multiple subjects in one provenance + statement. +- Use the checksum manifest as the primary SLSA subject. +- Use the SBOM file as the primary SLSA subject. + +## Decision Outcome + +Chosen option: "Keep checksum files and SBOMs out of the release asset subject digest", because the +initial GitHub Release asset profile's SLSA subject should describe exactly the selected asset +bytes, while checksum files and SBOMs are either independent release assets or supporting metadata. + +The production GitHub Release asset profile should keep the SLSA subject digest limited to the one +selected release asset: + +```json +"subject": [ + { + "name": "", + "digest": { + "sha256": "" + } + } +] +``` + +Checksum files, SBOM files, provenance sidecars, detached signatures, and other supporting files do +not extend or replace `subject[0].digest`. Their digests should not be inserted into the selected +release asset subject digest map under custom keys. The subject digest map should contain only +cryptographic digests of the selected release asset bytes, keyed by digest algorithm. + +When a checksum file, SBOM file, provenance sidecar, detached signature, or similar supporting file +is published as a GitHub Release asset, it should be handled by its own one-asset profile +invocation. For example, a `checksums.txt` release asset can have +`subject[0].name == "checksums.txt"` and a subject digest over the `checksums.txt` bytes. An SBOM +release asset can similarly be attested as the asset whose bytes are uploaded. + +When checksum files or SBOMs are generated only to support verification, diagnostics, release notes, +or orchestration policy, they may be recorded as byproducts, referenced by orchestration-layer +metadata, linked through a future release manifest, or attested with a separate predicate. They are +not part of the low-level profile's primary SLSA subject for another release asset. + +The architecture specification may require optional consistency checks when the caller supplies a +checksum file or SBOM for the selected asset. For example, a checksum file entry for the selected +asset may be required to match `subject[0].digest.sha256`, or an SBOM package/component hash for the +selected asset may be required to match the same bytes. Such checks are consistency gates; they do +not make the checksum file or SBOM bytes part of the selected asset subject digest. + +Cross-asset completeness checks remain orchestration-layer responsibilities. A higher-level release +workflow may verify that every asset listed in a checksum manifest was uploaded and attested, that +every binary has an SBOM, or that all SBOMs and checksum files are present before publishing an +immutable release. That orchestration must compose one-asset profile invocations rather than +changing the low-level profile's subject digest semantics. + +### Consequences + +- Good, because the SLSA subject digest has one clear meaning: the digest of the selected release + asset bytes. +- Good, because checksum manifests and SBOMs cannot accidentally become substitutes for + artifact-bound build provenance. +- Good, because checksum files, SBOMs, provenance sidecars, and detached signatures can still be + independently published and attested as release assets. +- Good, because consumer verification remains a direct comparison between downloaded asset bytes and + `subject[0].digest.sha256`. +- Good, because whole-release completeness stays in the orchestration layer selected by ADR 0039 and + ADR 0043. +- Neutral, because optional checksum or SBOM consistency checks may still be specified later without + changing the primary subject digest. +- Bad, because the low-level profile alone cannot prove that a complete release has a correct + checksum manifest and SBOM set. +- Bad, because callers that want whole-release guarantees must use additional orchestration or + release manifest policy. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- `subject[0].digest.sha256` as the digest of the selected release asset bytes only; +- no checksum file, SBOM file, provenance sidecar, detached signature, release manifest, or other + supporting-file digest in the selected release asset subject digest map; +- separate one-asset profile invocations when checksum files, SBOMs, provenance sidecars, detached + signatures, or similar files are themselves published GitHub Release assets; +- optional consistency checks, if supported, that compare checksum-file entries or SBOM component + hashes with the selected asset subject digest without changing subject semantics; +- byproduct or orchestration metadata rules for non-primary checksum files and SBOMs; +- documentation that whole-release checksum/SBOM completeness belongs to an orchestration layer or + future release manifest, not the low-level one-asset profile. + +Implementation review should verify that no production path can sign or upload provenance where the +selected release asset subject digest describes a checksum file, SBOM, sidecar, signature, manifest, +or any bytes other than the uploaded release asset. + +## Pros and Cons of the Options + +### Keep checksum files and SBOMs out of the release asset subject digest + +The selected release asset is the only SLSA subject. Checksum files and SBOMs are either independent +release assets, byproducts, consistency inputs, or orchestration metadata. + +- Good, because it preserves one subject and one primary digest per profile run. +- Good, because it matches SLSA's artifact-bound provenance model. +- Good, because it composes cleanly with releases that publish many binaries, checksum files, SBOMs, + provenance sidecars, and signatures. +- Good, because SBOM attestation can still describe the selected asset using an SBOM predicate + rather than changing the build provenance subject. +- Neutral, because the architecture still needs to define optional consistency checks and byproduct + fields. +- Bad, because whole-release completeness requires another layer. + +### Add checksum file and SBOM digests to the primary subject digest map + +The selected release asset subject contains additional digest-map keys for related checksum files or +SBOM files. + +- Good, because one subject entry appears to carry all related digest handles. +- Good, because producer-side checks could compare related files in one place. +- Bad, because a SLSA subject digest map is naturally an algorithm-keyed map for the same subject + bytes, not a collection of digests for different files. +- Bad, because custom digest keys for other files can confuse generic in-toto and SLSA tooling. +- Bad, because it blurs whether the subject is the release asset, the checksum file, the SBOM, or a + bundle of all three. + +### Include the release asset, checksum file, and SBOM as multiple subjects + +One provenance statement contains separate subject entries for the primary release asset, checksums, +SBOMs, and related files. + +- Good, because in-toto and `actions/attest` can represent multiple named subjects. +- Good, because each listed file has its own digest without overloading one digest map. +- Bad, because ADR 0039 selected exactly one production release asset subject for the initial + profile. +- Bad, because partial upload, duplicate names, retries, and verification failures become + multi-asset problems. +- Bad, because checksum files and SBOMs may describe assets outside the current profile run. + +### Use the checksum manifest as the primary SLSA subject + +The SLSA subject is the checksum file, and the checksum file lists the release asset digest. + +- Good, because checksum manifests are familiar release verification artifacts. +- Good, because one manifest can reference many release assets. +- Bad, because the SLSA provenance describes the manifest bytes rather than the selected release + asset bytes. +- Bad, because ADR 0039 rejected checksum-manifest-centered verification for the low-level profile. +- Bad, because manifest completeness and synchronization with uploaded assets are orchestration + concerns. + +### Use the SBOM file as the primary SLSA subject + +The SLSA subject is the SBOM file, and the SBOM package or component hashes describe the release +asset. + +- Good, because SPDX and CycloneDX can record package or component checksums. +- Good, because SBOM tooling can consume dependency and component metadata directly. +- Bad, because the SLSA build provenance subject becomes metadata rather than the built artifact. +- Bad, because GitHub `actions/attest` SBOM mode keeps the artifact as the subject and the SBOM as + the predicate, not the other way around. +- Bad, because SBOM format choices and optional availability would affect the primary build + provenance verification path. + +## More Information + +This decision follows ADR 0038, ADR 0039, ADR 0043, ADR 0044, and ADR 0045. It decides only the +relationship between the selected release asset subject digest and checksum or SBOM files. It does +not decide the checksum manifest schema, SBOM format, SBOM predicate policy, provenance sidecar +naming, detached signature policy, release manifest schema, or future whole-release orchestration +workflow. + +Reference points considered: + +- SLSA v1.2 Distributing Provenance says attestations should be bound to artifacts rather than + releases and that provenance should accompany the artifact at publish time. +- SLSA v1.2 Build Provenance represents build outputs through the top-level in-toto Statement + `subject` list and uses `byproducts` for additional artifacts generated during the build that are + not considered build outputs. +- SLSA v1.2 Verifying Artifacts recommends verifying that the Statement subject matches the digest + of the artifact being verified before checking `predicateType`, trusted builder identity, + `buildType`, and `externalParameters`. +- in-toto Statement v1 requires subject elements to have digest values and says subject artifacts + are matched purely by digest. +- in-toto ResourceDescriptor says a digest-bearing descriptor is assumed to refer to an immutable + resource or artifact, and the predicate type should document which field consumers match. +- GitHub `actions/attest` SBOM mode takes both `subject-path` for the artifact and `sbom-path` for + the SBOM predicate. Its checksum-file subject mode enumerates subjects from `shasum`-style lines, + but those lines identify attestation subjects rather than modifying one subject's digest map. +- The SLSA GitHub Generator generic workflow accepts `sha256sum`-style subject inputs, and common + release tooling such as GoReleaser and JReleaser can generate checksum files for release + artifacts. +- SPDX and CycloneDX SBOM formats can carry package or component hashes, but those hashes are SBOM + metadata about described artifacts rather than replacements for SLSA build provenance subject + digests. diff --git a/docs/decisions/0047-use-github-attestation-storage-as-canonical-release-asset-provenance-distribution.md b/docs/decisions/0047-use-github-attestation-storage-as-canonical-release-asset-provenance-distribution.md new file mode 100644 index 0000000..73790ad --- /dev/null +++ b/docs/decisions/0047-use-github-attestation-storage-as-canonical-release-asset-provenance-distribution.md @@ -0,0 +1,235 @@ +--- +parent: Decisions +nav_order: 47 +status: superseded by ADR-0051 +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Use GitHub Attestation Storage as Canonical Release Asset Provenance Distribution + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance for GitHub Release assets, with +`actions/attest` acting as the initial Sigstore signing and storage adapter. ADR 0039 scoped the +initial production profile to one GitHub Release asset per profile run. ADR 0044 selected a +three-job graph where `provenance-sign` signs the Windlass provenance and `release-upload` verifies +the signed bundle before uploading the release asset. ADR 0045 and ADR 0046 then fixed the release +asset subject name, digest, checksum, and SBOM semantics. + +The remaining distribution question is how consumers and downstream automation should discover and +retrieve the signed SLSA provenance for the release asset. GitHub `actions/attest` can upload the +Sigstore bundle to GitHub's artifact attestation storage and also exposes a local `bundle-path` JSON +file. GitHub Releases can also publish that bundle as a downloadable release asset sidecar, but +doing so creates another release asset with its own naming, duplicate handling, and orchestration +concerns. + +Should the initial GitHub Release asset profile distribute provenance primarily through GitHub +artifact attestation storage, through a GitHub Release asset sidecar, through both channels, or +through release-level attestation only? + +## Decision Drivers + +- Keep the production provenance discovery path aligned with ADR 0038's `actions/attest` adapter + choice. +- Preserve ADR 0039's one-primary-release-asset profile boundary. +- Keep the SLSA provenance bound to the selected release asset digest rather than to the whole + GitHub Release. +- Support GitHub-native verification with `gh attestation verify` and the artifact attestations API. +- Allow archival, air-gapped, or mirror-friendly bundle export when callers need it. +- Avoid making release asset sidecar upload mandatory for the low-level profile. +- Keep provenance sidecar naming, sidecar attestation, and whole-release UX in the orchestration + layer unless explicitly enabled. +- Preserve the `release-upload` job's producer-side verification of the signed bundle before release + mutation. + +## Considered Options + +- Use GitHub artifact attestation storage as the canonical distribution channel, with optional + release asset bundle export. +- Require both GitHub artifact attestation storage and a GitHub Release asset bundle sidecar. +- Use only a GitHub Release asset bundle sidecar. +- Use only workflow artifacts or job outputs for provenance distribution. +- Rely on GitHub immutable release attestations or release verification instead of artifact-bound + SLSA provenance distribution. + +## Decision Outcome + +Chosen option: "Use GitHub artifact attestation storage as the canonical distribution channel, with +optional release asset bundle export", because this keeps the initial profile on GitHub's native +attestation discovery and verification path while still allowing callers to publish a downloadable +Sigstore bundle when they need portability or archival behavior. + +The production GitHub Release asset profile should treat GitHub artifact attestation storage as the +canonical distribution channel for the signed SLSA provenance bundle. The `provenance-sign` job +should invoke the SHA-pinned `actions/attest` custom attestation mode selected by ADR 0038 with +GitHub attestation storage enabled. The resulting attestation should be associated with the +repository and release asset subject digest through GitHub's attestations API. + +The `provenance-sign` job should also capture the local JSON-serialized Sigstore bundle produced by +`actions/attest` through `bundle-path` and pass it to `release-upload` as a digest-checked workflow +artifact. That workflow artifact is an internal handoff artifact. It is required for ADR 0044's +producer-side verification before release mutation, but it is not the canonical public distribution +channel by itself. + +The `release-upload` job should verify the signed bundle before uploading the selected release +asset. Verification should include the expected Sigstore root, GitHub Actions reusable workflow +signer identity, SLSA `predicateType`, `builder.id`, `buildType`, `externalParameters`, release tag, +subject name, and subject digest. The job should fail before release mutation if the GitHub +attestation storage upload did not produce the expected attestation identity or if the local bundle +cannot be verified. + +Publishing the signed bundle as a GitHub Release asset sidecar should be optional. When enabled, it +is an export and compatibility channel, not the canonical provenance distribution channel. The +architecture specification should define the option name, default value, sidecar asset name, sidecar +digest outputs, and duplicate-name behavior. The production default should not require a sidecar +release asset. + +A sidecar bundle release asset should not change the selected release asset's SLSA subject. It +should not be inserted into `subject[0].digest`, should not replace GitHub artifact attestation +storage, and should not be required for ordinary `gh attestation verify` based verification. If the +sidecar is itself treated as a published release asset requiring SLSA provenance, that attestation +is a separate one-asset profile invocation or an orchestration-layer responsibility under ADR 0039 +and ADR 0046. + +The profile should not rely on workflow artifacts, unsigned job outputs, logs, release notes, or +GitHub immutable release attestations as the public SLSA provenance distribution mechanism. Those +surfaces may provide handles, diagnostics, or complementary release integrity evidence, but they do +not replace artifact-bound signed SLSA provenance discoverable through GitHub attestation storage. + +### Consequences + +- Good, because the canonical path matches GitHub's official artifact attestation storage and + `gh attestation verify` workflow. +- Good, because the release asset list does not need to contain provenance sidecars for the default + production path. +- Good, because ADR 0044 can still verify the local signed bundle before release mutation. +- Good, because projects that need offline, mirrored, or archival verification can opt into a + downloadable bundle sidecar. +- Good, because release-level attestations and immutable release verification remain complementary + evidence rather than substitutes for artifact-bound build provenance. +- Neutral, because consumers that avoid GitHub's attestations API need the optional sidecar or a + separate export workflow. +- Bad, because the canonical public discovery path depends on GitHub artifact attestation storage + availability and support boundaries. +- Bad, because optional sidecar support still requires naming, duplicate handling, and verification + rules when enabled. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and verifier guidance define: + +- GitHub artifact attestation storage as the canonical public distribution channel for the signed + Windlass SLSA provenance bundle; +- `actions/attest` custom mode with GitHub attestation storage enabled in the `provenance-sign` job; +- capture of the local `bundle-path` JSON bundle as a digest-checked workflow artifact for the + `release-upload` job; +- producer-side verification of the local signed bundle before GitHub Release asset upload; +- public outputs for the attestation ID, attestation URL, bundle digest, subject name, and subject + digest as appropriate; +- ordinary consumer verification guidance based on GitHub artifact attestation storage and + `gh attestation verify` or equivalent API-backed verification; +- an optional release asset sidecar export mode with explicit default, sidecar asset naming, + duplicate-name failure behavior, and verifier guidance; +- documentation that workflow artifacts, logs, release notes, and release-level attestations are not + the canonical SLSA provenance distribution mechanism. + +Implementation review should verify that no production path silently skips GitHub attestation +storage upload, treats an unsigned or unverified bundle as distributable provenance, requires a +release asset sidecar for the default verification path, or uses GitHub release-level attestation as +a substitute for the Windlass artifact-bound SLSA provenance bundle. + +## Pros and Cons of the Options + +### Use GitHub artifact attestation storage as canonical distribution, with optional sidecar export + +The signing job uploads the signed bundle to GitHub artifact attestation storage through +`actions/attest` and also passes the local bundle to the upload job for verification. Callers may +optionally publish the bundle as a release asset sidecar for portability. + +- Good, because it uses the native storage and discovery path provided by the selected signing + adapter. +- Good, because it supports `gh attestation verify` and digest-based attestation lookup. +- Good, because it keeps the low-level profile centered on one primary release asset. +- Good, because it leaves room for downloadable sidecars without making them mandatory. +- Neutral, because offline consumers need an explicit sidecar or prior bundle export. +- Bad, because GitHub artifact attestation storage is a provider-specific dependency. + +### Require both GitHub artifact attestation storage and a release asset bundle sidecar + +Every production profile run uploads the signed attestation to GitHub storage and publishes the same +bundle as a GitHub Release asset sidecar. + +- Good, because consumers get both GitHub-native verification and a downloadable bundle file. +- Good, because provenance can be mirrored or archived with the release assets. +- Bad, because the low-level one-asset profile would always perform at least two release asset + uploads. +- Bad, because sidecar upload failure creates ambiguity about whether the primary asset should be + uploaded or rolled back. +- Bad, because sidecar naming, duplicate detection, and sidecar attestation policy become mandatory + initial-profile complexity. + +### Use only a GitHub Release asset bundle sidecar + +The signed bundle is uploaded as a release asset next to the primary artifact, and GitHub artifact +attestation storage is not the canonical discovery channel. + +- Good, because provenance is visible and downloadable from the GitHub Release page. +- Good, because consumers can archive the artifact and bundle together. +- Bad, because it avoids the native GitHub artifact attestation storage and lookup path selected by + ADR 0038. +- Bad, because `gh attestation verify` defaults and GitHub attestation UI/API integration become + less central. +- Bad, because every release asset provenance file becomes release upload lifecycle surface. + +### Use only workflow artifacts or job outputs for provenance distribution + +The signed bundle is retained only as a workflow artifact or output from the reusable workflow. + +- Good, because it is simple for producer-side handoff between jobs. +- Good, because it avoids adding another release asset. +- Bad, because workflow artifacts are not the artifact's durable public provenance distribution + surface. +- Bad, because job outputs and logs are not signed provenance and are unsuitable for long-term + consumer verification. +- Bad, because consumers outside the workflow run would not have a stable discovery path. + +### Rely on GitHub immutable release attestations or release verification instead + +Consumers use GitHub release attestation and `gh release verify` or `gh release verify-asset` rather +than artifact-bound Windlass SLSA provenance distribution. + +- Good, because release verification is a simple GitHub-native integrity check for immutable + releases. +- Good, because it can prove that release assets match GitHub's signed release record. +- Bad, because release-level attestation does not carry the Windlass `buildType`, `builder.id`, or + complete SLSA `externalParameters` contract. +- Bad, because it treats release integrity as a substitute for artifact-bound build provenance. +- Bad, because it conflicts with ADR 0038's decision to generate Windlass-owned SLSA provenance for + release assets. + +## More Information + +This decision follows ADR 0038, ADR 0039, ADR 0043, ADR 0044, ADR 0045, and ADR 0046. It decides +only the public distribution channel for signed release asset SLSA provenance and the role of +optional bundle sidecar export. It does not decide the final sidecar filename convention, verifier +CLI UX, release manifest schema, SBOM attestation policy, or future whole-release orchestration +workflow. + +Reference points considered: + +- GitHub `actions/attest` creates Sigstore-signed in-toto attestations, uploads them to GitHub's + attestations API, associates them with the repository, and exposes a local JSON-serialized + Sigstore bundle through `bundle-path`. +- GitHub artifact attestation verification supports `gh attestation verify` against GitHub storage + and local bundle verification through downloaded or supplied bundles. +- GitHub's artifact attestations API stores attestations by subject digest and repository, enabling + digest-based discovery for artifacts. +- GitHub immutable releases and release attestations can verify release integrity and asset bytes + but do not replace artifact-bound SLSA build provenance with Windlass-specific verifier policy. +- SLSA v1.2 recommends binding provenance to artifacts rather than releases and distributing + provenance so consumers can map a downloaded artifact to its attestation. +- Windlass workflow hardening guidance requires SHA-pinned actions, minimal top-level permissions, + job-level permission elevation, OIDC for signing, and explicit permissions for artifact + attestations and release asset upload. diff --git a/docs/decisions/0048-make-linked-artifacts-storage-records-explicit-opt-in-for-release-assets.md b/docs/decisions/0048-make-linked-artifacts-storage-records-explicit-opt-in-for-release-assets.md new file mode 100644 index 0000000..440981e --- /dev/null +++ b/docs/decisions/0048-make-linked-artifacts-storage-records-explicit-opt-in-for-release-assets.md @@ -0,0 +1,227 @@ +--- +parent: Decisions +nav_order: 48 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Make Linked Artifacts Storage Records Explicit Opt-In for Release Assets + +## Context and Problem Statement + +ADR 0038 selected Windlass-generated SLSA provenance for GitHub Release assets with `actions/attest` +as the initial Sigstore signing and storage adapter. ADR 0044 selected a three-job permission +boundary and allowed `artifact-metadata: write` only when linked artifact metadata is intentionally +created. ADR 0047 then selected GitHub artifact attestation storage as the canonical release asset +provenance distribution channel, with release asset bundle sidecars only as an optional export path. + +GitHub's linked artifacts page can display storage and deployment records for artifacts built by an +organization. GitHub recommends uploading attested assets to the linked artifacts page so teams can +connect vulnerable artifacts to owning repositories, build runs, storage locations, and deployment +context. GitHub `actions/attest` can automatically create linked-artifact storage records, but the +documented automatic path is gated by `push-to-registry: true` and `artifact-metadata: write`, which +fits registry-published artifacts such as container images. GitHub Release assets are not pushed to +a package registry by the release asset profile. + +Should the initial GitHub Release asset profile upload linked-artifact storage records by default, +make them optional through the artifact metadata REST API, rely on `actions/attest` automatic +storage records, or leave the feature to higher-level orchestration? + +## Decision Drivers + +- Preserve the low-level one-release-asset profile boundary from ADR 0039. +- Avoid granting `artifact-metadata: write` unless linked artifact metadata is explicitly requested. +- Avoid treating a GitHub Release asset as a registry-pushed artifact just to trigger + `actions/attest` automatic storage record creation. +- Keep the default release asset path focused on signed provenance, producer-side verification, and + upload to an existing GitHub Release. +- Allow organizations that use GitHub linked artifacts to record release asset storage metadata. +- Ensure storage record metadata describes the uploaded release asset URL, name, digest, version, + and repository accurately. +- Avoid rollback or clobber behavior when linked metadata creation fails after the release asset has + already been uploaded. +- Keep deployment records and whole-release inventory policy outside the low-level asset profile. + +## Considered Options + +- Do not upload linked-artifact storage records by default; support explicit REST API opt-in after + verified release asset upload. +- Never support linked-artifact storage records in the low-level release asset profile. +- Require linked-artifact storage records for every release asset upload. +- Use `actions/attest` automatic storage records through `push-to-registry: true`. +- Leave all linked artifact storage and deployment records to a higher-level orchestration workflow. + +## Decision Outcome + +Chosen option: "Do not upload linked-artifact storage records by default; support explicit REST API +opt-in after verified release asset upload", because it preserves the default least-privilege +release asset path while allowing organizations to populate GitHub's linked artifacts inventory when +they intentionally accept the extra permission and metadata lifecycle. + +The initial GitHub Release asset profile should not create linked-artifact storage records by +default. The default production path should not request `artifact-metadata: write`, should not call +the artifact metadata REST API, and should not configure `actions/attest` only to make linked +artifact metadata appear. + +The profile may support an explicit opt-in input for linked-artifact storage record creation. When +enabled, storage record creation should happen only after the `release-upload` job has verified the +signed provenance bundle and successfully uploaded the selected release asset to the existing GitHub +Release. The implementation should use GitHub's artifact metadata REST API rather than +`actions/attest` `push-to-registry: true`, because the subject is a GitHub Release asset rather than +a registry-pushed package or container image. + +The linked-artifact storage record should describe the uploaded release asset, not the provenance +bundle, checksum file, SBOM, or whole GitHub Release. The architecture specification should define +the exact mapping, including at least: + +- `name` from the final GitHub Release asset name; +- `digest` as `sha256:`; +- `version` from the release tag or a profile-defined normalized version; +- `artifact_url` from the uploaded GitHub Release asset URL when available; +- `registry_url` as the profile-defined GitHub Release storage surface base URL; +- `repository` as the profile-defined repository or release asset storage path component; +- `github_repository` only when required by the API because an associated provenance attestation is + not available or not discoverable. + +When linked-artifact upload is enabled, the workflow should grant `artifact-metadata: write` only to +the job that creates the storage record. That job should not have `id-token: write`, +`attestations: write`, or GitHub Release mutation authority unless a later ADR explicitly combines +those responsibilities. Prefer a post-upload metadata job or a tightly scoped post-upload step over +adding metadata permissions to caller-controlled build steps. + +If the selected release asset uploads successfully but linked-artifact storage record creation +fails, the workflow should fail clearly without deleting, replacing, or clobbering the uploaded +release asset. The failure message and outputs should make the partial state explicit: the release +asset was uploaded, but linked artifact metadata was not recorded. Operators can then retry metadata +creation or use the artifact metadata REST API manually. + +Deployment records are out of scope for the low-level release asset profile. They describe runtime +or environment deployment state and should be handled by deployment systems, integrations, custom +REST API automation, or a higher-level orchestration workflow. + +### Consequences + +- Good, because the default release asset profile remains least-privilege and does not require + `artifact-metadata: write`. +- Good, because GitHub Release assets are not forced into the `actions/attest` registry-push model. +- Good, because organizations that use linked artifacts can still opt into accurate storage records. +- Good, because metadata creation happens after the signed asset has been verified and uploaded. +- Good, because metadata permissions are isolated from caller-controlled build steps and signing + authority. +- Neutral, because projects that want linked artifacts must provide additional inputs and accept a + post-upload metadata step. +- Bad, because linked artifacts inventory is incomplete for default release asset profile users. +- Bad, because an opt-in metadata failure can leave an uploaded release asset without its linked + storage record and requires manual or automated follow-up. + +### Confirmation + +This decision is confirmed when the initial GitHub Release asset profile architecture specification, +workflow implementation, tests, and documentation define: + +- linked-artifact storage record creation as disabled by default; +- no `artifact-metadata: write` permission in the default production path; +- an explicit opt-in input for storage record creation, if supported; +- artifact metadata REST API usage for GitHub Release asset storage records rather than + `actions/attest` `push-to-registry: true`; +- release asset metadata field mapping for `name`, `digest`, `version`, `artifact_url`, + `registry_url`, `repository`, and `github_repository` when applicable; +- storage record creation after verified release asset upload; +- clear failure behavior that does not delete, overwrite, or clobber uploaded release assets after + metadata failure; +- job-level permission isolation for `artifact-metadata: write`; +- documentation that deployment records and whole-release inventory policy belong outside the + low-level one-asset profile. + +Implementation review should verify that no default path grants artifact metadata write permission, +that no path pretends a GitHub Release asset is a registry-pushed artifact to trigger automatic +storage records, and that metadata failures cannot cause release asset overwrite or deletion. + +## Pros and Cons of the Options + +### Do not upload by default; support explicit REST API opt-in + +The low-level profile defaults to no linked-artifact storage record. When the caller opts in, a +post-upload step or job calls the artifact metadata REST API with metadata for the uploaded GitHub +Release asset. + +- Good, because it keeps the common path minimal and least-privilege. +- Good, because release asset metadata can use the final uploaded asset URL and digest. +- Good, because the feature composes with GitHub organizations that use linked artifacts for alert + prioritization. +- Good, because metadata permissions can be isolated to a narrow job or step. +- Neutral, because the profile must define a GitHub Release storage mapping for a registry-oriented + API shape. +- Bad, because the optional path requires more implementation and failure-state documentation. + +### Never support linked-artifact storage records in the low-level profile + +The profile produces signed provenance and uploads release assets, but it never records linked +artifact storage metadata. + +- Good, because it is the simplest low-level profile boundary. +- Good, because no additional permission or API lifecycle is needed. +- Bad, because organizations using GitHub linked artifacts cannot see release asset storage context + from this profile. +- Bad, because every repository would need custom follow-up automation for storage inventory. + +### Require linked-artifact storage records for every release asset upload + +Every production release asset upload also creates or updates a linked-artifact storage record. + +- Good, because organization inventory is complete for all release assets produced by the profile. +- Good, because security alert prioritization can rely on consistent storage metadata. +- Bad, because the default profile requires broader permissions and a GitHub organization feature. +- Bad, because metadata API failure becomes a required release failure after asset upload. +- Bad, because this adds lifecycle complexity before the initial release asset profile needs it. + +### Use `actions/attest` automatic storage records through `push-to-registry: true` + +The profile configures `actions/attest` to create storage records automatically by enabling +`push-to-registry` and granting `artifact-metadata: write`. + +- Good, because it follows GitHub's automatic storage record path for registry artifacts. +- Good, because storage record IDs can be exposed from the `actions/attest` outputs. +- Bad, because `push-to-registry` is documented for registry-published subjects with fully qualified + image or package names, not ordinary GitHub Release assets. +- Bad, because it can misrepresent the release asset as a registry artifact and blur storage + semantics. +- Bad, because it couples signing adapter configuration to linked-artifact inventory behavior. + +### Leave all linked artifact records to a higher-level orchestration workflow + +A release orchestration workflow creates storage and deployment records after all one-asset profile +invocations have completed. + +- Good, because whole-release inventory and deployment context naturally belong above the low-level + one-asset profile. +- Good, because the orchestrator can handle multi-asset releases, checksum files, SBOMs, and release + manifests together. +- Good, because the low-level profile remains simpler. +- Neutral, because this can coexist with a low-level opt-in storage record mode if scopes are clear. +- Bad, because users of only the low-level profile do not get linked artifact storage records + without additional tooling. + +## More Information + +This decision follows ADR 0038, ADR 0039, ADR 0043, ADR 0044, ADR 0045, ADR 0046, and ADR 0047. It +decides only linked-artifact storage record policy for the initial GitHub Release asset profile. It +does not decide deployment record policy, whole-release inventory aggregation, linked artifact UI +usage, alert prioritization rules, sidecar bundle naming, or release manifest schema. + +Reference points considered: + +- GitHub's artifact attestation guide recommends uploading attested assets to the organization's + linked artifacts page for build history, deployment records, storage details, and security alert + prioritization. +- GitHub's `actions/attest` automatic storage record path requires both `push-to-registry: true` and + `artifact-metadata: write`. +- GitHub's linked artifacts guide describes artifact metadata storage records as creatable through + artifact attestations, JFrog integration, or the artifact metadata REST API. +- GitHub's artifact metadata storage record REST API accepts artifact `name`, `digest`, `version`, + `artifact_url`, `registry_url`, `repository`, `status`, and `github_repository` metadata. +- GitHub's linked artifacts guide says unwanted records cannot be deleted from the linked artifacts + page, though storage or deployment records can be updated to reflect artifact status. +- Windlass workflow hardening guidance requires explicit minimal permissions and job-level elevation + only where required, including `artifact-metadata: write` only for linked artifact metadata. diff --git a/docs/decisions/0049-separate-artifact-production-from-github-release-asset-publication.md b/docs/decisions/0049-separate-artifact-production-from-github-release-asset-publication.md new file mode 100644 index 0000000..5af04e7 --- /dev/null +++ b/docs/decisions/0049-separate-artifact-production-from-github-release-asset-publication.md @@ -0,0 +1,263 @@ +--- +parent: Decisions +nav_order: 49 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Separate Artifact Production from GitHub Release Asset Publication + +## Context and Problem Statement + +ADR 0038 through ADR 0048 defined an initial GitHub Release asset profile as a sibling production +profile that would generate Windlass-owned SLSA provenance, sign it with `actions/attest`, and +upload one release asset to an existing GitHub Release. Those decisions intentionally deferred the +last unresolved boundary: how the release asset bytes are produced or ingested before signing and +upload. + +Further review of SLSA v1.2, `slsa-github-generator`, GitHub reusable workflows, and +`actions/attest` shows that a generic GitHub Release asset build profile would either need to run a +broad repository-defined build recipe or accept prebuilt artifacts from caller workflows. The first +path risks becoming a lowest-common-denominator generic build runner. The second path more closely +resembles a provenance generator or promotion workflow, because the release asset bytes are produced +outside the GitHub Release asset profile's trusted build boundary. + +Should Windlass keep a generic GitHub Release asset build profile, redefine the GitHub Release asset +workflow as a publisher that creates its own publication attestation, or redefine it as a verified +distributor that republishes producer-generated provenance unchanged? + +## Decision Drivers + +- Preserve the profile-owned architecture from ADR 0002 and ADR 0003 without turning the GitHub + Release asset surface into a generic arbitrary build runner. +- Keep source-to-artifact build semantics in ecosystem-specific profiles that can define precise + toolchain, dependency, subject, and verifier contracts. +- Keep GitHub Release upload policy separate from artifact production policy. +- Avoid claiming that a publisher workflow built an artifact from source when it only received, + verified, and uploaded already-produced bytes. +- Preserve SLSA v1.2 Build L3 expectations that `externalParameters` are complete and that signing + authority is isolated from user-controlled build steps. +- Keep room for Go, Rust, JS/TS, container, archive, and other producer profiles to publish through + the same GitHub Release asset publication surface. +- Require any production publication path to fail closed when upstream producer provenance is + missing or does not match the artifact being uploaded. +- Avoid the cost and semantic ambiguity of a publisher-owned SLSA provenance or custom publication + predicate in the default production path. + +## Considered Options + +- Redefine GitHub Release asset handling as a verified distributor for ecosystem-produced artifacts + and producer-generated provenance. +- Redefine GitHub Release asset handling as a publisher that signs its own publication or binding + attestation. +- Keep a generic GitHub Release asset build profile that runs a source-controlled build recipe. +- Accept arbitrary caller-provided build commands in the GitHub Release asset profile. +- Accept caller-produced workflow artifacts without mandatory upstream provenance verification. +- Skip a GitHub Release asset profile and leave publication entirely to ecosystem-specific profiles. + +## Decision Outcome + +Chosen option: "Redefine GitHub Release asset handling as a verified distributor for +ecosystem-produced artifacts and producer-generated provenance", because GitHub Release upload is a +distribution concern while the artifact's source-to-bytes SLSA provenance belongs to the producer +profile that knows the ecosystem. + +Windlass will not ship a generic production GitHub Release asset build profile. Ecosystem-specific +producer profiles should produce final artifact bytes and source-to-artifact SLSA provenance. A +GitHub Release asset publisher profile should then verify the produced artifact and its upstream +producer provenance, upload the exact verified bytes to an existing GitHub Release, and publish or +expose the producer-generated provenance without modifying or re-signing it. + +The publisher profile's public workflow entrypoint should be renamed away from +`.github/workflows/github-release-asset-slsa3.yml`. The production entrypoint should use publication +language such as `.github/workflows/github-release-asset-publish.yml` or an equivalent name selected +by the architecture specification. The old `slsa3` filename is misleading for a workflow that does +not itself build the asset from source. + +The publisher profile should not define a production SLSA `buildType` for GitHub Release asset +publication in the default path, because it does not produce the artifact from source and does not +generate a new SLSA provenance statement. The concrete GitHub Release asset `buildType` value +implied by ADR 0042 is removed for this surface. The producer profile's `buildType` remains the +source-to-artifact build type that consumers must verify. + +The semantic boundary is fixed by this ADR: the GitHub Release asset publisher profile verifies and +publishes. It does not claim to compile, package, otherwise produce arbitrary release asset bytes +from source, or issue an independent SLSA provenance statement for the publication operation. + +The publisher profile must not accept raw unsigned artifacts as a production SLSA-equivalent path. +If a produced artifact lacks acceptable upstream producer provenance, the default production +publisher path must fail before mutating the GitHub Release. A separate lower assurance ingestion +workflow or experimental mode may be added only through a future ADR with a distinct workflow name, +predicate or evidence model, and verifier guidance. + +This ADR supersedes the source-to-release-asset build-profile interpretation in: + +- ADR 0038, which selected Windlass-generated SLSA build provenance for GitHub Release assets as if + the release asset profile owned artifact production; +- ADR 0040, which selected `.github/workflows/github-release-asset-slsa3.yml` as the production + workflow entrypoint; +- ADR 0044, which selected a `build` → `provenance-sign` → `release-upload` graph where the release + asset profile runs caller-controlled build or packaging steps; +- the concrete GitHub Release asset `buildType` value implied by ADR 0042, while preserving ADR + 0042's domain and URI namespace decision. + +ADR 0039's one-asset-per-run boundary, ADR 0043's existing-release upload policy, ADR 0045's final +release asset subject name, ADR 0046's selected-asset digest semantics, and ADR 0048's +linked-artifact opt-in policy remain valid where they are interpreted as publication-profile rules +rather than artifact-production rules. + +### Consequences + +- Good, because each producer profile can define precise ecosystem build semantics instead of + forcing all release assets through a generic build recipe. +- Good, because the GitHub Release asset workflow becomes smaller and focused on verification, + upload policy, provenance distribution, and release asset binding. +- Good, because the architecture avoids overclaiming that a publisher workflow produced bytes it + only received. +- Good, because the default path avoids defining a Windlass custom publication predicate before the + project has a concrete need for one. +- Good, because future ecosystem profiles can compose with the same publication surface while + keeping distinct `buildType` and `builder.id` values. +- Neutral, because users need an ecosystem producer profile before they can use the production + publisher path. +- Neutral, because consumers verify producer provenance directly and rely on GitHub Release state + for publication discovery rather than a publisher-signed attestation. +- Bad, because the previous GitHub Release asset ADR sequence needs supersession and terminology + cleanup before stable architecture specifications are written. +- Bad, because the project must define a producer-to-publisher handoff contract before implementing + the publisher workflow. + +### Confirmation + +This decision is confirmed when architecture specifications, workflow implementations, tests, and +verifier guidance define: + +- no generic production GitHub Release asset build profile; +- ecosystem-specific producer profiles as the source-to-artifact build surfaces; +- a renamed GitHub Release asset publisher workflow entrypoint that does not use the old + `github-release-asset-slsa3.yml` name; +- no publisher-owned SLSA provenance or custom publication attestation in the default production + path; +- producer-generated SLSA provenance as the only source-to-artifact provenance for the uploaded + release asset; +- mandatory upstream producer provenance verification before production publication; +- failure before GitHub Release mutation when upstream producer provenance is missing, untrusted, or + mismatched with the artifact digest; +- verifier guidance that treats producer provenance as the provenance consumers must verify for the + uploaded release asset; +- separate builder identities for producer profiles and the publisher profile. + +Implementation review should verify that no production path silently accepts raw caller-produced +artifacts, generates publisher-owned SLSA provenance for the release asset, labels publication-only +evidence as source-to-artifact build provenance, or reuses the old generic release asset builder +identity after this boundary change. + +## Pros and Cons of the Options + +### Redefine GitHub Release asset handling as a verified distributor + +Ecosystem-specific producer profiles build final artifacts and generate source-to-artifact +provenance. The GitHub Release asset publisher profile verifies the artifact and upstream producer +provenance, uploads the verified bytes to an existing GitHub Release, and publishes or exposes the +producer-generated provenance unchanged. + +- Good, because artifact production semantics stay with the profile that understands the ecosystem. +- Good, because GitHub Release upload is treated as a distribution and publication concern. +- Good, because the publisher profile can be reused across producer profiles without becoming a + generic build runner. +- Good, because the trust statement is honest: producer profiles build and attest, the publisher + verifies and distributes. +- Good, because the default path does not require a new Windlass publication predicate. +- Neutral, because the publisher's verification action is not itself represented by a new signed + attestation. +- Bad, because implementation depends on a precise producer-to-publisher handoff contract. + +### Sign publisher-owned publication or binding evidence + +The GitHub Release asset publisher verifies producer provenance, then creates a new SLSA provenance +statement or custom publication predicate for the publication operation. + +- Good, because the publisher's verification result and release-slot binding are signed explicitly. +- Good, because downstream automation can verify a producer attestation and a publisher attestation + as a chain. +- Bad, because SLSA provenance is awkward for a workflow that does not build the artifact from + source. +- Bad, because a custom publication predicate adds schema design, versioning, documentation, and + verifier implementation cost. +- Bad, because consumers may mistake publisher evidence for source-to-artifact provenance. + +### Keep a generic release asset build profile with source-controlled recipe execution + +The GitHub Release asset profile checks out source, runs a repository-declared build script or +recipe, signs provenance for the resulting asset, and uploads it. + +- Good, because one workflow can build, sign, and upload one release asset. +- Good, because the signing and upload authorities can be isolated from the build job. +- Neutral, because SLSA v1.2 does not prohibit user-defined build steps when provenance and signing + controls are correct. +- Bad, because the profile becomes a broad generic build runner with weak ecosystem semantics. +- Bad, because verifier expectations must account for arbitrary repository recipes, output paths, + toolchains, environment behavior, and network access. +- Bad, because the profile can drift toward arbitrary command execution over time. + +### Accept arbitrary caller-provided build commands + +The caller passes a command string such as `make release` and the release asset profile executes it +in the build job. + +- Good, because existing workflows are easy to adapt. +- Bad, because command strings become verifier-relevant external parameters that are difficult to + review and constrain. +- Bad, because shell behavior, quoting, environment expansion, and network access create a broad and + fragile build interface. +- Bad, because this undermines the goal of keeping build definitions in source-controlled ecosystem + profiles. + +### Accept caller-produced artifacts without mandatory upstream provenance verification + +The caller builds an artifact in another workflow job and passes the bytes, workflow artifact name, +or artifact ID to the GitHub Release asset workflow for signing and upload. + +- Good, because it composes with existing release workflows and complex matrix builds. +- Good, because it resembles the `slsa-github-generator` generic generator pattern. +- Bad, because the publisher workflow did not produce the bytes and should not claim + source-to-artifact build provenance for them. +- Bad, because workflow artifact transport is not a substitute for trusted producer provenance. +- Bad, because missing upstream provenance would let arbitrary bytes enter the production publisher + path. + +### Leave publication entirely to ecosystem-specific profiles + +Each ecosystem-specific profile builds, signs, and uploads its own GitHub Release assets directly. + +- Good, because every ecosystem profile can fully own its build and release lifecycle. +- Good, because no separate producer-to-publisher handoff is needed. +- Bad, because release upload policy, existing-release checks, duplicate-asset behavior, optional + sidecars, and linked-artifact metadata would be duplicated across ecosystem profiles. +- Bad, because whole-project GitHub Release publication behavior would become inconsistent across + profiles. + +## More Information + +This decision follows ADR 0002, ADR 0003, ADR 0013, ADR 0038, ADR 0039, ADR 0040, ADR 0042, ADR +0043, ADR 0044, ADR 0045, ADR 0046, ADR 0047, and ADR 0048. It decides the architecture boundary +between artifact production and GitHub Release asset publication. It does not decide the exact +handoff fields, upstream provenance policy format, producer provenance distribution channel, or +final workflow implementation details; those are decided by ADR 0050, ADR 0051, and the architecture +specifications. + +Reference points considered: + +- SLSA v1.2 Build L3 requires unforgeable provenance, isolated builds, and complete + `externalParameters`, but it does not require one generic build interface for all artifact types. +- SLSA v1.2 Build Provenance separates `buildType`, `externalParameters`, `resolvedDependencies`, + `builder.id`, and subject digest so different build or publication operations can use distinct + schemas and trust boundaries. +- `slsa-github-generator` distinguishes ecosystem-specific builders, which build artifacts and + generate provenance, from generic generators, which attest subjects produced by an existing + workflow. +- GitHub reusable workflows accept typed inputs, secrets, and outputs; callers cannot inject steps + into a reusable workflow job. +- GitHub `actions/attest` can sign a subject name and digest with a custom predicate and publish the + resulting bundle to GitHub artifact attestation storage. diff --git a/docs/decisions/0050-define-producer-to-publisher-handoff-contract.md b/docs/decisions/0050-define-producer-to-publisher-handoff-contract.md new file mode 100644 index 0000000..096c143 --- /dev/null +++ b/docs/decisions/0050-define-producer-to-publisher-handoff-contract.md @@ -0,0 +1,252 @@ +--- +parent: Decisions +nav_order: 50 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Define the Producer-to-Publisher Handoff Contract + +## Context and Problem Statement + +ADR 0049 separated artifact production from GitHub Release asset publication. Ecosystem-specific +producer profiles now own source-to-artifact builds and provenance, while the GitHub Release asset +publisher profile verifies an already-produced artifact and publishes it to an existing GitHub +Release. + +That separation creates a new contract surface. The publisher profile must receive artifact bytes, +the release tag, and producer-generated SLSA provenance for the exact final release asset. The +workflow artifact, download path, job output, or other transport used to hand the bytes to the +publisher is not itself a trust root. The publisher must verify digest and producer provenance +before mutating the GitHub Release, then distribute the producer provenance unchanged according to +ADR 0051. + +What information must a producer profile hand to the GitHub Release asset publisher profile, and +what must the publisher verify before upload? + +## Decision Drivers + +- Make the publisher profile safe to compose with multiple ecosystem-specific producer profiles. +- Treat byte transport between producer and publisher jobs as untrusted until digest and provenance + verification succeeds. +- Require a complete, verifier-friendly interface for the publication profile's + `externalParameters`. +- Preserve ADR 0039's one-asset publication unit and ADR 0045's final release asset subject name. +- Keep upstream source-to-artifact provenance mandatory for the production publisher path. +- Require producer provenance to describe the final GitHub Release asset name and exact bytes, so + the publisher does not need a separate signed name-mapping predicate. +- Fail closed before uploading to GitHub Release when any handoff input is missing, ambiguous, or + mismatched. +- Keep future lower-assurance ingestion or mirror workflows separate from the production publisher + identity. + +## Considered Options + +- Require a digest- and provenance-verified producer handoff contract. +- Pass only artifact bytes and recompute the digest in the publisher. +- Pass artifact bytes plus an expected digest, but do not require upstream producer provenance. +- Use a producer-generated manifest as the only handoff input. +- Let each ecosystem producer profile define its own publisher input shape. + +## Decision Outcome + +Chosen option: "Require a digest- and provenance-verified producer handoff contract", because the +publisher profile must not treat arbitrary transported bytes as production release assets unless it +can bind those bytes to trusted upstream producer provenance and the requested GitHub Release asset +slot. + +The production GitHub Release asset publisher profile should accept a fixed, profile-owned handoff +contract from producer profiles. The exact input names belong in the architecture specification, but +the contract must include at least these semantic fields: + +- an artifact handle that lets the publisher retrieve the produced bytes within the same workflow + run or approved workflow boundary; +- the expected artifact digest as lowercase SHA-256, plus any tool-boundary representation such as + `sha256:` where required; +- the final GitHub Release asset name to upload; +- the release tag identifying the existing Git tag and existing GitHub Release target; +- the producer-generated SLSA provenance bundle or a locator from which the publisher can retrieve + and verify the bundle; +- the expected upstream producer `builder.id` or trusted builder policy; +- the expected upstream producer `buildType` or trusted build type policy; +- the expected upstream subject digest and subject name, both of which must match the final GitHub + Release asset; +- the source repository and resolved source revision when they are needed for release policy or + verifier expectations; +- optional sidecar and linked-artifact publication settings selected by earlier ADRs. + +The artifact handle is a transport handle only. It may identify a workflow artifact, file path, +artifact ID, job output, or future internal handoff format, but it must not be treated as proof that +the artifact is trusted. The publisher must retrieve the bytes, compute their digest, and compare +the result with the expected digest before upload. + +The upstream producer provenance must be verified before publication. Verification should include at +least: + +- the upstream attestation signature and trusted signer identity; +- the upstream `predicateType` expected for source-to-artifact SLSA provenance; +- the upstream `builder.id` and `buildType` allowed by the caller's or profile's trusted producer + policy; +- the upstream subject digest matching the expected artifact digest and the bytes to upload; +- the upstream subject name exactly matching the final GitHub Release asset name; +- source repository, source revision, release tag, package or artifact identity, and other producer + `externalParameters` required by the selected producer policy; +- absence of unrecognized or unexpected producer `externalParameters` where the policy requires + strict matching. + +The publisher profile should not sign independent publication evidence in the default production +path. Instead, it should publish or expose the producer-generated provenance bundle unchanged after +successful verification. If a future workflow needs signed publication evidence or a subject-name +mapping predicate, that workflow requires a separate ADR, predicate model, and verifier guidance. + +The publisher must fail before GitHub Release mutation when: + +- the artifact bytes cannot be retrieved; +- the computed digest differs from the expected digest; +- the release tag or target GitHub Release does not exist; +- the final release asset name is invalid or already present under the target release; +- upstream producer provenance is missing, unsigned, unverifiable, untrusted, or mismatched; +- the upstream subject digest does not match the bytes to upload; +- the upstream subject name differs from the final release asset name; +- the producer policy does not allow the upstream `builder.id`, `buildType`, source, release ref, or + external parameters. + +The production publisher profile must not expose an option to bypass upstream provenance +verification. A future lower-assurance upload or mirror workflow may accept raw bytes, rename +artifacts, or sign publication evidence, but it must use a distinct workflow name, evidence model, +and verifier guidance. + +### Consequences + +- Good, because the publisher profile can safely compose with many ecosystem producer profiles + without trusting their transport mechanism. +- Good, because release publication is bound to both artifact digest and upstream source-to-artifact + provenance. +- Good, because consumers verify the same producer provenance that the publisher verified before + upload. +- Good, because requiring the producer subject name to match the final release asset name avoids a + separate signed rename or mapping predicate. +- Neutral, because the publisher profile needs a policy format for trusted producer builders and + build types. +- Neutral, because publisher-side verification is an operational gate rather than independent signed + evidence. +- Bad, because producer profiles must expose stable handoff outputs and upstream provenance + locators. +- Bad, because producer profiles must produce final release asset names before provenance signing. + +### Confirmation + +This decision is confirmed when architecture specifications, workflow implementations, tests, and +documentation define: + +- the public producer-to-publisher input and output names; +- artifact handle retrieval rules and digest calculation behavior; +- mandatory expected SHA-256 digest comparison before upload; +- upstream producer provenance locator or bundle requirements; +- trusted producer policy fields for `builder.id`, `buildType`, signer identity, source, release + ref, subject, and external parameters; +- rejection of producer subject names that differ from the final GitHub Release asset name; +- producer provenance distribution rules and outputs, as selected by ADR 0051; +- failure behavior for missing artifact bytes, digest mismatch, missing or invalid upstream + provenance, invalid release target, duplicate asset name, and release upload failure; +- verifier guidance for checking producer provenance against the downloaded GitHub Release asset; +- tests that prove raw caller-produced artifacts cannot reach the production upload path without + successful upstream provenance verification. + +Implementation review should verify that no workflow artifact name, job output, file path, GitHub +artifact ID, or caller-provided digest can bypass upstream provenance verification, and that the +exact bytes uploaded to the GitHub Release match the expected digest, the verified upstream subject +digest, and the verified upstream subject name. + +## Pros and Cons of the Options + +### Require a digest- and provenance-verified producer handoff contract + +Producer profiles expose artifact bytes, digest, upstream provenance, and policy-relevant identity +metadata. The publisher verifies all of them before upload and republishes the producer provenance +unchanged. + +- Good, because the contract matches ADR 0049's separation of production and publication. +- Good, because the publisher can reject arbitrary bytes even when they arrive through a valid + workflow artifact handle. +- Good, because producer and publisher responsibilities are explicit and independently verifiable. +- Good, because the handoff is stable enough for multiple ecosystem producer profiles. +- Good, because the publisher does not need to define or sign a new attestation format. +- Neutral, because policy configuration is required to describe which producers are trusted. +- Bad, because this is the most implementation-heavy initial handoff option. +- Bad, because producers must align their SLSA subject name with the final GitHub Release asset + name. + +### Pass only artifact bytes and recompute the digest + +The publisher receives or downloads bytes, computes their digest, and uploads them without an +expected digest or upstream producer provenance. + +- Good, because the publisher implementation is small. +- Good, because it can publish any file-like artifact. +- Bad, because the publisher has no trusted source-to-artifact build evidence for the bytes. +- Bad, because a compromised or incorrect caller can substitute arbitrary bytes before publication. +- Bad, because this collapses the production publisher into a raw upload helper rather than a + trusted release asset publisher. + +### Pass artifact bytes plus expected digest, without producer provenance + +The publisher verifies the transported bytes against an expected digest but does not verify how +those bytes were produced. + +- Good, because it prevents transport corruption or accidental artifact mixups. +- Good, because it is compatible with many existing workflows. +- Bad, because the expected digest itself can be caller-controlled without a trusted provenance + root. +- Bad, because SLSA source-to-artifact claims remain absent. +- Bad, because consumers could mistake release upload success for build provenance. + +### Use a producer-generated manifest as the only handoff input + +The producer emits a manifest that names the artifact, digest, release tag, upstream provenance, and +publication metadata. The publisher accepts only the manifest and retrieves everything from it. + +- Good, because one manifest can make the handoff easy to pass through workflow boundaries. +- Good, because the manifest can be reused by release orchestration. +- Neutral, because this may become useful as the stable API after producer profiles mature. +- Bad, because the manifest itself must be authenticated or linked to producer provenance to avoid + becoming a caller-controlled bundle of claims. +- Bad, because it adds a manifest schema before the project has implemented any + producer-to-publisher handoff. + +### Let each ecosystem producer define its own publisher inputs + +Every producer profile exposes whatever outputs it wants, and the publisher adapts to each +ecosystem. + +- Good, because each producer can optimize its own ergonomics. +- Bad, because the publisher becomes a matrix of ecosystem-specific adapters. +- Bad, because verifier guidance and tests fragment across producer profiles. +- Bad, because adding a new ecosystem profile would require changing the publisher's public + contract. + +## More Information + +This decision follows ADR 0049 and replaces the unresolved build-or-ingest portion of the earlier +GitHub Release asset ADR sequence. It depends on ADR 0039's one-asset unit, ADR 0043's existing +release requirement, ADR 0045's final release asset subject name, ADR 0046's selected-asset digest +semantics, ADR 0048's linked-artifact opt-in policy, and ADR 0051's producer provenance distribution +model as publication-profile rules. + +This decision does not define the first ecosystem-specific producer profile, the exact JSON schema +for a future handoff manifest, a future publisher predicate type, or a lower-assurance raw upload +workflow. Those require architecture specifications or future ADRs. + +Reference points considered: + +- SLSA v1.2 Build Provenance treats `externalParameters` as the complete external interface to the + build or operation and expects verifiers to reject unexpected fields. +- SLSA v1.2 Build L3 requires provenance fields to be generated or verified by the trusted build + platform, except permitted tenant-controlled subject information. +- `slsa-github-generator` generic workflows accept caller-provided subject digests, while ecosystem + builders own artifact production semantics. +- GitHub reusable workflows exchange typed inputs, secrets, outputs, and artifacts; those transport + mechanisms are not by themselves provenance or trust roots. +- GitHub `actions/attest` can bind a subject name and digest to a custom predicate and expose a + local Sigstore bundle plus GitHub attestation storage handles for downstream verification. diff --git a/docs/decisions/0051-distribute-producer-provenance-with-release-assets.md b/docs/decisions/0051-distribute-producer-provenance-with-release-assets.md new file mode 100644 index 0000000..60c0fd5 --- /dev/null +++ b/docs/decisions/0051-distribute-producer-provenance-with-release-assets.md @@ -0,0 +1,223 @@ +--- +parent: Decisions +nav_order: 51 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Distribute Producer Provenance with GitHub Release Assets + +## Context and Problem Statement + +ADR 0049 redefined the GitHub Release asset profile as a verified distributor rather than a generic +release asset builder or independent provenance issuer. ADR 0050 then required producer profiles to +handoff artifact bytes and producer-generated SLSA provenance for the exact final GitHub Release +asset. That leaves a distribution question previously answered by ADR 0047 under an older premise: +ADR 0047 assumed the GitHub Release asset profile generated and signed its own Windlass SLSA +provenance bundle through `actions/attest` and then used GitHub attestation storage as the canonical +distribution channel. + +Under the new model, the publisher profile should not create new SLSA provenance or a custom +publication predicate in the default production path. It verifies producer-generated provenance and +publishes the artifact. The project now needs to decide how that already-signed producer provenance +is made discoverable to GitHub Release asset consumers. + +Should the GitHub Release asset publisher redistribute the producer provenance bundle as a release +asset sidecar, rely only on the producer's native attestation storage, generate new publisher +attestation evidence, or leave provenance distribution entirely to the producer profile? + +## Decision Drivers + +- Preserve the meaning of SLSA provenance as source-to-artifact build provenance generated by the + ecosystem-specific producer profile. +- Avoid creating a publisher-owned SLSA provenance statement or custom publication predicate in the + default production path. +- Make GitHub Release consumers able to find the producer provenance for the downloaded asset + without knowing the producer workflow internals. +- Keep the producer provenance bundle unchanged so its signature, subject, `builder.id`, + `buildType`, and `externalParameters` remain exactly as produced and verified. +- Keep the selected release asset subject digest bound to the primary asset bytes, not to provenance + sidecars, checksum files, SBOMs, or whole-release manifests. +- Support producer-native attestation storage when available, while providing a portable + release-page distribution path for archival, mirrored, or air-gapped consumers. +- Avoid treating publisher-side workflow artifacts, job outputs, logs, or release notes as durable + signed provenance. + +## Considered Options + +- Publish the verified producer provenance bundle as a release asset sidecar and expose native + provenance locators when available. +- Use only the producer's native attestation storage or registry provenance distribution. +- Generate a new publisher-owned attestation that references the producer provenance. +- Leave provenance distribution entirely to the producer profile. +- Store producer provenance only as a workflow artifact or job output. + +## Decision Outcome + +Chosen option: "Publish the verified producer provenance bundle as a release asset sidecar and +expose native provenance locators when available", because the publisher should not mint new +provenance but GitHub Release consumers still need a durable, release-adjacent way to retrieve the +provenance that the publisher verified. + +The production GitHub Release asset publisher profile should redistribute the exact verified +producer-generated provenance bundle as a GitHub Release asset sidecar. The sidecar is a +distribution copy of the producer provenance, not a new attestation created by the publisher. The +publisher must not alter, re-sign, wrap, or regenerate the producer provenance bundle before +publishing it. + +The publisher should also expose producer-native provenance locators when they exist, such as GitHub +artifact attestation URLs, registry provenance handles, or other ecosystem-owned discovery surfaces. +Those locators are useful for online verification and producer-system audit trails. The release +asset sidecar remains the GitHub Release distribution copy that travels with the released artifact. + +The sidecar asset name should be deterministic and derived from the final release asset name. The +architecture specification should define the exact naming convention, but it should preserve the +primary asset name visibly and avoid colliding with ordinary release assets. For example, a future +specification may choose a suffix such as: + +```text +.intoto.jsonl +``` + +The sidecar must not change the primary release asset's SLSA subject. The producer provenance +subject must already identify the final release asset name and digest selected by ADR 0045 and +ADR 0050. The sidecar's bytes are not inserted into `subject[0].digest`, are not treated as checksum +or SBOM coverage for the primary asset, and are not a substitute for verifying the producer +provenance signature and subject digest. + +The publisher must fail before uploading either the primary asset or sidecar when producer +provenance verification fails. If primary asset upload succeeds but sidecar upload fails, the +workflow should fail clearly without deleting, replacing, or clobbering the primary asset. The +failure message and outputs should make the partial state explicit so operators can retry sidecar +publication or remove the incomplete release asset according to repository policy. + +This ADR supersedes ADR 0047's canonical GitHub attestation storage decision for GitHub Release +assets. ADR 0047 assumed publisher-generated Windlass SLSA provenance. Under the current model, +producer-generated provenance and producer-native attestation storage remain authoritative for the +source-to-artifact claim, while the GitHub Release sidecar provides a durable publication copy. + +### Consequences + +- Good, because the publisher distributes the same signed SLSA provenance that the producer profile + generated and the publisher verified. +- Good, because consumers can download a GitHub Release asset and its provenance bundle from the + same release page or mirror. +- Good, because the project avoids defining and signing a new publisher-owned predicate solely to + describe publication. +- Good, because producer-native attestation storage can still be surfaced for online verification. +- Neutral, because ordinary releases now include at least one provenance sidecar per published + asset. +- Neutral, because sidecar naming and duplicate-asset behavior become part of the publisher profile + specification. +- Bad, because sidecar upload can fail after the primary asset upload, creating a partial release + state that must be documented and handled. +- Bad, because consumers that only use native attestation lookup may need guidance to choose between + native locators and release sidecar bundles. + +### Confirmation + +This decision is confirmed when architecture specifications, workflow implementations, tests, and +documentation define: + +- producer-generated provenance as the only SLSA provenance for the primary release asset in the + default production publisher path; +- mandatory verification of producer provenance before primary asset or provenance sidecar upload; +- publication of the verified producer provenance bundle as an unchanged GitHub Release asset + sidecar; +- deterministic sidecar naming, duplicate-name failure behavior, and outputs for sidecar name, URL, + and digest; +- exposure of producer-native provenance locators when available; +- consumer guidance for verifying the primary asset against the producer provenance bundle; +- failure behavior for sidecar upload failure after primary asset upload; +- documentation that workflow artifacts, job outputs, logs, release notes, and publisher-owned + unsigned metadata are not provenance distribution channels. + +Implementation review should verify that the publisher does not re-sign or mutate the producer +provenance bundle, that the sidecar corresponds to the exact primary asset digest and subject name, +and that a missing or unverifiable producer provenance bundle cannot reach the production upload +path. + +## Pros and Cons of the Options + +### Publish verified producer provenance as a sidecar and expose native locators + +The publisher uploads the primary release asset and a deterministic sidecar containing the unchanged +producer provenance bundle. It also exposes native producer attestation locators when available. + +- Good, because it keeps source-to-artifact provenance owned by the producer profile. +- Good, because release consumers have a durable bundle next to the asset. +- Good, because native attestation storage remains available for tools that prefer it. +- Neutral, because the release asset list includes provenance sidecars by default. +- Bad, because sidecar upload lifecycle and duplicate-name handling must be specified. + +### Use only producer-native attestation storage + +The publisher does not upload any provenance sidecar. It only exposes or documents the producer's +native provenance lookup surface. + +- Good, because it avoids extra release assets. +- Good, because it preserves the producer ecosystem's canonical discovery path. +- Bad, because GitHub Release consumers and mirrors may not have a durable provenance bundle next to + the downloaded asset. +- Bad, because producer-native storage may vary by ecosystem, repository, registry, or hosting + provider. + +### Generate a new publisher-owned attestation + +The publisher verifies producer provenance and then signs a new SLSA provenance statement or custom +publication predicate that references the producer provenance. + +- Good, because the publisher's verification and release-slot binding are signed explicitly. +- Good, because downstream automation can check a two-attestation chain. +- Bad, because SLSA provenance is semantically awkward for a workflow that did not build the + artifact from source. +- Bad, because a custom publication predicate adds schema, documentation, versioning, and verifier + implementation cost. +- Bad, because consumers may mistake publisher evidence for source-to-artifact provenance. + +### Leave provenance distribution entirely to producer profiles + +Producer profiles generate and distribute provenance, and the GitHub Release asset publisher uploads +only the primary release asset. + +- Good, because the publisher profile remains smallest. +- Good, because producer profiles fully own their artifact and provenance lifecycle. +- Bad, because GitHub Release consumers may not know where to find provenance for the downloaded + asset. +- Bad, because release-page and mirror-friendly provenance distribution becomes inconsistent across + producer profiles. + +### Store producer provenance only as a workflow artifact or job output + +The publisher uses workflow artifacts or outputs to move producer provenance internally but does not +publish it to the release page or expose native locators. + +- Good, because it is simple for internal handoff. +- Bad, because workflow artifacts and job outputs are not durable public provenance distribution + channels. +- Bad, because consumers outside the workflow run would not have a stable discovery path. + +## More Information + +This decision follows ADR 0049 and ADR 0050. It supersedes ADR 0047 for the GitHub Release asset +publisher profile because ADR 0047 was written for a profile that generated its own Windlass SLSA +provenance bundle. It preserves ADR 0039's one-primary-asset boundary, ADR 0045's subject name and +digest model, ADR 0046's rule that sidecars do not change the primary subject digest, and ADR 0048's +separate linked-artifact metadata policy. + +This decision does not define whole-release manifests, checksum or SBOM sidecar policy, detached +signature policy, a publisher-owned custom predicate, or deployment records. Those remain separate +future decisions. + +Reference points considered: + +- SLSA provenance identifies build outputs by subject name and digest; the producer profile's + provenance should already name the final artifact bytes selected for publication. +- GitHub Release assets provide a durable distribution surface for binary artifacts and sidecar + metadata files. +- GitHub artifact attestation storage can remain a producer-native provenance locator when the + producer profile uses GitHub `actions/attest`, but the publisher should not depend on minting a + new attestation to distribute already-generated producer provenance. +- Workflow artifacts and job outputs are useful handoff mechanisms but are not durable public + provenance distribution channels for release consumers. diff --git a/docs/decisions/0052-compose-npm-package-tarball-producer-with-release-asset-publisher.md b/docs/decisions/0052-compose-npm-package-tarball-producer-with-release-asset-publisher.md new file mode 100644 index 0000000..8c78c27 --- /dev/null +++ b/docs/decisions/0052-compose-npm-package-tarball-producer-with-release-asset-publisher.md @@ -0,0 +1,219 @@ +--- +parent: Decisions +nav_order: 52 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Compose the npm Package Tarball Producer with the GitHub Release Asset Publisher + +## Context and Problem Statement + +ADR 0049 redefined GitHub Release asset handling as publication rather than artifact production. ADR +0050 then required the publisher profile to accept a producer-to-publisher handoff that binds +artifact bytes, final release asset name, expected digest, and producer-generated SLSA provenance. +ADR 0051 requires the publisher to redistribute the verified producer provenance bundle as an +unchanged GitHub Release asset sidecar. + +Those decisions intentionally keep the GitHub Release asset publisher from becoming a generic build +profile. They also leave an initial composition question: which producer profile should first feed +the publisher with source-to-artifact provenance and final artifact bytes? + +The project already has a detailed initial producer candidate: the JS/TS npm package profile. That +profile produces an npm package tarball, generates Windlass-owned SLSA provenance for that tarball, +and returns package identity plus tarball digest outputs. The same tarball can be useful as a GitHub +Release asset for projects that want a release-page copy in addition to npm registry publication. + +Should the first GitHub Release asset producer composition use the JS/TS npm package profile's +published package tarball, introduce a generic release asset builder, wait for a Go binary or +archive producer profile, or leave producer composition unspecified until multiple producer profiles +exist? + +## Decision Drivers + +- Preserve ADR 0049's boundary that GitHub Release asset publication does not build or mint + source-to-artifact provenance in the default production path. +- Reuse the already-specified JS/TS npm package producer semantics instead of inventing a generic + artifact build model. +- Provide a concrete first end-to-end composition for projects that publish npm packages and also + attach the produced package tarball to a GitHub Release. +- Keep the GitHub Release asset publisher generic enough to compose with future producer profiles, + including Go archives, container images, SBOM producers, checksum producers, or other ecosystem + profiles. +- Require the producer provenance subject to match the final GitHub Release asset name and digest, + as required by ADR 0050, without adding a publisher-owned rename predicate. +- Avoid coupling the publisher's public contract, verification model, or implementation internals to + npm-specific fields that belong to the JS/TS npm package producer profile. +- Keep future producer profile additions possible without redesigning the publisher contract. + +## Considered Options + +- Use the JS/TS npm package profile's npm package tarball as the initial producer composition for + the GitHub Release asset publisher. +- Create a generic release asset builder profile before composing any ecosystem producer. +- Wait for a Go binary or archive producer profile before defining a first publisher composition. +- Leave the initial producer composition unspecified until several producer profiles exist. + +## Decision Outcome + +Chosen option: "Use the JS/TS npm package profile's npm package tarball as the initial producer +composition for the GitHub Release asset publisher", because it provides the first concrete +producer-to-publisher path while preserving the publisher as an abstract verifier and distributor +for producer-owned artifacts. + +The first production GitHub Release asset publisher composition should use the npm package tarball +produced by the JS/TS npm package profile as the primary release asset bytes. The JS/TS npm package +profile remains the source-to-artifact producer: it owns package-manager execution, package packing, +npm package identity, tarball digest calculation, Windlass-generated SLSA provenance, npm registry +publication, and npm-specific verification expectations. + +The GitHub Release asset publisher should receive the npm tarball through the producer-to-publisher +handoff contract selected by ADR 0050. The handoff should expose the artifact handle, expected +SHA-256 digest, final GitHub Release asset name, release tag, producer provenance bundle or locator, +producer `builder.id`, producer `buildType`, producer subject name, producer subject digest, source +identity, and producer-native provenance locators using the generic handoff semantics. The publisher +must treat npm-specific fields as producer policy inputs or opaque producer metadata unless the +generic handoff contract explicitly requires them. + +The publisher must verify that the npm tarball bytes it will upload match the expected digest, that +the producer provenance signature and trusted producer policy verify, and that the producer +provenance subject name and digest match the final GitHub Release asset name and bytes. It should +then upload the tarball as the primary GitHub Release asset and redistribute the unchanged producer +provenance bundle as the sidecar selected by ADR 0051. + +This composition does not make the GitHub Release asset publisher an npm-specific workflow. The +publisher's architecture specification, inputs, verification phases, failure behavior, and outputs +should be expressed in producer-neutral terms: artifact handle, expected digest, final asset name, +release target, producer provenance, trusted producer policy, native provenance locators, sidecar +publication, and linked-artifact options. npm package name, package version, registry URL, dist-tag, +package URL, npm integrity values, and package-manager metadata remain JS/TS npm package producer +fields unless a generic publisher rule explicitly needs a normalized value. + +Future producer profiles should be able to compose with the same publisher without changing the +publisher's trust boundary. Adding producers such as Go binary/archive, container image, checksum, +SBOM, or other ecosystem profiles should require those producers to satisfy the same handoff +contract, not require the publisher to learn the full semantics of each producer ecosystem. If a +future producer needs a materially different handoff, subject-name mapping, publication evidence, or +trust model, that change should be recorded by a future ADR and should preserve a clear distinction +between producer-owned source-to-artifact provenance and publisher-owned distribution behavior. + +The initial npm tarball composition should not support generic raw file upload, arbitrary caller +artifacts, unpublished package tarballs without accepted producer provenance, or publisher-generated +SLSA provenance. Those remain outside the production publisher path unless a future ADR defines a +distinct producer profile, lower-assurance workflow, or publication evidence model. + +### Consequences + +- Good, because the project gets a concrete first release asset composition without reintroducing a + generic release asset builder. +- Good, because the JS/TS npm package profile's existing package identity, tarball digest, and + provenance decisions become immediately useful for GitHub Release distribution. +- Good, because the publisher remains a verifier and distributor rather than an npm package profile + extension. +- Good, because future producer profiles can target the same publisher handoff contract. +- Good, because the architecture avoids treating raw caller-provided files as production release + assets without producer provenance. +- Neutral, because the first composition primarily serves projects that publish npm packages and + want a GitHub Release copy of the package tarball. +- Neutral, because the JS/TS npm package profile may need to expose handoff outputs that are more + precise than ordinary public workflow outputs. +- Bad, because projects that need Go binaries, generic archives, containers, SBOM-only assets, or + other release asset types still need later producer profiles. +- Bad, because requiring the producer subject name to match the final GitHub Release asset name may + force npm tarball naming to be decided before provenance signing. + +### Confirmation + +This decision is confirmed when architecture specifications, workflow implementations, tests, and +documentation define: + +- the JS/TS npm package profile's npm package tarball as the initial producer artifact for GitHub + Release asset publisher composition; +- JS/TS npm package profile outputs or internal handoff artifacts that provide the generic + producer-to-publisher fields required by ADR 0050; +- the GitHub Release asset publisher contract in producer-neutral terms rather than npm-specific + terms; +- publisher verification of npm tarball bytes, expected digest, producer provenance signature, + trusted producer `builder.id`, trusted producer `buildType`, producer subject name, producer + subject digest, source identity, and release asset target before upload; +- redistribution of the unchanged JS/TS npm package producer provenance bundle as the GitHub Release + sidecar selected by ADR 0051; +- rejection of raw npm tarballs or arbitrary files that lack acceptable producer provenance; +- documentation that future producer profiles can compose with the publisher by satisfying the same + handoff contract; +- tests proving that the publisher does not depend on npm-only fields except through producer policy + checks for this specific composition. + +Implementation review should verify that the GitHub Release asset publisher cannot silently become +an npm-specific publisher, cannot accept unauthenticated tarball transport as proof of artifact +trust, and cannot upload a release asset whose bytes, final asset name, or digest differ from the +verified producer provenance subject. + +## Pros and Cons of the Options + +### Use the JS/TS npm package tarball as the initial producer composition + +The JS/TS npm package profile produces the package tarball and SLSA provenance. The publisher +verifies the handoff, uploads the tarball to an existing GitHub Release, and publishes the unchanged +producer provenance sidecar. + +- Good, because it composes existing profile decisions into the first concrete release asset path. +- Good, because it keeps npm ecosystem semantics inside the npm producer profile. +- Good, because the publisher contract can be tested with a real producer before additional + producers exist. +- Neutral, because the first supported producer composition is npm-package-specific. +- Bad, because it does not help projects whose primary release assets are binaries, archives, or + containers. + +### Create a generic release asset builder profile first + +The project would define a builder that accepts arbitrary files, generates new provenance, and then +publishes those files as GitHub Release assets. + +- Good, because it appears flexible for many asset types. +- Bad, because it reverses ADR 0049's decision to separate artifact production from publication. +- Bad, because generic arbitrary-file provenance would have weak or unclear source-to-artifact + semantics. +- Bad, because it risks accepting raw caller-produced artifacts under a production SLSA-equivalent + identity. + +### Wait for a Go binary or archive producer profile + +The project would defer release asset publisher composition until a Go binary or archive producer +profile exists. + +- Good, because Go archives are likely to be common GitHub Release assets for this project family. +- Good, because it avoids npm-specific first examples. +- Bad, because it delays validation of the publisher handoff even though a specified npm producer is + already available. +- Bad, because it leaves npm package tarball redistribution unspecified despite being a natural + first composition. + +### Leave initial producer composition unspecified + +The project would write only an abstract publisher contract and wait for future producers to bind to +it. + +- Good, because it maximizes initial abstraction. +- Bad, because specifications and tests would lack a concrete end-to-end composition. +- Bad, because untested abstraction may hide fields that are too generic, too npm-specific, or + insufficient for real producer handoff. + +## More Information + +This decision follows ADR 0049, ADR 0050, and ADR 0051. It uses the JS/TS npm package profile +defined by ADR 0013 through ADR 0037 as the first producer profile that can hand off artifact bytes +and producer-generated SLSA provenance to the GitHub Release asset publisher. + +This decision does not add a generic file producer, Go binary/archive producer, container producer, +SBOM producer, checksum producer, publisher-owned publication predicate, raw artifact upload mode, +or lower-assurance mirror workflow. Those remain future producer-profile or workflow decisions. + +Reference points considered: + +- ADR 0049 requires ecosystem-specific producer profiles to own source-to-artifact provenance. +- ADR 0050 requires the publisher to verify producer provenance before release mutation. +- ADR 0051 requires unchanged producer provenance redistribution as a release asset sidecar. +- The JS/TS npm package profile already models a package tarball as the physical release artifact + for npm publication and exposes tarball digest outputs useful for composition. diff --git a/docs/decisions/0053-use-three-job-release-manifest-signing-boundary.md b/docs/decisions/0053-use-three-job-release-manifest-signing-boundary.md new file mode 100644 index 0000000..bad17a2 --- /dev/null +++ b/docs/decisions/0053-use-three-job-release-manifest-signing-boundary.md @@ -0,0 +1,273 @@ +--- +parent: Decisions +nav_order: 53 +status: accepted +date: 12026-07-07 +decision-makers: Yunseo Kim +--- + +# Use a Three-Job Release Manifest Signing Boundary + +## Context and Problem Statement + +ADR 0028 requires production consumers to execute profile-owned reusable workflows by full commit +SHA, and requires signed release metadata that maps human-readable Windlass release versions to the +exact workflow SHAs and SHA-based `builder.id` values. ADR 0031 then selected a Windlass-defined +release manifest carried in an in-toto Statement and signed as a Sigstore DSSE bundle as the +machine-verifiable release metadata mechanism. + +The project now needs to decide the workflow trust boundary for generating, signing, and publishing +that release manifest. The manifest is security-critical because verifiers use it to decide whether +a profile provenance `builder.id`, workflow path, release version, and `buildType` mapping are +trusted. If the same job can freely generate metadata, sign it, and mutate the GitHub Release, then +a bug or compromise in that job can affect both the trust metadata and its distribution channel. + +How should the release manifest signing workflow separate manifest generation, Sigstore signing, and +GitHub Release asset publication for the initial production release process? + +## Decision Drivers + +- Preserve the release manifest as the canonical machine-verifiable version-to-SHA trust metadata + selected by ADR 0031. +- Keep release metadata generation, signing authority, and release asset mutation in distinct trust + and permission boundaries where practical. +- Follow the project pattern of digest-verified handoff between isolated jobs selected for npm + publishing and release asset publication. +- Use GitHub-hosted ephemeral runners, full-SHA-pinned actions, minimal top-level permissions, and + job-level permission elevation. +- Avoid long-lived signing keys and use GitHub Actions OIDC with Sigstore as the initial signing + mechanism. +- Keep the initial implementation smaller than a dedicated reusable workflow or TUF-style metadata + system while leaving a clear migration path. +- Make future migration to a reusable signing workflow, direct Sigstore tooling, `sigstore-go`, or + TUF-style metadata an explicit follow-up decision rather than an accidental implementation drift. + +## Considered Options + +- Use a three-job release manifest workflow: `manifest-generate` → `manifest-sign` → + `manifest-upload`. +- Use a dedicated reusable workflow for release manifest signing from the beginning. +- Generate, sign, and upload the release manifest in one job. +- Use direct Sigstore tooling, such as `cosign` or future `sigstore-go`, instead of `actions/attest` + for the initial signing path. +- Rely on GitHub immutable release attestations, signed tags, release notes, or checksum files + rather than a Windlass-signed release manifest bundle. +- Adopt TUF-style release metadata from the beginning. + +## Decision Outcome + +Chosen option: "Use a three-job release manifest workflow: `manifest-generate` → `manifest-sign` → +`manifest-upload`", because it provides a clear least-privilege boundary for the initial release +metadata trust root while staying small enough for the first implementation. + +The initial Windlass release process should produce release manifest metadata through three primary +jobs: + +```text +manifest-generate -> manifest-sign -> manifest-upload +``` + +The `manifest-generate` job should create the unsigned Windlass release manifest JSON and the +in-toto Statement content required by ADR 0031. It should calculate the manifest digest, upload the +unsigned manifest material as workflow artifacts, and expose only the digest and artifact handles +needed by the signing job. This job should not have `id-token: write`, `attestations: write`, +`contents: write`, release mutation authority, long-lived signing credentials, or access to +publisher secrets. + +The `manifest-sign` job should download the manifest artifacts, recalculate their digests, verify +that the contents match the `manifest-generate` handoff, and sign the release manifest Statement as +a Sigstore bundle. The initial signing adapter should be full-SHA-pinned `actions/attest` in custom +predicate mode or an equivalent `actions/attest` path that signs the Windlass release manifest +predicate selected by ADR 0031. The signing job should have only the permissions needed for signing +and optional GitHub attestation storage: `contents: read`, `id-token: write`, and +`attestations: write`. It should not have `contents: write` or GitHub Release mutation authority. + +The `manifest-upload` job should download the unsigned manifest material and signed Sigstore bundle, +recalculate their digests, verify that those digests match the previous job handoff values, and +upload the release manifest artifacts to the selected existing GitHub Release. This job should have +only the release mutation permission it needs, normally `contents: write`, and should not have +`id-token: write`, `attestations: write`, signing credentials, or authority to create new signed +metadata. The upload job should fail rather than re-signing, regenerating, or mutating manifest +contents after signing. + +The signed Sigstore bundle is the canonical machine-verifiable release metadata artifact. Plain JSON +manifests, checksums, release notes, GPG-signed annotated tags, GitHub immutable release evidence, +and GitHub release-level attestations may provide discovery, diagnostics, defense-in-depth, or +human-readable release integrity evidence, but they do not replace the signed release manifest +bundle as the version-to-SHA trust root selected by ADR 0031. + +The release manifest architecture specification should define exact artifact names, bundle names, +digest encodings, predicate type, schema version, subject rules, signer identity, allowed workflow +path, release tag checks, release asset duplicate behavior, retry behavior, and verification +commands. Jobs must not trust workflow artifact names, logs, job outputs, release notes, or +unchecked files as substitutes for recalculating digests and verifying the signed bundle. + +This decision does not permanently reject a dedicated reusable workflow, direct Sigstore tooling, +`sigstore-go`, or TUF-style metadata. Those options should be reconsidered through follow-up ADRs if +one or more of the following conditions becomes true: + +- multiple release workflows or repositories need to reuse the same release manifest signing + contract; +- verifier policy needs a distinct, stable release-manifest signer identity separate from the main + release orchestration workflow; +- `actions/attest` cannot represent the required custom predicate, bundle, signer identity, + verification, offline validation, or distribution behavior; +- a Go-native trusted core is ready to own Sigstore bundle creation and verification through + `sigstore-go` or equivalent direct libraries; +- release metadata needs threshold signing, delegated roles, expiry, rollback/freeze attack defense, + multiple release channels, mirrored metadata, or independently managed signing authorities; +- GitHub artifact attestation storage, immutable releases, or release verification features change + enough to materially alter the release metadata trust model. + +Any migration to a dedicated reusable workflow, direct `cosign`/`sigstore-go` signing, or TUF-style +metadata must preserve or explicitly replace the verifier-visible trust contract for release +manifest predicate type, schema version, signer identity, release version, workflow path, workflow +SHA, `builder.id`, and `buildType` mapping. + +### Consequences + +- Good, because manifest generation, signing, and release upload have separate job permissions. +- Good, because release mutation authority cannot create or modify signed metadata. +- Good, because signing authority cannot upload or replace GitHub Release assets. +- Good, because the design follows the repository's existing digest-verified handoff pattern. +- Good, because the initial signing path uses GitHub Actions OIDC and avoids long-lived signing + keys. +- Good, because future migration paths are acknowledged without making the first implementation too + large. +- Neutral, because the workflow still depends on GitHub-hosted runners and GitHub-native Sigstore + integration. +- Neutral, because the signed manifest remains Windlass-specific and requires Windlass verifier + policy. +- Bad, because three jobs require more handoff artifacts, digest checks, and failure handling than a + single-job release process. +- Bad, because `actions/attest` remains a trusted action dependency until a future migration + replaces it. + +### Confirmation + +This decision is confirmed when release process specifications, workflow implementations, tests, and +verifier guidance define: + +- exactly three primary release manifest jobs named or equivalent to `manifest-generate`, + `manifest-sign`, and `manifest-upload`; +- minimal top-level workflow permissions and explicit job-level permission elevation; +- no signing authority or release mutation authority in `manifest-generate`; +- `manifest-sign` permissions limited to `contents: read`, `id-token: write`, and + `attestations: write` unless a later ADR changes the signing adapter; +- no `contents: write` or release mutation authority in `manifest-sign`; +- `manifest-upload` release mutation permission, normally `contents: write`, without signing + authority; +- digest-verified handoff from manifest generation to signing and from signing to upload; +- full-SHA pinning for `actions/attest` and any other external action used in the release manifest + signing path; +- the Windlass release manifest predicate type, schema version, subject rules, bundle artifact name, + plain manifest artifact name, digest encoding, signer identity, and verifier policy; +- release upload behavior for missing releases, duplicate manifest assets, partial upload failure, + retries, and immutable release publication flows; +- documentation that plain JSON manifests, checksums, release notes, signed tags, GitHub immutable + release evidence, and release-level attestations are complementary evidence, not substitutes for + the signed release manifest bundle; +- follow-up ADR criteria for migration to a dedicated reusable workflow, direct Sigstore or + `sigstore-go` signing, or TUF-style metadata. + +Implementation review should verify that no production path can publish or trust release manifest +metadata unless the manifest was generated, signed, digest-verified, and uploaded through the +selected boundary, and that no job combines signing authority with GitHub Release mutation authority +without a later ADR. + +## Pros and Cons of the Options + +### Use a three-job release manifest workflow + +Release manifest generation, signing, and upload run in distinct jobs with digest-verified handoff +between jobs. + +- Good, because each job has a small, auditable permission set. +- Good, because signing and release mutation authorities are separated. +- Good, because it aligns with the project's existing three-job profile patterns. +- Neutral, because it is GitHub Actions-specific. +- Bad, because it introduces cross-job artifact and digest-handling complexity. + +### Use a dedicated reusable workflow from the beginning + +Release manifest signing is moved into a standalone reusable workflow with its own public contract +and signer identity. + +- Good, because multiple release workflows can reuse the same signing contract. +- Good, because verifier policy can trust a distinct release-manifest signer workflow. +- Bad, because it adds public API, builder identity, release metadata, and bootstrap complexity + before reuse pressure exists. +- Bad, because the first implementation must version and document another reusable workflow surface. + +### Generate, sign, and upload in one job + +One release job creates the manifest, signs it, and uploads it to the GitHub Release. + +- Good, because it is simplest to implement. +- Good, because there are no cross-job handoff artifacts. +- Bad, because one job needs both signing authority and release mutation authority. +- Bad, because a compromise or bug in that job affects both the trust metadata and its distribution + channel. +- Bad, because it does not match the repository's least-privilege release profile pattern. + +### Use direct Sigstore tooling instead of `actions/attest` + +The release workflow invokes `cosign`, `sigstore-go`, or equivalent direct Sigstore tooling to +create the signed bundle. + +- Good, because it can provide more direct control over bundle format, offline verification, and + non-GitHub distribution. +- Good, because it aligns with the long-term Go trusted core direction if implemented through + `sigstore-go`. +- Bad, because it requires Windlass to own more Sigstore integration, compatibility, and verifier + behavior in the first release. +- Bad, because it bypasses the existing `actions/attest` adapter decision unless a follow-up ADR + changes that boundary. + +### Rely on GitHub release evidence or signed tags instead of a signed manifest bundle + +The release process uses GitHub immutable releases, release attestations, GPG-signed annotated tags, +release notes, checksums, or a plain manifest rather than a Windlass-signed release manifest bundle. + +- Good, because these mechanisms are familiar and useful as defense-in-depth evidence. +- Good, because signed tags and immutable release evidence remain valuable release integrity + signals. +- Bad, because they do not provide the Windlass-specific machine-readable version-to-SHA, + `builder.id`, workflow path, and `buildType` mapping selected by ADR 0031. +- Bad, because verifiers would have to infer trust from loosely related evidence instead of checking + one signed predicate schema. + +### Adopt TUF-style metadata from the beginning + +Release metadata is modeled as TUF targets, roles, expiry, and signed metadata rather than a single +signed in-toto release manifest. + +- Good, because TUF is strong for delegated roles, threshold signing, expiry, mirrors, rollback + defense, and multi-channel release metadata. +- Bad, because it is operationally heavy for the initial single-repository, single-maintainer + release process. +- Bad, because threshold and delegated-role benefits are limited until the organization has multiple + trusted authorities or release channels. + +## More Information + +This decision follows ADR 0028 and ADR 0031. It complements ADR 0035's initial `actions/attest` +signing adapter decision and the three-job permission-boundary pattern selected for production npm +publishing and release asset publication. + +This decision does not define the full release manifest JSON schema, exact artifact filenames, +consumer verifier CLI interface, TUF metadata repository, dedicated reusable workflow contract, +direct `sigstore-go` signing implementation, or long-term release-channel policy. Those belong in +architecture specifications or future ADRs. + +Reference points considered: + +- SLSA Build L3 requires provenance or equivalent metadata authenticity and strong resistance to + tenant forgery, including keeping signing material away from user-controlled build steps. +- GitHub Actions artifact attestations use short-lived OIDC identity and require explicit + `id-token: write` and `attestations: write` permissions. +- GitHub Actions hardening guidance recommends full commit SHA pinning, least-privilege + `GITHUB_TOKEN` permissions, hosted ephemeral runners, and caution around privileged workflows. +- Windlass organization security policy requires SHA-pinned actions, least-privilege job + permissions, OIDC instead of long-lived credentials, signed releases, protected release refs, and + SLSA Build L3-oriented provenance wherever feasible. diff --git a/docs/decisions/AGENTS.md b/docs/decisions/AGENTS.md index c98fba4..1ce61bf 100644 --- a/docs/decisions/AGENTS.md +++ b/docs/decisions/AGENTS.md @@ -3,37 +3,37 @@ ## OVERVIEW Architecture decision records for the SLSA builder. Each ADR is a MADR 4.0.0 document with a -sequential four-digit number and a kebab-case title. +sequential four-digit number and a kebab-case title. The sequence currently runs from `0000` through +`0053`. ## STRUCTURE ```text docs/decisions/ -├── 0000-use-markdown-architectural-decision-records.md +├── 0000-use-markdown-architectural-decision-records.md # MADR template ├── 0001-start-slsa-builder-as-clean-repository.md ├── 0002-use-extensible-trusted-reusable-workflow-foundation.md -├── 0003-use-thin-core-with-profile-owned-reusable-workflows.md -├── 0004-use-go-as-primary-implementation-language.md -├── 0005-use-dedicated-linter-toolchain.md -├── 0006-use-golangci-lint-as-go-linter-runner.md -├── 0007-use-shellcheck-for-shell-glue-linting.md -├── 0008-use-dedicated-formatters-for-go-markdown-and-shell.md -├── 0009-use-node-js-as-development-tool-runtime.md -├── 0010-use-pnpm-for-node-js-development-tooling.md -├── 0011-use-lefthook-for-local-git-hook-orchestration.md -└── 0012-development-tool-runtime-and-bootstrap.md +├── ... # 0003–0052 +├── 0053-use-three-job-release-manifest-signing-boundary.md +└── AGENTS.md ``` +ADR numbering is sequential from `0000` through `0053`. See the WHERE TO LOOK table below for topic +groupings. + ## WHERE TO LOOK -| Topic | ADR | Notes | -| ----------------------------- | ------------------------------ | ----------------------------------------------- | -| Why the repo exists | `0001` | Clean-repository foundation. | -| Trusted workflow architecture | `0002`, `0003` | Core vs. profile-owned reusable workflows. | -| Implementation language | `0004` | Go for trusted core; shell stays glue. | -| Linter choices | `0005`, `0006`, `0007` | golangci-lint, ShellCheck, no universal bundle. | -| Formatter choices | `0008` | gofmt/goimports, shfmt, Prettier for Markdown. | -| Dev tooling runtime | `0009`, `0010`, `0011`, `0012` | Node/pnpm, Lefthook, mise bootstrap. | +| Topic | ADR | Notes | +| ----------------------------- | ------------------------------ | --------------------------------------------------------------- | +| Why the repo exists | `0001` | Clean-repository foundation. | +| Trusted workflow architecture | `0002`, `0003` | Core vs. profile-owned reusable workflows. | +| Implementation language | `0004` | Go for trusted core; shell stays glue. | +| Linter choices | `0005`, `0006`, `0007` | golangci-lint, ShellCheck, no universal bundle. | +| Formatter choices | `0008` | gofmt/goimports, shfmt, Prettier for Markdown. | +| Dev tooling runtime | `0009`, `0010`, `0011`, `0012` | Node/pnpm, Lefthook, mise bootstrap. | +| JS/TS npm package profile | `0013`–`0035` | Package manager selection, OIDC publishing, SLSA3 npm workflow. | +| GitHub release asset profile | `0036`–`0052` | Release asset subject handling, attestation distribution. | +| Release manifest signing | `0053` | Three-job signing boundary. | ## CONVENTIONS