Secrets Management Standard: No Secrets in Code or Environment

Secrets Management Standard: No Secrets in Code or Environment
Published

15 Jun 2026

Author
Joseph Bridge

Joseph Bridge

Table of Contents

A developer pushes a feature branch to a personal GitHub fork over the weekend, not realising the branch carries a hardcoded database password from a Tuesday debugging session that never got cleaned up. Within hours, automated scanners crawling new public commits have indexed the credential. By Monday morning, the connection string is in three credential-stuffing toolkits. The team rotates the secret, audits the access logs, and writes a post-mortem. The breach has already happened. Rotation is damage control, not prevention.

This is the most boring class of security incident in modern software, and it keeps happening because most teams treat secrets management as intention rather than enforcement. They have a wiki page that says "don't commit secrets." They have a vault one engineer set up two years ago. What they do not have is a system that makes committing a secret structurally impossible — and that is the only standard that actually works.

Across our Built to Last™ 2.0 engagements, the pattern repeats often enough that we treat secrets as the artefact most likely to outlive any policy written about them. A secret in Git is a breach waiting for a search query. A secret in a .env file passed over Slack is a breach waiting for an account compromise. Real secrets management means a dedicated manager, automated rotation, signed audit trails, and a pipeline that refuses to deploy when any of those break.

What "in code or environment" actually means

The common defence — "we don't commit secrets, we use environment variables" — is wrong by a wide margin, and the phrase needs unpacking before the standard makes sense.

A secret is "in code" if it appears anywhere in the working tree, the Git history, or any branch ever pushed to a remote. Force-pushing does not erase a secret from forks, clones, GitHub's reflog, or the caches of crawlers that watched it land. If a credential was committed, treat it as exposed. That includes the supposedly-private mistake on a feature branch you swear nobody else cloned.

A secret is "in environment" if it sits in a .env file, a Docker Compose override, a Kubernetes ConfigMap, a plaintext CI/CD variable, a Jenkins parameter, a shared 1Password entry that should have been a service vault entry, or an environment variable on a long-lived server. Environment variables are not protection. They are convenience. Anything with access to the process, the host, the orchestrator, or the CI runner can read them — and that "anything" is a much longer list than most teams think it is.

The correct home for a secret is a purpose-built secrets manager — AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, Azure Key Vault, or 1Password Service Accounts being the common options. Defining properties: encryption at rest and in transit, identity-bound access, automatic rotation hooks, audit logging that cannot be silently disabled, and runtime integration so a secret is fetched at the moment of use, not staged in the environment.

The five components of an enforced standard

Standards without enforcement are wishes. The standard we apply across BTL 2.0 engagements has five components, each of which closes a specific class of incident.

Centralised secrets manager. Every secret — API keys, database passwords, signing keys, third-party credentials, encryption keys, internal service tokens — lives in a single, identity-controlled vault. No exceptions for "just this one." Every exception accumulates into the next breach. The vault becomes the source of truth; everything else is a fetch.

Pre-commit hooks for secret detection. Tools like gitleaks, trufflehog, and detect-secrets run as Git pre-commit hooks on every developer machine. The commit is blocked locally before the secret reaches a remote. This is the cheapest layer to install and the one that catches the most accidents.

Pipeline-level secret scanning. Pre-commit hooks can be skipped with --no-verify. The CI pipeline cannot. Every push and every pull request runs a secret scan across the diff and the history. A detected secret fails the build, blocks the merge, and triggers immediate rotation — because once a secret has been pushed, the rotation must happen even if the merge does not. GitHub's native secret scanning catches the well-known formats; a dedicated scanner catches the rest.

Automated rotation. A secret that never rotates is a secret slowly leaking out through every former employee, every compromised laptop, every old backup. The standard is short-lived credentials wherever the platform supports them (AWS IAM roles, GCP workload identity, Kubernetes service account tokens) and scheduled rotation everywhere else. Quarterly is a minimum; weekly is better; on-demand is the goal.

