DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

When Git Push Protection Saves You: Remove Secrets the Right Way (Azure AD/Entra + .NET)

When Git Push Protection Saves You: Remove Secrets the Right Way (Azure AD/Entra + .NET)

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 or git-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
Enter fullscreen mode Exit fullscreen mode

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 & secretsNew 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"
Enter fullscreen mode Exit fullscreen mode

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)"
Enter fullscreen mode Exit fullscreen mode

.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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Push as a PR (safe if main is protected):

git push -u origin sanitize/remove-secret
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
  • If main allows history rewrite:
  git push --force-with-lease origin main
Enter fullscreen mode Exit fullscreen mode
  • If main is protected:
  git switch -c sanitize/remove-secret
  git push -u origin sanitize/remove-secret
  # open a PR
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

.gitattributes (line endings, future LFS, etc.)

* text=auto eol=lf
Enter fullscreen mode Exit fullscreen mode

Pre-commit secret scanning (local guardrail)

  • Install gitleaks and add a pre-commit hook:
  gitleaks detect --staged
Enter fullscreen mode Exit fullscreen mode

.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

  1. Rotate in Entra; delete old secret.
  2. echo "**/appsettings*.json" >> .gitignore && git add .gitignore
  3. git rm --cached src/MultiTenantApi/appsettings.json && git commit -m "chore: stop tracking appsettings.json; ignore secrets"
  4. Add appsettings.json.example with placeholders; commit.
  5. A) rebase‑amend a few commits or B) git filter-repo to purge history.
  6. Verify with ls-tree, git grep, git log -- path.
  7. 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)