How actions/checkout Handles Credentials for Submodules in Containers
The actions/checkout action injects authentication tokens into submodules by creating a temporary shared credentials file and adding conditional includeIf.gitdir rules to both host-side and container-side Git configurations, ensuring seamless authentication whether Git commands run on the runner or inside a Docker container.
When running GitHub Actions jobs inside Docker containers, accessing private submodules requires careful credential propagation across filesystem boundaries. The actions/checkout action solves this through a sophisticated authentication helper that mirrors security tokens between the host runner and the containerized environment. According to the actions/checkout source code, this process centers on the configureSubmoduleAuth() method in src/git-auth-helper.ts, which dynamically generates Git configuration entries for both execution contexts.
The Submodule Authentication Architecture
Entry Point in git-auth-helper.ts
All submodule credential handling flows through GitAuthHelper.configureSubmoduleAuth() in src/git-auth-helper.ts (lines 57-130). This method orchestrates the creation of temporary credential files and the injection of Git configuration directives that apply conditionally based on the repository's location, ensuring that authentication material is available regardless of where Git commands execute.
Dual-Path Configuration Strategy
The helper operates on two distinct filesystem perspectives simultaneously. It identifies host-side Git configuration paths using git.getSubmoduleConfigPaths() (lines 71-74), while mapping these to container-specific locations under /github/workspace (lines 196-203). This dual-path approach ensures that Git commands execute correctly whether they run on the runner host or inside the containerized environment, with both configurations pointing to the same physical credential file through bind mounts.
Step-by-Step Credential Injection Process
1. Removal of Stale Configuration
Before injecting new credentials, the helper sanitizes existing Git configurations by removing previous insteadOf entries that might interfere with authentication (lines 57-60). This prevents credential leakage or conflicts from earlier workflow steps or previous checkouts.
2. Persist-Credentials Validation
The entire submodule authentication flow is gated by the persist-credentials input parameter. When set to false, the method exits immediately without writing sensitive data to disk (lines 61-63). By default, this value is true, enabling credential persistence for subsequent Git operations.
3. Shared Credentials File Creation
The helper generates a temporary file under RUNNER_TEMP named git-credentials-<uuid>.config (lines 24-30). This file contains the HTTP extra-header with the GitHub token and is designed to be accessible from both the host and the container via the runner's bind mount architecture (lines 33-40).
4. Host and Container Path Resolution
For each submodule, the helper determines two critical paths:
- Host path: The actual
.git/modules/<name>/configfile location retrieved viagit.getSubmoduleConfigPaths()(lines 71-74) - Container path: A POSIX-constructed path mirroring the workspace structure:
const containerSubmoduleGitDir = path.posix.join(
'/github/workspace',
relativeSubmoduleGitDir
)
This mapping appears at lines 196-203 in src/git-auth-helper.ts, translating host filesystem locations to the container's /github/workspace mount point.
5. Conditional IncludeIf Rules
The method inserts two includeIf.gitdir directives for every submodule configuration:
- Host rule:
includeIf.gitdir:<host-git-dir>.pathpointing to the credentials file (lines 84-90) - Container rule:
includeIf.gitdir:<container-git-dir>.pathreferencing the same file via/github/runner_temp(lines 105-112)
These conditional includes ensure Git only loads credentials when operating within specific submodule directories, preventing token leakage to unrelated repositories.
6. Protocol-Specific Handling
For SSH-based submodules, the helper configures core.sshCommand for each submodule using git submodule foreach. For HTTPS repositories, it creates URL rewrite rules using insteadOf to convert SSH URLs to HTTPS equivalents (lines 122-128). This applies to both host and container contexts, ensuring consistent behavior regardless of the transport protocol.
7. Secure Cleanup
When the checkout step completes, removeAuth() and removeSubmoduleGitConfig() delete the temporary credentials file and strip all includeIf entries from submodule configurations (lines 72-78, 132-138). This ensures no authentication material persists beyond the job's execution, preventing credential leakage to subsequent workflow steps or different jobs.
Configuration Requirements
To leverage this functionality in containerized workflows, configure your action with the appropriate inputs:
# .github/workflows/example.yml
name: Checkout with submodules in Docker
on: [push]
jobs:
test:
runs-on: ubuntu-latest
container: node:18 # any Docker container
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true # fetch submodules
persist-credentials: true # keep auth for submodule ops (default)
# optional: ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
The TypeScript implementation simplifies to these key calls:
// Inside the action (simplified)
await authHelper.configureAuth(); // configure host SSH/HTTPS token
await authHelper.configureSubmoduleAuth(); // adds includeIf for submodules
// Git submodule commands now use the same token, even inside the container
await exec.exec('git', ['submodule', 'update', '--init', '--recursive']);
Summary
actions/checkoutusessrc/git-auth-helper.tsto manage submodule credentials through theconfigureSubmoduleAuth()method (lines 57-130)- The system creates a temporary shared credentials file under
RUNNER_TEMPaccessible to both host and container via bind mounts - Dual
includeIf.gitdirrules target both host paths and container paths (/github/workspace) for each submodule - Authentication only persists when
persist-credentialsistrue, with the method exiting early at lines 61-63 if disabled - Automatic cleanup via
removeAuth()andremoveSubmoduleGitConfig()removes all credential files and Git configuration modifications after the step completes - Both SSH (
core.sshCommand) and HTTPS (insteadOfrewriting) protocols are supported for submodule authentication
Frequently Asked Questions
Does actions/checkout handle submodules differently in containers versus on the host?
No, the authentication mechanism operates transparently across both environments. The helper generates parallel Git configuration entries for host-side paths and container-side paths (under /github/workspace), ensuring the same credential file is referenced regardless of where Git commands execute. The includeIf.gitdir directives handle path matching automatically based on the current working directory.
What happens if I set persist-credentials to false?
When persist-credentials is set to false, the configureSubmoduleAuth() method exits immediately after the initial validation check (lines 61-63). No temporary credential files are created under RUNNER_TEMP, and no includeIf entries are written to submodule configurations. Consequently, subsequent Git operations requiring authentication against private submodules will fail with permission errors.
How does the action handle SSH keys versus HTTPS tokens for submodules?
If you provide an ssh-key input, the helper configures core.sshCommand for each submodule using git submodule foreach, enabling SSH-based authentication. For HTTPS-based authentication (the default when no SSH key is provided), the helper creates Git URL rewrite rules using insteadOf to convert SSH URLs to HTTPS equivalents, applying these rules to both host and container Git configurations (lines 122-128).
Where are the temporary credential files stored?
The action creates files named git-credentials-<uuid>.config in the directory specified by the RUNNER_TEMP environment variable (lines 24-30). These files contain the HTTP authentication headers and are mounted into containers at /github/runner_temp, allowing both environments to reference the same physical file through different absolute paths while maintaining restrictive file permissions.
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 →