Git Secrets Security and Accidental Secret Exposure
These are important Git security questions because deleting a secret from the latest file does not necessarily remove it from Git history.
1. Why should passwords, API keys, connection strings, and other secrets never be committed to Git?
What is it?
A secret is sensitive information used to authenticate or access systems and resources.
Examples include:
Passwords
API keys
Database connection strings containing credentials
JWT signing keys
OAuth client secrets
Cloud access keys
Private keys
Personal Access Tokens (PATs)
For example, this should not be committed:
{
"ConnectionStrings": {
"DefaultConnection": "Server=prod;Database=AppDb;User Id=admin;Password=Secret123!"
},
"Jwt": {
"Key": "my-production-secret-key"
}
}
The main problem is that Git keeps history.
Even if you later remove the password and commit again, the old secret may still exist in an earlier commit.
Simple Example
Suppose you accidentally commit:
Commit A
|
| appsettings.json
| Password=Secret123
v
Commit B
|
| Password removed
v
Commit C
The current file may no longer contain the password, but someone could inspect the old commit:
git show <commit-id>
and potentially recover it.
So:
Remove secret from current file
≠
Remove secret from Git history
This is one of the most important points to mention in an interview.
Why is committing secrets dangerous?
1. Git preserves historical versions
Consider:
git add appsettings.json
git commit -m "Add database configuration"
git push origin main
If that file contained:
Password=MyProdPassword123
the password becomes part of the repository's commit history.
Simply changing it later:
Password removed
doesn't automatically erase the earlier commit.
2. Other developers may already have copies
Once pushed:
Local Repository
↓
Remote Repository
↓
Developer A clone
Developer B clone
CI/CD system
Forks / backups / caches
Rewriting the central repository's history cannot guarantee that every existing copy disappears.
3. Automated scanners may detect exposed credentials
Public repositories, hosting platforms, and security systems can scan repositories for recognizable credentials.
An exposed credential could potentially be used to access:
- Databases
- Azure resources
- External APIs
- Storage
- Production applications
- CI/CD systems
4. .gitignore doesn't fix an already committed secret
Suppose you add:
appsettings.Production.json
.gitignore helps prevent untracked files from being added in the future.
But if the file is already tracked by Git, adding it to .gitignore doesn't erase its history.
For a currently tracked file you no longer want tracked, you may use:
git rm --cached appsettings.Production.json
and commit that change.
But again:
This removes the file from the current tracked state, not necessarily from previous commits.
Where should secrets be stored?
The exact mechanism depends on the environment.
For an ASP.NET Core application, common approaches include:
Local development
Use .NET User Secrets:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "..."
Environment variables
For example:
ConnectionStrings__DefaultConnection=...
Azure
Use services such as Azure Key Vault, often combined with managed identity.
CI/CD
Store secrets in the CI/CD platform's protected secret/variable mechanism rather than directly in source code.
The repository should generally contain configuration structure or placeholders rather than real production credentials.
For example:
{
"ConnectionStrings": {
"DefaultConnection": ""
}
}
2. What should you do if a secret is accidentally committed and pushed?
What is it?
Suppose a developer accidentally commits:
{
"ApiKey": "REAL-PRODUCTION-API-KEY"
}
and executes:
git push origin main
Now the credential should be treated as potentially compromised.
The first priority is usually not Git cleanup.
The first priority is:
Revoke or rotate the exposed secret.
Correct Response
A practical sequence is:
Secret discovered in Git
↓
Revoke / rotate credential immediately
↓
Update application/deployment with new secret
↓
Remove secret from source
↓
Clean Git history if required
↓
Coordinate rewritten history with team
↓
Review logs / exposure
↓
Prevent recurrence
Let's examine each step.
Step 1 — Revoke or Rotate the Secret
Suppose this API key was leaked:
OLD_API_KEY
Do not assume deleting it from Git makes it safe.
Instead, invalidate it at the system that issued it and create a replacement:
OLD_API_KEY → REVOKED
NEW_API_KEY → ACTIVE
Similarly:
Password
Change the password
API key
Revoke old key
Generate new key
PAT
Revoke old PAT
Generate replacement if needed
Database credentials
Change/rotate database credentials
This is critical because historical copies may still exist.
Step 2 — Move the Replacement Secret to Secure Storage
Do not replace:
Old secret in Git
with:
New secret in Git
That simply creates another exposure.
Instead:
Git Repository
|
| configuration only
v
Application
|
+---- Environment variables
|
+---- Secret store
|
+---- Azure Key Vault
Step 3 — Remove the Secret from the Current Code
For example, remove:
{
"ApiKey": "actual-secret"
}
and use an external configuration source.
Commit the sanitized code:
git add .
git commit -m "Remove exposed API key from source"
But remember:
Current file clean
≠
Git history clean
Step 4 — Remove the Secret from Git History if Required
If sensitive information must be purged from repository history, history-rewriting tools can be used.
A modern tool commonly used for this purpose is:
git filter-repo
For example, removing a sensitive file from history can involve:
git filter-repo --path appsettings.Production.json --invert-paths
Another tool you may encounter is BFG Repo-Cleaner.
Older documentation may show:
git filter-branch
but modern workflows generally prefer tools such as git filter-repo.
Step 5 — Force Push Rewritten History Carefully
History rewriting changes commit IDs.
Conceptually:
Before
A -- B -- C -- D
^
Secret
After rewrite
A' -- B' -- C' -- D'
Therefore, updating the remote may require a force push.
For example:
git push --force-with-lease --all
git push --force-with-lease --tags
Warning
Force pushing rewritten history is dangerous on a shared repository.
It can disrupt:
- Other developers' branches
- Open Pull Requests
- Existing clones
- CI/CD references
- Tags
The cleanup should therefore be coordinated with the team/repository administrators.
Developers may need to re-clone the repository or carefully synchronize their local repositories.
Step 6 — Investigate the Exposure
For a real production secret, determine:
- Was the repository public or private?
- How long was the secret exposed?
- Who had repository access?
- Was the credential used unexpectedly?
- Are there suspicious authentication/access logs?
- Did CI/CD logs or artifacts also expose it?
For a serious production credential, this becomes an incident-response activity, not merely a Git problem.
Step 7 — Prevent It From Happening Again
Several controls can reduce future exposure.
.gitignore
Keep local secret/configuration files out of Git where appropriate.
.env
appsettings.Production.json
secrets.json
But remember that .gitignore is preventive; it is not a secret-management system.
Secret management
Use:
.NET User Secrets
Environment Variables
Azure Key Vault
CI/CD secret variables
Managed identities where appropriate
Secret scanning
Repository hosting and security tooling can detect many credential patterns.
Pull Request review
Review changes before merging:
Developer
↓
Pull Request
↓
Review
↓
Security / Secret Scan
↓
CI
↓
Merge
What if the Secret Was Committed but NOT Pushed?
This distinction is useful in interviews.
Suppose:
Local commit
↓
Secret included
↓
NOT pushed
The exposure scope is much smaller because the commit has not been shared through the remote.
You can remove the secret and rewrite/amend your local commit before pushing.
For example, if it is only in the latest local commit:
# Remove secret from the file first
git add .
git commit --amend
Then verify that the secret isn't present in the commit you intend to push.
But if it has already been pushed:
Commit
↓
Push
↓
Remote repository
assume the credential may have been exposed and rotate/revoke it.
Important Comparison
| Situation | Main Action |
|---|---|
| Secret exists locally but was never committed | Remove it; don't commit it |
| Secret committed locally but never pushed | Remove it and rewrite/amend local history |
| Secret pushed to remote | Revoke/rotate immediately |
| Secret removed only from current file | Historical commit may still contain it |
Secret file added to .gitignore after commit |
Doesn't erase existing Git history |
| Secret pushed and history cleaned | Still rotate it because copies may exist |
Key Points
- Never intentionally store real passwords, API keys, tokens, private keys, or credential-bearing connection strings in Git.
- Git keeps historical versions of committed files.
- Removing a secret in a later commit does not automatically remove it from previous commits.
.gitignoreprevents appropriate untracked files from being added; it does not erase already committed history.- Once a secret has been pushed, treat it as potentially compromised.
- Revoke or rotate the secret first.
- Never replace an exposed secret with a new secret directly in Git.
- Store secrets in appropriate secret-management systems such as environment variables, CI/CD secret stores, or Azure Key Vault.
- Repository history can be rewritten with tools such as
git filter-repowhen necessary. - History rewriting and force pushing must be carefully coordinated on shared repositories.
- Secret scanning and Pull Request checks help prevent future incidents.
Interview Answer
Passwords, API keys, connection strings containing credentials, tokens, and other secrets should never be committed to Git because Git preserves commit history. Even if I delete a secret in a later commit, it can still exist in an earlier commit and may already have been copied through clones, forks, CI systems, or backups.
If a secret is accidentally committed and pushed, my first action is to revoke or rotate that credential because I treat it as potentially compromised. I then move the replacement secret to a secure store such as Azure Key Vault, environment variables, or protected CI/CD variables, remove the secret from the source, and clean the Git history if required. If history rewriting is necessary, I coordinate it carefully with the team because rewriting commits and force pushing can affect other developers and Pull Requests.