DEV Community· Abhinav Yadav·· 2 小時前AI 評分53
為 GitHub Actions 的 actions/checkout 設定 persist-credentials: false
Always Set persist-credentials: false in actions/checkout
AI 導讀
文章建議在所有 GitHub Actions 的 `actions/checkout` 步驟設定 `persist-credentials: false`,避免 GitHub token 留在同一 job 的後續步驟可讀取的檔案內。
正文
There's a default in actions/checkout\ that most teams never change — and that quietly leaves a GitHub token sitting in a readable file for every subsequent step in the job.
The fix is one line. The reasoning is worth understanding.
The Setting
- uses: actions/checkout@v4
with:
persist-credentials: false # default is true
~~~
By default (\`persist-credentials: true\`), the checkout action writes a git credential config file containing your GitHub token to \`$RUNNER_TEMP\`. That file persists for the lifetime of the job.
## What the File Looks Like
The checkout action creates a file at \`$RUNNER_TEMP/git-credentials-<UUID>.config\`:
~~~ini
[http "https://github.com/"]
extraheader = AUTHORIZATION: basic <Base64-encoded-string>
~~~
The Base64 string decodes to \`x-access-token:<GITHUB_TOKEN>\`. It's not encrypted. It's not write-protected. It sits in \`$RUNNER_TEMP\` for every step after checkout to read.
## How Easy It Is to Exploit
Any step in the same job — including actions from third-party repos — can extract the token with a shell one-liner:
~~~bash
GH_TOKEN=$(
cat "$RUNNER_TEMP"/git-credentials-*.config \
| awk 'NR==2 {print $5}' \
| base64 --decode \
| cut -d: -f2
)
echo "token: ${GH_TOKEN:0:10}..."
~~~
This doesn't require \`${{ github.token }}\` or \`${{ secrets.GITHUB_TOKEN }}\` to appear anywhere in the workflow. The token is already on disk.
> [!WARNING]
> If any action you use is compromised via a supply chain attack, or if your workflow is vulnerable to script injection through untrusted input, that action can silently exfiltrate your GitHub token via this file.
Setting \`persist-credentials: false\` makes the checkout action **delete the credentials file** after checkout completes. The file is gone before the next step runs.
## The Practical Risk
The most realistic threat vectors:
**1. Compromised third-party action:** You pin \`some-org/some-action@v2\`. The maintainer's account is compromised, a new patch release is pushed, and the action now exfiltrates credentials. If you reference by tag (not commit SHA), you get the new version automatically.
**2. Script injection via PR titles or branch names:** Workflows that echo \`${{ github.event.pull_request.title }}\` into a \`run:\` block are vulnerable. An attacker opens a PR with a crafted title that executes a shell command reading the credentials file.
The \`persist-credentials: false\` flag eliminates the file-based exfiltration path. It doesn't prevent all attacks, but it removes the easiest one.
## What If I Need Git Auth in Later Steps?
Setting \`persist-credentials: false\` means subsequent \`git pull\`, \`git push\`, or other authenticated operations will fail without credentials being re-established.
The clean solution is \`gh auth setup-git\`, which configures Git to use the GitHub CLI credential helper:
~~~yaml
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up git credentials via GH CLI
run: gh auth setup-git
env:
GH_HOST: github.com
- name: Pull latest
run: git pull origin main
env:
GH_TOKEN: ${{ github.token }}
~~~
This configures \`~/.gitconfig\` with:
~~~ini
[credential "https://github.com"]
helper =
helper = !/usr/bin/gh auth git-credential
~~~
The credential helper calls out to the GitHub CLI at auth time, which reads \`GH_TOKEN\` from the environment — **only when Git actually needs to authenticate**, and without writing the token to disk.
The token is scoped to the steps where \`GH_TOKEN\` is explicitly passed in \`env:\`. It's not sitting in a file readable by any arbitrary code in the job.
## Pinning Actions by Commit SHA
While hardening your checkout step, pin by full commit SHA rather than a mutable tag:
~~~yaml
# Mutable — tag can be force-pushed to point at new, malicious code
- uses: actions/checkout@v4
# Immutable — this exact commit is guaranteed
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
~~~
Combined with \`persist-credentials: false\`, this closes both the supply chain mutation vector and the credential-on-disk vector in one checkout block.
## Automated Detection
Human review will miss this. Use static analysis in CI:
**[zizmor](https://docs.zizmor.sh)** — Rust-based, runs as a GitHub Action:
~~~yaml
- uses: woodruffw/zizmor-action@v1
~~~
Output when \`persist-credentials\` is not set:
~~~plaintext
warning[artipacked]: credential persistence through GitHub Actions artifacts
--> .github/workflows/build.yml:7:9
|
7 | - uses: actions/checkout@v4
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false
|
= note: this finding has an auto-fix
~~~
**[ghasec](https://github.com/koki-develop/ghasec)** and **[ghalint](https://github.com/suzuki-shunsuke/ghalint)** also catch this and surface it with rule references.
Running any of these in CI ensures the check doesn't rely on engineers remembering to apply it manually.
> [!TIP]
> There is an open PR in the \`actions/checkout\` repo to flip the default to \`false\`, but it has been stalled for years. Until the default changes, enforce it via linting.
## Checklist
- [ ] \`persist-credentials: false\` on all \`actions/checkout\` steps
- [ ] Actions pinned to full commit SHAs, not mutable tags
- [ ] \`gh auth setup-git\` pattern for workflows that need post-checkout git auth
- [ ] zizmor or ghasec running in CI to catch new workflows
- [ ] Minimal \`permissions:\` scope on each job (\`contents: read\` unless write is required)
The same lesson applies to AI tooling: a token that sits where other code can read it will eventually be read. See [stop giving AI agents your production keys](https://yadui.dev/blog/stop-giving-ai-agents-production-keys) and the [LiteLLM 1.107.0 incident response guide](https://yadui.dev/blog/litellm-1-107-0-incident-response-ai-gateways) for what that looks like in practice.
---
*Originally published at [yadui.dev](https://yadui.dev/blog/github-actions-persist-credentials-security) by Abhinav Yadav.*
來源:DEV Community · dev.to