The most common security mistake in Git is committing an API key, a
database password, or AWS credentials. It counts as an exposure the
moment it enters the repo. Even if you delete it in a later commit,
the file sits in history, reachable via git show <sha>:path, and
anyone with repo access can read it.
In a public repo, a secret is compromised within minutes. Bots scan GitHub continuously and automatically try any keys they find. On AWS or a similar cloud provider, a leaked key can rack up significant charges (the typical scenario: launching heavy EC2 or GPU instances for mining) before you notice anything.
Defense in three layers
1. Keep secrets out of the repo entirely
- Put
.envfiles in.gitignore(gitignore). - Pull secrets from environment variables, not from files.
- Use placeholders in code; load real values from a vault, AWS Secrets Manager, Doppler, or 1Password.
2. Pre-commit hook with a scanner
The most popular tools:
- gitleaks: a Go binary, fast. Configured via
.gitleaks.toml. - detect-secrets (Yelp): Python, finer-grained control through a "baseline" file.
- trufflehog: Go, also checks entropy (catches strings that look like tokens even without a known pattern).
Via the pre-commit framework (gpg-signing):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.0
hooks:
- id: gitleaks
A commit containing a suspicious string is now blocked:
gitleaks failed: aws-access-key found in src/config.py:15
3. Server-side scanning
- GitHub Secret Scanning is built in for public repos at no cost, and available for private repos through GHAS (paid). It scans on push. When a key is found, it automatically notifies the provider (AWS, Stripe, etc.), which can then revoke the key.
- GitLab Secret Detection is the equivalent in its SAST suite.
- A custom pre-receive hook on a bare server that rejects pushes containing suspicious strings.
The server layer is the final safety net. Even if a developer
bypasses the pre-commit hook with --no-verify, the server scanner
catches it.
What to do after an exposure
In order:
1. Rotate the key. Now.
Not "fix the history and then rotate." Rotation first:
- AWS: create a new access key, delete the old one.
- Stripe: revoke the API key in the dashboard.
- GitHub PAT: revoke in Settings.
- Any provider: within thirty minutes.
This is the only action that eliminates the risk. History cleanup is hygiene, not security.
2. Notify the team
Someone may have cloned the repo before the fix. Let them know the secret has been rotated so they do not try to use the old value.
3. (Optional) Clean the history
If the repo is private and the secret has not reached an attacker, you can reduce the exposure window with git-filter-repo:
git filter-repo --replace-text replace.txt
# where replace.txt contains:
# AKIA********** ==> [REMOVED]
This rewrites all history. After that, git push --force (requires
permissions and temporary disabling of branch protection). All clones
become stale; everyone must clone again.
Do not clean without rotating. That creates a false sense of security. Rotation is required. Cleanup is optional.
What leaks most often
- AWS access keys (AKIA*): the most common and most dangerous.
- GitHub PAT/Personal Access Tokens (ghp_*).
- Stripe/Twilio/SendGrid API keys.
- Database passwords in
database.yml,application.properties. - OAuth client_secret in
.envfiles. - SSH keys copied to
.ssh/(see ssh-keys-git). - Certificates and private keys in
.pem,.key,.crt.
Scanners know most of these patterns out of the box. gitleaks ships with rules covering hundreds of providers.
Pitfalls
- False positives. A scanner may flag a random hash as a leaked
key. For detect-secrets,
--baselineremembers "these are known; do not alert." - Old secrets in history. When you first enable the scanner on CI, it may alert on old history. The strategy: clean the old ones separately (or accept them as baseline), then turn the scanner on for new commits only.
.env.exampleis also scanned. If it contains real values instead of placeholders, the scanner will catch them. Keep placeholders there (<your-key-here>).