From ba1e5c9e1f5895fc6f470423c5e2a279d15c2463 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Mon, 17 Aug 2026 08:28:57 -0400 Subject: [PATCH 1/3] ci: verify bestax-migrate's published install and provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bestax-migrate was excluded from verify-provenance because its published manifest carried an unresolved `workspace:^` specifier: `npm install` of it failed with EUNSUPPORTEDPROTOCOL and would have masked real signature failures. That is fixed and released (2.0.0), so restore it. Add it to the provenance assertion loop as well, not just the install list. Leaving that loop at two packages would mean a silently dropped attestation on bestax-migrate stays green forever, which is the exact hole that step exists to close. Also state what the install step is for. It already asserts installability — a failing `npm install` fails the job — but it reads as setup for the audit that follows. This is the only automated check that installs the *published* artifact rather than the workspace, which is why #412 shipped undetected. Closes #416 --- .github/workflows/supply-chain.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index c177a8b9d..626c1ab7c 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 - name: Audit registry signatures and provenance attestations run: | @@ -312,7 +317,7 @@ jobs: - name: Assert our packages actually carry provenance run: | fail=0 - for pkg in @allxsmith/bestax-bulma create-bestax; do + for pkg in @allxsmith/bestax-bulma create-bestax bestax-migrate; do # --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. From 8041ede09a2c709d3748f7fd2dd5fe2d65f50cb1 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Mon, 17 Aug 2026 08:41:08 -0400 Subject: [PATCH 2/3] ci: pin the provenance assertion to the installed versions `npm view "$pkg"` re-resolves the `latest` dist-tag, which is mutable. The install step and this assertion resolved it independently, so a publish landing between them would have the job vouch for a version it never installed or audited. Read the version back out of the scratch tree instead, so all three steps describe one set of artifacts. Absent from the tree gets its own error rather than being folded into the "no provenance" message: a missing install and a dropped attestation want different investigations. Both paths still fail closed, per rule 4. Raised by CodeRabbit on #524. --- .github/workflows/supply-chain.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 626c1ab7c..9942cd866 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -317,7 +317,27 @@ jobs: - name: Assert our packages actually carry provenance run: | fail=0 + cd "$RUNNER_TEMP/verify" for pkg in @allxsmith/bestax-bulma create-bestax bestax-migrate; 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. @@ -330,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" From c3b005154de78b39a451e97205f01baa3bdd0204 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Mon, 17 Aug 2026 11:08:52 -0400 Subject: [PATCH 3/3] ci: cover bestax-mcp too, and correct the stale supported-versions claims bestax-mcp was left out of verify-provenance on the strength of #502, which says CI has no release step for it. That issue is stale: ci.yml has had a `Semantic Release (bestax-mcp)` step since the pipeline change, and 1.0.0 is on the registry with a SLSA provenance attestation. Verified it installs clean alongside the other three, so it belongs in both lists. SECURITY.md's supported-versions table had drifted two majors behind on two packages and never gained a row for the fourth: create-bestax is 4.x not 3.x, bestax-migrate is 2.x not 1.x, and bestax-mcp was missing entirely. Both majors were the Node 22 requirement from #447, not an API break. That table is what tells users which line receives security fixes, so being wrong about it is a security-relevant inaccuracy rather than a docs nit. The provenance bullet said "all three published packages" for the same reason. The docs security guide carried a narrower version of the same claim, naming only two of the four packages. --- .github/workflows/supply-chain.yml | 4 ++-- SECURITY.md | 15 ++++++++------- docs/docs/guides/security.md | 7 ++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 9942cd866..7a4d00951 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -302,7 +302,7 @@ jobs: cd "$RUNNER_TEMP/verify" npm init -y > /dev/null npm install --ignore-scripts \ - @allxsmith/bestax-bulma create-bestax bestax-migrate + @allxsmith/bestax-bulma create-bestax bestax-migrate bestax-mcp - name: Audit registry signatures and provenance attestations run: | @@ -318,7 +318,7 @@ jobs: run: | fail=0 cd "$RUNNER_TEMP/verify" - for pkg in @allxsmith/bestax-bulma create-bestax bestax-migrate; do + 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 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.