Secrets, .env & push protection

Keep secrets out of Git

Lesson 19 of 20 · 12 min · Module 6: Safe releases and the capstone

Passwords, API keys and tokens must never be committed. Anyone who can read a repository can read every version of every file in it, forever.

Start this lesson →

Free. No installation: you write and run the code in your browser, and it is checked automatically.

By the end, you’ll be able to…

  • Explain why committed secrets stay exposed
  • Get past push protection by removing the secret
  • Keep secrets in ignored files with a committed example

The idea

Keep secrets in a file such as .env that Git ignores, and commit a .env.example with the same names and empty values so others know what to fill in. GitHub's push protection blocks pushes that contain known secret formats. Deleting the file in a new commit does not help, because the old commit still holds the secret: remove it from the commits you are about to push, for example with git reset. If a secret was ever pushed anywhere, treat it as leaked: revoke it and create a new one.

git reset HEAD~1
echo ".env" >> .gitignore
echo "CTPAY_SECRET_KEY=" > .env.example

01History never forgets

Every version of every file stays in the history, and every clone has a full copy. Deleting a secret in a later commit hides it from the latest version only. That is why secrets must never be committed at all.

02Push protection catches known secrets

GitHub scans what you push for known key formats and refuses the push. To get past it, remove the secret from the commits you are pushing. Here the commit is still local, so undo it with git reset and commit again without .env.

git push
Output
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 461 bytes | 461.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote:
remote: - GITHUB PUSH PROTECTION
remote:   —————————————————————————————————————————
remote:     Resolve the following violations before pushing again
remote:
remote:     - Push cannot contain secrets
remote:
remote:
remote:      (?) Learn how to resolve a blocked push
remote:      https://docs.github.com/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push
remote:
remote:
remote:       —— Code Temper Pay Secret Key ————————————————————————
remote:        locations:
remote:          - commit: 83045e22a2ee2ae496447673c2002053a41021ac
remote:            path: .env:1
remote:
remote:        (?) To push, remove secret from commit(s).
remote:
To https://github.com/learner/website.git
 ! [remote rejected] main -> main (push declined due to repository rule violations)
error: failed to push some refs to 'https://github.com/learner/website.git'

03Ignore the real file, share an example

.env holds the real values on your computer and is ignored. .env.example lists the same names with empty values, so teammates know what to fill in.

git reset HEAD~1
echo ".env" >> .gitignore
echo "CTPAY_SECRET_KEY=" > .env.example
git status --short
Output
?? .env.example
?? .gitignore
?? checkout.js

Your turn

Your commit Add online payments accidentally includes .env, which holds a secret key. Push it and read how push protection blocks it. Then undo the commit but keep your files with git reset HEAD~1, add .env to .gitignore, and create .env.example containing CTPAY_SECRET_KEY= with no value. Commit checkout.js, .gitignore and .env.example as Add online payments, and push.

What the checks look for

  • Push protection blocks the first push
  • The payments commit is on GitHub
  • The secret key never reached GitHub
  • .env is ignored, untracked, and still on your computer
  • .env.example is committed without a value
  • Everything is committed and pushed
Try it in the editor →

Take this with you

Keep secrets in ignored files, commit only examples, and revoke anything that leaked.

Go a little further

Run git log --all --oneline -- .env after the reset: no commit contains .env any more, which is exactly why the push now succeeds.

Learn Git & GitHub for free

20 short lessons with real code and instant checks.

See the whole course →