Software Securityhome
Fix a findingSearch
Phase 3 · Development

Secrets out of code

Keeping secrets out of code means passwords, API keys, tokens and private keys never appear in source, configuration files or build artifacts. The code holds only a reference, and the running workload gets the value at runtime from a secrets manager or a short-lived credential from its platform.

A committed secret is copied to every clone, fork and CI cache, and deleting the file leaves it in history. GitHub's own guidance is to revoke or rotate first, because a history rewrite cannot reach those copies. Blocking secrets at commit and push time stops most leaks before they spread, and loading them at runtime turns the fix for a leak into a rotation instead of a code change and redeploy.

Who
Every developer, with the platform team running the secrets manager and AppSec owning the scanners and leak response
When
Whenever code needs a credential, and at every commit, push and pull request
How often
Scanning on every commit and push; a full-history scan when you start, then regularly in CI
You end up with
Code that reads secrets by reference, scanners that block new secrets, and a rotated credential for every secret found in history

Do this

  1. List the secrets the code uses

    For each credential, record what it unlocks, who issued it, which workloads use it and who owns it. A database password, an API token, a signing key and an encryption key do different damage when stolen, so note the consequence before choosing where each lives. OWASP ASVS 5.0 V13.1.4 (level 3) asks for this list of critical secrets with a rotation schedule.

  2. Read secrets at runtime, by reference

    Replace each literal with a lookup: the code names the secret and gets the value from a secrets manager, or better, uses workload identity that issues short-lived credentials, such as OIDC federation from CI to the cloud instead of a stored cloud key. ASVS 5.0 V13.3.1 says secrets must not be in source code or build artifacts. Runtime secrets covers how the platform delivers them.

  3. Keep local and example configuration clean

    A common pattern is to commit a .env.example with placeholder names and list .env and key files in .gitignore. Give developers their own scoped development credentials rather than copies of production ones. Keep real values out of test fixtures, Dockerfiles, CI logs and front-end or mobile bundles, which ship to every user.

  4. Block secrets before they leave the laptop

    Run a secret scanner such as Gitleaks, TruffleHog or detect-secrets as a pre-commit hook, and turn on push protection at the host: GitHub push protection for the repository, which needs GitHub Secret Protection, or GitLab secret push protection on Ultimate. Restrict who may bypass a block, for example with GitHub's delegated bypass, and review each bypass.

  5. Scan every pull request and the full history in CI

    Hooks run on developer machines, git commit --no-verify skips them, and push protection checks only what is being pushed. Scan pull requests and the whole history in CI too, for example gitleaks git, which exits with status 1 when it finds a leak. Record old findings you have already rotated in a baseline, so the job fails only on new ones.

  6. Treat every finding as a leak

    A secret that reached a shared repository, log or ticket is compromised, even if it was deleted a minute later. Revoke or rotate it at the issuer first, check the issuer's logs from the time of exposure, then remove the copies. Rewriting history is cleanup, not the fix. Contain a leaked secret, then investigate and clean up has the runbook for each credential type.

  7. Prove the controls work

    Rotate one secret as a drill: issue the new value, move every consumer, revoke the old value and confirm the issuer rejects it. If the drill needs a commit or a rebuild, the secret is still in code. Then commit a dummy key on a throwaway branch and confirm that both the pre-commit hook and push protection block it.

Done when

No secret is in the repository or its history, and a leaked one can be rotated without changing code.

Mark it in the gap check

Common mistakes

  • Deleting the file or rewriting history instead of rotating. Clones, forks, cached views and pull requests that reference the commit keep the value, so the secret still works for anyone who copied it.
  • Moving the secret into an environment variable baked into an image or a committed config file. OWASP notes that environment variables are generally readable by all processes and may end up in logs or dumps.
  • Reading a clean scan as proof that no secret exists. Scanners match known formats and high-entropy strings, so a hand-made password or an unfamiliar token format can pass.
  • Letting anyone with write access bypass push protection unreviewed. On GitHub any writer can bypass by giving a reason; without delegated bypass, nobody approves it before the secret lands.
  • Sharing one secret across services and environments. OWASP warns this makes the source of a leak hard to identify, and rotating it breaks every consumer at once.
  • Printing secrets in error messages or CI output. The value then sits in a log store that usually has far wider access than the secrets manager.

Where each kind of secret belongs

Prefer credentials the platform issues for minutes or hours, and keep the static ones that remain in a secrets manager. These are common placements, not the only ones.

Where each kind of secret belongs: table
SecretWhere it belongsHow the code gets it
Cloud access for CI jobsNo stored key: OIDC federation from the CI system to the cloud providerA short-lived token for each job, limited by repository, branch or environment
Cloud access for a workloadAn attached role or managed identityCredentials issued by the platform at runtime, with no key file
Database passwordA secrets manager, or dynamic credentials with a leaseFetched or mounted at start-up; rotated by the manager
Third-party API keyA secrets manager, with the narrowest scope the provider offers and a named ownerFetched by name; rotated on a documented schedule and at once on exposure
Signing and encryption keysA KMS or HSM; ASVS 5.0 V13.3.3 (level 3) asks for an isolated security moduleThe code asks the module to sign or decrypt, and the key never leaves it
Local development valuesA .env file listed in .gitignore, or a personal entry in a development vaultDevelopment credentials only, never production values

Tools and guides

Standards

NIST SSDF 1.1
PW.5 Create Source Code by Adhering to Secure Coding Practices
OWASP SAMM 2
Secure Deployment

Sources

  1. OWASP Secrets Management Cheat Sheet
  2. OWASP ASVS 5.0.0: V13 Configuration (V13.1.4, V13.3.1, V13.3.3). Version 5.0.0.
  3. GitHub Docs: Push protection
  4. GitHub Docs: Secret scanning
  5. GitHub Docs: Removing sensitive data from a repository
  6. GitHub Docs: OpenID Connect in GitHub Actions
  7. GitLab Docs: Secret push protection
  8. Gitleaks: README (scan modes, pre-commit hook, exit codes, baseline)
  9. Git documentation: git commit (--no-verify)
  10. OWASP SAMM: Secure Deployment (Secret Management stream)
  11. NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1. February 2022.

Find your next answer

Type to search. Search runs in your browser.

Searches run in your browser, but the host can infer them from the index files they load. The address shows your search so you can bookmark or share it.