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.

High impact5 min readDevelopers, AdminsUpdated 31 October 2026

Fingerprint of this page: 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.

text.gitignore
# 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.

bash.git/hooks/pre-commit (make it executable with chmod +x)
#!/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

  1. Revoke or rotate the secret at the provider and deploy the new value.
  2. Check the provider’s logs for use of the old secret since the commit, and treat any unexplained use as an incident.
  3. Find where else the same value was used: other repositories, CI variables, servers, documentation.
  4. 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.
  5. Ask your code host to purge cached views and pull request references that still show the commit.
  6. 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

  1. OWASP Secrets Management Cheat Sheetcheatsheetseries.owasp.org
  2. Git documentation: gitignoregit-scm.com
  3. GitHub Docs: About push protectiondocs.github.com
  4. 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.

FAQ

Questions readers ask

Is a private repository safe enough for secrets?

No. Access to private repositories is usually broader than access to production, and every clone on a laptop or CI runner is another copy. Keep secrets in a secret manager and let the code refer to them by name.

Are environment variables secure?

They are better than hard-coded values, but other processes of the same user can read them, child processes inherit them, and they appear in crash dumps and logs. Use them as a delivery mechanism if you must, with the values coming from a secret manager at start-up.

How often should I rotate keys?

Rotate on a schedule that matches the risk, and always when a key may have been exposed or someone with access leaves. Automate rotation where the platform supports it: a manual procedure that is never practised tends to fail in an emergency.

Do I have to rewrite git history after a leak?

Not always. Rotation is what protects you; rewriting history only removes an already worthless value. Rewrite when the commit contains data you cannot rotate, and remember that forks and existing clones keep the old history.

Keep reading

More from Secure Code