From 2b31bda6d150a06b0bae5bbc00f66b29b491d862 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Sat, 1 Aug 2026 11:48:47 +0800 Subject: [PATCH] ci: clear the checkout token in every job that does not push (#5547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit actions/checkout writes its token into .git/config as an http extraheader, where it stays for the rest of the job. That matters more here than usual: pull_request jobs run on self-hosted runners and execute the PR's own build.rs, proc-macros and tests, any of which can read that file. Test and Lint holds actions: write on top of that, so its token can cancel runs and delete the Actions caches the whole pipeline now depends on — it was given persist-credentials: false when that permission was added, and this extends the same treatment to the other 45 checkouts. Two are exempt because the token IS the credential the job needs. helm-package's publish job pushes to rustfs/helm with it; clearing it would break chart publishing. nix-flake-update is exempt pending verification: it passes FLAKE_UPDATE_TOKEN to create-pull-request directly rather than reusing .git/config, so it very likely does not need persistence, but that is unproven and a broken weekly bot is not worth the guess. Both carry a persist-credentials-exempt comment saying which. scripts/security/check_persist_credentials.sh requires every checkout to either clear its credentials or carry that comment, so the decision stays visible in review rather than being an omission nobody notices. Refs: rustfs/backlog#1598, rustfs/backlog#1602 --- .../architecture-migration-rules.yml | 2 + .github/workflows/audit.yml | 11 +++ .github/workflows/build.yml | 6 ++ .github/workflows/ci-docs-only.yml | 4 ++ .github/workflows/ci.yml | 26 +++++++ .github/workflows/coverage.yml | 4 ++ .github/workflows/docker.yml | 3 + .github/workflows/e2e-replication-nightly.yml | 4 ++ .github/workflows/e2e-s3tests.yml | 4 ++ .github/workflows/fuzz.yml | 8 +++ .github/workflows/helm-package.yml | 4 ++ .github/workflows/minio-interop.yml | 2 + .github/workflows/mint.yml | 4 ++ .github/workflows/nix-flake-update.yml | 4 ++ .github/workflows/nix.yml | 2 + .github/workflows/performance-ab.yml | 5 ++ .../schedule-failure-alert-drill.yml | 2 + scripts/security/check_persist_credentials.sh | 72 +++++++++++++++++++ 18 files changed, 167 insertions(+) create mode 100755 scripts/security/check_persist_credentials.sh diff --git a/.github/workflows/architecture-migration-rules.yml b/.github/workflows/architecture-migration-rules.yml index 9153b69e0..365168dc7 100644 --- a/.github/workflows/architecture-migration-rules.yml +++ b/.github/workflows/architecture-migration-rules.yml @@ -49,6 +49,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Install ripgrep run: | diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index bdb4f9425..30d889763 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -80,6 +80,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false # cargo-deny compiles nothing, so the full setup composite (apt packages, # protoc, flatc, nextest, rustfmt/clippy) was pure overhead here. It does @@ -119,6 +121,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Report unpinned GitHub Actions run: ./scripts/security/check_workflow_pins.sh --enforce @@ -129,6 +133,9 @@ jobs: - name: Check every job declares a timeout run: ./scripts/security/check_job_timeouts.sh + - name: Check checkouts clear their credentials + run: ./scripts/security/check_persist_credentials.sh + - name: Check preview release workflow policy run: ./scripts/security/check_preview_release_workflow.sh @@ -143,6 +150,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Dependency Review uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5 @@ -174,6 +183,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f744e4bee..cb5bd7153 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,6 +99,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Determine build strategy id: check @@ -257,6 +259,7 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + persist-credentials: false fetch-depth: 0 - name: Setup Rust environment @@ -795,6 +798,7 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + persist-credentials: false fetch-depth: 0 - name: Create GitHub Release @@ -852,6 +856,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Download all build artifacts uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 diff --git a/.github/workflows/ci-docs-only.yml b/.github/workflows/ci-docs-only.yml index 5563383e1..9d366d1fd 100644 --- a/.github/workflows/ci-docs-only.yml +++ b/.github/workflows/ci-docs-only.yml @@ -79,6 +79,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Install ripgrep run: sudo apt-get update && sudo apt-get install -y ripgrep @@ -125,6 +127,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false # Docs-only PRs skip the full code CI, but they are exactly where a # planning-type document could be slipped in (git add -f bypasses diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 335fe949c..8eb28c886 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Typos check with custom config file uses: crate-ci/typos@37bb98842b0d8c4ffebdb75301a13db0267cef89 # master @@ -112,6 +114,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Install ripgrep run: sudo apt-get update && sudo apt-get install -y ripgrep @@ -353,6 +357,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -391,6 +397,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -433,6 +441,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -461,6 +471,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -492,6 +504,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -532,6 +546,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -594,6 +610,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false # Full setup with dependency caching: the smoke-suite step below # compiles the e2e_test crate, which pulls in most of the workspace. @@ -678,6 +696,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -721,6 +741,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Clean up previous test run run: | @@ -775,6 +797,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Download debug binary uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 @@ -847,6 +871,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Download debug binary uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index fb8a18b6f..baa3c87a7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -62,6 +62,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -116,6 +118,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3a8ef675b..4e9a72c0d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -98,6 +98,7 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + persist-credentials: false # For workflow_run events, checkout the specific commit that triggered the workflow ref: ${{ github.event.workflow_run.head_sha || github.sha }} @@ -305,6 +306,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Login to Docker Hub uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 diff --git a/.github/workflows/e2e-replication-nightly.yml b/.github/workflows/e2e-replication-nightly.yml index 2a0a3f532..1f5fb4f22 100644 --- a/.github/workflows/e2e-replication-nightly.yml +++ b/.github/workflows/e2e-replication-nightly.yml @@ -64,6 +64,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -124,6 +126,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/e2e-s3tests.yml b/.github/workflows/e2e-s3tests.yml index ce26fc235..f8eeaec10 100644 --- a/.github/workflows/e2e-s3tests.yml +++ b/.github/workflows/e2e-s3tests.yml @@ -142,6 +142,8 @@ jobs: TEST_MODE: ${{ matrix.test-mode }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false # Provision Python explicitly rather than trusting the runner image to # ship a working pip (ci-1: a bare python3 without pip is what broke the @@ -361,6 +363,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index fe8e683b4..f2967804f 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -87,6 +87,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -152,6 +154,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Download prebuilt fuzz binaries uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 @@ -207,6 +211,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Download prebuilt fuzz binaries uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 @@ -254,6 +260,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/helm-package.yml b/.github/workflows/helm-package.yml index 63c279caf..c575c3000 100644 --- a/.github/workflows/helm-package.yml +++ b/.github/workflows/helm-package.yml @@ -50,6 +50,8 @@ jobs: steps: - name: Checkout helm chart repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false # Both inputs reach the shell through env rather than `${{ }}` # interpolation. A git ref name may contain `$(...)` — anything without a @@ -121,6 +123,8 @@ jobs: - name: Checkout helm package repo uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + # persist-credentials-exempt: this checkout's token IS the push credential — + # the job git-pushes to rustfs/helm below. Clearing it breaks chart publishing. repository: rustfs/helm token: ${{ secrets.RUSTFS_HELM_PACKAGE }} diff --git a/.github/workflows/minio-interop.yml b/.github/workflows/minio-interop.yml index 6de00067a..ad801f842 100644 --- a/.github/workflows/minio-interop.yml +++ b/.github/workflows/minio-interop.yml @@ -58,6 +58,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup diff --git a/.github/workflows/mint.yml b/.github/workflows/mint.yml index 20f449310..dd9acc6f8 100644 --- a/.github/workflows/mint.yml +++ b/.github/workflows/mint.yml @@ -125,6 +125,8 @@ jobs: timeout-minutes: 120 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Enable buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 @@ -270,6 +272,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/nix-flake-update.yml b/.github/workflows/nix-flake-update.yml index 8a5ec5ba6..01c23936d 100644 --- a/.github/workflows/nix-flake-update.yml +++ b/.github/workflows/nix-flake-update.yml @@ -40,6 +40,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + # persist-credentials-exempt: update-flake-lock pushes the branch and opens + # the PR. It passes FLAKE_UPDATE_TOKEN to create-pull-request itself rather + # than reusing .git/config, but that is unverified — exempt until a + # workflow_dispatch run confirms it (rustfs/backlog#1602). - name: Install Nix uses: DeterminateSystems/determinate-nix-action@629b284231c2a82554b724e357e47fc6020833c8 # v3 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6dc80ef3d..cfa77f3a5 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -71,6 +71,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Install Nix uses: DeterminateSystems/determinate-nix-action@4eea0b33e3d1f02ecfe37cf16e7204c424009606 # v3.21.0 diff --git a/.github/workflows/performance-ab.yml b/.github/workflows/performance-ab.yml index ebab72c8d..8c75c56c8 100644 --- a/.github/workflows/performance-ab.yml +++ b/.github/workflows/performance-ab.yml @@ -99,6 +99,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Setup Rust environment uses: ./.github/actions/setup @@ -148,6 +150,7 @@ jobs: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: + persist-credentials: false fetch-depth: 0 # baseline is built from origin/main - name: Setup Rust environment @@ -394,6 +397,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/.github/workflows/schedule-failure-alert-drill.yml b/.github/workflows/schedule-failure-alert-drill.yml index 648cfb94c..d9380aca0 100644 --- a/.github/workflows/schedule-failure-alert-drill.yml +++ b/.github/workflows/schedule-failure-alert-drill.yml @@ -63,6 +63,8 @@ jobs: issues: write steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false - name: Open or update failure-tracking issue uses: ./.github/actions/schedule-failure-issue with: diff --git a/scripts/security/check_persist_credentials.sh b/scripts/security/check_persist_credentials.sh new file mode 100755 index 000000000..b24bf3523 --- /dev/null +++ b/scripts/security/check_persist_credentials.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Every actions/checkout must set persist-credentials: false, or carry an +# explicit exemption comment. +# +# By default checkout writes its token into .git/config as an http extraheader, +# where it stays for the rest of the job. On this repository that matters more +# than usual: pull_request jobs run on self-hosted runners and execute the PR's +# own build.rs, proc-macros and tests, any of which can read that file. The +# Test and Lint job additionally holds actions: write, so its token can cancel +# runs and delete the Actions caches the whole pipeline depends on. +# +# A checkout is exempt only when the token IS the credential the job needs — +# helm-package pushes to rustfs/helm with it. Mark those with a comment +# containing `persist-credentials-exempt` inside the step, so the decision is +# visible in review instead of being an omission. +# +# Usage: scripts/security/check_persist_credentials.sh +set -euo pipefail + +cd "$(dirname "$0")/../.." + +status=0 + +for file in .github/workflows/*.yml .github/workflows/*.yaml .github/actions/*/action.yml; do + [ -e "$file" ] || continue + + awk -v file="$file" ' + function flush() { + if (pending && !found_pc && !found_exempt) { + printf "%s:%d: actions/checkout without `persist-credentials: false`\n", file, checkout_line > "/dev/stderr" + bad++ + } + pending = 0; found_pc = 0; found_exempt = 0 + } + { + line = $0 + sub(/[[:space:]]+$/, "", line) + if (line ~ /^[[:space:]]*$/) next + + match(line, /^[[:space:]]*/) + indent = RLENGTH + + if (line ~ /uses:[[:space:]]*actions\/checkout@/) { + flush() + pending = 1 + # `- uses:` puts the step keys two columns right of the dash. + checkout_indent = (line ~ /^[[:space:]]*- /) ? indent + 2 : indent + checkout_line = NR + next + } + + if (!pending) next + if (indent < checkout_indent) { flush(); next } + if (line ~ /persist-credentials:/) found_pc = 1 + if (line ~ /persist-credentials-exempt/) found_exempt = 1 + } + END { flush(); exit (bad > 0) } + ' "$file" || status=1 +done + +if [ "$status" -ne 0 ]; then + echo "" >&2 + echo "Add to each checkout above:" >&2 + echo " with:" >&2 + echo " persist-credentials: false" >&2 + echo "" >&2 + echo "If the job genuinely needs the token to push, keep it and say why in a" >&2 + echo "comment containing 'persist-credentials-exempt'. See rustfs/backlog#1602." >&2 + exit 1 +fi + +echo "OK: every actions/checkout clears its credentials or is explicitly exempt"