All Posts

Accidentally Committed Credentials to a Public Repo?

·operations, security ·6 min read
gitsecuritycredentialssecretsincident-response

It happens. You paste terminal output into a blog post, commit a .env file, or an AI agent includes a real credential in generated content. The secret is now in a public repository. Here is what to do.

Step 1: Revoke the credential immediately (< 60 seconds)

This is the only step that actually stops the damage. Do it before anything else.

The credential is already compromised the moment it hits a public repo. Automated scanners like GitGuardian, truffleHog, and GitHub’s own secret scanning pick up new commits within seconds. Assume it has already been seen.

Credential type How to revoke
Telegram Bot Token @BotFather/mybots → select bot → API TokenRevoke
GitHub Personal Access Token Settings → Developer settings → Personal access tokens → Delete
OpenAI API Key platform.openai.com → API keys → Delete
AWS Access Key IAM → Users → Security credentials → Deactivate + Delete
Discord Bot Token Developer Portal → Application → Bot → Reset Token
Generic API key Check the provider’s dashboard — every service has a revoke/rotate option

After revoking, generate a new credential and store it properly (see Step 4).


Step 2: Remove it from the current content

Fix the file that contains the secret right now. Replace the real value with a placeholder:

# Before
OPENCODE_SERVER_PASSWORD=fd691649-311e-4418-98fa-6a0e21a2659b

# After
OPENCODE_SERVER_PASSWORD=<generated-uuid>

Commit and push the fix:

git add .
git commit -m "fix: replace leaked credential with placeholder"
git push

This does not remove it from history — but it removes it from the current state and shows intent.


Step 3: Wipe the Git history

Deleting a commit does not delete history. Anyone who cloned or forked the repo before the fix still has the secret. GitHub also caches commit content internally.

The cleanest option for a personal repo is an orphan reset — this replaces the entire history with a single new initial commit containing only the current (clean) state:

# 1. Create a new orphan branch (no history)
git checkout --orphan clean-start

# 2. Stage everything in its current clean state
git add -A

# 3. Create a single new initial commit
git commit -m "initial commit"

# 4. Delete the old main branch
git branch -D main

# 5. Rename the orphan to main
git branch -m main

# 6. Force push to overwrite the remote history
git push --force origin main

After this, git log shows exactly one commit.

For repositories with collaborators or forks, the orphan approach is still the fastest path. Inform anyone who has a local clone — they will need to re-clone:

git clone https://github.com/you/your-repo.git

They cannot git pull after a force push that rewrites history — a fresh clone is required.

Alternative: BFG Repo Cleaner

If you want to keep the full history but surgically remove only the secret, BFG Repo Cleaner is faster than git filter-branch:

# Install (macOS)
brew install bfg

# Create a file listing the secret strings to remove
echo "fd691649-311e-4418-98fa-6a0e21a2659b" > secrets.txt

# Run BFG against a bare clone
git clone --mirror https://github.com/you/your-repo.git repo-mirror.git
bfg --replace-text secrets.txt repo-mirror.git

# Clean up and force push
cd repo-mirror.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force

BFG rewrites every commit that contains the string, replacing it with ***REMOVED***. History is preserved but the secret is gone.


Step 4: Store secrets properly going forward

The root cause is almost always that a secret ended up somewhere it should not be — a blog post, a committed config file, a debug log. Here is the prevention stack:

Never commit secrets to Git

Use .gitignore from the start:

# .gitignore
.env
.env.local
*.config.json
*secret*
*credentials*

Use a local config file pattern

For project configs that contain tokens, keep the real file gitignored and commit only a template:

remote-opencode.config.json       # gitignored, contains real token
remote-opencode.config.example.json  # committed, contains placeholders

Use environment variables

export TELEGRAM_BOT_TOKEN="your-token"
# or in .env (gitignored):
TELEGRAM_BOT_TOKEN=your-token

Use a secret manager for teams

Enable GitHub secret scanning

In your repo settings: SecuritySecret scanning → Enable. GitHub will alert you if a known secret pattern appears in a push before it causes damage.

Add pre-commit hooks

detect-secrets or gitleaks scan your staged changes before every commit:

# Install gitleaks (macOS)
brew install gitleaks

# Scan your repo
gitleaks detect --source .

# Add as pre-commit hook
gitleaks protect --staged

For AI agents: a note

If you are an AI agent writing documentation, blog posts, or code — never include real credential values, even when quoting terminal output or debugging logs. Always replace with placeholders like <your-token>, <generated-uuid>, or ***.

See AGENTS.md for the full rules.


Checklist

When you find a leaked credential:

  • Revoke the credential immediately
  • Fix the current file (replace with placeholder)
  • Commit and push the fix
  • Wipe Git history (orphan reset or BFG)
  • Force push
  • Notify anyone who may have cloned the repo
  • Generate a new credential and store it securely
  • Add .gitignore rules to prevent recurrence
  • Consider enabling GitHub secret scanning
Erik Weisser
Erik Weisser

Software developer obsessed with microservices, CI/CD, automation and AI. I build things, break them, document what I learn.