diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2c48305 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + groups: + github-actions: + patterns: ["*"] + schedule: + interval: "weekly" + cooldown: + default-days: 7 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6032554..5111d9d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,10 +30,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install mdBook - uses: peaceiris/actions-mdbook@v2 + uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0 with: mdbook-version: '0.4.40' @@ -54,7 +54,7 @@ jobs: mdbook-mermaid install . - name: Lint markdown - uses: DavidAnson/markdownlint-cli2-action@v18 + uses: DavidAnson/markdownlint-cli2-action@eb5ca3ab411449c66620fe7f1b3c9e10547144b0 # v18.0.0 with: globs: 'docs/src/**/*.md' @@ -62,7 +62,7 @@ jobs: run: cd docs && mdbook build - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: book path: docs/book @@ -75,10 +75,10 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Install mdBook - uses: peaceiris/actions-mdbook@v2 + uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0 with: mdbook-version: '0.4.40' @@ -102,7 +102,7 @@ jobs: run: cd docs && mdbook build - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/book diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 9849392..623b847 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -46,8 +46,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 8 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -68,10 +68,10 @@ jobs: # Full history: the WS2022 baseline-repair suite re-derives its mapping # evidence with `git show ` instead of trusting the committed # tables, so a shallow checkout would make those assertions unrunnable. - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -106,8 +106,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 12 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: '24' cache: npm @@ -125,8 +125,8 @@ jobs: runs-on: windows-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -135,7 +135,7 @@ jobs: # re-downloads ~120 MB on first install of the apps/desktop # workspace. - name: Cache Electron binaries - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ${{ env.ELECTRON_CACHE }} @@ -151,7 +151,7 @@ jobs: # `npx playwright install chromium` runs in postinstall on # some plugin paths, so cache it just in case. - name: Cache Playwright browsers - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/AppData/Local/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} @@ -177,7 +177,7 @@ jobs: # can post-mortem without re-running the spec locally. - name: Upload Playwright artifacts on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: playwright-failure-artifacts path: | diff --git a/.github/workflows/release-mac.yml b/.github/workflows/release-mac.yml index e89f496..92bef98 100644 --- a/.github/workflows/release-mac.yml +++ b/.github/workflows/release-mac.yml @@ -39,7 +39,7 @@ jobs: timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: ref: ${{ inputs.release_tag }} fetch-depth: 0 @@ -58,7 +58,7 @@ jobs: test -n "$tag_commit" test "$(git rev-parse HEAD)" = "$tag_commit" - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -67,7 +67,7 @@ jobs: # comes from protected main. Check out the current dependency-free guard # separately so older tags cannot bypass a newly added packaging policy. - name: Check out protected packaging guard - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: ref: main path: .release-policy @@ -208,7 +208,7 @@ jobs: # workflow run page even if the gh release upload step had a hiccup. - name: Stash artifacts as workflow outputs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: installers-macos-author path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2f80b1..0175c1a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,8 +84,8 @@ jobs: os: [windows-latest, ubuntu-latest] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} cache: npm @@ -103,7 +103,7 @@ jobs: sudo apt-get install -y --no-install-recommends rpm - name: Cache Electron binaries - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: | ${{ env.ELECTRON_CACHE }} @@ -316,7 +316,7 @@ jobs: # publish step had a hiccup. Auto-deletes after 14 days. - name: Stash artifacts as workflow outputs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: installers-${{ matrix.os }} path: | diff --git a/scripts/release-metadata.test.mjs b/scripts/release-metadata.test.mjs index ddfef67..f55cc2c 100644 --- a/scripts/release-metadata.test.mjs +++ b/scripts/release-metadata.test.mjs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { access, readFile } from 'node:fs/promises'; +import { access, readFile, readdir } from 'node:fs/promises'; import { describe, expect, it } from 'vitest'; async function read(path) { @@ -54,7 +54,7 @@ describe('public release metadata', () => { expect(script).toContain('[string]$Repo = "Azure/ConfigForge"'); expect(script).toMatch(/gh workflow run "Release \(macOS author\)"[\s\S]*?--ref main/); expect(workflow).toMatch( - /uses: actions\/checkout@v4\s*\r?\n\s+with:\s*\r?\n\s+ref: \$\{\{ inputs\.release_tag \}\}/m, + /uses: actions\/checkout@[0-9a-f]{40}(?:\s+#\s+\S+)?\s*\r?\n\s+with:\s*\r?\n\s+ref: \$\{\{ inputs\.release_tag \}\}/m, ); expect(workflow).toContain('test "$(git rev-parse HEAD)" = "$tag_commit"'); expect(workflow).toContain('Expected exactly 5 macOS author assets'); @@ -65,6 +65,22 @@ describe('public release metadata', () => { expect(workflow).not.toContain('default:'); }); + it('pins external GitHub Actions to immutable commit SHAs', async () => { + const workflowDirectory = new URL('../.github/workflows/', import.meta.url); + const workflowNames = await readdir(workflowDirectory); + + for (const workflowName of workflowNames.filter((name) => /\.ya?ml$/.test(name))) { + const workflow = await read(`.github/workflows/${workflowName}`); + const actionReferences = [...workflow.matchAll(/^\s*-\s+uses:\s+([^\s#]+)/gm)] + .map((match) => match[1]) + .filter((reference) => !reference.startsWith('./') && !reference.startsWith('docker://')); + + for (const reference of actionReferences) { + expect(reference, `${workflowName}: ${reference}`).toMatch(/^[^@\s]+@[0-9a-f]{40}$/); + } + } + }); + it('keeps remote lockfile tarballs on the public npm registry', async () => { const lockfile = JSON.parse(await read('package-lock.json')); const nonPublic = Object.entries(lockfile.packages)