← Back to Articles         
Git Secrets Security and Accidental Secret Exposure

Git Secrets Security and Accidental Secret Exposure

Published on 03 Oct 2026     8 min read Git & GitHub
Git Security

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.
  • .gitignore prevents 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-repo when 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.

← Back to Articles