Securing Leaked API Credentials
Embed This Widget
Add the script tag and a data attribute to embed this widget.
Embed via iframe for maximum compatibility.
<iframe src="https://ipfyi.com/iframe/entity//" width="420" height="400" frameborder="0" style="border:0;border-radius:10px;max-width:100%" loading="lazy"></iframe>
Paste this URL in WordPress, Medium, or any oEmbed-compatible platform.
https://ipfyi.com/entity//
Add a dynamic SVG badge to your README or docs.
[](https://ipfyi.com/entity//)
Use the native HTML custom element.
An API key, database password, or service credential has been accidentally committed to a public Git repository, embedded in a publicly accessible file, or exposed in an application error response. The credential must be treated as fully compromised and rotated immediately, regardless of how briefly it was visible.
Symptoms
- ⚠ GitHub secret scanning alert or GitGuardian notification about a detected secret in a commit
- ⚠ Unexpected charges or quota exhaustion on a cloud/API service indicating unauthorised use
- ⚠ Unauthorised API calls appearing in service audit logs from unknown IP addresses
- ⚠ Error response body or stack trace visible in browser containing a database URL with credentials
- ⚠ `.env` file or `config.py` with credentials pushed to a public repository
Possible Root Causes
- • `.env` file accidentally staged and committed due to missing or incorrect `.gitignore` entry
- • Hardcoded credentials in source code pushed to a public or inadvertently public repository
- • Application error page (DEBUG=True in production) exposing database URL or secret key in traceback
- • Credentials embedded in a Docker image layer and pushed to Docker Hub
- • Secret written to application logs and log aggregator (Splunk, CloudWatch) with insufficient access control
Diagnosis Steps
1. Identify what was leaked and where
# Search git history for common secret patterns across all commits
git log --all --full-history -p -- '*.env' '*.py' '*.json' '*.yaml' '*.yml' \
| grep -E "(api_key|secret|password|token|credential)" | head -40
# Use trufflehog to scan entire git history
trufflehog git file://. --json 2>/dev/null | python3 -m json.tool | head -100
# Or gitleaks (faster, no Python dependency)
gitleaks detect --source . --report-format json --report-path leaks.json
cat leaks.json | python3 -m json.tool
2. Confirm whether the secret is still live in the repo
# Check current HEAD for secrets
git show HEAD -- .env config.py settings.py | grep -E "(api_key|secret|password|DB_URL)"
# Check if the file is in .gitignore
cat .gitignore | grep -i "env\|secret\|credential"
3. Check for unauthorised usage
# For AWS keys: check CloudTrail for unexpected API calls
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=leaked-iam-user \
--start-time $(date -d '24 hours ago' --iso-8601=seconds) \
--output table
# For a web-exposed credential, check your access logs
grep "YOUR_EXPOSED_PATH" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn
4. Check if the credential is still valid
# Test AWS key validity
aws sts get-caller-identity --profile leaked_profile
# Test a database URL
psql "postgresql://user:password@host:5432/dbname" -c "SELECT 1;" 2>&1
# If it connects — credential still active, rotate immediately
Solution
Step 1: Rotate the credential IMMEDIATELY (do this first)
Do not wait to clean git history before rotating — time spent cleaning is time the credential is active.
# Example: rotate an AWS IAM access key
aws iam create-access-key --user-name service-account
# Note the new key — then delete the old one
aws iam delete-access-key --user-name service-account --access-key-id AKIA_OLD_KEY_ID
# Example: rotate a PostgreSQL password
psql -c "ALTER USER myapp WITH PASSWORD 'NEW_STRONG_PASSWORD';"
# Example: regenerate a GitHub personal access token
# Settings → Developer settings → Personal access tokens → Revoke → Generate new
Step 2: Update all systems using the old credential
# Update .env.prod on the server
ssh apps-us "cd /var/www/yourdomain.com && sed -i 's/OLD_KEY/NEW_KEY/' .env.prod && sudo systemctl restart gunicorn-app"
# Update the secret in 1Password (if using 1Password)
op item edit "project-myapp" --vault dev api_key="NEW_KEY"
Step 3: Remove the secret from git history
# Remove a specific file from ALL git history (nuclear option — rewrites history)
git filter-repo --path .env --invert-paths
# Or use BFG Repo Cleaner (faster for large repos)
java -jar bfg.jar --delete-files .env
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push --force
# IMPORTANT: All collaborators must re-clone — old clones retain the history
Step 4: Notify affected services and audit
- Check service audit logs (AWS CloudTrail, GCP Audit Logs, Stripe Dashboard) for any actions taken with the leaked credential.
- If a database credential leaked: audit all tables for unexpected inserts, deletes, or exports.
- If a payment API key leaked: review recent transactions for fraudulent charges.
Step 5: Add protections to prevent re-occurrence
# Verify .gitignore covers secret files
echo '.env' >> .gitignore
echo '.env.*' >> .gitignore
echo '!.env.example' >> .gitignore
git add .gitignore && git commit -m "chore: ensure .env excluded from git"
Prevention
- Add pre-commit secret scanning: Install
pre-commitwith thedetect-secretsorgitleakshook so secrets are caught before they ever reach git history. - Use a secrets manager: Store all credentials in 1Password, AWS Secrets Manager, or HashiCorp Vault — never hardcode or commit them to source control.
- Never run DEBUG=True in production: Django and Flask debug pages print environment variables and stack traces containing database URLs. Set
DEBUG=Falseand configure proper error pages. - Enable GitHub secret scanning: In repository Settings → Security → Secret scanning, enable both push protection (blocks the push) and alerts (detects existing secrets).
- Principle of least privilege: Issue API keys with the minimum required permissions — a leaked read-only key causes far less damage than a leaked admin key.