Git credential helpers and CI/CD token management¶
Automated continuous integration runners, Docker build containers, and developer workstations require secure access to remote Git repositories. Embedding static personal access tokens into repository clone URLs causes tokens to leak into build logs, shell history, .git/config files, and shared process tables.
Git credential helpers solve this problem by decoupling repository URLs from authentication secrets. Git queries configured helpers on demand during network operations such as git fetch, git pull, and git push.
The Git credential helper protocol¶
Git communicates with credential helpers over standard input and standard output using a line-oriented key-value protocol. Each line contains a key, an equals sign, and a value. An empty line signals the end of the payload.
Supported protocol keys¶
| Key | Description |
|---|---|
protocol |
The network scheme (usually https or http). |
host |
Hostname and optional port (for example git.local.sneakysquid.xyz or github.com). |
path |
Repository path (for example hermes/git-master.git). Git sends this when credential.useHttpPath is true. |
username |
The authentication user account or token identifier. |
password |
The plaintext password, personal access token, or OAuth bearer token. |
password_expiry_utc |
Unix epoch timestamp indicating when the token expires (supported in Git 2.40+). |
quit |
Tells Git to stop querying downstream helpers in the chain if set to 1 or true. |
Helper actions¶
Git executes a helper executable with one of three positional arguments:
get: Git prompts the helper to provide credentials. The helper reads repository details from standard input and writesusername=...andpassword=...to standard output.store: Git notifies the helper that authentication succeeded. The helper records the credentials in persistent storage if supported.erase: Git notifies the helper that authentication failed. The helper purges the invalid credentials from its storage.
Built-in credential helpers¶
Git provides several built-in helper backends.
1. In-memory cache helper (git-credential-cache)¶
The cache helper stores credentials in a background daemon memory cache using Unix domain sockets. It never writes credentials to disk.
# Cache credentials in memory for 1 hour (3600 seconds)
git config --global credential.helper "cache --timeout=3600"
# Specify a custom socket path for isolated CI containers
git config --global credential.helper "cache --timeout=1800 --socket=/tmp/git-cache.sock"
To clear cached credentials immediately, run:
2. Disk store helper (git-credential-store)¶
The store helper saves credentials in an unencrypted flat file (default ~/.git-credentials).
# Configure standard store helper
git config --global credential.helper "store --file ~/.git-custom-credentials"
The file stores entries in standard URL format:
Lock file permissions to owner read/write only:
3. Operating system secret services¶
Production workstations should use native system keychains rather than unencrypted flat files:
# Linux (libsecret / GNOME Keyring / KWallet)
git config --global credential.helper libsecret
# macOS Keychain
git config --global credential.helper osxkeychain
# Windows Credential Manager
git config --global credential.helper wincred
Scoped credential configuration¶
You can scope credential helpers to specific domains, subdomains, or individual repository paths instead of applying a single helper globally.
# Global fallback
[credential]
helper = cache --timeout=300
# Scoped to internal Forgejo instance
[credential "https://git.local.sneakysquid.xyz"]
helper = store --file=/secrets/forgejo.creds
useHttpPath = false
# Scoped to specific GitHub organization
[credential "https://github.com/my-enterprise-org"]
helper = "!/usr/local/bin/vault-token-helper"
useHttpPath = true
When multiple helpers exist for a scope, Git executes them in order until one returns valid credentials.
Custom credential helpers for enterprise CI/CD¶
In automated pipelines, build agents can fetch dynamic short-lived tokens from secret managers (such as HashiCorp Vault, AWS Secrets Manager, or Kubernetes secrets) just in time.
Custom helper implementation in Bash¶
Create a standalone executable /usr/local/bin/ci-vault-git-helper:
#!/usr/bin/env bash
set -euo pipefail
# Read key-value pairs from standard input
declare -A INPUT_VARS
while IFS='=' read -r key value; do
[[ -z "$key" ]] && break
INPUT_VARS["$key"]="$value"
done
ACTION="${1:-}"
case "$ACTION" in
get)
TARGET_HOST="${INPUT_VARS[host]:-}"
TARGET_PATH="${INPUT_VARS[path]:-}"
# Fetch dynamic token from Vault or environment
if [[ "$TARGET_HOST" == "git.local.sneakysquid.xyz" ]]; then
TOKEN="${FORGEJO_TOKEN:-}"
if [[ -z "$TOKEN" && -f "/var/run/secrets/tokens/forgejo" ]]; then
TOKEN="$(cat /var/run/secrets/tokens/forgejo)"
fi
if [[ -n "$TOKEN" ]]; then
echo "username=oauth2"
echo "password=${TOKEN}"
echo "quit=true"
fi
fi
;;
store)
# Dynamic CI tokens are read-only; no-op on store
;;
erase)
# Optionally log or alert on failed authentication
;;
*)
exit 0
;;
esac
Make the script executable:
Register the script with Git using the exclamation mark syntax:
The exclamation mark ! tells Git to run the command through the system shell rather than searching Git's built-in exec-path.
CI/CD runner authentication patterns¶
DevOps engineers use three distinct patterns to authenticate Git on ephemeral build agents.
Pattern 1: Dynamic HTTP extra header (zero storage)¶
The cleanest pattern injects an HTTP Authorization header directly into git commands using -c. This bypasses the credential helper system entirely and leaves zero traces on disk.
# Forgejo / Gitea token injection
git -c http.extraHeader="Authorization: token ${FORGEJO_TOKEN}" clone https://git.local.sneakysquid.xyz/hermes/git-master.git
# GitHub / GitLab Bearer token injection
git -c http.extraHeader="Authorization: Bearer ${CI_JOB_TOKEN}" fetch origin
To configure this for an entire pipeline step in GitHub Actions or Forgejo Actions:
git config --global http."https://git.local.sneakysquid.xyz/".extraHeader "Authorization: token ${FORGEJO_TOKEN}"
Pattern 2: Global URL rewriting (insteadOf)¶
You can rewrite repository URLs transparently to include basic authentication tokens.
git config --global url."https://oauth2:${FORGEJO_TOKEN}@git.local.sneakysquid.xyz/".insteadOf "https://git.local.sneakysquid.xyz/"
Any subsequent call to git clone https://git.local.sneakysquid.xyz/repo.git automatically uses the rewritten authenticated URL.
Note: If git commands log their invoked URLs, the token will appear in logs. Use Pattern 1 when log masking is unavailable.
Pattern 3: Ephemeral in-memory helper injection¶
For multi-step pipelines requiring submodules and multiple remote fetches, initialize a memory cache before steps run:
# Start isolated daemon socket
SOCKET_PATH="/tmp/ci-git-cache-$$.sock"
git config credential.helper "cache --timeout=900 --socket=${SOCKET_PATH}"
# Pre-populate credentials
printf "protocol=https\nhost=git.local.sneakysquid.xyz\nusername=oauth2\npassword=%s\n\n" "$FORGEJO_TOKEN" | git credential approve
Clean up at job completion:
Debugging and testing credential helpers¶
You can test credential helpers directly on the terminal without triggering network operations.
Testing credential resolution (get / fill)¶
Expected output:
Manually storing credentials (store / approve)¶
printf "protocol=https\nhost=git.local.sneakysquid.xyz\nusername=devops\npassword=mysecret\n\n" | git credential approve
Manually clearing credentials (erase / reject)¶
Tracing network authentication¶
To inspect the raw HTTP headers and TLS handshake without printing tokens:
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git ls-remote https://git.local.sneakysquid.xyz/hermes/git-master.git
Git masks Authorization: Basic ... and Authorization: Bearer ... headers in trace outputs by default.
Security auditing and leak remediation¶
Embedding passwords or tokens inside .git/config is a critical vulnerability. If a repository directory is shared, archived, or exposed via a web server misconfiguration, those credentials leak immediately.
1. Audit repository for embedded credentials¶
Check local and global configuration for plaintext tokens:
# Check remote URLs in local config
git config --local --get-regexp '^remote\..*\.url$' | grep -E 'https?://[^:]+:[^@]+@'
# Check for hardcoded extraHeader credentials
git config --list --show-origin | grep -i 'extraHeader'
2. Sanitize repository remote URLs¶
Strip embedded credentials and replace them with standard HTTPS URLs: