From fcdc3c6054086e2205211f57e52e05005624c853 Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Sun, 16 Aug 2026 23:18:01 +0900 Subject: [PATCH 1/3] docs(decisions): add ADR 0082 pinning the builder-owned npm CLI version ADR 0082 decides that the floating, node-24-bundled npm provisioning is replaced by an explicitly pinned, integrity-verified npm distribution selected from a reviewed allowlist, with a bump procedure requiring an npm-internals diff-review checklist and per-version compatibility fixtures. Motivation: the issue #97 remediation mechanism depends on npm internals (oidc.js flow, config exclusive semantics, publishConfig flatten timing, sigstore-js major) that already vary across the currently allowed range (11.5.1/11.17.0/12.0.2), and the fourth dogfood showed fail-closed read-back protects acceptance but not registry state (0.1.2 is permanently burned). The decision completes the builder-owned npm principle (ADR 0016/0017) and the recorded pinned-setup intent of ADR 0027, and makes the specification's per-version fixture obligation satisfiable. Relations: partially-supersedes ADR 0027's npm provisioning clause (reverse edge added); see-also ADR 0016/0017/0029/0081 (reverse edges added). Accepted ADR bodies are untouched; only frontmatter relations were updated. adr-relations-check: 83 ADRs, 184 edges, NO PROBLEMS. Signed-off-by: Yunseo Kim --- ...corepack-for-pnpm-and-yarn-build-stages.md | 3 + ...cit-package-manager-version-enforcement.md | 2 + ...-hosted-ubuntu-2404-and-node-24-runtime.md | 8 + ...nerated-slsa-provenance-for-npm-publish.md | 2 + ...t-and-correct-token-lifetime-assumption.md | 2 + ...ied-provisioning-and-reviewed-allowlist.md | 195 ++++++++++++++++++ docs/decisions/README.ko.md | 158 +++++++------- docs/decisions/README.md | 158 +++++++------- 8 files changed, 372 insertions(+), 156 deletions(-) create mode 100644 docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md diff --git a/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md b/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md index 9f9bfac..aa77297 100644 --- a/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md +++ b/docs/decisions/0016-use-corepack-for-pnpm-and-yarn-build-stages.md @@ -4,6 +4,9 @@ nav_order: 16 status: accepted date: 12026-06-28 decision-makers: Yunseo Kim +relations: + - type: see-also + target: ADR-0082 --- # Use Corepack for pnpm and Yarn Build Stages diff --git a/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md b/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md index 705429a..9229a93 100644 --- a/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md +++ b/docs/decisions/0017-require-explicit-package-manager-version-enforcement.md @@ -7,6 +7,8 @@ decision-makers: Yunseo Kim relations: - type: see-also target: ADR-0070 + - type: see-also + target: ADR-0082 --- # Require Explicit Package Manager Version Enforcement diff --git a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md index 0e71353..4046c43 100644 --- a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md +++ b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md @@ -4,6 +4,14 @@ nav_order: 27 status: accepted date: 12026-07-04 decision-makers: Yunseo Kim +relations: + - type: partially-superseded-by + target: ADR-0082 + scope: + "the npm provisioning clause (the npm CLI is provided by the selected Node.js 24 toolchain, + floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image + constraints, the caller-input prohibitions, and the remaining npm clauses (floor check, + actual-version recording) remain in force" --- # Use GitHub-Hosted Ubuntu 24.04 and Node.js 24 Runtime 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 index 2eae282..35b27f0 100644 --- 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 @@ -20,6 +20,8 @@ relations: target: ADR-0070 - type: see-also target: ADR-0071 + - type: see-also + target: ADR-0082 --- # Use Windlass-Generated SLSA Provenance for npm Publish diff --git a/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md b/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md index 4ada28f..544abb3 100644 --- a/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md +++ b/docs/decisions/0081-pin-npm-oidc-exchange-response-contract-and-correct-token-lifetime-assumption.md @@ -11,6 +11,8 @@ relations: "the npm OIDC exchange success-response contract (unpinned in ADR-0076; pinned in ADR-0081 to the empirically observed shape) and the exchange-token lifetime assumption (the 'typically 1 hour' documentation note cited in ADR-0076; the observed lifetime is 15 minutes)" + - type: see-also + target: ADR-0082 --- # Pin the npm OIDC Exchange Response Contract to the Observed Shape and Correct the Token Lifetime Assumption diff --git a/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md new file mode 100644 index 0000000..81899e1 --- /dev/null +++ b/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md @@ -0,0 +1,195 @@ +--- +parent: Decisions +nav_order: 82 +status: accepted +date: 12026-08-16 +decision-makers: Yunseo Kim +relations: + - type: partially-supersedes + target: ADR-0027 + scope: + "the npm provisioning clause (the npm CLI is provided by the selected Node.js 24 toolchain, + floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image + constraints, the caller-input prohibitions, and the remaining npm clauses (floor check, + actual-version recording) remain in force" + - type: see-also + target: ADR-0016 + - type: see-also + target: ADR-0017 + - type: see-also + target: ADR-0029 + - type: see-also + target: ADR-0081 +--- + +# Pin the Builder-Owned npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist + +## Context and Problem Statement + +The JS/TS npm package profile's npm version selection is currently floating. All three jobs of the +reusable workflow (build, provenance-sign, publish) provision Node.js with `node-version: "24"` and +no npm installation step, so the npm CLI is whatever the resolved node-24 image bundles at run time +(the fourth M1 dogfood resolved node 24.19.0, npm 11.17.0). The contract around this is a floor plus +an equality check: the signed `externalParameters.runtime.npm_version` must satisfy SemVer +`>= 11.5.1` (the trusted-publishing minimum), and the publish job re-runs `npm --version` and +requires exact string equality with the build-time record. There is no upper bound, no allowlist, +and no review gate: any npm that a future node-24 image happens to bundle silently becomes the +production publish toolchain. + +ADR 0016 and ADR 0017 already established that npm is builder-owned: caller manifests must not +select the npm version (ADR 0017 explicitly rejected requiring a caller-side npm pin), because the +provenance-aware `npm publish` path runs on the toolchain's npm regardless of the package's +build-time package manager. ADR 0027 further recorded the intent that "the exact Node.js patch +version is selected by the builder implementation and its pinned setup mechanism" — an intent the +floating `node-version: "24"` workflow never implemented for the bundled npm. + +The fourth M1 dogfood +([vers-js run 31840088262](https://github.com/windlasstech/vers-js/actions/runs/31840088262), +tracked as [issue #97](https://github.com/windlasstech/slsa-builder/issues/97)) exposed why this +matters. npm trusted publishing auto-enables provenance and silently discards a caller-supplied +`--provenance-file` bundle; the run published `@windlass/vers-js@0.1.2` with an npm-generated +statement instead of the Windlass-signed Statement. The fail-closed read-back rejected the foreign +evidence exactly as designed — but the registry mutation had already committed, and npm policy makes +an unpublished version number permanently unreusable, so `0.1.2` remains publicly visible with the +wrong provenance. The remediation for #97 (suppressing npm's CI/OIDC detection for the publish +subprocess, injecting the publish-time exchanged token as the registry credential, and a closed +`publishConfig` allowlist) depends on npm **internal** behavior: `oidc()` call ordering and +early-return conditions, `@npmcli/config` exclusive-enforcement semantics, `publishConfig` flatten +timing, and `libnpmpublish`'s `verifyProvenance` with its bundled sigstore-js. + +Source review across the currently allowed range shows those internals already vary: + +| npm version | `oidc.js` auto-enable structure | Exclusive env carve-out | sigstore-js | +| ----------------- | --------------------------------------- | ------------------------ | ------------------ | +| 11.5.1 (floor) | `isDefaultProvenance \|\| intent` shape | absent (throws) | `^3.0.0` (→ 3.1.0) | +| 11.17.0 (dogfood) | intermediate shape | present (silently skips) | intermediate | +| 12.0.2 (latest) | adds CircleCI branch | present (silently skips) | `^5.0.0` | + +A mechanism verified against one npm can therefore be silently invalidated by the next node-24 image +update, and the failure mode is not hypothetical: it is a publish-then-reject incident that burns a +version number in every affected caller at once, detected only after mutation. + +Additionally, the JS/TS npm provenance and publish specification requires a PURL-derivation +compatibility fixture "for every supported npm CLI version", but no document enumerates the +supported set, so that obligation is currently unsatisfiable. And while the upstream defect is now +filed and fixed-pending ([npm/cli#9879](https://github.com/npm/cli/issues/9879), +[npm/cli#9882](https://github.com/npm/cli/pull/9882)), every npm release in the allowed range will +retain the defect permanently; adopting the upstream fix must become an explicit, reviewed event, +not toolchain drift. + +## Decision Drivers + +- The #97 remediation mechanism is source-dependent; a source-dependent mechanism under a floating + toolchain leaves the verification surface open and every guarantee past-tense. +- Fail-closed read-back protects acceptance, not registry state: rejection happens after mutation, + and burned versions cannot be republished (0.1.2 precedent). +- A node-24 image update is a correlated-failure vector: one image roll can break every caller's + publish simultaneously, at release time. +- Builder-owned npm is already the design principle (ADR 0016, ADR 0017); builder-side pinning is + the recorded intent (ADR 0027's "pinned setup mechanism"), so determinism completes existing + decisions rather than changing direction. +- The repository's supply-chain stance pins everything else in the trust boundary (action SHAs, + `mise.lock`, the Go toolchain); npm is the sole floating component whose internals the profile + depends on. +- The specification's per-version compatibility-fixture obligation requires an enumerable supported + set. +- Adopting the upstream npm fix (npm/cli#9882) should be a deliberate pinned bump with regression + evidence, not an accident of image maintenance. + +## Considered Options + +1. Pin the builder-owned npm: provision a reviewed npm version explicitly in the reusable workflow + with integrity verification, hold the supported set in a reviewed allowlist, and require a + diff-review checklist plus fixtures for every bump. +2. Keep the `>= 11.5.1` floor and verify the mechanism across the full range: matrix CI over every + supported npm minor with per-version compatibility fixtures. +3. Keep the current gating unchanged and trust the fail-closed read-back. + +## Decision Outcome + +Chosen option: "Pin the builder-owned npm", because it is the only option that closes the +verification surface, because it completes the builder-owned principle and the recorded pinning +intent rather than contradicting them, and because its costs (update ownership, a registry-fetch +provisioning path) are bounded and already priced in by the repository's existing pinning stance. + +### Single provisioning point with integrity verification + +The reusable workflow provisions npm explicitly in all jobs that invoke npm (build, provenance-sign, +publish), from one shared definition of version plus distribution SHA-256. The install step verifies +the distribution digest before first use. Node.js 24 remains the runtime per ADR 0027; only the npm +provisioning clause is superseded. Because build records `runtime.npm_version` and publish requires +exact equality with it, one provisioning point keeps the equality check satisfied by construction +and makes the recorded version deterministic. + +### Reviewed allowlist and bump procedure + +`runtime.npm_version` must equal a version in a reviewed allowlist recorded in the JS/TS npm +provenance and publish specification (initially a single version, pinned when the #97 fix lands and +re-verified there). A bump pull request must: + +1. update the pinned version and distribution digest together; +2. diff-review the npm-internals checklist for the new version: `lib/utils/oidc.js` early-return and + auto-enable structure, `libnpmpublish` `buildMetadata()` branch structure, `@npmcli/config` + exclusive enforcement (including the env-layer carve-out), `publishConfig` flatten timing, and + the bundled sigstore-js major version; +3. add or refresh the specification-required compatibility fixtures for the new pin; +4. pass the full CI gate on the new pin. + +### Relationship to existing decisions and safety nets + +This ADR partially supersedes ADR 0027's npm provisioning clause ("the npm CLI provided by the +selected Node.js 24 toolchain"); every other ADR 0027 clause remains in force. ADR 0016 and ADR 0017 +are untouched: npm stays builder-owned, and caller manifests still must not select an npm version. +The fail-closed publish read-back is unchanged and remains the permanent safety net for everything +outside the pinned surface; this ADR removes the need to rely on it for npm-version drift. When the +upstream fix (npm/cli#9882) ships in an npm release, adopting it is a pinned bump under the +procedure above; retiring the #97 workaround on fixed npm versions is a separate later decision. + +## Pros and Cons of the Options + +### Pin the builder-owned npm + +- Good, because the publish mechanism's behavior becomes deterministic and the verification surface + is closed and enumerable. +- Good, because npm changes become review events with an internals checklist, matching the action + SHA-pinning and `mise.lock` stance already applied to the rest of the trust boundary. +- Good, because the specification's per-version compatibility-fixture obligation becomes + satisfiable. +- Good, because correlated failure from node-24 image drift is eliminated for the publish path, and + upstream-fix adoption becomes a deliberate, evidence-backed bump. +- Bad, because npm security and feature updates no longer arrive automatically with node-24 image + updates; the project owns the npm update cadence and the review checklist. +- Bad, because the workflow fetches the npm distribution from the registry at run time instead of + using the node-bundled copy; this is mitigated by pinning the distribution digest, and is no worse + than the implicit, unverified trust currently placed in whatever the image bundles. + +### Keep the floor and verify the full range + +- Good, because callers keep toolchain flexibility and the specification's per-version fixtures get + built across the range. +- Bad, because the range is open-ended: a new npm ships in a node-24 image and runs in production + before any fixture or matrix entry can exist for it. The gate cannot test what does not yet exist, + so the riskiest moment is precisely the uncovered one. +- Bad, because the sigstore-js v3–v5 span across the range multiplies fixture and review maintenance + with npm's release cadence. + +### Keep current gating and trust fail-closed + +- Good, because it adds no machinery and the read-back rejection is proven live (dogfood 4). +- Bad, because fail-closed protects acceptance, not registry state: a drifted npm can still + publish-then-reject, burning version numbers and leaving mislabeled provenance publicly visible in + every affected caller at once. +- Bad, because incident cost is externalized to callers at release time, the worst possible moment. + +## More Information + +- Motivating incident and RCA: [issue #97](https://github.com/windlasstech/slsa-builder/issues/97) + and the fourth-attempt evidence in + [issue #30](https://github.com/windlasstech/slsa-builder/issues/30). +- Upstream: [npm/cli#9879](https://github.com/npm/cli/issues/9879) (report), + [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix pending). +- The initial pinned version is selected at #97 fix implementation time and recorded in the JS/TS + npm provenance and publish specification's allowlist, with the bump procedure above governing all + later changes. Recording the npm distribution capture in `resolvedDependencies` alongside the + existing pnpm/Yarn distribution captures is a natural follow-up and is left to the implementing + specification amendment. diff --git a/docs/decisions/README.ko.md b/docs/decisions/README.ko.md index 23150f4..94cbaa0 100644 --- a/docs/decisions/README.ko.md +++ b/docs/decisions/README.ko.md @@ -28,7 +28,7 @@ ## ADR 목록 ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 사용합니다. 현재 순번은 `0000`부터 -`0081`까지입니다. +`0082`까지입니다. | 범위 | 주제 | 설명 | | --------- | ---------------------------------- | ------------------------------------------------------------------------------ | @@ -62,6 +62,7 @@ ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 | 0079 | 호출자 지정 빌드 소스 ref | 모든 producer profile의 기본 릴리즈 재시도 경로인 태그 전용 `source-ref` 입력. | | 0080 | 인증서 클레임 = 호출 컨텍스트 | 소스 정책은 서명된 provenance 필드에 바인딩; cert 소스 클레임은 호출을 증명. | | 0081 | npm OIDC exchange 응답 계약 | 성공 본문을 실측 형태에 핀; exchange 토큰 수명은 15분. | +| 0082 | 빌더 소유 npm 버전 핀 | 무결성 검증된 npm 프로비저닝 + 검토 allowlist와 bump 체크리스트. | ## ADR status와 relations @@ -125,83 +126,84 @@ ADR은 영향을 주는 모든 선행 ADR을 열거해야 하며, 누락은 추 ### 채택된 ADR -| ADR | 결정 | 명세 매핑 | -| ---- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 0000 | Use Markdown Architectural Decision Records | 프로세스; 런타임 명세 불필요 | -| 0001 | Start slsa-builder as a clean repository | 기반 결정; 런타임 명세 불필요 | -| 0002 | Extensible trusted reusable workflow foundation | Core profile contract, SLSA provenance, verification policy | -| 0003 | Thin core with profile-owned reusable workflows | Core profile contract, SLSA provenance, verification policy | -| 0004 | Go as primary implementation language | Core profile contract | -| 0005 | Dedicated linter toolchain | 도구 전용 | -| 0006 | golangci-lint as Go linter runner | 도구 전용 | -| 0007 | ShellCheck for shell glue | 도구 전용 | -| 0008 | Dedicated formatters | 도구 전용 | -| 0009 | Node.js as development tool runtime | 도구 전용; core contract가 신뢰 로직 경계를 설명 | -| 0010 | pnpm for Node.js development tooling | 도구 전용 | -| 0011 | Lefthook for local git hook orchestration | 도구 전용 | -| 0012 | mise as unified development-tool runtime | 도구 전용 | -| 0013 | Scope initial JS/TS profile to npm packages | JS/TS npm profile, composition spec | -| 0014 | Support npm, pnpm, and Yarn for initial build stages | JS/TS npm build and pack | -| 0015 | Manifest-first package manager selection | JS/TS npm build and pack | -| 0016 | Corepack for pnpm and Yarn build stages | JS/TS npm build and pack | -| 0017 | Explicit package manager version enforcement | JS/TS npm build and pack | -| 0018 | Publish one JS/TS package per profile run | JS/TS npm profile, build and pack | -| 0019 | Validate package metadata through packed artifacts | JS/TS npm build and pack | -| 0022 | `js-ts-npm-package-slsa3.yml` workflow entrypoint | JS/TS npm profile | -| 0023 | `package-directory` as required package selector | JS/TS npm profile, build and pack | -| 0024 | OIDC trusted publishing without publish secrets | JS/TS npm profile, provenance and publish | -| 0025 | Return package identity and tarball digest outputs | JS/TS npm provenance and publish | -| 0026 | Document supported release caller patterns and runtime guards | JS/TS npm profile | -| 0027 | GitHub-Hosted Ubuntu 24.04 and Node.js 24 runtime | JS/TS npm profile, build and pack | -| 0028 | SHA-pinned reusable workflow builder identity | Identity and build types, common provenance, release manifest, verification policy | -| 0029 | Windlass-generated SLSA provenance for npm publish | Common provenance, JS/TS npm provenance and publish, verification policy | -| 0030 | Accept registry URL while guaranteeing only npmjs semantics | JS/TS npm profile, provenance and publish, verification policy | -| 0031 | Sigstore-signed in-toto release manifest | Identity and build types, release manifest | -| 0032 | Constrain manual dispatch releases to version tags | JS/TS npm profile | -| 0033 | Run build script only when declared | JS/TS npm build and pack | -| 0034 | Do not support private dependency credentials | JS/TS npm profile | -| 0035 | `actions/attest` as initial Sigstore signing adapter | Core profile contract, common provenance, JS/TS npm provenance and publish, release manifest | -| 0036 | Three-job digest-verified publish graph | JS/TS npm provenance and publish, verification policy | -| 0037 | Define initial verification deliverables | Verification policy and fixtures | -| 0039 | Scope release asset profile to one asset per run | GitHub Release asset publisher | -| 0042 | Use acquired domains for buildType URIs | Core profile contract, identity and build types | -| 0043 | Upload release assets to existing releases | GitHub Release asset publisher | -| 0045 | Use release asset name as SLSA subject name | GitHub Release asset publisher | -| 0046 | Keep checksums and SBOMs out of subject digest | GitHub Release asset publisher | -| 0048 | Make linked artifacts storage records explicit opt-in | GitHub Release asset publisher | -| 0049 | Separate artifact production from GitHub Release asset publication | Identity and build types, GitHub Release asset publisher, verification policy | -| 0050 | Define producer-to-publisher handoff contract | GitHub Release asset publisher, verification policy | -| 0051 | Distribute producer provenance with release assets | GitHub Release asset publisher, verification policy | -| 0052 | Compose npm package tarball producer with release asset publisher | Composition spec, verification policy | -| 0053 | Three-job release manifest signing boundary | Release manifest | -| 0054 | Use `slsa-builder.dev` release manifest predicate URI | Release manifest, verification policy | -| 0055 | `actions/attest` custom mode for Statement construction | Common provenance, JS/TS npm provenance and publish | -| 0056 | Treat non-selected lockfiles as stale diagnostics | JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy | -| 0057 | Provide public npm release-asset mode | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | -| 0058 | Define GitHub Release asset publisher authority boundary | GitHub Release asset publisher, composition spec, verification policy | -| 0059 | Define public npm release-asset mode interface | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | -| 0060 | Unify npm profile public entrypoint with release-asset mode | JS/TS npm profile, composition spec, verification policy | -| 0061 | Reject duplicate JSON members in signed SLSA Statements | Common provenance, JS/TS npm provenance and publish, verification policy | -| 0062 | Intersect trusted producer policies | Release manifest, GitHub Release asset publisher, verification policy | -| 0063 | Limit Yarn support to Berry v4 with Corepack metadata | JS/TS npm build and pack | -| 0064 | Use npm PURL subject with SHA-512 and SHA-256 digests | Common provenance, JS/TS npm specs, composition and publisher specs, verification policy | -| 0065 | Use a closed status grammar and a separate relations field | 프로세스; 런타임 명세 불필요 | -| 0066 | Serialize release mutations with job-class concurrency | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, JS/TS npm package profile, verification policy | -| 0067 | Converge repeated runs within run identity | Release manifest, GitHub Release asset publisher, JS/TS npm provenance and publish, verification policy | -| 0068 | Bind verification to immutable builder and source identities | Verification policy, identity and build types, release manifest, JS/TS npm provenance and publish | -| 0069 | Require Rekor transparency and govern the Sigstore trust root | Verification policy, common provenance, release manifest | -| 0070 | Record package manager distributions and runner image in resolvedDependencies | SLSA provenance v1, JS/TS npm provenance and publish, JS/TS npm build and pack, verification policy and fixtures | -| 0071 | Activate builder.version and builderDependencies for platform components | SLSA provenance v1, JS/TS npm provenance and publish, verification policy and fixtures | -| 0072 | Use sidecar-first pair binding for release asset run ownership | GitHub Release asset publisher, npm-to-release-asset composition, verification policy and fixtures | -| 0073 | Require published-attestation run identity for npm same-run convergence | JS/TS npm provenance and publish, verification policy and fixtures | -| 0074 | Use single-job mutation segments with detection-based cross-run safety | GitHub Release asset publisher, release manifest, npm-to-release-asset composition, verification policy and fixtures | -| 0075 | Queue mutation segment contenders with queue: max | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, verification policy and fixtures | -| 0076 | Use observation preflights and first-mutation classification | GitHub Release asset publisher, JS/TS npm package profile, verification policy and fixtures | -| 0077 | Use a Go-native Sigstore DSSE signer for Windlass provenance signing | Core profile contract, SLSA provenance v1, JS/TS npm package profile, JS/TS npm provenance and publish, release asset publisher, release manifest, composition, verification policy | -| 0078 | Treat settings-only pnpm-workspace.yaml as standalone root package mode | JS/TS npm build and pack, verification policy and fixtures | -| 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | -| 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | -| 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| ADR | 결정 | 명세 매핑 | +| ---- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0000 | Use Markdown Architectural Decision Records | 프로세스; 런타임 명세 불필요 | +| 0001 | Start slsa-builder as a clean repository | 기반 결정; 런타임 명세 불필요 | +| 0002 | Extensible trusted reusable workflow foundation | Core profile contract, SLSA provenance, verification policy | +| 0003 | Thin core with profile-owned reusable workflows | Core profile contract, SLSA provenance, verification policy | +| 0004 | Go as primary implementation language | Core profile contract | +| 0005 | Dedicated linter toolchain | 도구 전용 | +| 0006 | golangci-lint as Go linter runner | 도구 전용 | +| 0007 | ShellCheck for shell glue | 도구 전용 | +| 0008 | Dedicated formatters | 도구 전용 | +| 0009 | Node.js as development tool runtime | 도구 전용; core contract가 신뢰 로직 경계를 설명 | +| 0010 | pnpm for Node.js development tooling | 도구 전용 | +| 0011 | Lefthook for local git hook orchestration | 도구 전용 | +| 0012 | mise as unified development-tool runtime | 도구 전용 | +| 0013 | Scope initial JS/TS profile to npm packages | JS/TS npm profile, composition spec | +| 0014 | Support npm, pnpm, and Yarn for initial build stages | JS/TS npm build and pack | +| 0015 | Manifest-first package manager selection | JS/TS npm build and pack | +| 0016 | Corepack for pnpm and Yarn build stages | JS/TS npm build and pack | +| 0017 | Explicit package manager version enforcement | JS/TS npm build and pack | +| 0018 | Publish one JS/TS package per profile run | JS/TS npm profile, build and pack | +| 0019 | Validate package metadata through packed artifacts | JS/TS npm build and pack | +| 0022 | `js-ts-npm-package-slsa3.yml` workflow entrypoint | JS/TS npm profile | +| 0023 | `package-directory` as required package selector | JS/TS npm profile, build and pack | +| 0024 | OIDC trusted publishing without publish secrets | JS/TS npm profile, provenance and publish | +| 0025 | Return package identity and tarball digest outputs | JS/TS npm provenance and publish | +| 0026 | Document supported release caller patterns and runtime guards | JS/TS npm profile | +| 0027 | GitHub-Hosted Ubuntu 24.04 and Node.js 24 runtime | JS/TS npm profile, build and pack | +| 0028 | SHA-pinned reusable workflow builder identity | Identity and build types, common provenance, release manifest, verification policy | +| 0029 | Windlass-generated SLSA provenance for npm publish | Common provenance, JS/TS npm provenance and publish, verification policy | +| 0030 | Accept registry URL while guaranteeing only npmjs semantics | JS/TS npm profile, provenance and publish, verification policy | +| 0031 | Sigstore-signed in-toto release manifest | Identity and build types, release manifest | +| 0032 | Constrain manual dispatch releases to version tags | JS/TS npm profile | +| 0033 | Run build script only when declared | JS/TS npm build and pack | +| 0034 | Do not support private dependency credentials | JS/TS npm profile | +| 0035 | `actions/attest` as initial Sigstore signing adapter | Core profile contract, common provenance, JS/TS npm provenance and publish, release manifest | +| 0036 | Three-job digest-verified publish graph | JS/TS npm provenance and publish, verification policy | +| 0037 | Define initial verification deliverables | Verification policy and fixtures | +| 0039 | Scope release asset profile to one asset per run | GitHub Release asset publisher | +| 0042 | Use acquired domains for buildType URIs | Core profile contract, identity and build types | +| 0043 | Upload release assets to existing releases | GitHub Release asset publisher | +| 0045 | Use release asset name as SLSA subject name | GitHub Release asset publisher | +| 0046 | Keep checksums and SBOMs out of subject digest | GitHub Release asset publisher | +| 0048 | Make linked artifacts storage records explicit opt-in | GitHub Release asset publisher | +| 0049 | Separate artifact production from GitHub Release asset publication | Identity and build types, GitHub Release asset publisher, verification policy | +| 0050 | Define producer-to-publisher handoff contract | GitHub Release asset publisher, verification policy | +| 0051 | Distribute producer provenance with release assets | GitHub Release asset publisher, verification policy | +| 0052 | Compose npm package tarball producer with release asset publisher | Composition spec, verification policy | +| 0053 | Three-job release manifest signing boundary | Release manifest | +| 0054 | Use `slsa-builder.dev` release manifest predicate URI | Release manifest, verification policy | +| 0055 | `actions/attest` custom mode for Statement construction | Common provenance, JS/TS npm provenance and publish | +| 0056 | Treat non-selected lockfiles as stale diagnostics | JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy | +| 0057 | Provide public npm release-asset mode | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | +| 0058 | Define GitHub Release asset publisher authority boundary | GitHub Release asset publisher, composition spec, verification policy | +| 0059 | Define public npm release-asset mode interface | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | +| 0060 | Unify npm profile public entrypoint with release-asset mode | JS/TS npm profile, composition spec, verification policy | +| 0061 | Reject duplicate JSON members in signed SLSA Statements | Common provenance, JS/TS npm provenance and publish, verification policy | +| 0062 | Intersect trusted producer policies | Release manifest, GitHub Release asset publisher, verification policy | +| 0063 | Limit Yarn support to Berry v4 with Corepack metadata | JS/TS npm build and pack | +| 0064 | Use npm PURL subject with SHA-512 and SHA-256 digests | Common provenance, JS/TS npm specs, composition and publisher specs, verification policy | +| 0065 | Use a closed status grammar and a separate relations field | 프로세스; 런타임 명세 불필요 | +| 0066 | Serialize release mutations with job-class concurrency | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, JS/TS npm package profile, verification policy | +| 0067 | Converge repeated runs within run identity | Release manifest, GitHub Release asset publisher, JS/TS npm provenance and publish, verification policy | +| 0068 | Bind verification to immutable builder and source identities | Verification policy, identity and build types, release manifest, JS/TS npm provenance and publish | +| 0069 | Require Rekor transparency and govern the Sigstore trust root | Verification policy, common provenance, release manifest | +| 0070 | Record package manager distributions and runner image in resolvedDependencies | SLSA provenance v1, JS/TS npm provenance and publish, JS/TS npm build and pack, verification policy and fixtures | +| 0071 | Activate builder.version and builderDependencies for platform components | SLSA provenance v1, JS/TS npm provenance and publish, verification policy and fixtures | +| 0072 | Use sidecar-first pair binding for release asset run ownership | GitHub Release asset publisher, npm-to-release-asset composition, verification policy and fixtures | +| 0073 | Require published-attestation run identity for npm same-run convergence | JS/TS npm provenance and publish, verification policy and fixtures | +| 0074 | Use single-job mutation segments with detection-based cross-run safety | GitHub Release asset publisher, release manifest, npm-to-release-asset composition, verification policy and fixtures | +| 0075 | Queue mutation segment contenders with queue: max | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, verification policy and fixtures | +| 0076 | Use observation preflights and first-mutation classification | GitHub Release asset publisher, JS/TS npm package profile, verification policy and fixtures | +| 0077 | Use a Go-native Sigstore DSSE signer for Windlass provenance signing | Core profile contract, SLSA provenance v1, JS/TS npm package profile, JS/TS npm provenance and publish, release asset publisher, release manifest, composition, verification policy | +| 0078 | Treat settings-only pnpm-workspace.yaml as standalone root package mode | JS/TS npm build and pack, verification policy and fixtures | +| 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | +| 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0082 | Pin the builder-owned npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm build and pack, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### 대체 혹은 폐기된 ADR (과거 맥락으로만 참조) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index bfbb412..3c84515 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -29,7 +29,7 @@ accepted ADR, stop and write a new ADR rather than editing the accepted ADR body ## ADR inventory ADR files are MADR 4.0.0 documents with sequential four-digit numbers and kebab-case titles. The -sequence currently runs from `0000` through `0081`. +sequence currently runs from `0000` through `0082`. | Range | Topic | Notes | | --------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | @@ -63,6 +63,7 @@ sequence currently runs from `0000` through `0081`. | 0079 | Caller-specified build source ref | Tags-only `source-ref` input as the default release-retry path for all producer profiles. | | 0080 | Certificate claims as invocation context | Source policy binds signed provenance fields; cert source claims prove invocation. | | 0081 | npm OIDC exchange response contract | Success body pinned to the observed shape; exchange token lifetime is 15 minutes. | +| 0082 | Builder-owned npm version pinning | Pinned, integrity-verified npm provisioning with a reviewed allowlist and bump checklist. | ## ADR status and relations @@ -128,83 +129,84 @@ of this table; they are recorded in each ADR's `relations` frontmatter field. ### Accepted ADRs -| ADR | Decision | Spec mapping | -| ---- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 0000 | Use Markdown Architectural Decision Records | Process; no runtime spec needed | -| 0001 | Start slsa-builder as a clean repository | Foundation; no runtime spec needed | -| 0002 | Extensible trusted reusable workflow foundation | Core profile contract, SLSA provenance, verification policy | -| 0003 | Thin core with profile-owned reusable workflows | Core profile contract, SLSA provenance, verification policy | -| 0004 | Go as primary implementation language | Core profile contract | -| 0005 | Dedicated linter toolchain | Tooling-only | -| 0006 | golangci-lint as Go linter runner | Tooling-only | -| 0007 | ShellCheck for shell glue | Tooling-only | -| 0008 | Dedicated formatters | Tooling-only | -| 0009 | Node.js as development tool runtime | Tooling-only; core contract notes trusted logic boundary | -| 0010 | pnpm for Node.js development tooling | Tooling-only | -| 0011 | Lefthook for local git hook orchestration | Tooling-only | -| 0012 | mise as unified development-tool runtime | Tooling-only | -| 0013 | Scope initial JS/TS profile to npm packages | JS/TS npm profile, composition spec | -| 0014 | Support npm, pnpm, and Yarn for initial build stages | JS/TS npm build and pack | -| 0015 | Manifest-first package manager selection | JS/TS npm build and pack | -| 0016 | Corepack for pnpm and Yarn build stages | JS/TS npm build and pack | -| 0017 | Explicit package manager version enforcement | JS/TS npm build and pack | -| 0018 | Publish one JS/TS package per profile run | JS/TS npm profile, build and pack | -| 0019 | Validate package metadata through packed artifacts | JS/TS npm build and pack | -| 0022 | `js-ts-npm-package-slsa3.yml` workflow entrypoint | JS/TS npm profile | -| 0023 | `package-directory` as required package selector | JS/TS npm profile, build and pack | -| 0024 | OIDC trusted publishing without publish secrets | JS/TS npm profile, provenance and publish | -| 0025 | Return package identity and tarball digest outputs | JS/TS npm provenance and publish | -| 0026 | Document supported release caller patterns and runtime guards | JS/TS npm profile | -| 0027 | GitHub-Hosted Ubuntu 24.04 and Node.js 24 runtime | JS/TS npm profile, build and pack | -| 0028 | SHA-pinned reusable workflow builder identity | Identity and build types, common provenance, release manifest, verification policy | -| 0029 | Windlass-generated SLSA provenance for npm publish | Common provenance, JS/TS npm provenance and publish, verification policy | -| 0030 | Accept registry URL while guaranteeing only npmjs semantics | JS/TS npm profile, provenance and publish, verification policy | -| 0031 | Sigstore-signed in-toto release manifest | Identity and build types, release manifest | -| 0032 | Constrain manual dispatch releases to version tags | JS/TS npm profile | -| 0033 | Run build script only when declared | JS/TS npm build and pack | -| 0034 | Do not support private dependency credentials | JS/TS npm profile | -| 0035 | `actions/attest` as initial Sigstore signing adapter | Core profile contract, common provenance, JS/TS npm provenance and publish, release manifest | -| 0036 | Three-job digest-verified publish graph | JS/TS npm provenance and publish, verification policy | -| 0037 | Define initial verification deliverables | Verification policy and fixtures | -| 0039 | Scope release asset profile to one asset per run | GitHub Release asset publisher | -| 0042 | Use acquired domains for buildType URIs | Core profile contract, identity and build types | -| 0043 | Upload release assets to existing releases | GitHub Release asset publisher | -| 0045 | Use release asset name as SLSA subject name | GitHub Release asset publisher | -| 0046 | Keep checksums and SBOMs out of subject digest | GitHub Release asset publisher | -| 0048 | Make linked artifacts storage records explicit opt-in | GitHub Release asset publisher | -| 0049 | Separate artifact production from GitHub Release asset publication | Identity and build types, GitHub Release asset publisher, verification policy | -| 0050 | Define producer-to-publisher handoff contract | GitHub Release asset publisher, verification policy | -| 0051 | Distribute producer provenance with release assets | GitHub Release asset publisher, verification policy | -| 0052 | Compose npm package tarball producer with release asset publisher | Composition spec, verification policy | -| 0053 | Three-job release manifest signing boundary | Release manifest | -| 0054 | Use `slsa-builder.dev` release manifest predicate URI | Release manifest, verification policy | -| 0055 | `actions/attest` custom mode for Statement construction | Common provenance, JS/TS npm provenance and publish | -| 0056 | Treat non-selected lockfiles as stale diagnostics | JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy | -| 0057 | Provide public npm release-asset mode | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | -| 0058 | Define GitHub Release asset publisher authority boundary | GitHub Release asset publisher, composition spec, verification policy | -| 0059 | Define public npm release-asset mode interface | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | -| 0060 | Unify npm profile public entrypoint with release-asset mode | JS/TS npm profile, composition spec, verification policy | -| 0061 | Reject duplicate JSON members in signed SLSA Statements | Common provenance, JS/TS npm provenance and publish, verification policy | -| 0062 | Intersect trusted producer policies | Release manifest, GitHub Release asset publisher, verification policy | -| 0063 | Limit Yarn support to Berry v4 with Corepack metadata | JS/TS npm build and pack | -| 0064 | Use npm PURL subject with SHA-512 and SHA-256 digests | Common provenance, JS/TS npm specs, composition and publisher specs, verification policy | -| 0065 | Use a closed status grammar and a separate relations field | Process; no runtime spec needed | -| 0066 | Serialize release mutations with job-class concurrency | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, JS/TS npm package profile, verification policy | -| 0067 | Converge repeated runs within run identity | Release manifest, GitHub Release asset publisher, JS/TS npm provenance and publish, verification policy | -| 0068 | Bind verification to immutable builder and source identities | Verification policy, identity and build types, release manifest, JS/TS npm provenance and publish | -| 0069 | Require Rekor transparency and govern the Sigstore trust root | Verification policy, common provenance, release manifest | -| 0070 | Record package manager distributions and runner image in resolvedDependencies | SLSA provenance v1, JS/TS npm provenance and publish, JS/TS npm build and pack, verification policy and fixtures | -| 0071 | Activate builder.version and builderDependencies for platform components | SLSA provenance v1, JS/TS npm provenance and publish, verification policy and fixtures | -| 0072 | Use sidecar-first pair binding for release asset run ownership | GitHub Release asset publisher, npm-to-release-asset composition, verification policy and fixtures | -| 0073 | Require published-attestation run identity for npm same-run convergence | JS/TS npm provenance and publish, verification policy and fixtures | -| 0074 | Use single-job mutation segments with detection-based cross-run safety | GitHub Release asset publisher, release manifest, npm-to-release-asset composition, verification policy and fixtures | -| 0075 | Queue mutation segment contenders with queue: max | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, verification policy and fixtures | -| 0076 | Use observation preflights and first-mutation classification | GitHub Release asset publisher, JS/TS npm package profile, verification policy and fixtures | -| 0077 | Use a Go-native Sigstore DSSE signer for Windlass provenance signing | Core profile contract, SLSA provenance v1, JS/TS npm package profile, JS/TS npm provenance and publish, release asset publisher, release manifest, composition, verification policy | -| 0078 | Treat settings-only pnpm-workspace.yaml as standalone root package mode | JS/TS npm build and pack, verification policy and fixtures | -| 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | -| 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | -| 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| ADR | Decision | Spec mapping | +| ---- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0000 | Use Markdown Architectural Decision Records | Process; no runtime spec needed | +| 0001 | Start slsa-builder as a clean repository | Foundation; no runtime spec needed | +| 0002 | Extensible trusted reusable workflow foundation | Core profile contract, SLSA provenance, verification policy | +| 0003 | Thin core with profile-owned reusable workflows | Core profile contract, SLSA provenance, verification policy | +| 0004 | Go as primary implementation language | Core profile contract | +| 0005 | Dedicated linter toolchain | Tooling-only | +| 0006 | golangci-lint as Go linter runner | Tooling-only | +| 0007 | ShellCheck for shell glue | Tooling-only | +| 0008 | Dedicated formatters | Tooling-only | +| 0009 | Node.js as development tool runtime | Tooling-only; core contract notes trusted logic boundary | +| 0010 | pnpm for Node.js development tooling | Tooling-only | +| 0011 | Lefthook for local git hook orchestration | Tooling-only | +| 0012 | mise as unified development-tool runtime | Tooling-only | +| 0013 | Scope initial JS/TS profile to npm packages | JS/TS npm profile, composition spec | +| 0014 | Support npm, pnpm, and Yarn for initial build stages | JS/TS npm build and pack | +| 0015 | Manifest-first package manager selection | JS/TS npm build and pack | +| 0016 | Corepack for pnpm and Yarn build stages | JS/TS npm build and pack | +| 0017 | Explicit package manager version enforcement | JS/TS npm build and pack | +| 0018 | Publish one JS/TS package per profile run | JS/TS npm profile, build and pack | +| 0019 | Validate package metadata through packed artifacts | JS/TS npm build and pack | +| 0022 | `js-ts-npm-package-slsa3.yml` workflow entrypoint | JS/TS npm profile | +| 0023 | `package-directory` as required package selector | JS/TS npm profile, build and pack | +| 0024 | OIDC trusted publishing without publish secrets | JS/TS npm profile, provenance and publish | +| 0025 | Return package identity and tarball digest outputs | JS/TS npm provenance and publish | +| 0026 | Document supported release caller patterns and runtime guards | JS/TS npm profile | +| 0027 | GitHub-Hosted Ubuntu 24.04 and Node.js 24 runtime | JS/TS npm profile, build and pack | +| 0028 | SHA-pinned reusable workflow builder identity | Identity and build types, common provenance, release manifest, verification policy | +| 0029 | Windlass-generated SLSA provenance for npm publish | Common provenance, JS/TS npm provenance and publish, verification policy | +| 0030 | Accept registry URL while guaranteeing only npmjs semantics | JS/TS npm profile, provenance and publish, verification policy | +| 0031 | Sigstore-signed in-toto release manifest | Identity and build types, release manifest | +| 0032 | Constrain manual dispatch releases to version tags | JS/TS npm profile | +| 0033 | Run build script only when declared | JS/TS npm build and pack | +| 0034 | Do not support private dependency credentials | JS/TS npm profile | +| 0035 | `actions/attest` as initial Sigstore signing adapter | Core profile contract, common provenance, JS/TS npm provenance and publish, release manifest | +| 0036 | Three-job digest-verified publish graph | JS/TS npm provenance and publish, verification policy | +| 0037 | Define initial verification deliverables | Verification policy and fixtures | +| 0039 | Scope release asset profile to one asset per run | GitHub Release asset publisher | +| 0042 | Use acquired domains for buildType URIs | Core profile contract, identity and build types | +| 0043 | Upload release assets to existing releases | GitHub Release asset publisher | +| 0045 | Use release asset name as SLSA subject name | GitHub Release asset publisher | +| 0046 | Keep checksums and SBOMs out of subject digest | GitHub Release asset publisher | +| 0048 | Make linked artifacts storage records explicit opt-in | GitHub Release asset publisher | +| 0049 | Separate artifact production from GitHub Release asset publication | Identity and build types, GitHub Release asset publisher, verification policy | +| 0050 | Define producer-to-publisher handoff contract | GitHub Release asset publisher, verification policy | +| 0051 | Distribute producer provenance with release assets | GitHub Release asset publisher, verification policy | +| 0052 | Compose npm package tarball producer with release asset publisher | Composition spec, verification policy | +| 0053 | Three-job release manifest signing boundary | Release manifest | +| 0054 | Use `slsa-builder.dev` release manifest predicate URI | Release manifest, verification policy | +| 0055 | `actions/attest` custom mode for Statement construction | Common provenance, JS/TS npm provenance and publish | +| 0056 | Treat non-selected lockfiles as stale diagnostics | JS/TS npm build and pack, JS/TS npm provenance and publish, verification policy | +| 0057 | Provide public npm release-asset mode | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | +| 0058 | Define GitHub Release asset publisher authority boundary | GitHub Release asset publisher, composition spec, verification policy | +| 0059 | Define public npm release-asset mode interface | Composition spec, JS/TS npm provenance and publish, GitHub Release asset publisher | +| 0060 | Unify npm profile public entrypoint with release-asset mode | JS/TS npm profile, composition spec, verification policy | +| 0061 | Reject duplicate JSON members in signed SLSA Statements | Common provenance, JS/TS npm provenance and publish, verification policy | +| 0062 | Intersect trusted producer policies | Release manifest, GitHub Release asset publisher, verification policy | +| 0063 | Limit Yarn support to Berry v4 with Corepack metadata | JS/TS npm build and pack | +| 0064 | Use npm PURL subject with SHA-512 and SHA-256 digests | Common provenance, JS/TS npm specs, composition and publisher specs, verification policy | +| 0065 | Use a closed status grammar and a separate relations field | Process; no runtime spec needed | +| 0066 | Serialize release mutations with job-class concurrency | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, JS/TS npm package profile, verification policy | +| 0067 | Converge repeated runs within run identity | Release manifest, GitHub Release asset publisher, JS/TS npm provenance and publish, verification policy | +| 0068 | Bind verification to immutable builder and source identities | Verification policy, identity and build types, release manifest, JS/TS npm provenance and publish | +| 0069 | Require Rekor transparency and govern the Sigstore trust root | Verification policy, common provenance, release manifest | +| 0070 | Record package manager distributions and runner image in resolvedDependencies | SLSA provenance v1, JS/TS npm provenance and publish, JS/TS npm build and pack, verification policy and fixtures | +| 0071 | Activate builder.version and builderDependencies for platform components | SLSA provenance v1, JS/TS npm provenance and publish, verification policy and fixtures | +| 0072 | Use sidecar-first pair binding for release asset run ownership | GitHub Release asset publisher, npm-to-release-asset composition, verification policy and fixtures | +| 0073 | Require published-attestation run identity for npm same-run convergence | JS/TS npm provenance and publish, verification policy and fixtures | +| 0074 | Use single-job mutation segments with detection-based cross-run safety | GitHub Release asset publisher, release manifest, npm-to-release-asset composition, verification policy and fixtures | +| 0075 | Queue mutation segment contenders with queue: max | GitHub Release asset publisher, JS/TS npm provenance and publish, release manifest, verification policy and fixtures | +| 0076 | Use observation preflights and first-mutation classification | GitHub Release asset publisher, JS/TS npm package profile, verification policy and fixtures | +| 0077 | Use a Go-native Sigstore DSSE signer for Windlass provenance signing | Core profile contract, SLSA provenance v1, JS/TS npm package profile, JS/TS npm provenance and publish, release asset publisher, release manifest, composition, verification policy | +| 0078 | Treat settings-only pnpm-workspace.yaml as standalone root package mode | JS/TS npm build and pack, verification policy and fixtures | +| 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | +| 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0082 | Pin the builder-owned npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm build and pack, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### Superseded or deprecated ADRs (historical only) From 496cf2adb9f800d87b814c7c8df5567441b536ab Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Sun, 16 Aug 2026 23:42:12 +0900 Subject: [PATCH 2/3] docs(decisions): scope ADR 0082 to the publish stage only Rescope the decision per review: the npm pin applies to the publish job only; build-stage npm selection stays with the Node.js 24 toolchain npm and remains governed by ADR 0016/0017/0027 unchanged. The publish-side contract changes from exact equality with the build-time runtime.npm_version record to validation against the reviewed allowlist pin, since build and publish npm versions now diverge by design. The ADR 0027 partial-supersession scope narrows to the publish-stage npm provisioning clause. Retitle the ADR and filename accordingly and update both index READMEs. Signed-off-by: Yunseo Kim --- ...-hosted-ubuntu-2404-and-node-24-runtime.md | 8 +- ...ied-provisioning-and-reviewed-allowlist.md | 195 ---------------- ...ied-provisioning-and-reviewed-allowlist.md | 217 ++++++++++++++++++ 3 files changed, 221 insertions(+), 199 deletions(-) delete mode 100644 docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md create mode 100644 docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md diff --git a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md index 4046c43..60ff3f8 100644 --- a/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md +++ b/docs/decisions/0027-use-github-hosted-ubuntu-2404-and-node-24-runtime.md @@ -8,10 +8,10 @@ relations: - type: partially-superseded-by target: ADR-0082 scope: - "the npm provisioning clause (the npm CLI is provided by the selected Node.js 24 toolchain, - floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image - constraints, the caller-input prohibitions, and the remaining npm clauses (floor check, - actual-version recording) remain in force" + "the npm provisioning clause for the publish stage (the publish job uses the npm CLI provided + by the selected Node.js 24 toolchain, floating with the node-24 image); the Node.js 24 runtime + selection, the runner and OS image constraints, the caller-input prohibitions, the build-stage + npm clauses, and the floor check and actual-version recording clauses remain in force" --- # Use GitHub-Hosted Ubuntu 24.04 and Node.js 24 Runtime diff --git a/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md deleted file mode 100644 index 81899e1..0000000 --- a/docs/decisions/0082-pin-builder-owned-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -parent: Decisions -nav_order: 82 -status: accepted -date: 12026-08-16 -decision-makers: Yunseo Kim -relations: - - type: partially-supersedes - target: ADR-0027 - scope: - "the npm provisioning clause (the npm CLI is provided by the selected Node.js 24 toolchain, - floating with the node-24 image); the Node.js 24 runtime selection, the runner and OS image - constraints, the caller-input prohibitions, and the remaining npm clauses (floor check, - actual-version recording) remain in force" - - type: see-also - target: ADR-0016 - - type: see-also - target: ADR-0017 - - type: see-also - target: ADR-0029 - - type: see-also - target: ADR-0081 ---- - -# Pin the Builder-Owned npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist - -## Context and Problem Statement - -The JS/TS npm package profile's npm version selection is currently floating. All three jobs of the -reusable workflow (build, provenance-sign, publish) provision Node.js with `node-version: "24"` and -no npm installation step, so the npm CLI is whatever the resolved node-24 image bundles at run time -(the fourth M1 dogfood resolved node 24.19.0, npm 11.17.0). The contract around this is a floor plus -an equality check: the signed `externalParameters.runtime.npm_version` must satisfy SemVer -`>= 11.5.1` (the trusted-publishing minimum), and the publish job re-runs `npm --version` and -requires exact string equality with the build-time record. There is no upper bound, no allowlist, -and no review gate: any npm that a future node-24 image happens to bundle silently becomes the -production publish toolchain. - -ADR 0016 and ADR 0017 already established that npm is builder-owned: caller manifests must not -select the npm version (ADR 0017 explicitly rejected requiring a caller-side npm pin), because the -provenance-aware `npm publish` path runs on the toolchain's npm regardless of the package's -build-time package manager. ADR 0027 further recorded the intent that "the exact Node.js patch -version is selected by the builder implementation and its pinned setup mechanism" — an intent the -floating `node-version: "24"` workflow never implemented for the bundled npm. - -The fourth M1 dogfood -([vers-js run 31840088262](https://github.com/windlasstech/vers-js/actions/runs/31840088262), -tracked as [issue #97](https://github.com/windlasstech/slsa-builder/issues/97)) exposed why this -matters. npm trusted publishing auto-enables provenance and silently discards a caller-supplied -`--provenance-file` bundle; the run published `@windlass/vers-js@0.1.2` with an npm-generated -statement instead of the Windlass-signed Statement. The fail-closed read-back rejected the foreign -evidence exactly as designed — but the registry mutation had already committed, and npm policy makes -an unpublished version number permanently unreusable, so `0.1.2` remains publicly visible with the -wrong provenance. The remediation for #97 (suppressing npm's CI/OIDC detection for the publish -subprocess, injecting the publish-time exchanged token as the registry credential, and a closed -`publishConfig` allowlist) depends on npm **internal** behavior: `oidc()` call ordering and -early-return conditions, `@npmcli/config` exclusive-enforcement semantics, `publishConfig` flatten -timing, and `libnpmpublish`'s `verifyProvenance` with its bundled sigstore-js. - -Source review across the currently allowed range shows those internals already vary: - -| npm version | `oidc.js` auto-enable structure | Exclusive env carve-out | sigstore-js | -| ----------------- | --------------------------------------- | ------------------------ | ------------------ | -| 11.5.1 (floor) | `isDefaultProvenance \|\| intent` shape | absent (throws) | `^3.0.0` (→ 3.1.0) | -| 11.17.0 (dogfood) | intermediate shape | present (silently skips) | intermediate | -| 12.0.2 (latest) | adds CircleCI branch | present (silently skips) | `^5.0.0` | - -A mechanism verified against one npm can therefore be silently invalidated by the next node-24 image -update, and the failure mode is not hypothetical: it is a publish-then-reject incident that burns a -version number in every affected caller at once, detected only after mutation. - -Additionally, the JS/TS npm provenance and publish specification requires a PURL-derivation -compatibility fixture "for every supported npm CLI version", but no document enumerates the -supported set, so that obligation is currently unsatisfiable. And while the upstream defect is now -filed and fixed-pending ([npm/cli#9879](https://github.com/npm/cli/issues/9879), -[npm/cli#9882](https://github.com/npm/cli/pull/9882)), every npm release in the allowed range will -retain the defect permanently; adopting the upstream fix must become an explicit, reviewed event, -not toolchain drift. - -## Decision Drivers - -- The #97 remediation mechanism is source-dependent; a source-dependent mechanism under a floating - toolchain leaves the verification surface open and every guarantee past-tense. -- Fail-closed read-back protects acceptance, not registry state: rejection happens after mutation, - and burned versions cannot be republished (0.1.2 precedent). -- A node-24 image update is a correlated-failure vector: one image roll can break every caller's - publish simultaneously, at release time. -- Builder-owned npm is already the design principle (ADR 0016, ADR 0017); builder-side pinning is - the recorded intent (ADR 0027's "pinned setup mechanism"), so determinism completes existing - decisions rather than changing direction. -- The repository's supply-chain stance pins everything else in the trust boundary (action SHAs, - `mise.lock`, the Go toolchain); npm is the sole floating component whose internals the profile - depends on. -- The specification's per-version compatibility-fixture obligation requires an enumerable supported - set. -- Adopting the upstream npm fix (npm/cli#9882) should be a deliberate pinned bump with regression - evidence, not an accident of image maintenance. - -## Considered Options - -1. Pin the builder-owned npm: provision a reviewed npm version explicitly in the reusable workflow - with integrity verification, hold the supported set in a reviewed allowlist, and require a - diff-review checklist plus fixtures for every bump. -2. Keep the `>= 11.5.1` floor and verify the mechanism across the full range: matrix CI over every - supported npm minor with per-version compatibility fixtures. -3. Keep the current gating unchanged and trust the fail-closed read-back. - -## Decision Outcome - -Chosen option: "Pin the builder-owned npm", because it is the only option that closes the -verification surface, because it completes the builder-owned principle and the recorded pinning -intent rather than contradicting them, and because its costs (update ownership, a registry-fetch -provisioning path) are bounded and already priced in by the repository's existing pinning stance. - -### Single provisioning point with integrity verification - -The reusable workflow provisions npm explicitly in all jobs that invoke npm (build, provenance-sign, -publish), from one shared definition of version plus distribution SHA-256. The install step verifies -the distribution digest before first use. Node.js 24 remains the runtime per ADR 0027; only the npm -provisioning clause is superseded. Because build records `runtime.npm_version` and publish requires -exact equality with it, one provisioning point keeps the equality check satisfied by construction -and makes the recorded version deterministic. - -### Reviewed allowlist and bump procedure - -`runtime.npm_version` must equal a version in a reviewed allowlist recorded in the JS/TS npm -provenance and publish specification (initially a single version, pinned when the #97 fix lands and -re-verified there). A bump pull request must: - -1. update the pinned version and distribution digest together; -2. diff-review the npm-internals checklist for the new version: `lib/utils/oidc.js` early-return and - auto-enable structure, `libnpmpublish` `buildMetadata()` branch structure, `@npmcli/config` - exclusive enforcement (including the env-layer carve-out), `publishConfig` flatten timing, and - the bundled sigstore-js major version; -3. add or refresh the specification-required compatibility fixtures for the new pin; -4. pass the full CI gate on the new pin. - -### Relationship to existing decisions and safety nets - -This ADR partially supersedes ADR 0027's npm provisioning clause ("the npm CLI provided by the -selected Node.js 24 toolchain"); every other ADR 0027 clause remains in force. ADR 0016 and ADR 0017 -are untouched: npm stays builder-owned, and caller manifests still must not select an npm version. -The fail-closed publish read-back is unchanged and remains the permanent safety net for everything -outside the pinned surface; this ADR removes the need to rely on it for npm-version drift. When the -upstream fix (npm/cli#9882) ships in an npm release, adopting it is a pinned bump under the -procedure above; retiring the #97 workaround on fixed npm versions is a separate later decision. - -## Pros and Cons of the Options - -### Pin the builder-owned npm - -- Good, because the publish mechanism's behavior becomes deterministic and the verification surface - is closed and enumerable. -- Good, because npm changes become review events with an internals checklist, matching the action - SHA-pinning and `mise.lock` stance already applied to the rest of the trust boundary. -- Good, because the specification's per-version compatibility-fixture obligation becomes - satisfiable. -- Good, because correlated failure from node-24 image drift is eliminated for the publish path, and - upstream-fix adoption becomes a deliberate, evidence-backed bump. -- Bad, because npm security and feature updates no longer arrive automatically with node-24 image - updates; the project owns the npm update cadence and the review checklist. -- Bad, because the workflow fetches the npm distribution from the registry at run time instead of - using the node-bundled copy; this is mitigated by pinning the distribution digest, and is no worse - than the implicit, unverified trust currently placed in whatever the image bundles. - -### Keep the floor and verify the full range - -- Good, because callers keep toolchain flexibility and the specification's per-version fixtures get - built across the range. -- Bad, because the range is open-ended: a new npm ships in a node-24 image and runs in production - before any fixture or matrix entry can exist for it. The gate cannot test what does not yet exist, - so the riskiest moment is precisely the uncovered one. -- Bad, because the sigstore-js v3–v5 span across the range multiplies fixture and review maintenance - with npm's release cadence. - -### Keep current gating and trust fail-closed - -- Good, because it adds no machinery and the read-back rejection is proven live (dogfood 4). -- Bad, because fail-closed protects acceptance, not registry state: a drifted npm can still - publish-then-reject, burning version numbers and leaving mislabeled provenance publicly visible in - every affected caller at once. -- Bad, because incident cost is externalized to callers at release time, the worst possible moment. - -## More Information - -- Motivating incident and RCA: [issue #97](https://github.com/windlasstech/slsa-builder/issues/97) - and the fourth-attempt evidence in - [issue #30](https://github.com/windlasstech/slsa-builder/issues/30). -- Upstream: [npm/cli#9879](https://github.com/npm/cli/issues/9879) (report), - [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix pending). -- The initial pinned version is selected at #97 fix implementation time and recorded in the JS/TS - npm provenance and publish specification's allowlist, with the bump procedure above governing all - later changes. Recording the npm distribution capture in `resolvedDependencies` alongside the - existing pnpm/Yarn distribution captures is a natural follow-up and is left to the implementing - specification amendment. diff --git a/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md new file mode 100644 index 0000000..606edd6 --- /dev/null +++ b/docs/decisions/0082-pin-publish-stage-npm-cli-version-with-integrity-verified-provisioning-and-reviewed-allowlist.md @@ -0,0 +1,217 @@ +--- +parent: Decisions +nav_order: 82 +status: accepted +date: 12026-08-16 +decision-makers: Yunseo Kim +relations: + - type: partially-supersedes + target: ADR-0027 + scope: + "the npm provisioning clause for the publish stage (the publish job uses the npm CLI provided + by the selected Node.js 24 toolchain, floating with the node-24 image); the Node.js 24 runtime + selection, the runner and OS image constraints, the caller-input prohibitions, the build-stage + npm clauses, and the floor check and actual-version recording clauses remain in force" + - type: see-also + target: ADR-0016 + - type: see-also + target: ADR-0017 + - type: see-also + target: ADR-0029 + - type: see-also + target: ADR-0081 +--- + +# Pin the Publish-Stage npm CLI Version with Integrity-Verified Provisioning and a Reviewed Allowlist + +## Context and Problem Statement + +The JS/TS npm package profile's npm version selection is currently floating, on every stage. All +three jobs of the reusable workflow (build, provenance-sign, publish) provision Node.js with +`node-version: "24"` and no npm installation step, so the npm CLI is whatever the resolved node-24 +image bundles at run time (the fourth M1 dogfood resolved node 24.19.0, npm 11.17.0). The contract +around this is a floor plus an equality check: the signed `externalParameters.runtime.npm_version` +must satisfy SemVer `>= 11.5.1` (the trusted-publishing minimum), and the publish job re-runs +`npm --version` and requires exact string equality with the build-time record. There is no upper +bound, no allowlist, and no review gate: any npm that a future node-24 image happens to bundle +silently becomes the production publish toolchain. + +ADR 0016 and ADR 0017 already established that npm is builder-owned: caller manifests must not +select the npm version (ADR 0017 explicitly rejected requiring a caller-side npm pin), because the +provenance-aware `npm publish` path runs on the toolchain's npm regardless of the package's +build-time package manager. ADR 0027 further recorded the intent that "the exact Node.js patch +version is selected by the builder implementation and its pinned setup mechanism" — an intent the +floating `node-version: "24"` workflow never implemented for the bundled npm. + +The fourth M1 dogfood +([vers-js run 31840088262](https://github.com/windlasstech/vers-js/actions/runs/31840088262), +tracked as [issue #97](https://github.com/windlasstech/slsa-builder/issues/97)) exposed why this +matters specifically on the **publish** stage. npm trusted publishing auto-enables provenance and +silently discards a caller-supplied `--provenance-file` bundle; the run published +`@windlass/vers-js@0.1.2` with an npm-generated statement instead of the Windlass-signed Statement. +The fail-closed read-back rejected the foreign evidence exactly as designed — but the registry +mutation had already committed, and npm policy makes an unpublished version number permanently +unreusable, so `0.1.2` remains publicly visible with the wrong provenance. The remediation for #97 +(suppressing npm's CI/OIDC detection for the publish subprocess, injecting the publish-time +exchanged token as the registry credential, and a closed `publishConfig` allowlist) depends on npm +**internal** behavior on the publish path: `oidc()` call ordering and early-return conditions, +`@npmcli/config` exclusive-enforcement semantics, `publishConfig` flatten timing, and +`libnpmpublish`'s `verifyProvenance` with its bundled sigstore-js. + +Source review across the currently allowed range shows those internals already vary: + +| npm version | `oidc.js` auto-enable structure | Exclusive env carve-out | sigstore-js | +| ----------------- | --------------------------------------- | ------------------------ | ------------------ | +| 11.5.1 (floor) | `isDefaultProvenance \|\| intent` shape | absent (throws) | `^3.0.0` (→ 3.1.0) | +| 11.17.0 (dogfood) | intermediate shape | present (silently skips) | intermediate | +| 12.0.2 (latest) | adds CircleCI branch | present (silently skips) | `^5.0.0` | + +A publish mechanism verified against one npm can therefore be silently invalidated by the next +node-24 image update, and the failure mode is not hypothetical: it is a publish-then-reject incident +that burns a version number in every affected caller at once, detected only after mutation. + +Additionally, the JS/TS npm provenance and publish specification requires a PURL-derivation +compatibility fixture "for every supported npm CLI version", but no document enumerates the +supported set, so that obligation is currently unsatisfiable. And while the upstream defect is now +filed and fix-pending ([npm/cli#9879](https://github.com/npm/cli/issues/9879), +[npm/cli#9882](https://github.com/npm/cli/pull/9882)), every npm release in the allowed range will +retain the defect permanently; adopting the upstream fix must become an explicit, reviewed event, +not toolchain drift. + +This ADR scopes its decision to the publish stage, where the incident occurred and where the +mechanism's npm-internal dependence lives. Build-stage npm version selection is unchanged by this +ADR: the build stage continues to use the npm bundled with the Node.js 24 toolchain per ADR 0016, +ADR 0017, and ADR 0027. + +## Decision Drivers + +- The #97 remediation mechanism is source-dependent on the publish path; a source-dependent + mechanism under a floating toolchain leaves the verification surface open and every guarantee + past-tense. +- Fail-closed read-back protects acceptance, not registry state: rejection happens after mutation, + and burned versions cannot be republished (0.1.2 precedent). +- A node-24 image update is a correlated-failure vector: one image roll can break every caller's + publish simultaneously, at release time. +- Builder-owned npm is already the design principle for the publish path (ADR 0016, ADR 0017); + builder-side pinning is the recorded intent (ADR 0027's "pinned setup mechanism"), so determinism + completes existing decisions rather than changing direction. +- The repository's supply-chain stance pins everything else in the trust boundary (action SHAs, + `mise.lock`, the Go toolchain); npm is the sole floating component whose internals the publish + path depends on. +- The specification's per-version compatibility-fixture obligation requires an enumerable supported + set on the publish path. +- Adopting the upstream npm fix (npm/cli#9882) should be a deliberate pinned bump with regression + evidence, not an accident of image maintenance. + +## Considered Options + +1. Pin the publish-stage npm: provision a reviewed npm version explicitly in the publish job with + integrity verification, hold the supported set in a reviewed allowlist, and require a diff-review + checklist plus fixtures for every bump. +2. Keep the `>= 11.5.1` floor and verify the publish mechanism across the full range: matrix CI over + every supported npm minor with per-version compatibility fixtures. +3. Keep the current gating unchanged and trust the fail-closed read-back. + +## Decision Outcome + +Chosen option: "Pin the publish-stage npm", because it is the only option that closes the publish +path's verification surface, because it completes the builder-owned principle and the recorded +pinning intent rather than contradicting them, and because its costs (update ownership, a +registry-fetch provisioning path) are bounded and already priced in by the repository's existing +pinning stance. + +### Publish-job provisioning with integrity verification + +The publish job of the reusable workflow provisions npm explicitly before any npm invocation, from +one shared definition of version plus distribution SHA-256. The install step verifies the +distribution digest before first use. Node.js 24 remains the runtime per ADR 0027; only the +publish-stage npm provisioning clause is superseded. The build and provenance-sign jobs are out of +scope: they continue to use the Node.js 24 toolchain's bundled npm as today. + +### Publish-side version contract + +The publish job's npm version must equal a version in a reviewed allowlist recorded in the JS/TS npm +provenance and publish specification (initially a single version, pinned when the #97 fix lands and +re-verified there). This replaces the current publish-side validation, which required exact equality +with the build-time `runtime.npm_version` record: since the build-stage npm remains +toolchain-bundled and floating while the publish npm is pinned, equality between the two is no +longer the invariant. The build stage continues to record the actual npm version it used in +`externalParameters.runtime.npm_version` unchanged; the implementing specification amendment +redefines the publish-side check as pin validation and records the publish npm version in the +outcome evidence. + +### Bump procedure + +An allowlist bump pull request must: + +1. update the pinned version and distribution digest together; +2. diff-review the npm-internals checklist for the new version: `lib/utils/oidc.js` early-return and + auto-enable structure, `libnpmpublish` `buildMetadata()` branch structure, `@npmcli/config` + exclusive enforcement (including the env-layer carve-out), `publishConfig` flatten timing, and + the bundled sigstore-js major version; +3. add or refresh the specification-required compatibility fixtures for the new pin; +4. pass the full CI gate on the new pin. + +### Relationship to existing decisions and safety nets + +This ADR partially supersedes ADR 0027's npm provisioning clause for the publish stage only; every +other ADR 0027 clause remains in force, including the build-stage use of the toolchain npm. ADR 0016 +and ADR 0017 are untouched: npm stays builder-owned, and caller manifests still must not select an +npm version on any stage. The fail-closed publish read-back is unchanged and remains the permanent +safety net for everything outside the pinned surface; this ADR removes the need to rely on it for +publish npm-version drift. When the upstream fix (npm/cli#9882) ships in an npm release, adopting it +is a pinned bump under the procedure above; retiring the #97 workaround on fixed npm versions is a +separate later decision. + +## Pros and Cons of the Options + +### Pin the publish-stage npm + +- Good, because the publish mechanism's behavior becomes deterministic and its verification surface + is closed and enumerable. +- Good, because publish npm changes become review events with an internals checklist, matching the + action SHA-pinning and `mise.lock` stance already applied to the rest of the trust boundary. +- Good, because the specification's per-version compatibility-fixture obligation becomes satisfiable + on the publish path. +- Good, because correlated failure from node-24 image drift is eliminated for the publish path, and + upstream-fix adoption becomes a deliberate, evidence-backed bump. +- Good, because the build stage is untouched: no provenance-schema split between build-time and + publish-time npm is introduced beyond redefining the publish-side check, and no new caller-facing + constraint appears. +- Bad, because publish npm security and feature updates no longer arrive automatically with node-24 + image updates; the project owns the publish npm update cadence and the review checklist. +- Bad, because the workflow fetches the npm distribution from the registry at run time instead of + using the node-bundled copy; this is mitigated by pinning the distribution digest, and is no worse + than the implicit, unverified trust currently placed in whatever the image bundles. + +### Keep the floor and verify the full range + +- Good, because callers keep toolchain flexibility and the specification's per-version fixtures get + built across the range. +- Bad, because the range is open-ended: a new npm ships in a node-24 image and runs in production + before any fixture or matrix entry can exist for it. The gate cannot test what does not yet exist, + so the riskiest moment is precisely the uncovered one. +- Bad, because the sigstore-js v3–v5 span across the range multiplies fixture and review maintenance + with npm's release cadence. + +### Keep current gating and trust fail-closed + +- Good, because it adds no machinery and the read-back rejection is proven live (dogfood 4). +- Bad, because fail-closed protects acceptance, not registry state: a drifted npm can still + publish-then-reject, burning version numbers and leaving mislabeled provenance publicly visible in + every affected caller at once. +- Bad, because incident cost is externalized to callers at release time, the worst possible moment. + +## More Information + +- Motivating incident and RCA: [issue #97](https://github.com/windlasstech/slsa-builder/issues/97) + and the fourth-attempt evidence in + [issue #30](https://github.com/windlasstech/slsa-builder/issues/30). +- Upstream: [npm/cli#9879](https://github.com/npm/cli/issues/9879) (report), + [npm/cli#9882](https://github.com/npm/cli/pull/9882) (fix pending). +- The initial pinned version is selected at #97 fix implementation time and recorded in the JS/TS + npm provenance and publish specification's allowlist, with the bump procedure above governing all + later changes. +- Build-stage npm version selection (including any future parity with the manifest-first + package-manager policy of ADR 0015–ADR 0017) is explicitly out of scope for this ADR and remains + governed by the existing decisions unchanged. From 6c1b1efe7b045189613d851b720a27121004a00c Mon Sep 17 00:00:00 2001 From: Yunseo Kim Date: Sun, 16 Aug 2026 23:43:54 +0900 Subject: [PATCH 3/3] docs(decisions): update index rows for publish-scoped ADR 0082 Signed-off-by: Yunseo Kim --- docs/decisions/README.ko.md | 4 ++-- docs/decisions/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/decisions/README.ko.md b/docs/decisions/README.ko.md index 94cbaa0..6e35420 100644 --- a/docs/decisions/README.ko.md +++ b/docs/decisions/README.ko.md @@ -62,7 +62,7 @@ ADR 파일은 MADR 4.0.0 문서이며, 네 자리 순번과 kebab-case 제목을 | 0079 | 호출자 지정 빌드 소스 ref | 모든 producer profile의 기본 릴리즈 재시도 경로인 태그 전용 `source-ref` 입력. | | 0080 | 인증서 클레임 = 호출 컨텍스트 | 소스 정책은 서명된 provenance 필드에 바인딩; cert 소스 클레임은 호출을 증명. | | 0081 | npm OIDC exchange 응답 계약 | 성공 본문을 실측 형태에 핀; exchange 토큰 수명은 15분. | -| 0082 | 빌더 소유 npm 버전 핀 | 무결성 검증된 npm 프로비저닝 + 검토 allowlist와 bump 체크리스트. | +| 0082 | publish 단계 npm 버전 핀 | 무결성 검증된 publish npm 프로비저닝 + 검토 allowlist. | ## ADR status와 relations @@ -203,7 +203,7 @@ ADR은 영향을 주는 모든 선행 ADR을 열거해야 하며, 누락은 추 | 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | -| 0082 | Pin the builder-owned npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm build and pack, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### 대체 혹은 폐기된 ADR (과거 맥락으로만 참조) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 3c84515..a04e25f 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -63,7 +63,7 @@ sequence currently runs from `0000` through `0082`. | 0079 | Caller-specified build source ref | Tags-only `source-ref` input as the default release-retry path for all producer profiles. | | 0080 | Certificate claims as invocation context | Source policy binds signed provenance fields; cert source claims prove invocation. | | 0081 | npm OIDC exchange response contract | Success body pinned to the observed shape; exchange token lifetime is 15 minutes. | -| 0082 | Builder-owned npm version pinning | Pinned, integrity-verified npm provisioning with a reviewed allowlist and bump checklist. | +| 0082 | Publish-stage npm version pinning | Pinned, integrity-verified publish npm provisioning with a reviewed allowlist. | ## ADR status and relations @@ -206,7 +206,7 @@ of this table; they are recorded in each ADR's `relations` frontmatter field. | 0079 | Support a tags-only caller-specified build source ref for release retries across profiles | Core profile contract, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | | 0080 | Bind source identity policy to signed provenance fields, certificate claims as invocation context | Core profile contract, SLSA provenance v1, identity and build types, JS/TS npm provenance and publish, verification policy and fixtures | | 0081 | Pin the npm OIDC exchange response contract to the observed shape, correct token lifetime | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | -| 0082 | Pin the builder-owned npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm build and pack, JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | +| 0082 | Pin the publish-stage npm CLI version with integrity-verified provisioning and a reviewed allowlist | JS/TS npm package profile, JS/TS npm provenance and publish, verification policy and fixtures | ### Superseded or deprecated ADRs (historical only)