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>/config file location retrieved via git.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>.path pointing to the credentials file (lines 84-90)
  • Container rule: includeIf.gitdir:<container-git-dir>.path referencing 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/checkout uses src/git-auth-helper.ts to manage submodule credentials through the configureSubmoduleAuth() method (lines 57-130)
  • The system creates a temporary shared credentials file under RUNNER_TEMP accessible to both host and container via bind mounts
  • Dual includeIf.gitdir rules target both host paths and container paths (/github/workspace) for each submodule
  • Authentication only persists when persist-credentials is true, with the method exiting early at lines 61-63 if disabled
  • Automatic cleanup via removeAuth() and removeSubmoduleGitConfig() removes all credential files and Git configuration modifications after the step completes
  • Both SSH (core.sshCommand) and HTTPS (insteadOf rewriting) 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:

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 →