Skip to content
← All writing
September 30, 2026 · 16 min read

Where Should Your Secrets Live? What Juniors, Seniors & Enterprises Do Differently

  • secrets-management
  • security
  • devops
  • infisical
  • backend
Where Should Your Secrets Live? What Juniors, Seniors & Enterprises Do Differently

Every serious breach post-mortem has the same boring line: "an attacker obtained a valid credential." Nobody broke the encryption or wrote a zero-day. Someone just found a key where a key shouldn't have been.

GitGuardian's State of Secrets Sprawl 2025 counted 23.8 million new secrets leaked on public GitHub in 2024 alone. That's up about 25% on the year before. Uber's 2016 breach started with AWS keys sitting in a private GitHub repo. Toyota left an access key in a public repository for close to five years before anyone noticed.

So the question isn't whether your app has secrets. It has plenty: database passwords, JWT signing keys, SMTP credentials, payment API keys, S3 access keys. The real question is where they live, who can read them, and how fast you can change them when one leaks.

This post covers the three levels I've seen (and lived through): what juniors usually do, what seniors do, and what a well-run engineering org does. After that I'll show how I manage the secrets for my own projects today with Infisical, and why it's the first tool that made doing the right thing easier than doing the lazy thing.


What's inside

  1. What actually counts as a secret
  2. Level 1: How juniors usually handle secrets
  3. Level 2: How seniors handle secrets
  4. Level 3: How good enterprises handle secrets
  5. Secrets managers compared
  6. How I manage my project secrets with Infisical
  7. You leaked a secret. Now what? (a 15-minute playbook)
  8. A checklist you can run on your project today
  9. FAQ

What actually counts as a secret

A secret is anything that grants access or proves identity. If an attacker gets it, they can act as your application.

  • Credentials: database connection strings, Redis passwords, SMTP logins
  • API keys and tokens: Stripe/Razorpay, OpenAI/Anthropic, GitHub PATs, webhook signing secrets
  • Cryptographic material: JWT signing keys, TLS private keys, encryption keys, SSH keys
  • Cloud access: AWS access keys, service-account JSON files, S3/MinIO/RustFS keys

What is not a secret: your API's public URL, feature flags, log levels, the port you listen on. That's configuration. Keeping the two apart matters more than it sounds. Once "config" and "secrets" share one file, everyone who needs to change a log level can also read the production database password.

Rule of thumb: if leaking it would force you to rotate it, it's a secret. If leaking it would just be mildly embarrassing, it's config.


Level 1: How juniors usually handle secrets

This isn't about shaming anyone. We've all shipped at least one of these. You fix them by knowing the patterns first.

1. Hardcoding it in source

// "Just for now, I'll move it later"
var conn = "Host=10.0.0.5;Username=postgres;Password=SuperSecret123";

"Later" never comes. The string gets committed, pushed, and cloned onto every laptop and CI runner. Git never forgets, so deleting the line in the next commit doesn't remove it from history.

2. Committing the .env file

The .env file is a fine idea. The mistake is the missing .gitignore entry, or a git add . at 1 a.m. Bots scan public GitHub continuously, and a leaked AWS key is often abused within minutes of the push.

3. Sharing secrets over chat

"Hey, can you DM me the prod DB password?" After that the password lives in Slack/WhatsApp history forever, it's searchable, and every device that person is logged into has a copy.

4. One key for every environment

The same Stripe key or database password in local, staging and production. Now a compromised dev laptop is a compromised production system.

5. Never rotating anything

The JWT signing key from the first commit, three years ago, still signs tokens today. Former teammates still know it.

The common thread: secrets get treated as strings you need to make the app run, not as access you're handing out.


Level 2: How seniors handle secrets

A senior engineer has usually cleaned up at least one leak, and it shows. The habits shift from "make it work" to "limit the blast radius."

Split config from secrets and document the shape

Commit a .env.example with keys and no values, so a new developer knows exactly what the app needs:

