Our services

If you can think it, we can make it brainsoft.

Storing secrets so they are not in the repo

Written By: BrainSoft In Security

The first time you find an AWS key in a commit from eight months ago, you learn two things. It is still valid, and it is still in the history even after you delete the file. Git remembers everything. A secret that touches the repo is a secret you have to rotate, and rotation is the part everyone forgets until an alert fires.

The fix is to keep secrets out of the working tree and out of history: load them from the environment locally, from a managed secret store in production, and from CI variables in the pipeline. Below is the setup I use, step by step, plus a scanner to catch mistakes before they land.

  1. Ignore the local env file before you create it

Do this first. If .env is not ignored, someone will commit it within the week. Add the patterns and commit that change on its own so the ignore rule is in history before any secret exists.

# .gitignore
.env
.env.*
!.env.example
*.pem
*.p12
  1. Commit a template, never the real values

New developers need to know which variables exist, not what they are worth. Keep an example file with empty or obviously fake values and document where the real ones come from.

# .env.example
DATABASE_URL=
STRIPE_SECRET_KEY=
JWT_SIGNING_KEY=
  1. Read config from the environment, not from code

The application should crash loudly at boot if a required variable is missing. That is better than a silent default that only fails in production. Here is the shape in Node, but the idea is the same in any language.

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env var: ${name}`);
  return value;
}

export const config = {
  databaseUrl: required('DATABASE_URL'),
  stripeKey: required('STRIPE_SECRET_KEY'),
};
  1. Use a secret manager in deployed environments

Locally, a plain .env file is fine. In production, stop shipping secrets as container environment variables baked into a deploy. Pull them at runtime from a store the app authenticates to with its own workload identity. AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault and Doppler all do this; pick the one that matches your cloud.

aws secretsmanager get-secret-value \
  --secret-id prod/app/db \
  --query SecretString \
  --output text

The app fetches the value once at startup, keeps it in memory, and never writes it to disk. Rotation becomes a store-side operation plus a restart, not a code change. If your team needs help wiring this into an existing deployment, that is the kind of thing our services cover.

  1. Put pipeline secrets in the CI provider, not in the YAML

CI files are in the repo. Anything you paste into them is public to everyone with read access. Reference a masked variable instead, and mark it as protected so it only resolves on protected branches.

deploy:
  script:
    - ./deploy.sh
  variables:
    REGISTRY_TOKEN: $REGISTRY_TOKEN
  1. Scan before the commit, not after the incident

A pre-commit hook that runs a secret scanner catches the mistake while it is still local. gitleaks and trufflehog both work well. Install the hook once per clone and add it to onboarding docs.

gitleaks protect --staged --verbose
  1. If a secret did get committed, rotate it

Rewriting history with git filter-repo is worth doing to clean the tree, but it does not make the value safe. Anyone who cloned or forked has it. Rotate the credential first, then clean history, then tell the team to re-clone.

Frequently asked questions

Is a private repository safe enough for secrets?

No. Private repos still have every collaborator, every CI runner, every fork and every backup copy. Access gets wider over time and history is permanent. Treat the repo as public and keep secrets out of it.

How do I remove a secret that is already in Git history?

Rotate the credential first, because the old value is already exposed. Then use git filter-repo to strip the file from history, force-push, and have everyone re-clone. Deleting the file in a new commit does not remove it from earlier commits.

Should developers share one .env file over chat?

Not for anything real. Use a shared secret manager with per-person access and an audit log, or issue separate development credentials per engineer. A file pasted into chat lives in that channel forever.


#Security