securitydevopssecretsconfiguration

Secrets management: how not to leak API keys, .env files or tokens

Where project secrets should live and how to protect them: environment variables, .env in gitignore, rotation after leaks and secrets managers.

August 27, 2026·8 min read

Leaked credentials remain the number-one entry vector in real incidents — and almost no leak is sophisticated: it's a .env committed to GitHub, a key pasted into a public log, or a token inside the frontend bundle. This article organizes where each secret must live by context, and what to do when (not if) something leaks.

The foundational rule: code never contains secrets

Three layers where secrets must NOT appear:

  1. Source code: neither hardcoded nor as constants. Repos get replicated, cloned, inherited.
  2. The repository: not even in old commits. Git remembers everything; deleting the file in the latest commit doesn't erase history.
  3. The frontend: everything reaching the browser is public by definition. An API key in your JS bundle is a gift-wrapped present for scrapers — bots crawl public repos hunting key patterns within minutes (GitHub scans automatically and notifies vendors: AWS revokes leaked keys without asking).

The correct separation: code reads process.env.DATABASE_URL / os.environ["..."]; real values live outside the code, injected by the runtime environment.

Environment variables: the operating standard

The 12-factor pattern governing practically all modern deployments:

# .env (LOCAL, never in git)
DATABASE_URL=postgres://app:secret@db:5432/app
STRIPE_KEY=sk_live_...
RESEND_API_KEY=re_...

Operating rules:

  • .env in .gitignore from minute zero — before the first commit, not after. Our .gitignore generator includes it by default alongside other sensitive artifacts.
  • .env.example DOES go to the repo: structure with empty or dummy keys documenting WHAT the app needs without revealing values.
  • One .env per environment; production uses native platform variables (Vercel/Netlify/Docker secrets/K8s secrets), never loose files.
  • Context prefixes (VITE_, NEXT_PUBLIC_, REACT_APP_) mark what DOES reach the browser: seeing a real key behind those prefixes is an active security bug.

Taxonomy: which secret fits where

Secret Frontend-safe? Correct store
Payment API key (Stripe secret) Never Backend env
Publishable API key Yes (that's its job) Frontend env
JWT signing secret Never Backend env / KMS
CI/CD service token Never CI secrets
TLS private key Never Server / cert manager
Firebase public config Yes Frontend (with real security rules)

Important nuance: "public" keys (Stripe publishable, Firebase config) are designed for exposure — their security relies on server-side restrictions (Firestore rules, allowed domains). Exposing them isn't a leak; exposing their private siblings is.

When it already leaked: emergency protocol

A secret in a public commit means assume it compromised NOW, even deleting it a second later — automated bots capture it in seconds. Protocol:

  1. Rotate/revoke immediately in the vendor panel. It's the only action that matters; everything else is forensics.
  2. Review usage logs: any anomalous activity between leak and rotation?
  3. Clean history ONLY if the repo is private and you keep it anyway (BFG Repo-Cleaner or git filter-repo) — on public repos it's pointless: already crawled. Rotation rules.
  4. Add preventive detection: gitleaks or trufflehog in pre-commit and CI block pushes containing key patterns.

And the awkward special case: keys inside Docker images. ENV values are baked into layers visible via docker history. Build secrets → build args used-and-deleted in one layer, or BuildKit mount secrets.

Secrets managers: when to level up

Environment variables suffice up to a point; their limits push toward dedicated platforms:

  • Centralized rotation: changing one credential in Vault/AWS Secrets Manager/Doppler propagates to every consumer without redeploy.
  • Audit: who read which secret when — impossible with files.
  • Dynamic secrets: ephemeral DB credentials generated per use (TTL hours), shrinking the value of any theft.
  • Encryption at rest and role-based access: secrets don't sleep in plaintext on shared disks.

For personal projects, well-stored env vars + encrypted backup suffice. The ladder climbs with team size and service count.

Cheap daily hygiene

  • Never paste tokens into issues, PRs, Slack or commit messages.
  • Logs: never log authorization headers or full bodies of sensitive endpoints.
  • Keep .env.example updated when adding new variables.
  • Periodic review: occasionally run git log -p -- '*.env*' '*/config/*', and gitleaks over all history (gitleaks detect --log-opts="--all").

FAQ

Is my private repo safe? Relatively: still a single point of failure (stolen laptop, compromised account, departing collaborator). Secrets live better outside the repo even when private.

Encrypting .env inside the repo (git-crypt, SOPS)? A legitimate mature solution for teams: repo carries the encrypted file, key lives in KMS/cloud. Extra complexity justified only with multiple environments and large teams.

What about third-party client keys (maps, analytics)? Restrict by domain/referrer in the vendor panel and budget quota — that's their intended design. Ones that don't allow restriction shouldn't be there.


Make sure your .env never enters the repo with our .gitignore generator, free and right in your browser.

Try it without code

.gitignore Generator

Combine technologies to generate your .gitignore.

Open .gitignore Generator

Built by

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

I develop web apps, digital tools and full projects — from design to deployment.

Contact me