What to Do When Django Secrets Leak
Respond to a leaked Django SECRET_KEY, database password, or API key: assess exposure, rotate in the right order, use SECRET_KEY_FALLBACKS only when appropriate, invalidate sessions and tokens, purge Git history, and prevent the next leak.
Problem statement
A .env file was pushed to a public repository, a DEBUG = True page exposed settings, a CI log printed environment variables, or a former contractor still has the production credentials. Secret scanners (GitHub's, or attackers') find leaked keys within minutes of publication. What matters now is speed and order: rotate what an attacker could use, invalidate what they could have forged or captured, check for misuse, and close the hole that leaked it, without causing a self-inflicted outage.
- Assume compromise. Rotating is cheaper than investigating whether the secret was used.
- Rotate external credentials first (database, cloud keys, payment, email APIs), because they grant direct access.
- Rotate
SECRET_KEYwithout fallbacks if it leaked: sessions, password reset tokens, and signed cookies become invalid, which is the point. - Revoke API tokens and OAuth tokens that were exposed; force password resets if password hashes leaked.
- Remove the secret from the repository and history, but rotate anyway; forks and caches keep copies.
- Check logs for misuse, fix the cause, and add secret scanning.
Step-by-step solution
1. Contain and assess
Make the repository private or remove the file, disable DEBUG, delete exposed CI logs. Then list every secret in the leaked material: the whole .env, not just SECRET_KEY. Note when it was exposed and where (public repo, internal chat, logs).
2. Rotate external credentials
Order by blast radius:
- Cloud provider keys (AWS access keys can create resources and read buckets): deactivate the leaked key and issue a new one, or better, move to roles.
- Database password: change it (
ALTER ROLE app PASSWORD '...'), update the app's secret, restart, then confirm old connections are gone. - Payment, email, storage API keys: roll them in each provider's dashboard (Stripe supports rolling keys with a grace period; for a leak, use a short or no overlap).
- Third-party webhooks secrets: regenerate.
Update the secret store first, then restart processes so they pick up new values.
3. Rotate SECRET_KEY properly
SECRET_KEY signs sessions, password reset links, messages, and anything using django.core.signing. With the key, an attacker can forge session cookies for any user if you use signed-cookie sessions, and create valid signed values. For a leak:
SECRET_KEY = env("SECRET_KEY") # new value
SECRET_KEY_FALLBACKS = [] # do NOT keep the leaked key
All users are logged out and outstanding reset links stop working. That is acceptable and necessary. SECRET_KEY_FALLBACKS is for planned rotation of a key that is still secret; see Rotate Django SECRET_KEY Safely.
4. Invalidate tokens and sessions
- Delete all sessions if you use database or cache sessions:
python manage.py shell -c "from django.contrib.sessions.models import Session; Session.objects.all().delete()"(or flush the session cache). - Revoke DRF tokens, JWT signing keys (rotating the key invalidates issued tokens), and OAuth refresh tokens.
- If the database itself was exposed, force password resets and notify users as your legal obligations require.
5. Clean the repository
Remove the secret from history with git filter-repo or BFG, force-push, and ask collaborators to re-clone. Contact GitHub support to purge cached views if needed. This reduces future exposure but cannot recall copies already taken, which is why rotation comes first.
6. Investigate and prevent
Check cloud audit logs (CloudTrail), database logs, payment provider logs, and Django logs for activity during the exposure window. Then fix the cause: add .env to .gitignore, enable GitHub secret scanning and push protection, add gitleaks to pre-commit and CI, move secrets to a manager, and make sure DEBUG cannot be enabled in production.
Verification check: the leaked values no longer work (test the old database password and API keys and confirm they are rejected), the app runs on new secrets, old session cookies are rejected, and secret scanning now blocks a test commit containing a fake key.
Explanation
A secret's value lies in being unknown; once exposed, it must be treated as known to attackers, because automated scanners harvest public repositories and logs continuously. Credentials that grant direct access to data or infrastructure carry the highest risk, so they are rotated first. Django's SECRET_KEY protects integrity rather than granting access, but forged sessions and tokens can amount to account takeover, so it must be replaced without keeping the old key valid. Cleaning history and adding scanning reduce future risk but do not undo the exposure.
Edge cases / notes
- Encrypted fields using keys derived from
SECRET_KEY(some third-party packages) need data re-encryption with a migration before rotating; check before you rotate. - Multiple environments that shared a key all need rotation.
- Legal and notification duties may apply if personal data could have been accessed.
- Practice. A rotation drill in staging makes the real event faster and calmer.
Next steps
- Routine rotation without logouts: Rotate Django SECRET_KEY Safely.
- Prepare the wider response with the Django Incident Response Runbook.
FAQ
What should I do if my Django SECRET_KEY leaked?
Generate a new key, deploy it without the old one in SECRET_KEY_FALLBACKS, and accept that users are logged out.
Is removing the secret from Git history enough?
No. Copies may already exist. Rotate the secret, then clean history.
Can attackers log in with a leaked SECRET_KEY?
They can forge signed data, including sessions with the signed-cookie backend and password reset tokens in some cases. Rotate immediately.
How do I prevent secrets leaking into Git?
Use .gitignore, secret scanning with push protection, gitleaks in pre-commit and CI, and a secrets manager.