appsettings.json Secrets Committed to Git

A real connection string, API key, or signing secret ended up in appsettings.json — not appsettings.Development.json — and got pushed. Deleting the line in a new commit removes it from the file. It does not remove it from history, and anyone who already cloned, forked, or has GitHub's cache of that commit still has it.

reference guide ASP.NET Core git

// first: rotate it, before anything else

If a real secret was pushed to a remote — even briefly, even to a private repo, even if you deleted it thirty seconds later — treat it as compromised and rotate it. Force-pushing or scrubbing history afterward doesn't undo the fact that it was already retrievable; GitHub caches commit contents independently of the repo, and any fork or clone made in that window keeps its own copy regardless of what happens to the original history. Rotating the credential is the only step that actually closes the exposure. Everything below is real and worth doing, but it isn't a substitute for this one.

// checking whether it's actually in history, not just the current file

git log -p -- appsettings.json shows every version of the file across every commit — including ones a later commit "removed." If the secret shows up anywhere in that output, it's in history, and every clone of the repo has it.

// removing it from history

tools that actually rewrite history (not git filter-branch)
git-filter-repoGit's own documentation recommends this over filter-branch, which it describes as slow and easy to get wrong. Rewrites every commit that touched the file.
BFG Repo-CleanerNarrower and simpler for exactly this case — pass it the secret string or the file, and it strips it from every commit in one pass.
Either tool rewrites commit hashes for everything after the exposed commit. Every collaborator needs to re-clone or hard-reset to the rewritten history afterward — a normal git pull will merge the old history right back in.

// structuring config so this can't happen again

The reliable fix isn't remembering to be careful — it's making the real secret physically unable to live in a file git tracks.

where each kind of value actually belongs
appsettings.json (committed)Structure and non-secret defaults only — keys present, values empty or placeholder. This is what tells the next developer what config exists at all.
Local development secretsdotnet user-secrets init, then dotnet user-secrets set "ConnectionStrings:Default" "…" — stored outside the project folder entirely, on a path the ASP.NET Core configuration system reads automatically in Development, never on disk inside the repo.
Production secretsEnvironment variables, or a managed secret store (Azure Key Vault, AWS Secrets Manager) — injected at deploy time, never written to a file that ships with the code.

GitHub's push protection and secret scanning catch a lot of common key formats automatically on public repos (and on private repos with the feature enabled) and will block a push outright — useful as a backstop, but the config structure above is what prevents the situation rather than catching it after the fact. The appsettings.json Validator flags plaintext-looking secrets in a pasted file for the same reason: catching it before git commit is a lot cheaper than catching it after git push.