Skip to content
Shell & TerminalDeep Dive Published Updated 9 min readViews unavailable

Git Credential Helpers: Keep Secrets Out of Shell Command Text

Configure Git credential helpers with clear scope and storage lifetime, and implement the stdin/stdout protocol without shell evaluation or secret leakage.

Git credential helpers let Git obtain or store authentication data without asking the user to type it for every operation. They are ordinary programs launched by Git, and their interface is a small line-oriented protocol over standard input and output. This makes helpers easy to integrate with an operating-system keychain, a short-lived cache, or a credential broker. It also means that helper configuration is executable behavior and that secrets can leak through careless shell tracing, debug output, or a plaintext storage choice.

The correct design separates helper selection, credential storage, and shell parsing. Choose a helper appropriate to the machine and threat model; scope credentials to the right host or path; and treat credential values as data. Do not build a shell command by concatenating a token, call eval, or print git credential fill output into logs. The upstream Git documentation is the authority for the helper protocol and configuration syntax; check it with the Git version installed by the job.

What Git sends to a credential helper

Git asks a helper to perform an operation such as get, store, or erase. The helper receives a credential description on standard input in a blank-line-terminated key/value format. A get helper may return attributes such as username and password on standard output. Git combines responses according to its configured helper list and may ask for credentials interactively if no helper supplies them.

The protocol is not a shell program. A helper must parse lines as fields, stop at the blank separator, and avoid interpreting values as commands. A password can contain characters that are meaningful to a shell, regular expression, or configuration syntax. The protocol’s delimiters and escaping rules should be implemented exactly as documented; do not parse it with eval, source, or sh -c.

For example, a helper should conceptually do this:

  1. Read key/value records from standard input until the protocol terminator.
  2. Parse the credential context, such as protocol, host, path, and username.
  3. Ask an approved secure store for a matching credential.
  4. Write only recognized response attributes to standard output, followed by the required blank line.
  5. Send diagnostics to standard error without including passwords, tokens, or full secret-bearing records.

This separation matters because Git consumes helper stdout as protocol data. A debug printf on stdout can corrupt the response. Conversely, a secret printed to stderr may still be captured in CI logs. Keep standard output protocol-only and redact or suppress secret values in all diagnostics.

Select a helper by storage lifetime and platform

Git documents built-in cache and store helpers, as well as common operating-system-specific credential managers. A cache helper holds credentials in memory for a bounded period; it reduces prompts without writing the secret to a long-lived plaintext file. The store helper persists credentials on disk in a file and is generally not appropriate when the filesystem is accessible to other users or when the credential must remain confidential. Prefer an OS-backed keychain or a vetted credential manager when available.

Configure the helper with the exact name installed on the host:

git config --global credential.helper manager

The helper name shown here is illustrative; verify the actual executable installed by the platform’s Git distribution. On macOS, the system keychain helper is often named osxkeychain; Linux installations may use a Secret Service integration; Windows commonly uses Git Credential Manager or a Windows credential helper. Availability and names differ by distribution and Git packaging, so inspect git help -a or platform documentation instead of assuming the helper exists.

The cache helper can be configured with a timeout, for example:

git config --global credential.helper 'cache --timeout=900'

The timeout is the cache lifetime, not a server token expiration guarantee. A cached token may expire earlier or be revoked. Do not rely on the helper as the only source of credential validity; handle an authentication rejection by invalidating or replacing the credential through the approved provider flow.

Avoid credential.helper store for secrets unless you have deliberately accepted its plaintext storage model and protected the file and backup copies accordingly. File mode 0600 prevents ordinary other-user reads but does not encrypt the content against the account owner, malware running as that user, privileged administrators, or an exposed backup. Never commit a credential file into a repository or put it under a directory shared with build artifacts.

Scope credentials to the intended context

Git can configure credential behavior globally or for a matching URL context. A username can be set per host, and credential.useHttpPath controls whether the path is considered when matching HTTP credentials. By default, Git commonly treats credentials as host-scoped rather than separate credentials for every repository path. If multiple repositories on one host must use distinct identities, decide whether path-scoping is required and verify the behavior with the helper you use.

git config --global credential.https://code.example.net.username build-bot
git config --global credential.https://code.example.net.useHttpPath true

This sets context metadata, not a password. Review URL matching carefully: a broad host entry may apply to more repositories than intended, and a path prefix can match multiple repositories. Avoid putting a token in a remote URL; URLs can be recorded in .git/config, logs, process arguments, or error messages. Keep authentication material in the credential provider and use a non-secret remote URL.

Configuration can come from system, global, local repository, worktree, included files, and command-line overrides. A repository-level configuration may be controlled by the repository contents or by whoever can edit its metadata. Review effective configuration with care before running Git in an untrusted checkout. git config --show-origin --get-all credential.helper identifies where helper entries came from, but only run such diagnostic commands in a controlled log because other configuration values can contain sensitive paths or commands.

