Git Credentials Helper
Use dotsecenv as a git credential helper. Tokens for HTTPS remotes are encrypted at rest in your vault instead of sitting in plaintext ~/.git-credentials, and instead of the OS keychain once you point git at dotsecenv alone.
The same helper backs two workflows. In the first, git asks for a username and password once and dotsecenv keeps the token encrypted, replacing credential.helper store or osxkeychain. In the second, you pair it with a generator like git-credential-oauth: git keeps a short-lived access token in play and dotsecenv holds the refresh token that mints the next one, so no long-lived personal access token ever lands on disk. Both the access token and the refresh token are stored, encrypted; the access token is the one that expires on its own.
The OAuth workflow needs git 2.41 or newer. That is the first release where oauth_refresh_token survives a round trip through a storage helper. Older git drops the field without saying anything, so the generator opens a browser on every fetch. Ubuntu 22.04 ships 2.34 and Debian 12 ships 2.39. A stored token works on any version.
How It Works
Section titled “How It Works”git calls the helper with three operations and exchanges key=value attributes on stdin/stdout:
| Operation | git sends | dotsecenv behavior |
|---|---|---|
get |
protocol, host, path, sometimes username | Returns the stored credential, or nothing when none matches |
store |
the full credential | Saves the credential fields as one encrypted JSON secret |
erase |
protocol, host, path | Marks the secret deleted (idempotent) |
The helper derives the secret name from the request context. https://gitlab.com becomes the vault key git::HTTPS.GITLAB_DOT_COM_END. Characters that git hosts carry but dotsecenv keys reject are encoded (. to _DOT_, / to _SLASH_, - to _DASH_, : to _COLON_, and _ itself to _UND_), so hyphenated hosts, ports, IPv6 literals, and IP addresses all resolve to a valid key. Each part of the context is encoded on its own and the parts are joined with a dot, which is the one character the encoder never produces, so a path can never be mistaken for an account. The _END suffix keeps the name off a trailing underscore, which dotsecenv rejects.
Because store keeps every credential attribute git sends, the OAuth fields oauth_refresh_token and password_expiry_utc survive a round trip. That is all a generator helper needs to refresh an expired access token without a browser. An ephemeral credential is the exception: git marks those as valid for one request, and the helper drops them rather than storing a value that is already dead.
By default the key covers protocol and host, which is what git sends. Turning on credential.useHttpPath adds the repository path, so each repository gets its own credential.
A repository keeps one entry however you spell the remote. git strips a trailing slash before the helper sees the path, and the helper drops a trailing .git, so https://host/team/api and https://host/team/api.git share a credential rather than prompting separately the way credential.helper store does.
Path scoping and the username scoping below solve different problems and compose: one credential per repository, one credential per account.
Two accounts on one host
Section titled “Two accounts on one host”The key does not include the username, so a second account on the same host would overwrite the first. The helper refuses to paper over that: when git names the account it wants and the stored record belongs to someone else, get returns nothing and git prompts.
To keep both, give each account its own key:
git config --global credential.dotsecenv.useUsername trueThis is off by default because git often asks without naming an account. A plain git clone https://host/repo sends no username, and with scoping on, that request finds nothing and prompts. Turn it on when you actually have two accounts on one host, and put the username in the remote URL (https://alice@host/repo) so git always names it.
Where credentials are stored
Section titled “Where credentials are stored”The helper does not pick a vault itself. Vault selection follows your dotsecenv configuration, exactly like any other dotsecenv secret command. The default config lists the repo-local .dotsecenv/vault first and your home vault (~/.local/share/dotsecenv/vault) second.
Inside a repository that carries a committed vault, that vault is a resolvable target: storing a credential there encrypts it to the vault’s recipients and ships it with the repo. This is by design — it is how a team shares a deploy token or CI credential alongside the code that needs it.
When more than one configured vault resolves, dotsecenv asks you to pick one on store and erase. Git runs the helper with your terminal attached, so the picker works during a normal git push. Without a terminal (a GUI client, CI), the operation fails with multiple vaults configured and no terminal available; please specify target vault using -v.
To route git credentials through a dedicated setup, point DOTSECENV_CONFIG at an alternate config file listing only the vault you want. The variable is the CLI’s own config override, and the helper inherits it from git’s environment — set it where git runs, since GUI clients don’t source your shell profile.
Prerequisites
Section titled “Prerequisites”- dotsecenv installed and logged in, with at least one vault (see Getting Started)
jqon yourPATH- git with an HTTPS remote
- For the OAuth workflow: git 2.41 or newer, and a generator helper such as
git-credential-oauth
Installation
Section titled “Installation”git looks for git-credential-<name> on your PATH, so the helper has to sit in a PATH directory.
The git-credential-dotsecenv helper is included in the deb, rpm, and Arch packages and installed to /usr/bin/.
Verify it’s available:
git-credential-dotsecenv 2>&1 | head -1The cask puts the helper on your PATH alongside dotsecenv, so there is nothing to install. Check it resolved:
command -v git-credential-dotsecenvHomebrew relinks it on every upgrade.
Copy the helper from contrib/git-credential-dotsecenv or download it, then place it in a PATH directory:
BIN_DIR="${HOME}/.local/bin" # any directory on your PATHmkdir -p "$BIN_DIR"curl -fsSL https://raw.githubusercontent.com/dotsecenv/dotsecenv/main/contrib/git-credential-dotsecenv \ -o "$BIN_DIR/git-credential-dotsecenv"chmod +x "$BIN_DIR/git-credential-dotsecenv"Configuration
Section titled “Configuration”-
Verify the helper resolves
git should find it under the short name
dotsecenv:Terminal window git credential-dotsecenv 2>&1 | head -1# usage: git-credential-dotsecenv <get|store|erase> -
Pick a workflow
Clear the helper list, then point it at dotsecenv:
Terminal window git config --global --replace-all credential.helper ""git config --global --add credential.helper dotsecenvThe next time git prompts for a password on an HTTPS remote, it hands the token to dotsecenv, which encrypts it in your vault.
Chain dotsecenv as the store with a generator that runs last:
Terminal window git config --global --replace-all credential.helper ""git config --global --add credential.helper dotsecenvgit config --global --add credential.helper oauthOn the first push, dotsecenv has nothing, so the generator runs an OAuth flow and git saves the access token,
oauth_refresh_token, andpassword_expiry_utcthrough dotsecenv. Once the access token expires, git reads the refresh token back from dotsecenv, the generator mints a fresh access token, and git re-stores the rotated result. -
Confirm dotsecenv is the only helper
Terminal window git config --get-all credential.helper# dotsecenvAn empty first line is the reset and is expected. Any other name means git will consult that helper too.
git drives the helper on its own during clone, fetch, and push. You can also call it directly to debug or script:
# Storeprintf 'protocol=https\nhost=gitlab.com\nusername=me\npassword=glpat-xxx\n\n' \ | git-credential-dotsecenv store
# Retrieveprintf 'protocol=https\nhost=gitlab.com\n\n' | git-credential-dotsecenv get
# Eraseprintf 'protocol=https\nhost=gitlab.com\n\n' | git-credential-dotsecenv eraseConfirm the secret landed in your vault:
dotsecenv secret get 'git::HTTPS.GITLAB_DOT_COM_END' --jsonTroubleshooting
Section titled “Troubleshooting”| Problem | Cause | Solution |
|---|---|---|
| git prompts for a password every time | Helper not found, or get failing |
Check git credential-dotsecenv runs, that you ran dotsecenv login, and that jq is installed |
jq is required for store |
jq missing |
Install jq and retry |
multiple vaults configured and no terminal available; please specify target vault using -v |
store/erase ran without a terminal while several vaults resolve |
Run the git command in a terminal to get the vault picker, or set DOTSECENV_CONFIG to a config listing a single vault |
| git prompts on every fetch even when stored | GPG agent not caching | Set default-cache-ttl 3600 and max-cache-ttl 14400 in ~/.gnupg/gpg-agent.conf, then gpgconf --reload gpg-agent. See the GPG Agent guide |
| OAuth stops working, browser opens again | Refresh token revoked or expired | Re-run the generator’s login flow; dotsecenv stores the new refresh token on the next store |
| The generator opens a browser on every fetch | git older than 2.41 drops oauth_refresh_token |
Check git --version and upgrade. A stored token works on any version |
| git still uses an old token after you erased it | Another helper is ahead of dotsecenv in the list | git config --get-all credential.helper should show only the reset and dotsecenv |
| A second account on a host gets no credential | The stored record belongs to the other account | Set credential.dotsecenv.useUsername to true and put the username in the remote URL |
Next Steps
Section titled “Next Steps”- Getting Started: set up dotsecenv and your first vault
- CLI Reference: full command documentation
- Security Model: how encryption and access control work
