From d486972f08e307e9dd5fa8fae230d5d9d1e587c8 Mon Sep 17 00:00:00 2001 From: Yohay Nahmany Date: Mon, 19 Oct 2026 12:31:14 +0200 Subject: [PATCH] ci: harden the workflows to the shared template; release 10.0.1 [PRV-7340] Bring CI and publish.yml up to the squiss-ts template: actions pinned to SHAs (Dependabot weekly, 7-day cooldown), permissions: {} with id-token: write only on the publish job, explicit secrets instead of secrets: inherit, persist-credentials: false, no cache in the publish job, pre-release skip and release-tag check, workflow_dispatch, and a zizmor CI job. Removes the debug step that expanded github.head_ref (a branch name an attacker controls) into a shell script. Patch release 10.0.1 proves the npm trusted publisher, now pointed at PruvoNet. No code change. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 14 ++++++++++ .github/workflows/ci.yml | 33 +++++++++++++++++++---- .github/workflows/publish.yml | 49 +++++++++++++++++++++++------------ .github/zizmor.yml | 4 +++ package.json | 2 +- 5 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a12f510 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + # Actions are pinned to commit SHAs; this keeps the pins current. + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + # Wait a week after a release: a hijacked action version is usually caught and pulled by then. + cooldown: + default-days: 7 + groups: + github-actions: + patterns: + - '*' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a1f304..f782d09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,14 @@ on: branches: - '**' workflow_call: + secrets: + SNYK_TOKEN: + required: false + QLTY_COVERAGE_TOKEN: + required: false + +permissions: + contents: read env: PRIMARY_NODE_VERSION: 24.x @@ -23,18 +31,19 @@ jobs: node-version: [ 20.x, 22.x, 24.x ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 name: Checkout with: ref: ${{ github.event.client_payload.ref || github.ref }} + persist-credentials: false - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' @@ -46,7 +55,7 @@ jobs: - name: Snyk security check if: matrix.node-version == env.PRIMARY_NODE_VERSION && matrix.os == env.PRIMARY_OS - uses: snyk/actions/node@master + uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0 env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: @@ -63,7 +72,7 @@ jobs: - name: Upload coverage if: matrix.node-version == env.PRIMARY_NODE_VERSION && matrix.os == env.PRIMARY_OS - uses: qltysh/qlty-action/coverage@v2 + uses: qltysh/qlty-action/coverage@c9b09987143d1e4ac955f4803c7bea742102feb5 # v2.4.0 with: token: ${{ secrets.QLTY_COVERAGE_TOKEN }} files: coverage/lcov.info @@ -71,3 +80,17 @@ jobs: - name: Run unit tests if: "!(matrix.node-version == env.PRIMARY_NODE_VERSION && matrix.os == env.PRIMARY_OS)" run: pnpm test:unit + + zizmor: + name: Workflow security (zizmor) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + persist-credentials: false + + - uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1 + with: + advanced-security: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3987a10..b6cbfdd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,12 +3,12 @@ name: Publish on: release: types: [ published ] + workflow_dispatch: -permissions: - id-token: write # Required for OIDC authentication - contents: read # Allows checking out the repository contents +permissions: {} env: + # Trusted publishing needs npm >= 11.5.1, which ships with Node 24. NODE_VERSION: 24.x REGISTRY: https://registry.npmjs.org/ @@ -16,40 +16,57 @@ jobs: test: name: Run tests before publish uses: ./.github/workflows/ci.yml - secrets: inherit + permissions: + contents: read + secrets: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + QLTY_COVERAGE_TOKEN: ${{ secrets.QLTY_COVERAGE_TOKEN }} publish_version: - name: Publish to NPM + name: Publish runs-on: ubuntu-latest needs: [ test ] + # A pre-release must never become the stable `latest` version on npm. + if: github.event_name != 'release' || !github.event.release.prerelease + permissions: + id-token: write # OIDC token for npm trusted publishing + contents: read steps: - name: Checkout - uses: actions/checkout@v5 - - - name: fetch - run: | - git fetch --prune --unshallow + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + persist-credentials: false - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0 with: version: 10 - name: Use Node.js ${{ env.NODE_VERSION }} - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: node-version: ${{ env.NODE_VERSION }} registry-url: ${{ env.REGISTRY }} - cache: 'pnpm' + # No cache in the job that publishes: a poisoned cache entry would ship in the package. + package-manager-cache: false - name: Install dependencies and build run: | - pnpm install + pnpm install --frozen-lockfile pnpm build - - name: Ensure npm supports trusted publishing (OIDC) - run: npm install -g npm@latest + # npm never lets a published version be reused, so a tag/manifest mismatch must fail before publishing. + - name: Check the release tag matches package.json + if: github.event_name == 'release' + env: + TAG: ${{ github.event.release.tag_name }} + run: | + VERSION=$(node -p "require('./package.json').version") + if [ "${TAG#v}" != "$VERSION" ]; then + echo "::error::Release tag $TAG does not match package.json version $VERSION" + exit 1 + fi - name: Publish run: | diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000..e4d8b71 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,4 @@ +rules: + # Calls to this repo's own reusable workflow use `./`; zizmor suggests a newer `$/` syntax. + self-repository: + disable: true diff --git a/package.json b/package.json index 812b68e..4cf6ecd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-expression-eval", - "version": "10.0.0", + "version": "10.0.1", "description": "json serializable rule engine / boolean expression evaluator", "main": "dist/index.js", "types": "dist/index.d.ts",