How actions/checkout Handles Credential Persistence and Security from v6 Onwards
Starting with version 6.0.0, actions/checkout isolates all authentication tokens and SSH keys in temporary, runner-specific files instead of storing them directly in the repository's .git/config, using a placeholder technique to prevent credentials from appearing in audit logs.
The official GitHub actions/checkout action introduced a fundamental security redesign in version 6.0.0 to harden credential handling during repository clones. This article breaks down how the action now leverages temporary credential files, scoped Git configurations, and automatic cleanup routines to prevent secret leakage, based on the implementation in src/git-auth-helper.ts and related source files.
Temporary Credential Isolation
The core security improvement in version 6 involves moving authentication material out of the permanent Git configuration and into ephemeral files with restricted lifetimes.
The Unique Credentials File Structure
According to the source code in src/git-auth-helper.ts (lines 24-28), the action generates a unique git-credentials-*.config file directly under the RUNNER_TEMP directory. This file contains the Base-64 encoded token required for HTTPS authentication. By storing credentials in a dedicated temporary file rather than the repository's local .git/config, the action ensures that sensitive data never persists in the working directory beyond the job's execution.
Audit Log Protection via Placeholders
To prevent the token from appearing in process-creation audit events, the helper implements a two-stage write process (lines 30-38). First, it writes a placeholder string AUTHORIZATION: basic *** to the temporary credentials file. Then, it immediately replaces this placeholder with the actual Base-64 encoded token. This technique ensures that command-line logging or process monitoring systems capture only the masked placeholder rather than the real secret.
Scoped Git Configuration with includeIf
Rather than modifying the global Git configuration permanently, the action uses conditional includes to scope authentication to specific repository paths.
Repository-Scoped Authentication
As implemented in src/git-auth-helper.ts (lines 73-84), the helper adds includeIf.gitdir:<repo>.path and work-tree equivalent entries to the Git configuration. These entries point to the temporary credentials file, instructing Git to load the authentication data only when operating within the checked-out repository's directory structure. This provides least-privilege exposure by ensuring the token is not available to arbitrary Git operations outside the intended scope.
Cross-Environment Support
The implementation accounts for both host runner and containerized environments by creating separate includeIf entries. One entry targets the standard host path (/github/workspace/...), while another points to the container environment (/github/runner_temp/...). This dual-entry approach ensures consistent credential access regardless of whether the workflow executes directly on the runner or inside a Docker container.
Global Git Configuration and Temporary HOME
When persist-credentials is set to true (the default), the action must handle global Git configuration without polluting the runner's actual HOME directory.
As detailed in src/git-auth-helper.ts (lines 85-95), the helper creates a temporary HOME directory for the duration of the job. It copies any existing global .gitconfig into this temporary location, then appends the includeIf entries to the temporary global config. The HOME environment variable is updated to point to this ephemeral directory, ensuring all Git processes read from the isolated configuration. The temporary HOME and its contents are removed after the checkout step completes.
SSH Key Isolation and Management
For repositories using SSH authentication, the action applies similar isolation principles to private keys and known_hosts files.
Based on src/git-auth-helper.ts (lines 50-70), when an SSH key is supplied via the ssh-key input, the helper writes both the key and a custom known_hosts file to RUNNER_TEMP. It then constructs a GIT_SSH_COMMAND environment variable that explicitly references these temporary files. When credential persistence is enabled, this SSH command configuration is also written to the local or temporary global Git config, ensuring subsequent Git operations can authenticate without exposing the key material in process listings.
Automatic Credential Cleanup
Regardless of the persistence setting, the action implements strict cleanup procedures to ensure no secrets survive the job.
The removeAuth method (lines 72-78) orchestrates the removal of all temporary artifacts. It invokes removeToken to delete the git-credentials-*.config file and removeSsh to remove the SSH key and known_hosts files. Additionally, it strips all injected includeIf entries from the Git configuration. This guarantees that credentials exist only for the minimum necessary duration and are irrevocably destroyed after the checkout step finishes, even if the job subsequently fails or is canceled.
Configuring Credential Persistence in Workflows
The persist-credentials input in action.yml controls whether these security mechanisms are active for subsequent steps.
By default, persist-credentials: true maintains the temporary credential file and Git configuration entries so later steps in the job can execute authenticated Git commands (such as git push). To disable this behavior and remove credentials immediately after the initial clone, set the input to false:
- uses: actions/checkout@v6
with:
persist-credentials: false
Even when set to false, the action still uses the token or SSH key for the initial checkout operation; however, it skips writing the temporary credentials file and includeIf entries, leaving subsequent Git commands unauthenticated.
For SSH-based authentication, specify the key explicitly:
- uses: actions/checkout@v6
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
persist-credentials: true
Summary
- Temporary File Isolation: Credentials are stored in
git-credentials-*.configunderRUNNER_TEMP, never in the permanent.git/config. - Placeholder Technique: The action writes a masked placeholder before substituting the real token to avoid audit log exposure in process creation events.
- Scoped Authentication:
includeIf.gitdirdirectives restrict credential usage to the specific repository path and environment (host or container). - Ephemeral HOME Directory: Global Git configs are isolated in a temporary HOME directory when persistence is enabled, preventing cross-job contamination.
- Automatic Cleanup: The
removeAuthroutine deletes all temporary files and configuration entries after the checkout step completes.
Frequently Asked Questions
What is the primary security improvement in actions/checkout v6?
The primary improvement is the isolation of authentication tokens and SSH keys into temporary files under RUNNER_TEMP, referenced via includeIf directives, rather than storing them directly in the repository's .git/config. This prevents secrets from persisting in the working directory and reduces their visibility in system audit logs.
Where does actions/checkout store the GitHub token during workflow execution?
According to src/git-auth-helper.ts, the token is stored in a uniquely named git-credentials-*.config file located in the RUNNER_TEMP directory. Git accesses this file through conditional includeIf.gitdir entries injected into the configuration, rather than reading the token from the standard credential helper or config file.
How does the action prevent credentials from appearing in process audit logs?
The helper uses a placeholder substitution technique (lines 30-38): it first writes AUTHORIZATION: basic *** to the temporary credentials file, then immediately replaces the asterisks with the actual Base-64 token. This ensures process-creation event monitors log only the placeholder value.
Does setting persist-credentials: false disable authentication entirely?
No. When persist-credentials: false is set, the action still uses the provided token or SSH key to perform the initial repository clone. However, it does not create the temporary credentials file or inject includeIf entries into the Git config, meaning subsequent steps cannot perform authenticated Git operations unless they provide their own credentials.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →