Skip to content

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.

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.

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:

Terminal window
git config --global credential.dotsecenv.useUsername true

This 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.

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.

  • dotsecenv installed and logged in, with at least one vault (see Getting Started)
  • jq on your PATH
  • git with an HTTPS remote
  • For the OAuth workflow: git 2.41 or newer, and a generator helper such as git-credential-oauth

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:

Terminal window
git-credential-dotsecenv 2>&1 | head -1
  1. 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>
  2. 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 dotsecenv

    The next time git prompts for a password on an HTTPS remote, it hands the token to dotsecenv, which encrypts it in your vault.

  3. Confirm dotsecenv is the only helper

    Terminal window
    git config --get-all credential.helper
    # dotsecenv

    An 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:

Terminal window
# Store
printf 'protocol=https\nhost=gitlab.com\nusername=me\npassword=glpat-xxx\n\n' \
| git-credential-dotsecenv store
# Retrieve
printf 'protocol=https\nhost=gitlab.com\n\n' | git-credential-dotsecenv get
# Erase
printf 'protocol=https\nhost=gitlab.com\n\n' | git-credential-dotsecenv erase

Confirm the secret landed in your vault:

Terminal window
dotsecenv secret get 'git::HTTPS.GITLAB_DOT_COM_END' --json
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