When Git Push Protection Saves You: Remove Secrets the Right Way (Azure AD/Entra + .NET)
TL;DR — GitHub push protection flagged an Azure AD (Microsoft Entra) client secret committed to
src/MultiTenantApi/appsettings.json. Do this: (0) rotate secret, (1) stop tracking and ignore, (2) scrub history (rebase orgit-filter-repo), (3) verify, (4) keep protection on and add local tooling (User Secrets, gitleaks).
The Message You Saw
GH013: Repository rule violations found for refs/heads/main
Push cannot contain secrets
… Azure Active Directory Application Secret …
commit: 07e7743697089bad3b902fee5d5df5faef954abb
path: src/MultiTenantApi/appsettings.json:13
This is a good block. Treat it as an incident: rotate → remove → verify.
0) Rotate the secret now (don’t skip)
- Entra portal → App registrations → your app → Certificates & secrets → New client secret.
- Update everywhere it’s used (Azure Key Vault, App Service config, pipeline variables).
- Delete the old secret.
Rotating first ensures even a past leak can’t be used.
1) Remove secrets from the working tree and prevent re‑adds
From repo root:
# Ignore appsettings across the repo
echo "**/appsettings*.json" >> .gitignore
git add .gitignore
# Keep your local file, but stop tracking it
git rm --cached src/MultiTenantApi/appsettings.json
git commit -m "chore: stop tracking appsettings.json; ignore secrets"
Add a safe template so newcomers know the shape:
cp src/MultiTenantApi/appsettings.json src/MultiTenantApi/appsettings.json.example
# open the .example and replace real values with placeholders
git add src/MultiTenantApi/appsettings.json.example
git commit -m "docs: add appsettings.json.example (no secrets)"
.NET local dev → User Secrets (keeps secrets out of Git):
dotnet user-secrets init --project src/MultiTenantApi
dotnet user-secrets set "AzureAd:ClientSecret" "<NEW-SECRET>" --project src/MultiTenantApi
2) Scrub the secret from history (what GitHub flagged)
Choose one path.
A) Only a few recent commits contain the secret
Interactive rebase and amend those commits.
git fetch origin
git switch -c sanitize/remove-secret origin/main
git rebase -i origin/main~20 # pick a base far enough back
# mark commits that touched appsettings.json as: edit
# for each paused commit
git rm --cached src/MultiTenantApi/appsettings.json
git commit --amend --no-edit
git rebase --continue
Push as a PR (safe if main is protected):
git push -u origin sanitize/remove-secret
B) The secret may appear anywhere in history
Use git-filter-repo (modern, recommended over BFG).
# install once
pip install git-filter-repo
# safety tag
git tag backup-before-sanitize-$(date +%Y%m%d%H%M%S)
git switch main
git filter-repo --force --path src/MultiTenantApi/appsettings.json --invert-paths
Commit ignore/template if needed:
echo "**/appsettings*.json" >> .gitignore
git add .gitignore src/MultiTenantApi/appsettings.json.example
git commit -m "chore: ignore appsettings; add .example template"
- If
mainallows history rewrite:
git push --force-with-lease origin main
- If
mainis protected:
git switch -c sanitize/remove-secret
git push -u origin sanitize/remove-secret
# open a PR
Azure DevOps note: branch policies may also block direct pushes; PR path is standard.
3) Verify before pushing
# 1) File no longer tracked
git ls-tree -r HEAD | grep appsettings || echo "✅ no tracked appsettings.json"
# 2) No literal secrets left (search for a known token or pattern)
git grep -n "CLIENT_SECRET_VALUE" || echo "✅ literal secret not found"
# 3) History scrubbed (no commits still referencing the path)
git log --oneline -- src/MultiTenantApi/appsettings.json || echo "✅ no history entries"
If verification passes, push your branch / open the PR.
4) About the “Allow this secret” link
GitHub provides a temporary allow‑push URL. Do not use it unless:
1) you have rotated the secret and
2) you have completely removed it from history.
Otherwise you will publish compromised credentials.
Long‑Term Hygiene (copy‑paste policy)
.gitignore
# secrets & environment
**/appsettings*.json
!.*/ # keep hidden folders ignored unless explicitly whitelisted
# local env examples
!**/appsettings*.json.example
.gitattributes (line endings, future LFS, etc.)
* text=auto eol=lf
Pre-commit secret scanning (local guardrail)
- Install gitleaks and add a pre-commit hook:
gitleaks detect --staged
.NET practice
- User Secrets for local dev.
- Azure Key Vault or pipeline variables for CI/CD and prod.
-
Never commit real secrets; templates (
*.example) only.
FAQ
Q: I already force‑pushed. Am I safe after rotation?
A: Rotation neutralizes the exposed secret. Still scrub history so the leak isn’t public knowledge or referenceable.
Q: Should I choose rebase or filter‑repo?
A: If only a couple of commits were affected, rebase is shortest. If uncertain or widespread, filter‑repo is faster and more reliable.
Q: Team is large and main is protected?
A: Push a sanitize/ branch and open a PR; your platform’s merge will handle policy checks.
Copy‑Ready Incident Runbook
- Rotate in Entra; delete old secret.
-
echo "**/appsettings*.json" >> .gitignore && git add .gitignore -
git rm --cached src/MultiTenantApi/appsettings.json && git commit -m "chore: stop tracking appsettings.json; ignore secrets" - Add
appsettings.json.examplewith placeholders; commit. -
A) rebase‑amend a few commits or B)
git filter-repoto purge history. - Verify with
ls-tree,git grep,git log -- path. - Push branch / open PR. Keep push protection on.
— Written by Cristian Sifuentes — Full‑stack developer & Git/DevOps practitioner. Clean histories, zero‑secret repos, reproducible builds.

Top comments (0)