Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXIV · Git SecurityCredentialStorage

Credential storage and rotation — where the credential lives, how often it changes, and the leak surface

Advanced⏱ ~26 mingit

What you'll learn

  • Map each credential-helper backend to its threat model and its leak surface
  • Configure credential.helper with the right backend for a workstation, a runner, and a CI job
  • Define a rotation cadence that limits the window of exposure for any credential that has been written to plaintext
  • Identify the leak surfaces (CI logs, shell history, backups, dotfiles sync) that turn a stored credential into a public one

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A Git credential is a string with a lifetime. Where the string lives for that lifetime determines who can read it, how it leaks, and how the leak is contained. This lesson walks the storage layer (where the credential sits between authentication events), the rotation cadence (how often the credential is changed to limit the window of exposure), and the leak surface (the systems that turn a stored credential into a public one).

The credential-helper architecture

Git does not store credentials itself. The credential.helper configuration names an external program that Git invokes to fetch, store, or erase credentials; the program’s backend is where the credential lives. The choice of helper is the choice of where the credential sits, and where the credential sits is the choice of who can read it.

flowchart LR
    A["git push"] --> B{"need credential?"}
    B -->|yes| C["helper get"]
    C --> D{"backend"}
    D -->|cache| E["in-memory for N seconds"]
    D -->|store| F["plaintext ~/.git-credentials"]
    D -->|osxkeychain| G["macOS Keychain"]
    D -->|wincred| H["Windows Credential Manager"]
    D -->|manager| I["GCM cross-platform"]
    D -->|libsecret| J["Linux Secret Service"]
    E --> K["credential returned"]
    F --> K
    G --> K
    H --> K
    I --> K
    J --> K

The trade-off between backends is the trade-off between persistence and exposure:

  • cache keeps the credential in memory for a configurable timeout. The credential never touches disk. Use it on shared runners and CI jobs where the credential’s lifetime is the job’s lifetime.
  • store writes the credential to a plaintext file at ~/.git-credentials. The file is the credential. Avoid it for anything that matters.
  • osxkeychain, wincred, libsecret delegate to the OS-provided secure store. The credential is encrypted at rest with the user’s login keys. The right choice for workstations.
  • manager (Git Credential Manager) is a cross-platform helper that uses the OS secure store and adds features like MFA prompts and Azure DevOps integration. The right choice for mixed-platform teams.
git config credential.helper cache --timeout 3600
git config credential.helper manager
git config credential.helper store

The first configuration holds the credential in memory for one hour and is appropriate for a CI job or a deploy step. The second delegates to GCM and is appropriate for a workstation. The third writes to plaintext and is appropriate only for throwaway test environments.

Rotation cadence

A credential that lives for a year is a credential that has been exposed to a year of backups, logs, and dotfiles syncs. The rotation cadence limits the window of exposure: the shorter the cadence, the smaller the window.

flowchart LR
    A["credential issued"] --> B["in use for N days"]
    B --> C{"rotation date"}
    C -->|arrived| D["new credential issued"]
    D --> E["clients switched to new credential"]
    E --> F["old credential revoked"]
    B --> G{"compromise suspected"}
    G -->|yes| H["credential revoked immediately"]
    H --> I["incident response"]

Three rotation cadences, by use case:

  • Engineer workstation personal access tokens. Rotate every 90 days; the cadence matches the expected duration of a working session and limits the window of exposure to a quarter. Revoke immediately on any suspected compromise.
  • CI runners. Use per-job tokens (GITHUB_TOKEN, CI_JOB_TOKEN) so the credential’s lifetime is the job’s lifetime. No rotation cadence is needed because the credential does not outlive the job.
  • Production deploy hosts (long-lived SSH keys or deploy tokens). Rotate every 180 days; the cadence matches the expected duration of a host’s deployment and limits the window of exposure to a half-year. Revoke immediately on any suspected compromise.

The discipline is to set the cadence at issuance, automate the rotation where possible (per-job tokens are the strongest form of automation), and treat any credential that has touched plaintext storage as on a shorter cadence than one that has lived only in a secure-store helper.

The leak surface

A stored credential is a public credential if any of the following systems has seen it. The leak surface is the set of systems that copy, log, or back up the credential’s storage:

  • CI logs. A git clone https://TOKEN@host/path.git URL appears in the CI log. A credential helper that fetches the credential in plaintext writes it to the helper’s stderr (depending on the helper). The CI log is the leak vector most often caught only after a search engine has indexed it.
  • Shell history. A git clone URL with the token in the command line is recorded in ~/.bash_history (or the shell’s equivalent). The history file is a leak surface that survives the shell session.
  • Backup snapshots. A plaintext credential in ~/.git-credentials is included in every home-directory backup. The backup is a leak surface that the user does not see; the credential is in every snapshot since the helper was first used.
  • Dotfiles sync. A plaintext credential in a file matched by .* is replicated to every new machine the user’s dotfiles sync touches. The sync is a leak surface that grows with the user’s footprint.
  • Process listings. A git invocation with the token in the URL has the token in the process’s argv, which is visible to every other process on the system via the process command line (Linux exposes this at /proc/${PID}/cmdline; ps shows it everywhere).

The right defence against each leak surface is different:

  • CI logs: use credential helpers that fetch in memory (cache) and avoid URLs with embedded tokens; configure the runner to mask known secrets in log output as a backstop.
  • Shell history: configure the shell to ignore commands matching *token* or *password*; use a credential helper so the URL never needs the token.
  • Backup snapshots: never use the store helper; rotate any credential that has ever been written by store.
  • Dotfiles sync: never include credential files in dotfiles; the helper’s backend is the credential’s home, not the user’s version-controlled files.
  • Process listings: use credential helpers; the helper’s get invocation returns the credential over stdin, not in the process’s argv.

Production discipline

  1. Use the most secure helper available on the deployment. cache on CI jobs; OS secure-store helpers on workstations; never store.
  2. Rotate every credential that has ever been written to plaintext. The rotation cadence is the team’s policy; the immediate rotation is on any suspected compromise.
  3. Set per-job tokens on CI runners. The token’s lifetime is the job’s lifetime; no rotation cadence is needed because the credential does not outlive the job.
  4. Audit the helper list quarterly. git config --list --show-origin | grep credential is the diagnostic for every client.
  5. Document the leak surface for each deployment. A workstation has different leak surfaces than a CI runner; the storage and rotation choices should match.

Cross-course references

  • Git, CI/CD & GitOps — Part XXVI-05 (Credential helpers) — the helper protocol and the backend trade-offs that this lesson extends.
  • Git, CI/CD & GitOps — Part XXXIV-01 (Authentication options) — the SSH-vs-HTTPS framing that determines which helper applies.
  • Git, CI/CD & GitOps — Part XXXIII-06 (Signing policy) — the analogous rotation cadence for signing keys, with the same on-schedule and on-compromise triggers.
  • Linux for Production Sysadmins — Part XXXIV (ConfigMgmt) — the analogous storage and rotation discipline for system credentials.

Quiz

Knowledge check · 4 questions

  1. Q1. A CI runner authenticates to the forge with a PAT issued a year ago with no expiry, stored by the `store` helper in `~/.git-credentials`. The home directory was included in last night's backup snapshot, and the snapshot was exfiltrated this morning. What is the right containment?

  2. Q2. A credential that has been written to disk by the `cache` helper is on a longer rotation cadence than a credential that has only been written to `~/.git-credentials` by the `store` helper.

  3. Q3. Name three leak surfaces that turn a stored Git credential into a public credential, and the defence against each one.

  4. Q4. Diagnose why a long-lived PAT is in the next breach dump, and recommend the storage and rotation changes that prevent recurrence.

    An engineer's workstation has been configured with `git config --global credential.helper store` for the past two years. Every PAT the engineer has authenticated with is in `~/.git-credentials`. The engineer's home directory is backed up nightly to a network share; the share was compromised last week by an attacker who exfiltrated a month of snapshots. The PATs from the past two years — including one for the production remote — are now in the attacker's hands. The forge has not been notified; the PATs remain valid.

Passing score: 75%. Answers are checked in this browser.