From 922b6a3945309356d46bc93930297c87c3da4e61 Mon Sep 17 00:00:00 2001 From: Franz Hemmer Date: Thu, 5 Nov 2026 20:18:50 -0500 Subject: [PATCH] ci: pin workflow actions to Node 24 release SHAs (#301) Every `uses:` in CI and Package now names a full commit SHA with its release in a trailing comment, and each action moves to a release whose action.yml runs on node24: - actions/checkout v4 -> v7.0.1 - actions/upload-artifact v4 -> v7.0.2 - actions/setup-node v4 -> v7.1.0 - actions/setup-python v5 -> v7.1.0 - actions/setup-dotnet v4 -> v6.1.0 - github/codeql-action/upload-sarif v4 -> v4.39.3 - Swatinem/rust-cache v2 -> v2.9.3 Inputs, triggers, artifact names and paths are unchanged. A new workflow binding test rejects any action reference that is not a 40-character SHA with a version comment. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 34 +++++++++++++++--------------- .github/workflows/package.yml | 16 +++++++------- scripts/tests/ci-workflow.test.cjs | 13 ++++++++++++ 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0430c65a..55572c8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: security-events: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 @@ -47,7 +47,7 @@ jobs: - name: Upload security scan reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: security-scan-reports path: artifacts/security/ @@ -55,7 +55,7 @@ jobs: - name: Upload Gitleaks SARIF if: always() && github.event_name == 'push' - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 with: sarif_file: artifacts/security/gitleaks.sarif category: gitleaks @@ -69,7 +69,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # GPUI's X11 and Wayland backends link these; the tray icon and the keyring speak D-Bus in pure Rust. - name: Install Linux build dependencies @@ -85,7 +85,7 @@ jobs: run: rustup show active-toolchain || rustup toolchain install - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Format Check run: cargo fmt --all --check @@ -103,7 +103,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.sha }} @@ -139,7 +139,7 @@ jobs: cat artifacts/rust-metrics/gate.json >> "$GITHUB_STEP_SUMMARY" fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: always() with: name: rust-metrics @@ -152,12 +152,12 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Install pinned Rust mutation tool run: python3 scripts/rust-mutations.py install @@ -175,7 +175,7 @@ jobs: cat artifacts/rust-mutations/summary.md >> "$GITHUB_STEP_SUMMARY" fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: always() with: name: rust-mutations @@ -186,12 +186,12 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 with: python-version: '3.13' @@ -205,12 +205,12 @@ jobs: - name: Setup .NET id: dotnet if: steps.inputs.outputs.retired == 'true' - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0 with: dotnet-version: '9.0.x' - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: node-version: '24' cache: 'npm' @@ -227,7 +227,7 @@ jobs: Get-Content artifacts/npm-audit/report.json exit $auditExit - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: always() with: name: npm-audit @@ -290,7 +290,7 @@ jobs: - name: Upload Mutation Report if: always() && steps.inputs.outputs.retired == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: stryker-report path: src/CodexBar.Core.Tests/StrykerOutput/**/reports/ @@ -330,7 +330,7 @@ jobs: - name: Upload CI input selection if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ci-inputs path: artifacts/ci-inputs.json diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 8500db43..459a5fb0 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -40,7 +40,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 @@ -48,7 +48,7 @@ jobs: run: rustup show active-toolchain || rustup toolchain install - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Install, update, roll back and uninstall shell: pwsh @@ -59,7 +59,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # The app shows a version with the commit count (#244), which a shallow clone would make 1. with: fetch-depth: 0 @@ -68,7 +68,7 @@ jobs: run: rustup show active-toolchain || rustup toolchain install - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Build CodexBar.app and the DMG run: OUT=dist ./package-macos.sh @@ -81,7 +81,7 @@ jobs: hdiutil verify dist/CodexBar-*.dmg - name: Upload the DMG - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: codexbar-macos path: dist/CodexBar-*.dmg @@ -92,7 +92,7 @@ jobs: timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # The app shows a version with the commit count (#244), which a shallow clone would make 1. with: fetch-depth: 0 @@ -108,7 +108,7 @@ jobs: run: rustup show active-toolchain || rustup toolchain install - name: Cache Cargo - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - name: Build the .deb run: OUT=dist ./package-linux.sh @@ -121,7 +121,7 @@ jobs: test -f /usr/share/icons/hicolor/256x256/apps/HemSoft.CodexBar.png - name: Upload the .deb - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: codexbar-linux path: dist/codexbar_*.deb diff --git a/scripts/tests/ci-workflow.test.cjs b/scripts/tests/ci-workflow.test.cjs index 099465df..39135b8c 100644 --- a/scripts/tests/ci-workflow.test.cjs +++ b/scripts/tests/ci-workflow.test.cjs @@ -64,3 +64,16 @@ test('selection uses complete history and final verification sees only this job' assert.ok(build.steps.indexOf(final) > build.steps.findIndex(step => step.id === id)); } }); + +// Every action is pinned to a reviewed commit, with the release it came from in a trailing comment (#301). +// A movable tag such as `@v4` could change what runs after review, so a new reference must also be a SHA. +test('every action reference in CI and Package is a full commit SHA with its version', () => { + for (const name of ['ci.yml', 'package.yml']) { + const text = fs.readFileSync(path.join(__dirname, '../../.github/workflows', name), 'utf8'); + const references = text.split(/\r?\n/).filter(line => /^\s*(- )?uses:/.test(line)); + assert.ok(references.length > 0, name); + for (const line of references) { + assert.match(line, /^\s*(- )?uses: [\w.-]+\/[\w./-]+@[0-9a-f]{40} # v\d+\.\d+\.\d+$/, `${name}: ${line.trim()}`); + } + } +});