From 7b710a2790d1b905f9a9f5ebd4dd972bde0eb055 Mon Sep 17 00:00:00 2001 From: Alisa <62909685+alisa-alisa@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:43:12 -0700 Subject: [PATCH] new linting check: github-actions-pinning (#23808) --- .github/actions/push-sandbox/action.yml | 8 +- .github/actions/verify-release/action.yml | 2 +- .github/workflows/ci.yml | 7 +- .../gemini-scheduled-stale-issue-closer.yml | 4 +- .../gemini-scheduled-stale-pr-closer.yml | 4 +- .../workflows/label-backlog-child-issues.yml | 8 +- .github/workflows/label-workstream-rollup.yml | 2 +- .../pr-contribution-guidelines-notifier.yml | 2 +- .github/workflows/release-change-tags.yml | 2 +- .github/workflows/release-notes.yml | 6 +- .github/workflows/test-build-binary.yml | 8 +- .../workflows/unassign-inactive-assignees.yml | 4 +- scripts/lint.js | 80 +++++++++++++++++++ 13 files changed, 110 insertions(+), 27 deletions(-) diff --git a/.github/actions/push-sandbox/action.yml b/.github/actions/push-sandbox/action.yml index bab85af453..dd2d96c4a1 100644 --- a/.github/actions/push-sandbox/action.yml +++ b/.github/actions/push-sandbox/action.yml @@ -34,7 +34,7 @@ runs: JSON_INPUTS: '${{ toJSON(inputs) }}' run: 'echo "$JSON_INPUTS"' - name: 'Checkout' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 with: ref: '${{ inputs.github-sha }}' fetch-depth: 0 @@ -45,11 +45,11 @@ runs: shell: 'bash' run: 'npm run build' - name: 'Set up QEMU' - uses: 'docker/setup-qemu-action@v3' + uses: 'docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130' # ratchet:docker/setup-qemu-action@v3 - name: 'Set up Docker Buildx' - uses: 'docker/setup-buildx-action@v3' + uses: 'docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f' # ratchet:docker/setup-buildx-action@v3 - name: 'Log in to GitHub Container Registry' - uses: 'docker/login-action@v3' + uses: 'docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9' # ratchet:docker/login-action@v3 with: registry: 'docker.io' username: '${{ inputs.dockerhub-username }}' diff --git a/.github/actions/verify-release/action.yml b/.github/actions/verify-release/action.yml index 261715c1b9..4e0c6c6f72 100644 --- a/.github/actions/verify-release/action.yml +++ b/.github/actions/verify-release/action.yml @@ -36,7 +36,7 @@ runs: run: 'echo "$JSON_INPUTS"' - name: 'setup node' - uses: 'actions/setup-node@v4' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version: '20' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e1f329d5a..d40b49bb69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: cache: 'npm' - name: 'Cache Linters' - uses: 'actions/cache@v4' + uses: 'actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830' # ratchet:actions/cache@v4 with: path: '${{ env.GEMINI_LINT_TEMP_DIR }}' key: "${{ runner.os }}-${{ runner.arch }}-linters-${{ hashFiles('scripts/lint.js') }}" @@ -76,7 +76,7 @@ jobs: run: 'npm ci' - name: 'Cache ESLint' - uses: 'actions/cache@v4' + uses: 'actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830' # ratchet:actions/cache@v4 with: path: '.eslintcache' key: "${{ runner.os }}-eslint-${{ hashFiles('package-lock.json', 'eslint.config.js') }}" @@ -114,6 +114,9 @@ jobs: - name: 'Run sensitive keyword linter' run: 'node scripts/lint.js --sensitive-keywords' + - name: 'Run GitHub Actions pinning linter' + run: 'node scripts/lint.js --check-github-actions-pinning' + link_checker: name: 'Link Checker' runs-on: 'ubuntu-latest' diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 2b7b163d88..cfbecd6490 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -28,14 +28,14 @@ jobs: steps: - name: 'Generate GitHub App Token' id: 'generate_token' - uses: 'actions/create-github-app-token@v2' + uses: 'actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349' # ratchet:actions/create-github-app-token@v2 with: app-id: '${{ secrets.APP_ID }}' private-key: '${{ secrets.PRIVATE_KEY }}' permission-issues: 'write' - name: 'Process Stale Issues' - uses: 'actions/github-script@v7' + uses: 'actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b' # ratchet:actions/github-script@v7 env: DRY_RUN: '${{ inputs.dry_run }}' with: diff --git a/.github/workflows/gemini-scheduled-stale-pr-closer.yml b/.github/workflows/gemini-scheduled-stale-pr-closer.yml index cc33848941..7a8e3c1fd5 100644 --- a/.github/workflows/gemini-scheduled-stale-pr-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-pr-closer.yml @@ -27,13 +27,13 @@ jobs: APP_ID: '${{ secrets.APP_ID }}' if: |- ${{ env.APP_ID != '' }} - uses: 'actions/create-github-app-token@v2' + uses: 'actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349' # ratchet:actions/create-github-app-token@v2 with: app-id: '${{ secrets.APP_ID }}' private-key: '${{ secrets.PRIVATE_KEY }}' - name: 'Process Stale PRs' - uses: 'actions/github-script@v7' + uses: 'actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b' # ratchet:actions/github-script@v7 env: DRY_RUN: '${{ inputs.dry_run }}' with: diff --git a/.github/workflows/label-backlog-child-issues.yml b/.github/workflows/label-backlog-child-issues.yml index a819bf4e71..697e605d51 100644 --- a/.github/workflows/label-backlog-child-issues.yml +++ b/.github/workflows/label-backlog-child-issues.yml @@ -18,10 +18,10 @@ jobs: runs-on: 'ubuntu-latest' steps: - name: 'Checkout' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 - name: 'Setup Node.js' - uses: 'actions/setup-node@v4' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version: '20' cache: 'npm' @@ -40,10 +40,10 @@ jobs: runs-on: 'ubuntu-latest' steps: - name: 'Checkout' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 - name: 'Setup Node.js' - uses: 'actions/setup-node@v4' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version: '20' cache: 'npm' diff --git a/.github/workflows/label-workstream-rollup.yml b/.github/workflows/label-workstream-rollup.yml index 97d699d09b..9a44a9c25d 100644 --- a/.github/workflows/label-workstream-rollup.yml +++ b/.github/workflows/label-workstream-rollup.yml @@ -15,7 +15,7 @@ jobs: issues: 'write' steps: - name: 'Check for Parent Workstream and Apply Label' - uses: 'actions/github-script@v7' + uses: 'actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b' # ratchet:actions/github-script@v7 with: script: | const labelToAdd = 'workstream-rollup'; diff --git a/.github/workflows/pr-contribution-guidelines-notifier.yml b/.github/workflows/pr-contribution-guidelines-notifier.yml index 5ee1b37f57..bd08aac0ce 100644 --- a/.github/workflows/pr-contribution-guidelines-notifier.yml +++ b/.github/workflows/pr-contribution-guidelines-notifier.yml @@ -19,7 +19,7 @@ jobs: APP_ID: '${{ secrets.APP_ID }}' if: |- ${{ env.APP_ID != '' }} - uses: 'actions/create-github-app-token@v2' + uses: 'actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349' # ratchet:actions/create-github-app-token@v2 with: app-id: '${{ secrets.APP_ID }}' private-key: '${{ secrets.PRIVATE_KEY }}' diff --git a/.github/workflows/release-change-tags.yml b/.github/workflows/release-change-tags.yml index c7c3f3f2d2..3a7c5648f8 100644 --- a/.github/workflows/release-change-tags.yml +++ b/.github/workflows/release-change-tags.yml @@ -40,7 +40,7 @@ jobs: issues: 'write' steps: - name: 'Checkout repository' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 with: ref: '${{ github.ref }}' fetch-depth: 0 diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml index 13bb2c2ca8..a5a2f90db8 100644 --- a/.github/workflows/release-notes.yml +++ b/.github/workflows/release-notes.yml @@ -29,14 +29,14 @@ jobs: pull-requests: 'write' steps: - name: 'Checkout repository' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 with: # The user-level skills need to be available to the workflow fetch-depth: 0 ref: 'main' - name: 'Set up Node.js' - uses: 'actions/setup-node@v4' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version: '20' @@ -86,7 +86,7 @@ jobs: - name: 'Create Pull Request' if: "steps.validate_version.outputs.CONTINUE == 'true'" - uses: 'peter-evans/create-pull-request@v6' + uses: 'peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c' # ratchet:peter-evans/create-pull-request@v6 with: token: '${{ secrets.GEMINI_CLI_ROBOT_GITHUB_PAT }}' commit-message: 'docs(changelog): update for ${{ steps.release_info.outputs.VERSION }}' diff --git a/.github/workflows/test-build-binary.yml b/.github/workflows/test-build-binary.yml index f11181a9f0..d0069b8b15 100644 --- a/.github/workflows/test-build-binary.yml +++ b/.github/workflows/test-build-binary.yml @@ -33,7 +33,7 @@ jobs: steps: - name: 'Checkout' - uses: 'actions/checkout@v4' + uses: 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' # ratchet:actions/checkout@v4 - name: 'Optimize Windows Performance' if: "matrix.os == 'windows-latest'" @@ -46,7 +46,7 @@ jobs: shell: 'powershell' - name: 'Set up Node.js' - uses: 'actions/setup-node@v4' + uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4 with: node-version-file: '.nvmrc' architecture: '${{ matrix.arch }}' @@ -63,7 +63,7 @@ jobs: - name: 'Setup Windows SDK (Windows)' if: "matrix.os == 'windows-latest'" - uses: 'microsoft/setup-msbuild@v2' + uses: 'microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce' # ratchet:microsoft/setup-msbuild@v2 - name: 'Add Signtool to Path (Windows)' if: "matrix.os == 'windows-latest'" @@ -153,7 +153,7 @@ jobs: npm run test:integration:sandbox:none -- --testTimeout=600000 - name: 'Upload Artifact' - uses: 'actions/upload-artifact@v4' + uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4 with: name: 'gemini-cli-${{ matrix.platform_name }}' path: 'dist/${{ matrix.platform_name }}/' diff --git a/.github/workflows/unassign-inactive-assignees.yml b/.github/workflows/unassign-inactive-assignees.yml index dd09f0feaf..e3b9905b5d 100644 --- a/.github/workflows/unassign-inactive-assignees.yml +++ b/.github/workflows/unassign-inactive-assignees.yml @@ -40,13 +40,13 @@ jobs: steps: - name: 'Generate GitHub App Token' id: 'generate_token' - uses: 'actions/create-github-app-token@v2' + uses: 'actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349' # ratchet:actions/create-github-app-token@v2 with: app-id: '${{ secrets.APP_ID }}' private-key: '${{ secrets.PRIVATE_KEY }}' - name: 'Unassign inactive assignees' - uses: 'actions/github-script@v7' + uses: 'actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b' # ratchet:actions/github-script@v7 env: DRY_RUN: '${{ inputs.dry_run }}' with: diff --git a/scripts/lint.js b/scripts/lint.js index 279421a979..6b814e26b2 100644 --- a/scripts/lint.js +++ b/scripts/lint.js @@ -394,6 +394,82 @@ export function runTSConfigLinter() { } } +export function runGithubActionsPinningLinter() { + console.log('\nRunning GitHub Actions pinning linter...'); + + let files = []; + try { + files = execSync( + "git ls-files '.github/workflows/*.yml' '.github/workflows/*.yaml' '.github/actions/**/*.yml' '.github/actions/**/*.yaml'", + ) + .toString() + .trim() + .split('\n') + .filter(Boolean); + } catch (e) { + console.error('Error finding GitHub Actions workflow files:', e.message); + process.exit(1); + } + + let violationsFound = false; + // Improved regex to capture action name and ref, handling optional quotes and comments. + const USES_PATTERN = /uses:\s*['"]?([^@\s'"]+)@([^#\s'"]+)['"]?/; + const SHA_PATTERN = /^[0-9a-f]{40}$/i; + + for (const file of files) { + if (!existsSync(file) || lstatSync(file).isDirectory()) { + continue; + } + const content = readFileSync(file, 'utf-8'); + const lines = content.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const match = line.match(USES_PATTERN); + if (match) { + const action = match[1]; + let ref = match[2]; + + // Clean up any trailing quotes that might have been captured + ref = ref.replace(/['"]$/, ''); + + // Skip local actions (starting with ./), docker actions, and explicit exclusions + if ( + action.startsWith('./') || + action.startsWith('docker://') || + line.includes('# github-actions-pinning:ignore') + ) { + continue; + } + + if (!SHA_PATTERN.test(ref)) { + violationsFound = true; + const lineNum = i + 1; + console.error( + `::error file=${file},line=${lineNum}::Action "${action}" uses "${ref}" instead of a 40-character SHA.`, + ); + } + } + } + } + + if (violationsFound) { + console.error(` +GitHub Actions pinning violations found. Please use exact commit hashes. + +To automatically fix these, you can use the "ratchet" tool (https://github.com/sethvargo/ratchet): + - Mac/Linux (Homebrew): brew install ratchet && ratchet pin .github/workflows/*.yml .github/actions/**/*.yml + - Other platforms: Download from GitHub releases and run "ratchet pin .github/workflows/*.yml .github/actions/**/*.yml" + +If you must use a tag, you can ignore this check by adding a comment (discouraged): + uses: some-action@v1 # github-actions-pinning:ignore +`); + process.exit(1); + } else { + console.log('No GitHub Actions pinning violations found.'); + } +} + function main() { const args = process.argv.slice(2); @@ -421,6 +497,9 @@ function main() { if (args.includes('--tsconfig')) { runTSConfigLinter(); } + if (args.includes('--check-github-actions-pinning')) { + runGithubActionsPinningLinter(); + } if (args.length === 0) { setupLinters(); @@ -431,6 +510,7 @@ function main() { runPrettier(); runSensitiveKeywordLinter(); runTSConfigLinter(); + runGithubActionsPinningLinter(); console.log('\nAll linting checks passed!'); } }