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:
- Existing system entries: Reads the current
~/.ssh/known_hostsif it exists - User-supplied entries: Appends the value from the
ssh-known-hostsinput - 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-strictistrue, 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
GitAuthHelperinsrc/git-auth-helper.tsorchestrates the entire SSH authentication flow.- The action writes the private key to a temporary file with
0o600permissions 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_COMMANDenvironment variable. - When
persist-credentialsis enabled, the configuration is saved tocore.sshCommandfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →