Why Private Repositories Still Leak Secrets
The repository was private. The credential was still plaintext
A developer commits a production API key to a private repository, notices it in review,
deletes the line, and adds .env to .gitignore. The repository never became public, so
the incident gets closed as housekeeping rather than a credential exposure.
That conclusion confuses two controls. Repository privacy decides who may ask the Git host for objects. Encryption decides whether someone who obtains those objects can read the secret. A private repository provides the first control, not the second.
GitGuardian’s State of Secrets Sprawl 2025 reports that it scanned 69.6 million public repositories in 2024. Its separate customer dataset found plaintext secrets in 35% of scanned private repositories and says private repositories were nine times more likely to contain a secret than public ones. Those populations are different, so this is not a clean public-versus-private experiment. It is, however, strong operational evidence that teams relax handling rules behind an access gate.
Model the readers, not the repository label
Once committed, a value may reach developer laptops, build runners, code-indexing services, backup systems, IDE extensions, and every account allowed to clone. The relevant question is not “is the repo private?” but “how many principals and systems can recover this value?”
OWASP’s Secrets Management Cheat Sheet
is explicit: engineers should not have access to every secret, fine-grained access control
should apply to each object, and secrets should be revocable. A repository-wide permission
cannot express “Ana may read STAGING_DB_URL but not PROD_STRIPE_KEY.”
Deletion is not containment
Removing the line creates a new commit; it does not invalidate the old credential. Rewriting history can reduce accidental discovery, but it cannot recall clones or prove that the value was never copied. The containment action is to revoke or rotate the credential, then clean the repository to reduce residual exposure.
The safer design is to keep reviewable encrypted material beside the code. envseal
encrypts each .env value to recipient keys and stores the encrypted vault in git. A clone
receives ciphertext; authorization to the repository is no longer automatically authorization
to every runtime secret. Because values are encrypted independently, changing one variable
does not rewrite the entire vault into an opaque diff.
This does not replace a cloud secret manager for every production architecture. It closes a specific gap: teams that need versioned, offline-friendly configuration should not have to choose between plaintext in git and an unreviewable out-of-band file.
Extend the boundary to the work around the code
The same credential can bypass git entirely when someone pastes it into a Jira issue or a Confluence runbook. Repository controls and envseal cannot inspect that path. Secret Sentinel covers the collaboration boundary by detecting and redacting supported credential types inside Jira and Confluence. The two controls protect different copies of the same secret: encrypted configuration in git, accidental plaintext in the work system.
Frequently asked questions
Is it acceptable to commit secrets to a private repository?
No. Repository access control reduces exposure, but every clone, CI runner, backup, integration, and compromised maintainer account can still receive the plaintext credential. Store only encrypted secret material in git.
Does adding a file to .gitignore remove an older committed copy?
No. Gitignore affects future untracked files. It does not remove objects already present in repository history, existing clones, forks, or caches.