# Security Implications of Using Tokens with actions/checkout: Risks and Mitigations

> Discover security risks with tokens in actions/checkout. Learn about log leaks, residual files, and over-scoped permissions, plus essential mitigations to secure your workflow.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: security
- Published: 2026-08-29

---

**Using tokens with `actions/checkout` writes credentials to the runner's Git configuration, creating exposure risks through log leaks, residual files, and over-scoped permissions that require specific mitigations to secure.**

The `actions/checkout` GitHub Action is the standard mechanism for retrieving repository code in CI/CD workflows. When accessing private repositories or authenticated submodules, it accepts a personal access token (PAT) via the `token` input defined in [`action.yml`](https://github.com/actions/checkout/blob/main/action.yml). Understanding how this credential is processed in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) and [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) is essential for preventing supply chain attacks and credential leakage.

## How actions/checkout Handles Authentication Tokens

In [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) (lines 56-58), the action reads the `token` input and stores it in the settings object as `authToken` defined in [`src/git-source-settings.ts`](https://github.com/actions/checkout/blob/main/src/git-source-settings.ts). The actual authentication implementation resides in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) (lines 55-65), where the token is encoded as a base64 basic-auth header and written to Git's configuration as an HTTP extra-header.

The action immediately masks the token using `core.setSecret` (line 62) to prevent accidental exposure in workflow logs. However, because the credential is physically written to disk in Git's config files, several attack surfaces emerge if proper precautions are not taken.

## Critical Security Risks and Code-Level Mitigations

### Token Exposure Through Log Injection

Although `core.setSecret` masks the raw token value, any subsequent `console.log` of the Git configuration file or environment variables could reveal the base64-encoded credential. The token is formatted as `x-access-token:${token}` before encoding.

**Mitigation:** Avoid custom logging of Git config files or environment inspection scripts after the checkout step completes.

### Credentials Persisting on the Runner

The token is written to a temporary credentials configuration path (`credentialsConfigPath`) as implemented in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) (lines 322-470). The `removeAuth` function attempts to clean up these files, but if a workflow terminates abnormally or the cleanup step fails, the token may remain on disk—particularly problematic for self-hosted runners.

**Mitigation:** Set `persist-credentials: false` (the default) and ensure workflows terminate cleanly without forced cancellations during the checkout phase.

### Submodule Configuration Leakage

When `persist-credentials` is explicitly set to `true`, the token propagates to submodule Git configurations (lines 61-66 in [`git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/git-auth-helper.ts)). This exposes the credential to other workflows, containers, or processes that might access the workspace directory.

**Mitigation:** Only enable `persist-credentials` when private submodules require authentication. For public repositories or internal submodules, explicitly disable this setting.

### Global Git Config Contamination

The `configureGlobalAuth` function (lines 28-46) can write the token to the global `~/.gitconfig` if global authentication mode is activated. On shared runners or systems hosting multiple jobs, this leaks credentials across workflow boundaries.

**Mitigation:** Use the default per-run Git configuration. Avoid global authentication unless explicitly required by your infrastructure.

### Scope Over-Granting and Token Reuse

The action does not enforce minimal token scope. Users frequently provide PATs with full `repo` access when only `contents:read` is required. If compromised, an attacker could push, delete, or alter any repository accessible to that token.

**Mitigation:** Generate short-lived, read-only PATs scoped specifically to `repo:public_repo` or `contents:read`. Prefer the built-in `${{ secrets.GITHUB_TOKEN }}` when possible, as it provides minimal, workflow-scoped permissions.

## Secure Configuration Examples

Use the built-in `GITHUB_TOKEN` with credential persistence disabled for standard operations:

```yaml
- uses: actions/checkout@v4
  with:
    token: ${{ secrets.GITHUB_TOKEN }}
    persist-credentials: false   # Prevents token storage in submodule configs

```

When private submodules require authentication, use a minimally scoped PAT:

```yaml
- name: Checkout with submodule auth
  uses: actions/checkout@v4
  with:
    token: ${{ secrets.SUBMODULE_PAT }}   # PAT limited to repo:read

    persist-credentials: true            # Only enable when required

```

The following TypeScript demonstrates how the action encodes the token internally, illustrating why raw config output should be avoided:

```typescript
// Implementation detail from git-auth-helper.ts
import * as core from '@actions/core';
import { Buffer } from 'buffer';

const token = core.getInput('token', { required: true });
const encoded = Buffer.from(`x-access-token:${token}`, 'utf8').toString('base64');
core.setSecret(encoded); // Masks in logs, but value exists in Git config
// Git uses header: AUTHORIZATION: basic <encoded>

```

## Summary

- **Token storage:** Credentials are written to Git config via [`git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/git-auth-helper.ts) and require proper cleanup via `removeAuth` (lines 322-470).
- **Persistence:** Keep `persist-credentials: false` unless submodules explicitly require authentication, preventing credential leakage to subsequent steps.
- **Scope minimization:** Use read-only tokens scoped to `contents:read` rather than full repository access.
- **Global config:** Avoid `configureGlobalAuth` (lines 28-46) to prevent cross-job contamination on shared runners.
- **Cleanup assurance:** Rely on the built-in cleanup logic rather than custom scripts that might leave credentials in `credentialsConfigPath`.

## Frequently Asked Questions

### Does actions/checkout encrypt the token at rest?

No. The token is base64-encoded as a basic auth header but remains plaintext in the Git configuration file during job execution. While `core.setSecret` masks the value in workflow logs, the credential exists unencrypted on the runner's filesystem until the `removeAuth` cleanup function executes.

### What happens if persist-credentials is not specified?

By default, `persist-credentials` is `false`, meaning the action removes the token from Git config immediately after checkout completes. This is the safest configuration for standard repository access and prevents the credential from being available to subsequent workflow steps or submodules.

### Can I use the default GITHUB_TOKEN for private submodules in other repositories?

No. The automatic `${{ secrets.GITHUB_TOKEN }}` only has access to the current repository. To checkout private submodules hosted in different repositories, you must use a Personal Access Token with `repo` or `contents:read` scope for those specific repositories, configured with minimal permissions and short expiration.

### How can I verify that credentials were properly removed?

The `removeAuth` function in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) handles cleanup by deleting the temporary credential files. You can verify successful removal by adding a post-checkout step that runs `git config --global --get-regexp 'http.*extraheader'`—it should return no output if `persist-credentials` is false or if cleanup succeeded.