Audit logging and access review. Every read, write, and rotation against the vault is logged with identity, timestamp, and source. Logs flow to the same observability stack that catches application incidents. Access lists are reviewed monthly. The audit is not for the auditor — it is for the morning when something looks wrong and you need to know who touched what.

When all five operate together, the typical failure modes collapse. A developer who commits a secret has their commit rejected locally. If they bypass the hook, the pipeline catches it. If a secret somehow reaches production, rotation is automatic and the blast radius is measured in minutes. None of these layers is sufficient alone. All five together approach the standard.

Failure modes even when the standard is "in place"

Most teams claim some version of this standard. Few of them survive contact with deadline pressure. The failure modes are predictable.

The vault gets installed but never adopted — new services keep using .env files because that is what every tutorial shows. The fix is to make vault integration the path of least resistance: an SDK trivially easier to use than process.env, and a deployment that fails if the service is not pulling from the vault.

Rotation is configured but never tested. A scheduled rotation that has never been exercised will fail the first time it runs, and that failure will be discovered during an outage. Run a quarterly fire drill in staging, end-to-end, including the cutover moment.

Audit logs exist but nobody reads them. Logs are infrastructure, not protection, until somebody acts on them. Alerting closes the gap — anomalous access patterns, unexpected reads, rotations that did not match a change request, all paging in real time.

Implementing the standard from a cold start

Most teams reading this already have secrets somewhere they should not be. The right starting point is a 90-day plan, not a single sprint of heroics.

In the first two weeks, audit what you have. Scan every active repository with trufflehog or gitleaks against the full Git history, not just the working tree. Identify every active credential. Map every place a secret currently sits — environment files, CI variables, configuration management tools, developer machines. Build the inventory before you build the vault.

By week four, deploy the secrets manager and migrate the highest-risk credentials first: production database passwords, payment gateway keys, release signing keys, any credential whose compromise would be reportable under the Australian Privacy Principles or analogous regimes. Lower-risk credentials can wait a sprint.

By week six, secret scanning runs in CI on every pull request, blocking merge on detection. Pre-commit hooks ship as part of the developer onboarding script. The new path is the easy path.

By week eight, rotation is automated for any credential the platform supports, and quarterly rotation is scheduled for the rest. The first rotation runs in staging as a drill, with the runbook updated based on what breaks.

By week twelve, audit logging is wired into your observability stack, anomaly alerts are configured, and access lists are reviewed for the first time. Any service still pulling from environment variables instead of the vault is flagged for the next sprint.

What to avoid: do not let the perfect become the enemy of the good. A vault with 80% of secrets migrated is dramatically safer than a wiki page with 100%. Migrate the riskiest secrets first, then iterate. This work depends on least-privilege IAM and DevSecOps build standards being in place or in motion — secrets management without least-privilege IAM is a vault with one giant key, and least-privilege IAM without proper secrets management means the vault is the right shape with the wrong content.

A fintech that learned the hard way

A Sydney-based fintech we worked with had a clean security posture on paper. They used a secrets manager for production. They had a written policy. What they did not have was enforcement in the development pipeline.

A junior engineer, debugging a sandbox issue against staging, pasted a staging API key directly into a unit test file to reproduce a failing case. The test file was committed, the pull request was reviewed, the change was merged. The key was a staging credential — but staging shared a logging backend with production, and the key inherited read access to logs containing production transaction metadata.

The exposure was caught six weeks later, during a quarterly access review, when the audit log showed reads against the staging key from an IP block in another country. The team rotated, traced the exposure to the merged PR, and discovered the key had been indexed by a public-commit scanner within four hours of the push. The incident did not become a breach — only because the reading party turned out to be a research scanner, not an attacker. The same incident with a different actor would have been a notifiable data breach under the Australian Privacy Principles.

The remediation was structural, not procedural. They added gitleaks as a pre-commit hook, enabled GitHub secret scanning at the organisation level, and made CI fail on any commit containing a secret pattern. They also migrated test infrastructure to use short-lived credentials issued by the secrets manager at test runtime, so even an accidentally-committed test file could not contain a usable secret. The same class of incident has not recurred.

When this matters most, when it can wait

