From a00f2fa1548e41f9af3f1ac47fdda9bcec7f95c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sun, 18 Oct 2026 18:38:33 +0300 Subject: [PATCH 1/2] ci: harden npm OIDC release workflow with verification and approval gate --- .github/workflows/publish.yml | 105 +++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f0652ea..4f35f10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,30 +1,109 @@ name: Publish to npm +# Only version tags can trigger a release. Do not publish from PRs. on: push: - tags: ["v*"] + tags: + - "v*" permissions: contents: read - id-token: write + +concurrency: + group: npm-publish + cancel-in-progress: false jobs: - publish: + verify: + name: Verify release runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - name: Check out release commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: "24" - registry-url: https://registry.npmjs.org - - name: Verify version tag + package-manager-cache: false + + - name: Verify tag, package version, and branch shell: bash run: | + set -euo pipefail expected="v$(node -p 'require("./package.json").version')" - test "$GITHUB_REF_NAME" = "$expected" || { - echo "Expected tag $expected, got $GITHUB_REF_NAME" >&2 + if [[ "$GITHUB_REF_TYPE" != "tag" || "$GITHUB_REF_NAME" != "$expected" ]]; then + echo "Expected version tag $expected, received $GITHUB_REF_NAME" >&2 + exit 1 + fi + + # Reject tags pointing to commits not reachable from main. + git fetch --no-tags origin main:refs/remotes/origin/main + if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main; then + echo "Release tag must reference a commit on main" >&2 exit 1 - } - - run: npm install --no-audit --no-fund - - run: npm test - - run: npm publish --access public + fi + + - name: Install dependencies without lifecycle scripts + run: npm install --ignore-scripts --no-audit --no-fund + + - name: Check and test + run: | + npm run check + npm test + + - name: Verify npm package contents + run: npm pack --dry-run --ignore-scripts + + publish: + name: Publish with npm Trusted Publishing (OIDC) + if: github.repository == 'Angular-RU/prettier-plugin-angular-comment' + needs: verify + runs-on: ubuntu-latest + + # Configure required reviewers for this environment in GitHub settings. + # npm Trusted Publisher must use the same environment name. + environment: npm-publish + + permissions: + contents: read + id-token: write + + steps: + - name: Check out verified release commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Node.js and npm registry + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: "24" + registry-url: https://registry.npmjs.org + package-manager-cache: false + + - name: Require OIDC-compatible npm + shell: bash + run: | + npm --version + node -e ' + const [major, minor, patch] = require("node:child_process") + .execFileSync("npm", ["--version"], { encoding: "utf8" }) + .trim() + .split(".") + .map(Number); + if (major < 11 || (major === 11 && (minor < 5 || (minor === 5 && patch < 1)))) { + console.error("npm >= 11.5.1 is required for OIDC trusted publishing"); + process.exit(1); + } + ' + + # No npm install or dependency scripts in the OIDC-enabled job. + # npm automatically generates provenance for public packages. + - name: Publish to npm via OIDC + run: npm publish --access public --ignore-scripts From 8fecb3f68ac94232e04c4c9ac81d331841d61c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Sun, 18 Oct 2026 18:38:35 +0300 Subject: [PATCH 2/2] docs: explain secure npm trusted publishing bootstrap and release --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f4f04e5..c8959cf 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,45 @@ npm test npm pack --dry-run ``` -## npm releases +## npm releases (Trusted Publishing / OIDC) -Create the npm package first if it does not yet exist. Configure [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) for `Angular-RU/prettier-plugin-angular-comment` and the workflow `publish.yml`; trusted publishing requires an existing npm package and matching owner access. After merging a version bump, push its matching `vX.Y.Z` Git tag. The GitHub Actions workflow tests and publishes via OIDC. Never put a long-lived npm token in the repository. +This repository uses [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/) with GitHub Actions. No persistent `NPM_TOKEN` or `NODE_AUTH_TOKEN` secret is required. + +### First release (one-time setup) + +npm requires the package to exist before a Trusted Publisher can be configured. If the package has not been published yet, its npm owner must bootstrap it locally: + +```sh +npm login +npm install +npm test +npm publish --access public +``` + +Keep the version in `package.json` synchronized with the first published version. Do **not** push a matching tag for the manually published version. + +### Enable Trusted Publishing + +1. Open `https://www.npmjs.com/package/prettier-plugin-angular-comment/access` or the package's npm settings, then **Trusted publishing** → **Add trusted publisher** → **GitHub Actions**. +2. Enter **Organization/user**: `Angular-RU`, **Repository**: `prettier-plugin-angular-comment`, **Workflow filename**: `publish.yml` (not a path), **Environment**: `npm-publish`. +3. Enable **Allow npm publish** in the npm trusted publisher's allowed actions. Staging-only permission is insufficient for this workflow. +4. In GitHub **Settings → Environments**, create or configure `npm-publish`. Add **required reviewers** and deployment-tag restrictions if desired, so a release requires approval. +5. Complete the first successful OIDC publication within **2 days** of creating the trusted publisher, or recreate the expired configuration. + +### Publish the next version + +Update `package.json` to an unpublished version (for example, `0.1.1`), merge it to `main`, and confirm CI is green. Then push the corresponding tag **from a commit on main**: + +```sh +git switch main +git pull --ff-only +git tag v0.1.1 +git push origin v0.1.1 +``` + +The [publish workflow](./.github/workflows/publish.yml) verifies the exact version and that the tag commit is on `main`, runs tests without OIDC permissions, and publishes in a separate approval-gated environment. The publisher job disables npm lifecycle scripts and uses npm's short-lived OIDC authentication and automatic provenance. Pinned GitHub Action commit SHAs and disabled release-job caching reduce supply-chain risk. + +**After the first successful OIDC release**, consider selecting **Require two-factor authentication and disallow tokens** under npm **Settings → Publishing access**, and protect release tags with a GitHub ruleset. Traditional npm token publishing will then be blocked while OIDC continues working. ## License