diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index c177a8b9d..7a4d00951 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -287,17 +287,22 @@ jobs: # the loop by running it ourselves against the real published tarballs # rather than the local workspace. npm (not pnpm) on purpose: the # signature audit needs an npm-resolved tree and lockfile. - # bestax-migrate is deliberately absent: its published manifest still - # carries an unresolved `workspace:^` specifier, so `npm install` of it - # fails outright and would mask real signature failures here. Add it back - # once that is fixed. + # + # This step is itself the assertion, not just setup for the audit below: + # `npm install` exits non-zero on an uninstallable package, so the job + # goes red whether the artifact is unsigned or simply broken. That is the + # check #412 did not have — it shipped an uninstallable `workspace:^` + # manifest precisely because everything else in CI resolves against the + # workspace, where the specifier works. + # + # Keep this list in sync with the provenance loop below. - name: Install published packages into a scratch tree run: | mkdir -p "$RUNNER_TEMP/verify" cd "$RUNNER_TEMP/verify" npm init -y > /dev/null npm install --ignore-scripts \ - @allxsmith/bestax-bulma create-bestax + @allxsmith/bestax-bulma create-bestax bestax-migrate bestax-mcp - name: Audit registry signatures and provenance attestations run: | @@ -312,7 +317,27 @@ jobs: - name: Assert our packages actually carry provenance run: | fail=0 - for pkg in @allxsmith/bestax-bulma create-bestax; do + cd "$RUNNER_TEMP/verify" + for pkg in @allxsmith/bestax-bulma create-bestax bestax-migrate bestax-mcp; do + # Pin to the version that was actually installed and audited above. + # A bare `npm view "$pkg"` re-resolves the `latest` dist-tag, which + # is mutable, so a publish landing between the install step and this + # one would have this step vouch for a version the job never + # installed. Reading the version back out of the tree keeps all + # three steps talking about the same artifacts. Local read, no + # network, so it belongs outside the retry loop below. + # + # Absent from the tree is its own failure, reported as such rather + # than folded into the "no provenance" message below — a missing + # install and a dropped attestation want different investigations. + version=$(npm ls "$pkg" --depth=0 --json 2>/dev/null \ + | jq -r --arg pkg "$pkg" '.dependencies[$pkg].version // empty' 2>/dev/null || true) + if [ -z "$version" ]; then + echo "::error::$pkg is not in the installed tree; cannot verify its provenance" + fail=1 + continue + fi + # --json + a type guard so only a real string counts as present: # null, undefined, [] and non-strings all collapse to empty. The # array branch covers npm aggregating a bare spec into one element. @@ -325,16 +350,16 @@ jobs: # spend three attempts before believing provenance is really gone. predicate="" for attempt in 1 2 3; do - predicate=$(npm view "$pkg" dist.attestations.provenance.predicateType --json 2>/dev/null \ + predicate=$(npm view "$pkg@$version" dist.attestations.provenance.predicateType --json 2>/dev/null \ | jq -r 'if type=="array" then .[0] else . end | select(type=="string")' 2>/dev/null || true) [ -n "$predicate" ] && break [ "$attempt" -lt 3 ] && sleep $((attempt * 5)) done if [ -z "$predicate" ]; then - echo "::error::$pkg has NO provenance attestation on the registry" + echo "::error::$pkg@$version has NO provenance attestation on the registry" fail=1 else - echo "ok: $pkg -> $predicate" + echo "ok: $pkg@$version -> $predicate" fi done exit "$fail" diff --git a/SECURITY.md b/SECURITY.md index 30c5d2e98..381af57f9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,16 +3,17 @@ ## Supported Versions Security fixes land on the **latest release line only** — currently -`@allxsmith/bestax-bulma` 5.x, `create-bestax` 3.x, and `bestax-migrate` 1.x. -All three packages release automatically from `main` (semantic-release), so the -latest published version is always the patched one. Older majors may still work -but receive no security updates; please upgrade. +`@allxsmith/bestax-bulma` 5.x, `create-bestax` 4.x, `bestax-migrate` 2.x, and +`bestax-mcp` 1.x. All four packages release automatically from `main` +(semantic-release), so the latest published version is always the patched one. +Older majors may still work but receive no security updates; please upgrade. | Package | Supported | Unsupported | | ------------------------- | ------------ | ----------- | | `@allxsmith/bestax-bulma` | 5.x (latest) | < 5.0 | -| `create-bestax` | 3.x (latest) | < 3.0 | -| `bestax-migrate` | 1.x (latest) | — | +| `create-bestax` | 4.x (latest) | < 4.0 | +| `bestax-migrate` | 2.x (latest) | < 2.0 | +| `bestax-mcp` | 1.x (latest) | — | ## Supply-Chain Security @@ -42,7 +43,7 @@ Measures active in this repository and its release pipeline: reviewed lockfile resolves. (The React 18/19 compatibility matrix is the one deliberate exception: it re-resolves to pin the requested React major for testing, and never publishes.) -- **npm provenance** — all three published packages set +- **npm provenance** — all four published packages set `publishConfig.provenance`, so every release carries a signed attestation linking the tarball to the exact commit and CI run that built it. - **OIDC trusted publishing** — releases authenticate to npm with diff --git a/docs/docs/guides/security.md b/docs/docs/guides/security.md index ab8ce5e34..50a2924b0 100644 --- a/docs/docs/guides/security.md +++ b/docs/docs/guides/security.md @@ -19,9 +19,10 @@ npm via **OIDC trusted publishing** (short-lived, per-run tokens), so there is no long-lived npm token that could leak and be used to push a rogue release. Only the **latest release line** of each package receives security fixes -(currently bestax-bulma 5.x and create-bestax 3.x). Releases are fully -automated from `main` via semantic-release, so the newest published version is -always the patched one — staying current is the supported posture. See +(currently bestax-bulma 5.x, create-bestax 4.x, bestax-migrate 2.x, and +bestax-mcp 1.x). Releases are fully automated from `main` via semantic-release, +so the newest published version is always the patched one — staying current is +the supported posture. See [SECURITY.md](https://github.com/allxsmith/bestax/blob/main/SECURITY.md) for the full policy.