# .env.example — committed
DATABASE_URL=
JWT_SIGNING_KEY=
SMTP_PASSWORD=
S3_ACCESS_KEY=
S3_SECRET_KEY=

The real .env goes in .gitignore from the first commit of the repo, not after the first scare.

Use the platform's local-dev tools

In .NET, keep secrets out of appsettings.json completely during development:

dotnet user-secrets init
dotnet user-secrets set "Jwt:SigningKey" "local-only-dev-key"

They're stored outside the project folder, so they physically can't be committed.

Block leaks before they happen

Add a secret scanner as a pre-commit hook and in CI. gitleaks and trufflehog are both free and catch the classic mistakes before they reach the remote:

gitleaks detect --source . --verbose

Separate credentials per environment and least privilege

Dev, staging and prod each get their own keys. The app's database user can read and write its own schema. It is not a superuser, and it can't drop the database. If the staging key leaks, production doesn't care.

Keep secrets out of Docker images

This is a subtle one that even experienced people miss. ENV and ARG values end up in image layers, and anyone who can pull the image can read them with docker history or by inspecting the layers.

# ❌ Baked into the image forever
ENV STRIPE_KEY=sk_live_xxx

# ✅ Mounted at build time only, never stored in a layer
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci

At runtime, inject secrets when the container starts. Never bake them in when you build it.

Know that environment variables leak too

Env vars are better than hardcoding, but they aren't a vault. They show up in docker inspect, /proc/<pid>/environ, crash dumps, error-tracking payloads, and every child process your app spawns. Seniors either scrub them from logs and error reporters or prefer file-mounted secrets for the most sensitive values.

Have a rotation story

A senior can answer "how would we rotate the DB password without downtime?" before it becomes an emergency. Often the answer is: support two valid keys at once, deploy the new one, then retire the old one.

Where Level 2 still breaks down: secrets are still spread across .env files on servers, CI variables, and people's laptops. Nobody can answer "who accessed the production DB password last month?" Onboarding means someone sends a .env file around. Offboarding means hoping everyone remembers which keys that person knew.


Level 3: How good enterprises handle secrets

At scale, secrets management stops being a developer habit and becomes infrastructure. Mature orgs converge on the same ideas.

1. One source of truth

Every secret lives in a centralized secrets manager: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, or a developer-focused platform like Infisical or Doppler. No .env files get copied around. Apps and pipelines fetch secrets at runtime.

2. Identity-based access instead of shared passwords

Humans log in with SSO. Machines authenticate with workload identity: a Kubernetes service account, a cloud IAM role, or GitHub Actions OIDC tokens. The goal is to have no long-lived static keys in CI at all. The pipeline proves who it is and gets a token that expires in minutes.

3. Short-lived, dynamic secrets

Instead of one database password that lives forever, the secrets manager generates a unique credential per service with a TTL of, say, one hour. A leaked credential expires on its own before anyone can do much with it.

4. Automatic rotation

Static secrets that must exist, like third-party API keys, are rotated on a schedule by automation, not by a calendar reminder someone ignores.

5. Audit logs for every read

Every read, write and change is logged with who, what, when and from where. When something goes wrong, "who could have seen this?" takes minutes to answer instead of days.

6. Access policies and approvals

RBAC per project and environment. Developers can read dev and staging but not production. Changing a production secret can require approval, just like a pull request.

7. Encryption with key hierarchy

Secrets are encrypted at rest with envelope encryption: data keys encrypted by a master key held in a KMS or HSM, so even a database dump of the secrets store is useless on its own.

8. Solving the "secret zero" problem

The hardest question in this space: if the app fetches secrets from a vault, how does it authenticate to the vault? Enterprises answer it with platform identity (cloud IAM, Kubernetes service accounts, OIDC). The bootstrap credential is never a string someone copy-pasted.

9. A practiced incident runbook

Leaks are treated as when, not if. There's a documented, rehearsed process for revoking, rotating and investigating.

