How actions/checkout Configures SSH Authentication and Known Hosts

The actions/checkout GitHub Action handles SSH authentication through a dedicated GitAuthHelper class that writes the private key and known-hosts to temporary files, constructs a custom SSH command, and exports it via the GIT_SSH_COMMAND environment variable.

When cloning private repositories or accessing submodules via SSH in GitHub Actions, actions/checkout automatically manages authentication without requiring manual SSH agent setup. Understanding how the action configures SSH authentication and known hosts helps debug connectivity issues and secure self-hosted runner environments.

The GitAuthHelper Architecture

All SSH logic is encapsulated in the GitAuthHelper class located in src/git-auth-helper.ts. When the action receives the optional ssh-key input (and optionally ssh-known-hosts), the main entry point configureAuth() delegates to configureSsh() to set up the authentication context.

The SSH Configuration Pipeline

The configureSsh() method executes a three-phase process to prepare Git for SSH operations.

Step 1: Secure Private Key Storage

configureSsh() generates a unique temporary file under $RUNNER_TEMP and writes the trimmed private key contents with strict Unix permissions (0o600). The file path is stored in the action state as SshKeyPath (defined in src/state-helper.ts), ensuring the post-run cleanup phase can locate and remove the sensitive material.

// From src/git-auth-helper.ts
await fs.promises.writeFile(this.sshKeyPath, `${this.settings.sshKey.trim()}\n`, {mode: 0o600});

Step 2: Building the Known Hosts File

The action constructs a temporary known-hosts file by merging three sources:

  1. Existing system entries: Reads the current ~/.ssh/known_hosts if it exists
  2. User-supplied entries: Appends the value from the ssh-known-hosts input
  3. Default GitHub entry: Always appends a hard-coded RSA fingerprint for github.com

This concatenated content is written to another temporary file under $RUNNER_TEMP, and its path is saved in the action state as SshKnownHostsPath.

Step 3: Injecting the SSH Command

The helper locates the system ssh executable and builds a command string that points to the temporary credentials:

  • Identity file: -i "$RUNNER_TEMP/<keyfile>"
  • Known hosts: -o "UserKnownHostsFile=$RUNNER_TEMP/<hostsfile>"
  • Strict checking: If ssh-strict is true, adds -o StrictHostKeyChecking=yes -o CheckHostIP=no

The resulting command is exported to the GIT_SSH_COMMAND environment variable so all subsequent git operations use the custom configuration.

// Command construction from src/git-auth-helper.ts
this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"` +
  (this.settings.sshStrict ? ' -o StrictHostKeyChecking=yes -o CheckHostIP=no' : '') +
  ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(this.sshKnownHostsPath)}"`;
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand);

Persistence for Submodules and Later Steps

If the persist-credentials input is set to true, the same SSH command is also written to the repository's local Git configuration under core.sshCommand. This ensures that submodule operations or subsequent workflow steps reuse the same authentication context without re-exporting environment variables.

// Persistence logic from src/git-auth-helper.ts
if (this.settings.persistCredentials) {
  await this.git.config('core.sshCommand', this.sshCommand);
}

Automatic Cleanup

During the post-job phase, the action retrieves the stored SshKeyPath and SshKnownHostsPath from the state (via src/state-helper.ts) and invokes removeSsh() to securely delete the temporary files, preventing credential leakage between jobs on self-hosted runners.

Configuration Example

Use the following workflow syntax to enable SSH authentication with strict host checking for a self-hosted Git server:

steps:
  - uses: actions/checkout@v4
    with:
      ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
      ssh-known-hosts: |
        selfhost.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE...
      ssh-strict: true
      persist-credentials: true

Summary

  • GitAuthHelper in src/git-auth-helper.ts orchestrates the entire SSH authentication flow.
  • The action writes the private key to a temporary file with 0o600 permissions and tracks it via action state.
  • Known hosts are assembled from the system, user input, and a hard-coded GitHub fingerprint, then stored in a temporary file.
  • Git operations receive the custom SSH configuration through the GIT_SSH_COMMAND environment variable.
  • When persist-credentials is enabled, the configuration is saved to core.sshCommand for submodule support.
  • Temporary files are automatically removed during the post-job cleanup phase.

Frequently Asked Questions

Where does actions/checkout store the SSH private key?

The action writes the private key to a uniquely named temporary file under $RUNNER_TEMP with strict permissions (0o600). The path is stored in the action state as SshKeyPath (defined in src/state-helper.ts) so the post-run cleanup can locate and delete it.

How does actions/checkout handle known hosts for GitHub.com?

The action automatically appends a hard-coded RSA fingerprint for github.com to the known-hosts file. This default entry is concatenated with any existing entries from ~/.ssh/known_hosts and additional hosts provided via the ssh-known-hosts input.

Can I use actions/checkout with a self-hosted Git server?

Yes. Provide your server's host key via the ssh-known-hosts input and your private key via the ssh-key input. The action will include your custom host entries alongside the default GitHub entry in the temporary known-hosts file.

What is the difference between the ssh-strict and persist-credentials inputs?

The ssh-strict input adds -o StrictHostKeyChecking=yes to the SSH command, forcing strict host key verification. The persist-credentials input saves the constructed SSH command to the Git config key core.sshCommand, ensuring that submodule operations or later workflow steps reuse the same authentication configuration.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →