# How actions/checkout Manages and Secures SSH Keys in GitHub Actions

> Learn how actions/checkout securely manages and protects SSH keys in GitHub Actions. Discover its temporary file handling, environment variable injection, and automatic cleanup for robust security.

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

---

**The `actions/checkout` action secures SSH keys by writing them to temporary files with restrictive 600 permissions (or Windows ACLs), injecting them into a custom `GIT_SSH_COMMAND` environment variable, and automatically destroying all credential files and Git configuration during the POST cleanup phase.**

The official `actions/checkout` action supports cloning private repositories via SSH through four dedicated inputs—`ssh-key`, `ssh-known-hosts`, `ssh-strict`, and `ssh-user`. Understanding how this process handles sensitive private key material is critical for securing CI/CD pipelines. This article traces the complete lifecycle of SSH credentials through the action's TypeScript source code, from initial input validation in [`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts) to guaranteed removal during the post-run cleanup.

## SSH Authentication Inputs and Configuration

SSH authentication is triggered when you provide the `ssh-key` input in your workflow. The action collects four related inputs in **[`src/input-helper.ts`](https://github.com/actions/checkout/blob/main/src/input-helper.ts)** (lines 60-64) and stores them in a `GitSourceSettings` object defined in **[`src/git-source-settings.ts`](https://github.com/actions/checkout/blob/main/src/git-source-settings.ts)**.

The supported inputs are:

- **`ssh-key`** – The raw private key material (typically from `secrets.SSH_PRIVATE_KEY`)
- **`ssh-known-hosts`** – Additional known hosts entries to trust
- **`ssh-strict`** – Whether to enable strict host key checking (defaults to `true`)
- **`ssh-user`** – The SSH username (defaults to `git`)

When `configureAuth()` is invoked during checkout, the action creates a `GitAuthHelper` instance in **[`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)** (lines 27-32). This helper orchestrates the secure handling of credentials throughout the job lifecycle.

## Secure Key Storage in Temporary Files

The `configureSsh()` method in **[`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)** handles the physical storage of your private key. Rather than persisting credentials to disk permanently, the action creates ephemeral files in the runner's temporary directory (`$RUNNER_TEMP`).

On Unix systems, the key is written with mode `0o600` (read/write for owner only):

```typescript
// src/git-auth-helper.ts, lines 55-66
await fs.promises.writeFile(
  this.sshKeyPath,
  this.settings.sshKey.trim() + '\n',
  {mode: 0o600}
);

```

On Windows, the action explicitly restricts access using `icacls.exe` to remove inherited permissions and grant access only to the current user (lines 68-75). This ensures the private key is never world-readable, even on Windows runners where POSIX permissions don't apply.

The paths to these temporary files are immediately saved to the action state via **[`src/state-helper.ts`](https://github.com/actions/checkout/blob/main/src/state-helper.ts)** (lines 19-22) using `saveState()`:

```typescript
// State variables stored for POST phase cleanup
export const SshKeyPath = 'sshKeyPath'
export const SshKnownHostsPath = 'sshKnownHostsPath'

```

## Known Hosts Verification and MITM Protection

Before constructing the SSH command, the action builds a comprehensive known-hosts file to prevent man-in-the-middle attacks. In **[`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)** (lines 78-96), the `configureSsh()` method concatenates three sources:

1. **Existing user known hosts** – Contents of `~/.ssh/known_hosts` if it exists
2. **Input-provided hosts** – Entries from the `ssh-known-hosts` input
3. **Hard-coded GitHub host keys** – A hardcoded set of GitHub.com SSH host keys injected automatically

This composite file is written adjacent to the temporary key file, and its location is stored in the action state (lines 97-100). By default, the action enforces strict host key checking unless disabled.

## Environment Variable and Git Configuration

With the temporary files secured, the action constructs a custom `GIT_SSH_COMMAND` environment variable. In **[`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)** (lines 101-113), the command is built to reference the temporary key and known-hosts files:

```typescript
// Command construction with strict checking
this.sshCommand = `"${sshPath}" -i "${this.sshKeyPath}"`;
if (this.settings.sshStrict) {
  this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no';
}
this.sshCommand += ` -o "UserKnownHostsFile=${this.sshKnownHostsPath}"`;
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand);

```

If `persist-credentials` is set to `true`, the action persists this configuration into the repository's local Git config under `core.sshCommand` (lines 115-118). This allows subsequent Git operations—such as submodule updates or fetch commands—to reuse the same SSH authentication without requiring the key to be exposed again:

```typescript
if (this.settings.persistCredentials) {
  await this.git.config('core.sshCommand', this.sshCommand);
}

```

## Automatic Cleanup in the POST Phase

Security guarantees are fulfilled during the POST phase when `removeAuth()` calls `removeSsh()` in **[`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts)** (lines 36-46). This method retrieves the temporary file paths from the action state (saved earlier via [`state-helper.ts`](https://github.com/actions/checkout/blob/main/state-helper.ts)) and performs guaranteed cleanup:

```typescript
// POST cleanup removes all credential material
await io.rmRF(this.sshKeyPath || stateHelper.SshKeyPath);
await io.rmRF(this.sshKnownHostsPath || stateHelper.SshKnownHostsPath);
await this.removeGitConfig('core.sshCommand');
await this.removeSubmoduleGitConfig('core.sshCommand');

```

This ensures no private key material persists on the runner after the job completes, even if the workflow fails or is cancelled. The cleanup explicitly targets both the main repository and any submodule configurations that might have inherited the SSH settings.

## Summary

The `actions/checkout` SSH security model follows a strict ephemeral workflow:

- **Input isolation** – Keys are accepted via the `ssh-key` input and processed by `GitAuthHelper` without logging
- **Filesystem protection** – Temporary files are created with `600` permissions (Unix) or restricted Windows ACLs
- **Defense in depth** – Known hosts verification combines user-defined entries with hardcoded GitHub keys to prevent MITM attacks
- **Process isolation** – Credentials are injected via `GIT_SSH_COMMAND` environment variables rather than persistent SSH agent configuration
- **Guaranteed cleanup** – The POST phase automatically deletes temporary files and scrubs Git configuration using state persisted via [`state-helper.ts`](https://github.com/actions/checkout/blob/main/state-helper.ts)

## Frequently Asked Questions

### Where does actions/checkout store SSH keys during the workflow?

The action stores SSH keys in temporary files under the `$RUNNER_TEMP` directory, with specific paths saved to the action state via `stateHelper.SshKeyPath`. These files are created by the `configureSsh()` method in [`src/git-auth-helper.ts`](https://github.com/actions/checkout/blob/main/src/git-auth-helper.ts) and are isolated to the specific workflow run.

### What file permissions are set on the temporary SSH key files?

On Unix runners, the action explicitly sets mode `0o600` (read/write for owner only) when writing the key file. On Windows runners, it uses `icacls.exe` to remove inherited permissions and grant access exclusively to the current user, ensuring the key is never readable by other processes or users on the runner.

### Does the SSH key remain available after the checkout step completes?

No, unless you set `persist-credentials: true`. By default, the POST cleanup phase immediately deletes the temporary key files and unsets the `GIT_SSH_COMMAND` environment variable. When persistence is enabled, the key remains available for subsequent Git operations within the same job, but is still deleted during the POST phase when the job ends.

### How does the action prevent man-in-the-middle attacks during SSH authentication?

The action constructs a known-hosts file that includes hardcoded GitHub host keys alongside any user-provided entries. When `ssh-strict` is enabled (the default), it configures SSH with `StrictHostKeyChecking=yes`, causing the connection to fail immediately if the remote host's key does not match the expected values, preventing MITM attacks on the repository clone operation.