The three levels at a glance

Concern Junior Senior Enterprise
Where secrets live Source code, committed .env Git-ignored .env, CI variables Central secrets manager
Sharing with teammates Chat / DMs Password manager, .env.example RBAC + SSO in the vault
Environments One key everywhere Separate keys per env Separate keys + policies per env
CI/CD access Pasted static key Encrypted CI secrets OIDC / workload identity, no static keys
Lifetime Forever Rotated manually Dynamic, short-lived, auto-rotated
Leak prevention Hope Pre-commit + CI scanning Scanning + push protection + alerts
"Who accessed it?" No idea Not really Full audit log
Leak response Panic Rotate by hand Runbook, often automated

You don't need Level 3 on day one. You do need to know where you are and what the next step looks like.


Secrets managers compared

Tool Best for Open source Self-hostable Developer experience
HashiCorp Vault Large platform teams, dynamic secrets at scale Source-available (BSL since 2023) Yes Powerful but heavy to run and learn
AWS Secrets Manager Teams fully on AWS No No Great inside AWS, awkward outside it
Azure Key Vault .NET / Azure shops No No Solid Azure integration, keys + certs + secrets
GCP Secret Manager Teams on Google Cloud No No Simple and cheap, IAM-native
Doppler Teams wanting a polished SaaS No No Excellent CLI and dashboard
Infisical Startups to enterprises wanting open source + great DX Yes (MIT core) Yes, or managed cloud Excellent CLI, dashboard and integrations

My honest take: if you're deep inside one cloud, its native manager is a fine default. If you run on VPSs, multiple clouds, or want to own your infrastructure, you want something cloud-agnostic, and that's where Infisical has been great for me.


How I manage my project secrets with Infisical

For a long time my own setup was solidly Level 2: git-ignored .env files, separate keys per environment, and a mental list of which server had which file. It worked until it didn't. Updating one SMTP password meant SSH-ing into a box, editing a file, restarting a container, and hoping I hadn't forgotten another copy somewhere.

Infisical replaced all of that. It's an open-source secrets management platform, and it's the first tool where the secure path is also the fastest path.

Projects, environments and folders

Each app is a project, with environments like dev, staging and prod built in. Inside each, you can organise secrets into folders (/backend, /mail, /storage). The dashboard shows the same key side by side across environments, so it's immediately obvious when staging is missing a value that prod has.

The CLI injects secrets at runtime

This is the part that changed my workflow. There's no .env file on disk at all:

infisical login
infisical init          # links the folder to a project (.infisical.json holds no secrets)
infisical run --env=dev -- dotnet run
infisical run --env=dev -- npm run start

infisical run fetches the secrets for that environment and injects them as environment variables into that one process only. When the process exits, they're gone. A new machine or a new teammate needs login + init, and nobody sends a .env file over chat.

Machine identities for servers and CI

Servers and pipelines don't use my personal login. They get a machine identity with access scoped to one project and one environment:

export INFISICAL_TOKEN=$(infisical login --method=universal-auth \
  --client-id="$INFISICAL_CLIENT_ID" \
  --client-secret="$INFISICAL_CLIENT_SECRET" \
  --silent --plain)

infisical run --env=prod --projectId="$PROJECT_ID" -- dotnet Portfolio.API.dll

The production server can read production secrets for this project and nothing else. If that identity is ever compromised, I revoke it in one click without touching my own account or any other project.

Features that pushed it past "nice to have"

  • Versioning and point-in-time recovery: every change is versioned, so "who changed the SMTP password and what was it before?" is a click away
  • Secret referencing: compose values like ${prod.DB_HOST} so shared values are defined once
  • Audit logs: who read or changed what, and when
  • Secret scanning: infisical scan catches secrets in your repo and can run as a pre-commit hook
  • Integrations: sync to GitHub Actions, Vercel, Docker, Kubernetes (via an operator), and more
  • Rotation and dynamic secrets: for teams ready to go all the way to Level 3
  • Self-hosting: run it on your own server with Docker, or use their managed cloud

