diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index a683c9f..32ef86c 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -61,12 +61,51 @@ jobs: - name: Install a working npm (bundled npm in Node 22.22.2 is broken) # Node 22.22.2 ships with an npm install that is missing the # `promise-retry` dependency, so `npm install -g npm@latest` fails - # with MODULE_NOT_FOUND before it can upgrade itself. Use corepack - # to install a known-good npm version that supports OIDC trusted - # publishing. + # with MODULE_NOT_FOUND before it can upgrade itself. Corepack's + # `prepare --activate` stages a new npm in the corepack cache but + # does not actually replace the npm binary in the PATH that this + # shell sees (it prints the old version). Instead, download the + # npm@11.6.0 tarball directly and extract it over the broken + # bundled install. npm 11.6.0 supports OIDC trusted publishing. + # + # SECURITY: we verify the tarball against the sha512 integrity + # value published in the registry's package metadata before + # extracting it. Both requests go to registry.npmjs.org (the + # same origin we're about to publish to), so the trust model is + # unchanged, but this protects against in-transit tampering + # between the metadata fetch and the tarball fetch, and ensures + # we install the exact bytes the registry says 11.6.0 should be. run: | - corepack enable - corepack prepare npm@11.6.0 --activate + set -euo pipefail + NPM_VERSION=11.6.0 + NODE_BIN=$(which node) + NODE_DIR=$(dirname $(dirname $NODE_BIN)) + NPM_DIR="$NODE_DIR/lib/node_modules/npm" + TMP=$(mktemp -d) + + EXPECTED_INTEGRITY=$( + curl -fsSL "https://registry.npmjs.org/npm/$NPM_VERSION" \ + | python3 -c "import json, sys; print(json.load(sys.stdin)['dist']['integrity'])" + ) + if [[ "$EXPECTED_INTEGRITY" != sha512-* ]]; then + echo "unexpected integrity format from registry: $EXPECTED_INTEGRITY" >&2 + exit 1 + fi + + curl -fsSL "https://registry.npmjs.org/npm/-/npm-$NPM_VERSION.tgz" -o "$TMP/npm.tgz" + + ACTUAL_INTEGRITY="sha512-$(openssl dgst -sha512 -binary "$TMP/npm.tgz" | base64 -w0)" + if [ "$ACTUAL_INTEGRITY" != "$EXPECTED_INTEGRITY" ]; then + echo "npm tarball integrity check failed" >&2 + echo " expected: $EXPECTED_INTEGRITY" >&2 + echo " actual: $ACTUAL_INTEGRITY" >&2 + exit 1 + fi + + tar -xzf "$TMP/npm.tgz" -C "$TMP" + sudo rm -rf "$NPM_DIR" + sudo mv "$TMP/package" "$NPM_DIR" + hash -r npm --version - name: Install dependencies