Secure Code/Guide
Secrets management: keeping keys and passwords out of your code
An API key in a repository is copied to every clone and every fork. How to keep secrets out of code, where to store them instead and what to do in the first hour after a key was committed.
sha256("/secure-code/secrets-management/")A secret is anything that grants access: a database password, an API key, a private key, a webhook signing secret, a cloud access token. Once a secret lands in a git repository it is copied to every clone, every fork and every backup, and removing it later is slow and never complete. The goal of secrets management is easy to state: secrets never live in code, every secret has an owner and a lifetime, and a leak is a routine event with a known procedure.
How secrets end up in code
Nobody plans to commit a key. It happens when a developer pastes a token to test something quickly, when a configuration file with real values is swept in by git add ., or when a debug log containing credentials is checked in. Public repositories are scanned by code hosts and by attackers’ automated tools, so a key pushed to one should be treated as compromised at once.
Private repositories are safer, but not safe. More people can read them than can reach production, and they are cloned onto laptops, CI runners and contractors’ machines. Treat a private repository as a place where secrets leak more slowly, not as a vault.
Step 1: keep configuration out of git
Separate code from configuration. The application reads its secrets from the environment at runtime, and on your machine a .env file supplies those values. That file never leaves your machine. What you commit instead is a .env.example with the variable names and harmless placeholders, so a new team member knows what to set.
# Local configuration with real values
.env
.env.*
!.env.example
# Private keys and certificate bundles
*.pem
*.key
*.p12
# Infrastructure state files can contain secrets in plain text
*.tfstate
*.tfstate.*A .gitignore entry only stops untracked files from being added. If .env was committed before, remove it from the index with git rm --cached .env, commit that change and treat every value in the file as leaked.
Environment variables are a step up from hard-coded values, not the end point. They are visible to other processes of the same user, are inherited by child processes and turn up in crash reports, debug pages and logs. Never bake secrets into container images with ENV or ARG instructions: they stay readable in the image metadata and history.
Step 2: move secrets into a manager
A , sometimes called a vault, stores secrets encrypted, controls who and what may read each one and logs every access. Cloud platforms include one, and there are self-hosted open-source options. For a small team the platform’s own service is usually the easiest start, because identities and permissions already exist there. Whatever you choose, use one central system rather than secrets scattered across CI settings, servers and chat messages.
- Encryption at rest, without key handling on your side
- Access policies per secret or per path, bound to the identity of a person, server or pipeline
- An audit log that shows who read which secret and when
- Versions, so you can rotate a secret and roll back if a deployment breaks
- An API or agent, so applications fetch secrets at start-up instead of reading files from disk
Step 3: short-lived credentials and least privilege
The best secret is one that expires before anyone can misuse it. Many platforms issue : a database user or cloud token created on demand, valid for minutes or hours and revoked automatically. CI pipelines can often authenticate to cloud providers with OpenID Connect () federation. The pipeline presents a signed identity token and receives a short-lived access token, so no long-lived key sits in the CI settings at all.
Where static keys are unavoidable, scope them tightly. An API key for sending email does not need permission to manage domains, and a deployment token for one repository should not work on all of them. Rotate static keys on a schedule, and immediately when someone with access leaves the team.
Step 4: scan before and after the push
Secret scanning tools look for the formats of known credentials and for long random strings. Run them in three places: on the developer’s machine as a , on the code host as , and over the full history in CI. Several code hosts offer secret scanning, and some can reject a push that contains a recognised credential. Check what your host enables by default, because on some platforms push protection must be switched on per repository.
#!/bin/sh
# Block staged files that look like secrets. Allow the committed example file.
blocked=$(git diff --cached --name-only --diff-filter=ACM \
| grep -E '(^|/)\.env($|\.)|\.(pem|key|p12)$' \
| grep -v '\.env\.example$')
if [ -n "$blocked" ]; then
echo "Commit blocked. These files look like secrets:"
echo "$blocked"
exit 1
fi
# Then run your chosen secret scanner on the staged changes here.A hook in .git/hooks is not shared when someone clones the repository, and git commit --no-verify skips it. Distribute hooks with a hook manager and repeat the same scan in CI, where it cannot be bypassed quietly.
When a secret was committed: rotate first
- Revoke or rotate the secret at the provider and deploy the new value.
- Check the provider’s logs for use of the old secret since the commit, and treat any unexplained use as an incident.
- Find where else the same value was used: other repositories, CI variables, servers, documentation.
- If the repository must be cleaned, rewrite history with a dedicated tool such as
git filter-repo, force-push and ask everyone to re-clone instead of merging old branches back. - Ask your code host to purge cached views and pull request references that still show the commit.
- Record what happened and add the pattern to your scanner so the same mistake is caught next time.
Once a key is rotated, rewriting history is often optional. The old value is worthless, and rewriting a shared repository disrupts everyone working on it. Do it when the commit contains something you cannot rotate, such as personal data, or when your policy requires it.
Secrets hygiene
0 of 8 done
Your ticks stay in this browser.
Sources and further reading
- OWASP Secrets Management Cheat Sheetcheatsheetseries.owasp.org
- Git documentation: gitignoregit-scm.com
- GitHub Docs: About push protectiondocs.github.com
- GitHub Docs: Removing sensitive data from a repositorydocs.github.com
General information, checked on 31 October 2026. Test changes on one system first and keep a way back. Found something outdated? Tell the editors.