Helper configuration can execute a shell command

Git transforms a helper name into an executable name, usually by adding the git-credential- prefix. An absolute path can specify a program directly. If a helper string begins with !, Git treats the remainder as a shell snippet. Helper strings can also include arguments that are parsed for command execution. This is a powerful extension, but it makes helper configuration code, not inert metadata.

For example, this is a shell command:

git config --global credential.helper '!/usr/local/libexec/git-credential-company'

The helper executable should be owned and writable only by an appropriate administrator or the account that intentionally controls the credentials. Do not copy an unexplained ! helper from a repository README and run it before reviewing the code. A malicious repository config could try to select an attacker-controlled helper when a developer or CI job performs an authenticated Git operation.

Prefer installing a named helper as a reviewed executable on PATH or configuring an absolute trusted path. Avoid shell snippets that interpolate usernames, passwords, remote URLs, or tokens. If a wrapper needs to choose among fixed actions, use a validated case statement and pass data through the documented protocol rather than constructing source text.

Implementing a custom helper without eval

A custom helper receives its operation as an argument and the credential description on stdin. It should handle only operations it supports and should not assume field order. The following sketch illustrates dispatch while intentionally leaving secret storage to a vetted provider:

#!/bin/sh
set -eu

operation=${1:?missing credential operation}
case $operation in
  get|store|erase) ;;
  *) exit 0 ;;
esac

exec /usr/local/libexec/credential-broker "$operation"

The broker should parse the key/value protocol safely and use a keychain or credential service. The wrapper does not read and re-emit credentials, so it avoids accidental logging or ad hoc parsing. If the helper must parse the protocol itself, use a real parser with an explicit field allowlist, reject malformed records, handle duplicate keys deliberately, and never evaluate a value as shell syntax.

A get operation can return a quit=true attribute to stop additional helpers and prompting, but that should be used only when the helper intentionally owns the full policy. A helper that returns only a username should allow later providers to supply the secret. For store and erase, output is ignored by Git, but diagnostics still belong on stderr and should not reveal secrets.

Credentials may arrive in URLs, environment variables, command-line arguments, or standard input from other tools. Reduce copies and lifetime. Do not enable set -x around protocol handling; shell xtrace prints expanded command arguments and can expose values. Avoid redirecting helper input to a trace file. Ensure temporary files, if unavoidable, are created with restrictive permissions, removed on every exit path, and not stored in a shared workspace artifact.

Understand helper ordering and reset behavior

When multiple credential.helper values are configured, Git can consult them in order until it has the needed information. It can send store or erase operations through the configured helpers as well. An empty helper value resets the helper list inherited from lower-priority configuration, which can be useful in a narrowly scoped config but can also surprise an operator auditing only the global file.

Inspect the effective list for the exact repository and URL context. Do not assume git config --global is the only source. A worktree-specific setting, system config, included file, environment override, or -c argument may change behavior. Build tooling that runs Git from untrusted directories should use an isolated config directory or explicitly set the intended helper, remote, and safe directory policy.

Credential helpers do not authorize a Git operation. The server decides whether a credential can read, write, or administer a repository. Use short-lived and least-privilege tokens with repository and organization scope limited to the job. Revoke them on incident or worker retirement. A helper reduces repeated manual entry; it does not improve a token that is overprivileged or never expires.

Test without printing secrets

Test helper behavior against a disposable account or a local fake credential service. Verify get, store, and erase; test a username-only response, no match, expired credential, malformed input, unknown operation, and a password containing spaces or punctuation. Assert protocol bytes and exit status while redacting values in the test harness. Do not paste real git credential fill output into a terminal capture or issue report: it may include the password in plaintext.

For repository diagnostics, record helper names and their config origins, not secret values. Check that the intended helper binary resolves to the expected path, that its parent directories are trusted, and that it is available on the noninteractive CI PATH. Test authentication from the same job identity and environment used in production; an interactive desktop keychain may not be unlocked in a service account session.

If a helper hangs, set a bounded timeout in the surrounding job and investigate whether it is waiting for an unavailable prompt or desktop service. Noninteractive workflows should fail clearly when credentials cannot be retrieved. Avoid silently falling back to a plaintext file or a token embedded in a remote URL just to make the job green.

The core contract is compact: Git sends a structured request over stdin, helpers return structured attributes over stdout, and configuration selects executable code. Keep each part visible. Choosing a suitable storage provider, controlling configuration provenance, and keeping the protocol out of shell evaluation prevents the credential helper from becoming an accidental secret-leak path.

Related:

Sources:


Comments