# How actions/checkout Handles GitHub Token and SSH Authentication

> actions/checkout securely handles GitHub token and SSH authentication in temporary files for robust repository access. Learn how it works.

- Repository: [GitHub Actions/checkout](https://github.com/actions/checkout)
- Tags: internals
- Published: 2026-07-16

---

**The `actions/checkout` action secures repository access through [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) by implementing token-based HTTP authentication using placeholder replacement in temporary credential files, and SSH key authentication via `GIT_SSH_COMMAND` with temporary key files scoped to the `RUNNER_TEMP` directory.**

The `actions/checkout` action is the standard mechanism for accessing repository code in GitHub Actions workflows. Understanding how it handles **actions/checkout authentication** is crucial for securing private repositories and managing deployment keys. The action implements two distinct pathways in the `GitAuthHelper` class: one for HTTPS using personal access tokens or `GITHUB_TOKEN`, and one for SSH using private keys.

## Token-Based HTTP Authentication

The token-based authentication flow in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) ensures that bearer tokens never appear in shell logs or process arguments. This mechanism intercepts HTTPS requests and injects authorization headers through Git's configuration system.

### Constructing the Authorization Header

The `GitAuthHelper` builds a Basic authentication header using the provided token. In the constructor, the code creates a base64-encoded credential string from `x-access-token` and the supplied auth token, then immediately masks it using `core.setSecret()`.

```typescript
const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8')
                            .toString('base64')
core.setSecret(basicCredential)
this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`
this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`

```

This creates two values: a **placeholder** containing asterisks and the **actual** header containing the encoded token.

### The Placeholder Replacement Strategy

To prevent the token from appearing in command-line arguments during `git config` operations, the helper employs a two-stage write process. First, it writes the placeholder value to a temporary credentials file using `git config`. Then it replaces the placeholder with the real header by reading the file, performing a string substitution, and writing it back.

```typescript
// Write placeholder first
await this.git.config(this.tokenConfigKey,
                      this.tokenPlaceholderConfigValue,
                      false, false,
                      credentialsConfigPath)

// Replace with actual token
let content = (await fs.promises.readFile(credentialsConfigPath)).toString()
content = content.replace(this.tokenPlaceholderConfigValue,
                          this.tokenConfigValue)
await fs.promises.writeFile(credentialsConfigPath, content)

```

### Temporary Credential File Management

The temporary credentials file lives under `RUNNER_TEMP` (e.g., `git-credentials-<uuid>.config`). The repository's `.git/config` references this file through an `includeIf.gitdir:` directive, causing Git to include the authorization header on every HTTPS request to the remote. When the job completes, these temporary files are removed to prevent credential leakage.

## SSH Key Authentication

When the `ssh-key` input is provided in [`action.yml`](https://github.com/actions/checkout/blob/main/action.yml), the action switches to SSH authentication. This pathway creates temporary key files and configures Git to use them via environment variables and configuration entries.

### Secure Key File Creation

The `configureSsh()` method writes the SSH private key to a unique file path within `RUNNER_TEMP` with strict permissions (mode `0600`). This ensures only the current process can read the key material.

```typescript
this.sshKeyPath = path.join(runnerTemp, uniqueId)
await fs.promises.writeFile(this.sshKeyPath,
                            this.settings.sshKey.trim() + '\n',
                            {mode: 0o600})

```

### Known Hosts Configuration

The action constructs a **known-hosts** file by concatenating existing entries from `~/.ssh/known_hosts`, the optional `ssh-known-hosts` input, and built-in GitHub host keys. This file is also stored in `RUNNER_TEMP` to prevent man-in-the-middle attacks while maintaining isolation between workflow runs.

### GIT_SSH_COMMAND Configuration

Rather than modifying system SSH configuration, the action sets the `GIT_SSH_COMMAND` environment variable to point to the temporary key and known-hosts files. This command is constructed in `configureSsh()` and includes strict host checking options when `ssh-strict` is enabled.

```typescript
this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"`
if (this.settings.sshStrict) {
  // Add strict checking options
}
this.sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(this.sshKnownHostsPath)}"`
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand)

```

When `persist-credentials` is true, this command is also stored in the repository's Git config as `core.sshCommand`, ensuring that subsequent Git operations like submodules inherit the same authentication.

## Authentication Orchestration and Cleanup

The `configureAuth()` method in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) orchestrates the setup by clearing previous authentication state and invoking both `configureToken()` and `configureSsh()` as needed. For workflows running in Docker containers or requiring global configuration, `configureGlobalAuth()` copies the host's `.gitconfig` to a temporary location and applies credentials globally.

For repositories with submodules, `configureSubmoduleAuth()` propagates the temporary credentials to nested repository configurations. This ensures that `git submodule update` commands authenticate correctly using the same token or SSH key as the parent repository.

The `removeSsh()` and `removeToken()` methods perform cleanup by deleting temporary files and unsetting configuration entries, preventing credential persistence across job steps.

## Configuration Examples

Configure HTTPS authentication using a Personal Access Token:

```yaml

# .github/workflows/token-auth.yml

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.PAT }}
          persist-credentials: true

```

Configure SSH authentication with a deployment key:

```yaml

# .github/workflows/ssh-auth.yml

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
          ssh-known-hosts: |
            github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+P...
          ssh-strict: true
          persist-credentials: true

```

## Summary

- **actions/checkout authentication** implements two distinct pathways: token-based HTTPS using `AUTHORIZATION` headers and SSH using temporary key files.
- The **placeholder replacement** technique in `configureToken()` ensures tokens never appear in process arguments or logs during Git configuration.
- **SSH keys** are written to `RUNNER_TEMP` with `0600` permissions and referenced via the `GIT_SSH_COMMAND` environment variable set in `configureSsh()`.
- Temporary credential files are scoped to the repository using `includeIf.gitdir:` directives and cleaned up after execution.
- The `GitAuthHelper` class in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) coordinates authentication through `configureAuth()`, while `configureSubmoduleAuth()` ensures nested repositories inherit credentials.

## Frequently Asked Questions

### How does actions/checkout prevent tokens from appearing in logs?

The action uses a **placeholder mechanism** where it first writes `AUTHORIZATION: basic ***` to a temporary config file using standard Git commands, then replaces the asterisks with the actual base64-encoded token by directly modifying the file. This prevents the token from appearing in shell history or process listings, and `core.setSecret()` masks the token in GitHub Actions logs.

### What is the difference between token and SSH authentication in actions/checkout?

**Token authentication** modifies HTTPS URLs to include `x-access-token` credentials via Git's `extraheader` configuration, while **SSH authentication** uses the `GIT_SSH_COMMAND` environment variable to specify a private key file. Token auth is suitable for `GITHUB_TOKEN` or PATs, whereas SSH auth is required for deployment keys or when organizations mandate SSH protocols.

### How does persist-credentials work with SSH keys?

When `persist-credentials` is set to `true`, the action stores the constructed SSH command in the repository's Git configuration as `core.sshCommand`. This persists the `GIT_SSH_COMMAND` equivalent across subsequent steps, allowing later Git operations like `git fetch` or `git submodule update` to use the same temporary key without requiring reinjection.

### Where are temporary authentication files stored?

All temporary files—including credential configs, SSH keys, and known-hosts files—are stored in the directory specified by the `RUNNER_TEMP` environment variable. These files are created with restrictive permissions (mode `0600` for SSH keys) and are explicitly deleted during the cleanup phase to prevent credential leakage between jobs or steps.