Before and after

Before (.env files) After (Infisical)
Updating a secret SSH, edit file, restart, hope Change in dashboard, restart / redeploy
New machine setup Find and copy the right .env infisical login + infisical init
Knowing what changed Nothing Versioned history + audit log
Secrets on disk Plain text on every server None, injected at runtime
Env drift Found in production Visible side by side in the dashboard

For a solo developer or a small team, this puts you close to Level 3 for an afternoon of setup.


You leaked a secret. Now what? (a 15-minute playbook)

It happens to good engineers too. What separates the levels is the response.

  1. Revoke or rotate first. Investigate second. A leaked key is compromised the moment it's public. Deleting the commit doesn't un-leak it, because bots have already cloned it.
  2. Issue the new secret and deploy it. With a secrets manager this is one update plus a restart.
  3. Check the provider's logs (AWS CloudTrail, Stripe dashboard, DB logs) for any use of the old credential after the leak.
  4. Clean the history with git filter-repo or BFG, and force-push. Treat this as hygiene, not remediation: step 1 is the real fix.
  5. Find the root cause: why did the scanner not catch it? Add the pattern, add the hook, fix the process.
  6. Write it down. A short blameless note makes the next person faster.

If you only remember one thing: rotation is the fix. Removing the secret from Git is cleanup.


A checklist you can run on your project today

  • ☐ .env and other secret files are in .gitignore, and .env.example has keys only
  • ☐ gitleaks detect (or infisical scan) on the full history comes back clean
  • ☐ A secret scanner runs as a pre-commit hook and in CI
  • ☐ Dev, staging and prod use different credentials
  • ☐ App database users have only the permissions they need
  • ☐ No secrets in Dockerfile ENV/ARG or in image layers
  • ☐ Error trackers and logs don't dump environment variables
  • ☐ Secrets live in a secrets manager, not in files copied between servers
  • ☐ CI uses scoped machine identities or OIDC, not a personal token
  • ☐ You know how you'd rotate each critical secret, and you've done it at least once

If you can tick all ten, you're ahead of most production systems out there.


FAQ

Is it safe to store secrets in a .env file?

For local development, a git-ignored .env is acceptable. On servers it's a liability: plain text on disk, copied by hand, no audit trail, and easy to get out of sync. Use a secrets manager that injects values at runtime instead.

Are environment variables secure enough?

They're far better than hardcoding, but they leak through process inspection, docker inspect, crash dumps and child processes. They're a fine delivery mechanism for secrets fetched from a manager. They are not a storage strategy.

Is Infisical free?

The core platform is open source and can be self-hosted, and there's a free tier on the managed cloud. Advanced enterprise features such as approval workflows and some rotation/dynamic-secret options sit on paid plans. Check their pricing page for current limits.

Infisical vs HashiCorp Vault: which should I choose?

Vault is extremely powerful and battle-tested for large platform teams, but it takes real effort to run and learn. Infisical gets most teams to the same practical outcome (central storage, RBAC, audit logs, runtime injection, machine identities) with much less operational overhead and a friendlier developer experience.

How often should I rotate secrets?

Rotate immediately on any suspected leak or when someone with access leaves. Otherwise, aim for automated rotation on a regular schedule (every 30–90 days is a common baseline) and prefer short-lived dynamic credentials where your stack supports them.


The takeaway

Juniors store secrets where the code can find them. Seniors store them where attackers can't easily find them. Good enterprises make sure that even if an attacker finds one, it's scoped, short-lived, audited, and already being rotated.

You don't need a platform team to get most of the way there. Start with the checklist, move your secrets into a manager, and let your tooling make the secure path the easy one. For me, that tool has been Infisical, and I haven't copied a .env file between servers since.

If you're setting up infrastructure for a product and want a second pair of eyes on how your secrets, deployments and backups are wired, get in touch. It's the kind of thing I help teams with.