Git HTTPS Access Token from an Environment Variable (Without Storing It)
Use a token from an environment variable for Git HTTPS remotes via GIT_CONFIG_COUNT and a credential helper, so it never lands in .git/config.
To let a container, CI job or service pull and push over HTTPS with an access token, without the token ending up in the remote URL, .git/config or a credentials file, supply it through a credential helper defined entirely in environment variables.
The usual shortcuts leak the token:
https://<api-token>@github.com/<owner>/<repo>.gitis saved in.git/configand appears ingit remote -voutput and some error messages.git config credential.helper storewrites it in plain text to~/.git-credentials.
How it works
Since Git 2.31, Git reads extra configuration from GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n>. These settings apply only to processes that inherit the environment, and nothing is written to disk. A credential.helper value that starts with ! is run as a shell snippet: Git calls it with get and reads username= and password= lines from its output.
Fix
Set these variables in the environment of whatever runs Git:
export GIT_TOKEN="<api-token>"
export GIT_TERMINAL_PROMPT=0
export GIT_CONFIG_COUNT=2
export GIT_CONFIG_KEY_0=credential.helper
export GIT_CONFIG_VALUE_0=
export GIT_CONFIG_KEY_1=credential.helper
export GIT_CONFIG_VALUE_1='!f() { test "$1" = get || exit 0; echo username=x-access-token; echo "password=$GIT_TOKEN"; }; f'
What each part does:
- The empty first value clears any helpers inherited from the system or global config (such as a desktop keychain helper), so only this helper is asked.
- The helper refers to
$GIT_TOKENinstead of containing the token, sogit config --listnever shows the secret. test "$1" = get || exit 0ignores thestoreanderasecalls Git makes after a request succeeds or fails.GIT_TERMINAL_PROMPT=0makes Git fail instead of waiting for a password prompt that no one can answer.
The remote stays a plain URL:
git remote add origin https://github.com/<owner>/<repo>.git
git push origin main
From a program
When a service runs Git as a subprocess, pass the variables in that subprocessβs environment only:
import os
import subprocess
HELPER = (
'!f() { test "$1" = get || exit 0; '
'echo "username=$GIT_USERNAME"; echo "password=$GIT_TOKEN"; }; f'
)
env = os.environ | {
"GIT_TERMINAL_PROMPT": "0",
"GIT_USERNAME": "x-access-token",
"GIT_TOKEN": token,
"GIT_CONFIG_COUNT": "2",
"GIT_CONFIG_KEY_0": "credential.helper",
"GIT_CONFIG_VALUE_0": "",
"GIT_CONFIG_KEY_1": "credential.helper",
"GIT_CONFIG_VALUE_1": HELPER,
}
result = subprocess.run(
["git", "push", "origin", "main"],
cwd=repo_path, env=env, capture_output=True, text=True,
)
if result.returncode != 0:
# Redact before logging, in case the token appears in an error.
raise RuntimeError(result.stderr.replace(token, "***"))
Verify
Ask Git which credentials it would use:
printf 'protocol=https\nhost=github.com\n\n' | git credential fill
The output should include username=x-access-token and the token as password=. Then confirm where the helper comes from and that nothing was stored:
git config --show-origin --get-all credential.helper # helper listed with origin "command line:"
grep -c "<part-of-token>" .git/config # 0
Notes
- Requires Git 2.31 or later. Tested with Git 2.43 and 2.54.
- For GitHub, a fine-grained personal access token limited to one repository, with Contents read and write, is enough to pull and push. GitHub should accept any non-empty username with a token;
x-access-tokenis the conventional value. - Every child process inherits these variables. If other code runs in the same environment, set them only on the Git subprocess, as in the Python example.
- For SSH remotes, the equivalent is a deploy key: write the private key to a file readable only by its owner and set
GIT_SSH_COMMAND="ssh -i <key-file> -o IdentitiesOnly=yes".