For any product that touches payments, personal data, health information, or anything covered by the Australian Privacy Principles, GDPR, or analogous regimes, secrets management is non-negotiable from sprint one. The cost of a notifiable breach — investigation, notification, regulator engagement, customer compensation, reputational impact — is multiples of the cost of doing this properly from the start.

For an internal tool with no external data and no production credentials, you can defer the full standard for a sprint or two, but only if you also defer connecting that tool to anything real. The moment the tool gets a production API key, the standard applies. There is no halfway state where it is safe to put a real secret in an environment variable "just for now."

For embedded squads and TaaS engagements, the standard is structural: an external team accessing your codebase needs the audit logs and least-privilege controls more than your internal team does, not less. Vault access should be identity-scoped and revocable in seconds.

What to do next

Run the audit this week. Two days of gitleaks or trufflehog against your full Git history produces the map you need. Every credential it finds gets rotated and added to the vault. Every place a secret currently lives outside the vault becomes a migration target. If you want to see how secrets management sequences alongside the broader build-time security work, the DevOps service overview walks through how we put the gates in place from sprint one.

Frequently Asked Questions

How do we manage secrets safely across development, staging, and production?

The principle is the same in every environment: a secrets manager, identity-scoped access, audit logging, no plaintext storage anywhere. The difference is the scope of access. Developer machines should fetch staging-tier credentials only, with short-lived tokens issued at session start. Staging environments use staging credentials issued by the same vault but scoped to staging resources. Production credentials never leave production identities — no developer should have direct production read access except through a break-glass procedure that triggers an immediate audit notification.

What about rotation — how often is enough?

Rotation cadence depends on the platform. For credentials that support short-lived tokens (AWS IAM roles, GCP workload identity, Kubernetes service accounts), the answer is "every session" — these are issued at runtime and expire automatically. For credentials that do not support short-lived tokens (third-party API keys, SaaS integrations, legacy systems), quarterly is a defensible minimum and monthly is better. Any credential that has never rotated should be treated as compromised; the question is when, not whether.

What if a secret leaks despite all of this?

The runbook is fixed. Within fifteen minutes: rotate the secret in the vault, revoke the leaked version at the provider, verify the new credential is in use across all consuming services. Within an hour: review audit logs for any access using the leaked credential, identify any data that may have been read, assess whether notification is required under applicable privacy law. Within twenty-four hours: blameless post-mortem and a specific change to the standard that closes the path that allowed the leak. The runbook is written before the leak, not during it.

How do we audit who has access to what?

The secrets manager is the source of truth, and the answer should be a query, not a meeting. Every secret has an access policy listing the identities — human or service — that may read it. Every read produces an audit log entry with identity, timestamp, source IP, and calling service. Monthly access review compares current policies against the team and service inventory; anything that should not be there gets removed. Quarterly, the audit log is reviewed for anomalous patterns.

Do we still need environment variables anywhere?

For configuration that is not sensitive — feature flags, log levels, public endpoints, region selection — environment variables are fine. The line is the sensitivity of the value, not the mechanism. Anything that grants access to data, money, infrastructure, or identity is a secret and belongs in the vault. Anything that controls behaviour without conferring access is configuration and can live in the environment.

How do we handle secrets in CI/CD pipelines?

The pipeline runner pulls secrets from the vault at job runtime using an identity issued to the runner itself. Secrets are never stored as plaintext pipeline variables, never echoed to build logs (mask them at the pipeline level), and never persisted after the job completes. For pull requests from forks, secrets are unavailable by default — public-repository projects in particular should treat any PR-triggered job as a potential exfiltration surface.

Is a password manager like 1Password or Bitwarden enough?

For team-shared human credentials — admin logins, SaaS account passwords, shared accounts that should not exist but do — a password manager with audit and access control is appropriate. For machine-to-machine credentials, API keys, and runtime secrets, a password manager is the wrong tool. The integration patterns are not designed for service accounts, the rotation primitives are not API-driven enough, and the audit logs are not granular enough. Use a password manager for humans and a secrets manager for machines.