Bump pinned action SHAs to versions on a supported runtime #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # Least privilege by default. Jobs that need more request it explicitly. | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # 20.x is the floor the code targets; 22.x guards against drift on the | |
| # Node APIs this depends on (AbortSignal.any, node:test, fetch). | |
| node: ['20.x', '22.x'] | |
| steps: | |
| # Third-party actions are pinned to a commit SHA, not a tag. Tags are | |
| # mutable: the tj-actions/changed-files compromise (CVE-2025-30066) worked | |
| # by repointing existing version tags at malicious code, which every | |
| # workflow tracking @v3 then executed with its own secrets in scope. | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Set up Node ${{ matrix.node }} | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: npm | |
| - name: Install | |
| run: npm ci | |
| - name: Typecheck | |
| run: npm run typecheck | |
| - name: Test | |
| run: npm test | |
| audit: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: 20.x | |
| cache: npm | |
| - run: npm ci | |
| # There are no runtime dependencies, so this should stay trivially clean. | |
| # If it ever fails, a dependency was added and is worth a conversation. | |
| - name: Audit production dependencies | |
| run: npm audit --omit=dev --audit-level=low | |
| - name: Assert zero runtime dependencies | |
| run: | | |
| count=$(node -p "Object.keys(require('./package.json').dependencies || {}).length") | |
| if [ "$count" -ne 0 ]; then | |
| echo "::error::patchwatch claims zero runtime dependencies but package.json lists $count" | |
| exit 1 | |
| fi | |
| echo "confirmed: 0 runtime dependencies" | |
| docker: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| # Building on every PR catches a broken Dockerfile before it reaches a | |
| # deploy, which is the only place it would otherwise be noticed. | |
| - name: Build image | |
| run: docker build -t patchwatch:ci . | |
| - name: Smoke test the image | |
| run: | | |
| # No token is configured, so the process must exit 78 (EX_CONFIG) with a | |
| # readable complaint rather than crashing or, worse, starting anyway. | |
| set +e | |
| output=$(docker run --rm patchwatch:ci 2>&1) | |
| code=$? | |
| set -e | |
| echo "$output" | |
| if [ "$code" -ne 78 ]; then | |
| echo "::error::expected exit 78 for missing config, got $code" | |
| exit 1 | |
| fi | |
| echo "$output" | grep -q "TELEGRAM_BOT_TOKEN is required" |