Black Shard

Insights11 March 2026

Secrets Management Past the .env File

The .env file fails at distribution, rotation and revocation, not storage. A field note on secret managers, managed identity, short-lived credentials and finding what has already leaked into git history.

Open brushed steel bank vault door with cold cyan light glowing inside

The file is not the problem, the lifecycle is

The .env file solved one problem: keeping credentials out of source code. It does nothing for the three problems that actually get Australian businesses breached. Distribution: how a secret reaches every machine that legitimately needs it. Rotation: changing it without an outage. Revocation: killing it the day a laptop walks out the door or a contractor rolls off. A secret that lives in a flat file gets copied, and every copy is one you cannot see, cannot audit and cannot revoke.

The test worth applying to your own setup is not "are our secrets encrypted". It is "if a developer's laptop were stolen tonight, could we rotate every credential it held by morning, and would we even know which credentials those were". For most teams running on .env files the honest answer is no on both counts. Getting to yes takes four moves: a secret manager as the single source of truth, managed identity so most credentials never exist, short-lived tokens where a secret is unavoidable, and a rotation drill you have actually run. The rest of this note is the build order.

How .env files actually leak

Git is the big one. A .env committed once lives in the repository forever. Deleting the file in a later commit removes it from the working tree, not from history, and anyone with clone access can check out the old commit. The quieter variant is .env.example drift: the template file starts clean, then someone pastes a real connection string in "temporarily" to unblock a teammate, and it ships in the next push. Nobody reviews changes to an example file with the same eyes they review code.

The build pipeline leaks in ways the repository never shows. A Dockerfile with a broad COPY and no .dockerignore bakes the .env into an image layer, and anyone with registry pull access can extract it with standard tooling. CI systems echo environment variables into build logs. Error trackers capture process state in crash reports. Framework debug pages left on in production will print the entire environment on an unhandled exception: a full credential dump served over HTTP.

Then there is the human channel: onboarding a new developer by pasting the .env into Slack or email. The secret now lives in a searchable message archive with its own retention policy and its own breach surface. If those credentials unlock personal information, a leak like this is how a routine incident becomes an eligible data breach under the Privacy Act's Notifiable Data Breaches scheme, with OAIC notification obligations attached once serious harm to individuals is likely. APP 11 expects reasonable steps to secure personal information, and "the production database password was in a chat thread from 2023" is not a defensible reading of reasonable.

What a secret manager actually buys you

The value of a proper secret manager (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, or a lighter tool like Bitwarden Secrets Manager or Doppler for small teams) is not encryption at rest. Your disk was already encrypted. The value is a single source of truth with an audit log, per-secret access control, and one place to change a value. When a credential lives in one vault, rotation is a single write. When it lives in forty .env files across laptops, servers and CI settings, rotation is an archaeology project you will keep deferring.

Two traps catch teams who adopt a manager and declare victory. The first is the bootstrap problem: the application still needs a credential to reach the vault, and if that credential is a long-lived API key sitting in a .env file, the problem has moved, not shrunk. The fix is platform identity, covered next. The second is the shared-vault trap: a manager where every developer can read every production secret is a .env file with a nicer interface. Scope access so production secrets are readable by production workloads and the small set of humans who operate them. The ACSC's reasoning behind the Essential Eight's restrict administrative privileges strategy applies to secret scopes unchanged: broad standing access is the thing attackers monetise.

For local development, stop materialising the file at all. Modern managers ship CLIs that inject secrets into a process environment at launch, so a developer runs the app through the injector and the plaintext never touches disk. Dev environments get dev-scoped secrets, so a compromised laptop yields staging credentials, not production ones.

The best credential is one that does not exist

On Azure, a VM, container app or function can carry a managed identity: an Entra ID identity the platform owns, with tokens minted and rotated automatically and retrieved by the workload from a local metadata endpoint. There is no secret to store, leak or rotate, and the same shape exists as IAM roles on AWS and service accounts on GCP. If your workload runs on a major cloud and still authenticates to its own database with a password in an environment variable, you are doing extra work to be less secure: Azure SQL, Azure Postgres and Azure Storage all accept Entra tokens directly.

Federation kills the other big credential stash: CI. GitHub Actions can authenticate to Entra ID through OIDC workload identity federation, where the pipeline presents a short-lived signed token, Entra verifies the repository and branch claims, and issues a scoped access token in return. No service principal secret sits in repository settings waiting for a malicious action somewhere in your dependency tree to read it. Where a real secret is unavoidable, third-party APIs and SMTP being the usual holdouts, prefer the short-lived option the provider offers. A stolen credential is worth its remaining lifetime, and driving that lifetime towards an hour turns most leak scenarios into non-events.

Rotation is a drill, not a policy document

A rotation policy that lives in a document and has never been executed is a wish. The capability is what matters, and the capability has a specific shape: two credentials valid at once. Many managed databases and API providers issue dual keys precisely so you can cut traffic over to the new key and retire the old one with zero downtime. If rotating your database password requires a maintenance window and a group chat, you will not do it under pressure, which is the only time it matters.

So run the drill. Rotate one production credential on an ordinary Tuesday and watch what breaks. The failures are the map: the cron job on a forgotten VM, the analyst's local script, the integration a departed staff member configured against the raw key. Every breakage is a hidden copy of the credential, discovered on your schedule rather than an attacker's. Quarterly is a reasonable cadence for a small team; the first drill is the one that pays for all of them.

Assume git already knows, and scan before an attacker does

Run gitleaks or TruffleHog across full history and every branch, not just the current tree. The rule for anything they find is simple: a credential that has ever appeared in a commit is burned, so rotate it. Rewriting history with git filter-repo is cleanup, not remediation, because it does nothing about the clones and forks that already exist.

Treat public repositories as a separate severity class. Automated harvesters watch the public commit stream and try captured credentials very quickly after a push, so a token that reaches a public repo should be treated as compromised from the moment it lands, not from the moment you notice. GitHub's push protection blocks known token formats before they land, and secret scanning flags any already in history; enable both everywhere, but they cannot recognise your internal secret formats, so a scanner in CI with custom rules that fails the build is the layer you own.

A working order of operations

The sequence that works for a small engineering team, the same one we run in our builds and client Azure security reviews, is deliberately boring.

The finish line is not "no secrets in files". It is that every credential in the estate is scoped, auditable, short-lived where the platform allows, and killable in minutes when it is not. Teams that get there stop treating a lost laptop or a leaked repo as a crisis, because the thing that leaked stopped being valuable the hour it left.

  • Inventory: list every credential, where it lives, and what it unlocks. The list will be longer than you expect.
  • Stand up a secret manager and move production secrets into it with scoped access, apps and operators only.
  • Replace stored credentials with managed identity everywhere the platform supports it, starting with your own databases and storage.
  • Cut CI over to OIDC federation and delete the long-lived deploy secrets from repository settings.
  • Scan full git history on every repo, rotate everything found, and add push protection plus a CI scanner.
  • Run a rotation drill on one production credential, fix what breaks, then put the drill on a quarterly cadence.

Ship software you can defend.

Australia-wide, from our Brisbane head office. Someone will contact you as soon as possible.

Open a briefinfo@blackshard.com.au