Secrets management standards
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Applies to the operational lifecycle of already-generated secrets: deciding on and deploying the
manager, modelling paths and policies, dynamic secrets with TTL, the injection mechanism into
the consumer (file, API, systemd credential, Kubernetes operator), automatic rotation
and testing it, responding to the exposure of a secret, detecting secrets in
code and history, encrypting secrets when they must live in the repository, secrets in
images, container layers, build artifacts, logs and backups, auditing and least
privilege over the manager itself, HA/sealing and behaviour when it goes down, and the separation
between human secrets and machine secrets.
Triggers: vault/bao CLI, vault.hcl, HCL policies, bao operator unseal,
ExternalSecret/ClusterSecretStore/PushSecret, SecretProviderClass, SealedSecret,
.sops.yaml, sops -e, age/age-keygen, LoadCredential=/LoadCredentialEncrypted=/
systemd-creds, .gitleaks.toml/.gitleaksignore, gitleaks detect, trufflehog git,
bws, infisical run, aws secretsmanager get-secret-value, az keyvault secret show,
gcloud secrets versions access, "rotate credential", "leaked secret", "push protection".
Guiding principle: the best secret is the one that does not exist. Before storing a
credential, the job is to eliminate it: short-lived federated identity (CI OIDC into
the cloud, the provider's workload identity, SPIFFE/SPIRE between services) replaces the
static secret with no custody, no rotation and no leak risk. The secrets manager is for
what cannot be eliminated, and its real goal is not to be a vault of eternal
passwords, but to issue dynamic credentials with a short TTL. Corollary: an architecture
with many stored secrets is not a well-guarded architecture, it is a badly
designed one.
Not applicable: see cryptography-pki-standards (algorithm choice, key
generation, randomness, PKI and certificate issuance, KMS/HSM as a cryptographic primitive and
envelope encryption — all of that is theirs; here only the custody, distribution, operational
rotation and consumption of already-generated material),
identity-access-management-standards (the preferred alternative to the static secret: IdP,
OAuth 2.1/OIDC flows, tokens, federation, SPIFFE/SPIRE, PAM/JIT — before storing a secret
you check there whether it can not exist at all), cicd-standards (the pipeline, its OIDC, pinning
of actions and the gates that run the scan), kubernetes-standards (the Secret object,
encryption in etcd, RBAC and admission), iac-standards (the Terraform state contains secrets
in clear text and sensitive variables; handling the state is theirs), aws-standards /
azure-standards / gcp-standards (configuration, IAM and cost of the specific managed
service — here the criteria for use and consumption), data-platform-standards (credentials and
encryption at rest of the data engine), windows-server-ad-standards (gMSA/dMSA, LAPS and
krbtgt rotation: directory secrets), bash-linux-scripting-standards (the
script that consumes the secret without leaking it through ps or the log),
incident-response-forensics-standards (mass credential rotation during a
compromise: there the incident process, here the mechanism that makes it possible in hours and
not in weeks), incident-management-standards (declaration, severity and communication),
detection-engineering-standards (shared and bidirectional boundary: the telemetry of
this domain —anomalous reads from the manager, a secret detected in a push, use of a
credential from an impossible origin, a secret read after having been revoked— is a
first-order detection source; the scanning is mine, the rule that turns the finding
into an alert and its runbook are theirs), privacy-engineering-standards (a personal data item is not
a secret and is not managed with these tools), grc-compliance-standards (the regulatory
control that demands custody and rotation, and its evidence), bcdr-standards (custody of recovery
keys outside the backed-up system and their role in the continuity plan), and the
language skills —among them powershell-standards, which delegates here the choice of manager
and secrets scanner and keeps the code criteria: SecretManagement/SecretStore as
front-end, and the prohibition of ConvertTo-SecureString -AsPlainText -Force with a secret written
in the file—, developer-workstation-standards (the choice of secrets manager and scanner
belong here; custody on the developer's machine is theirs —key in hardware, PIN and
mandatory touch, a credential.helper that does not write in clear text, token outside the shell
history and the ~/.netrc, and the credential scope a coding agent can reach—),
and solidity-standards (the contract is theirs; custody of the deployment
key and of the owner/upgrader key belongs here —HSM or multisig, rotation, threshold—.
Fact that orders the priority: private key compromise explains more than 25 % of on-chain
thefts and four of the ten largest; an upgradeable contract shifts all the risk to whoever
holds that key, so a single hot key is not an acceptable architecture).
2. Default decisions
Data verified Aug 2026. Verify version, licence and governance on the web before
committing to a platform (§8): this domain had licence, foundation and
maintenance changes in 2025-2026, and several of them invalidate earlier recommendations.
2.1 The decision hierarchy (in this order, always)
- Can the secret not exist? → OIDC/workload identity federation (CI→cloud),
SPIFFE/SPIRE (service→service), the provider's managed identity. With no secret there is no
custody, no rotation and no leak.
- Can it be dynamic? → the manager generates the credential on the fly with a TTL (database
user, temporary cloud credential, short-lived certificate). The secret exists for
minutes, not years.
- Can it be short-lived and revocable? → token with TTL and effective revocation.
- Only then: static secret in the manager, with an owner, policy, auditing and
tested rotation.
2.2 Manager
| Need |
Default |
Justifiable alternative / vetoed |
| A single cloud, workloads only in it |
The native manager (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) |
It is the KISS option: IAM already exists, native integration and zero operations. Deploying Vault/OpenBao "just in case" with a single cloud is over-engineering |
| Multi-cloud, on-prem or serious dynamic secrets |
OpenBao 2.6.1 (MPL-2.0, OpenSSF/Linux Foundation, Sandbox level since Jun 2025) |
HashiCorp Vault 2.0.3 is still valid and is more mature in replication — but its licence is BUSL 1.1 since Aug 2023 and has not changed after the acquisition by IBM closed (27 Feb 2025): today the Licensor in the LICENSE file is IBM. It is an ADR decision with legal review, not a technical one |
| "Product-grade" manager with DX and UI |
Infisical 0.162.15 (MIT core, ee/ under a proprietary Enterprise licence) |
An honest open core model but with key paid features (dynamic secrets are in the Advanced tier). Valid for small teams; check what you need that sits in ee/ before committing |
| You already use Bitwarden for the team |
Bitwarden Secrets Manager |
Little-known licence warning: the server is AGPLv3, but the SDK where the bws client lives (bitwarden/sdk-sm) is NOT open source — a proprietary licence that forbids using it with software other than Bitwarden. The 2024 GPL controversy was resolved for the password manager, not for Secrets Manager. Its Kubernetes operator is officially installed with --devel (pre-release channel): undeclared maturity |
| Human secrets (team) |
Password manager with shared vaults and MFA |
It is not the same problem nor the same tool (§3.7). Vetoed: using the service secrets manager as the team's password vault, or the other way round |
On Vault↔OpenBao compatibility (the most volatile fact in this skill): it remains
practical in the essentials (API, HCL policies, migration by Raft snapshot), but the
divergence is real and accelerating — verify it before assuming portability:
- OpenBao removed
stored_shares (2.6.0) and is retiring the built-in seals
(awskms, azurekeyvault, gcpckms, ocikms, pkcs11, alicloudkms) in 2.7.0,
moving them to external kms plugins: it is a deployment change, not a cosmetic one.
- Its namespaces have different semantics from those of Vault Enterprise (identity store per
namespace, no group inheritance;
unsafe_cross_namespace_identity flag, false by default,
to restore Vault's behaviour).
- In OpenBao's favour: namespaces at no cost (in Vault they are Enterprise), namespace sealing,
distroless and non-root images, transactional storage, plugins via OCI. Verifiable real
adoption: GitLab Secrets Manager is built on OpenBao and EdgeX 4.0 made it
its default secret store, replacing Vault.
- In Vault's favour: DR and performance replication, integrated automated snapshots,
Sentinel (OpenBao offers CEL). (Comparison from a secondary source: confirm it.)
2.3 Injection into the consumer — best to worst
| Mechanism |
When |
Note |
| No secret: federated token obtained at runtime |
Whenever the platform allows it |
The goal. identity-access-management-standards |
| Manager API from the process, with in-memory cache and TTL |
Application that can be integrated |
Allows rotation without restart, which is half the value of rotating |
File in tmpfs / $CREDENTIALS_DIRECTORY |
systemd services, containers |
In systemd: non-swappable memory, immutable if privileges allow, accessible only to the service's UID and destroyed on exit — the best mechanism in the Linux ecosystem |
| Mounted volume (CSI driver, K8s secret as a volume) |
Kubernetes |
It is refreshed when the secret changes; environment variables are not |
| Environment variable |
Last defensible resort |
See below |
Why environment variables are the worst defensible option — with the
correct argument, because the one usually going around is badly framed:
/proc/PID/environ is NOT world-readable: access to it is governed by a
PTRACE_MODE_READ_FSCREDS check (same UID, or root/CAP_SYS_PTRACE). Using "anyone can
read it" as an argument is incorrect and discredits you in the technical discussion.
- The arguments that do hold: they are inherited automatically by the whole process
tree (any subprocess, including the one that should not see it, and any
curl you
launch from there); there is no per-credential access control (all or nothing); they appear in
dumps and crash traces; they are readable by any process with the same UID and by root;
they end up exposed in introspection interfaces (kubectl describe pod, docker inspect);
they cannot be rotated without restarting the process; and they have size limits and problems with
binary data. The systemd credentials document itself enumerates exactly these
defects against its model, where access is checked in the kernel on every use.
- OWASP (Secrets Management Cheat Sheet) is explicit: "using environment variables is
therefore not recommended unless the other methods are not possible", and forbids Docker
ENV/ARG for secrets.
2.4 Supporting tools
| Area |
Default |
Critical note |
| Secret scanning |
gitleaks 8.30.1 (MIT) as the already-integrated baseline; Betterleaks 1.7.3 (MIT, drop-in, reads .gitleaks.toml and .gitleaksignore) as the successor |
Fact that changes the recommendation: gitleaks declared itself feature complete — "future releases will be security patches only" — and its own README points to Betterleaks, maintained by the same authors. And its GitHub Action is another matter: gitleaks-action since v2.0.0 left MIT and requires a commercial licence (GITLEAKS_LICENSE) for organizations; furthermore v2 stops working on 16 Sep 2026. Scan with the binary, not with the action |
| Deep scanning / live credential verification |
trufflehog 3.96.0 (AGPL-3.0) |
Its ability to verify whether the credential is still live is what sets it apart. Beware AGPL if you integrate it into a product |
| Alternatives |
Kingfisher (MongoDB, Rust), ggshield (requires SaaS), Titus (Praetorian) |
Nosey Parker has been archived since 2026-04-24 (points to Titus) and Yelp's detect-secrets has gone ~27 months without a release: do not start anything new on them |
| Encrypting secrets in the repo |
SOPS 3.13.3 (MPL-2.0, CNCF Sandbox, getsops org) with age 1.3.1 (BSD-3) or KMS |
Open governance risk: cncf/toc#2098 (Mar 2026) declares SOPS "active but has some health issues" and is evaluating whether it can be relicensed away from MPL; if not, it contemplates archiving it or removing it from the CNCF. It remains the best option, but with vigilance and a plan B |
| GitOps alternative on K8s |
sealed-secrets 0.38.4 |
Hard minimum 0.36.0+ (CVE-2026-22728: /v1/rotate allowed widening the scope to cluster-wide). Verified trap: bitnamilegacy/sealed-secrets-controller exists frozen at ~0.31.0 as a side effect of Bitnami's image archiving — repointing there nails you to an unpatched image. Valid alternative registry, signed with cosign: ghcr.io/bitnami/sealed-secrets-controller |
| Syncing to Kubernetes |
External Secrets Operator 2.8.0 |
See §3.4: it is powerful but has a history of governance issues and critical CVEs you must know before adopting it |
| Direct mount in the pod |
Secrets Store CSI Driver 1.6.0 (Kubernetes SIG Auth) |
Avoids materialising a K8s Secret. Trade-off: privileged DaemonSet with the kubelet's hostPath, and its maintainers are publicly asking for hands (low bus factor). In 1.6.0 rotation changed to the requiresRepublish: true model and the rotation RBAC was removed |
| Workload identity |
SPIRE 1.15.2 (SPIFFE and SPIRE graduated in the CNCF since Aug 2022) |
Vault 2.0.0 already accepts SPIFFE JWT-SVID; Entra ID documents federation with SPIFFE/SPIRE |
3. Structure and conventions
3.1 Dynamic secrets: the real goal
- Database credential generated on the fly with a TTL (minutes or hours), revoked when
the lease expires. It eliminates in one go: manual rotation, the secret shared between
services, the "we don't know who uses this password" and most of the impact of a
leak.
- Design requirements people discover late: the application must renew or
reconnect when the lease expires (a connection pool with an expired credential fails at
the worst moment); the manager becomes a dependency on the critical path (§3.6); and the
data engine must support creating/deleting users at that rate.
- It also applies to temporary cloud credentials, short-lived certificates and service
tokens. A secret with a long TTL is a decision, and is justified in the PR.
- Cloud managers do not rotate on their own, except in specific cases — verify it before
promising it:
- AWS Secrets Manager: managed rotation without Lambda only for Aurora/RDS/DocumentDB/
Redshift and a set of external integrations; the rest still requires your Lambda.
Since Jul 2026 it emits change notifications to EventBridge at no cost: use them to
detect rotations that do not happen.
- Azure Key Vault: it has no native rotation of secrets (keys do). The
official pattern is a
SecretNearExpiry event → Event Grid → Function, with a
dual-key scheme. Also, since Mar 2026 vaults created with the 2026-02-01 API use
Azure RBAC by default, and the earlier control plane APIs are retired on
27 Feb 2027.
- GCP Secret Manager: it does not rotate; it emits a
SECRET_ROTATE message to Pub/Sub at
next_rotation_time and you create the new version. Cloud SQL credential rotation
is in Preview (Jul 2026).
3.2 Modelling, policies and auditing of the manager
- The manager is an extremely high-value target: its compromise is the organization's worst
day. It is treated as Tier 0: segmented network, administrative access separated from
consumption access, MFA for humans, and its telemetry watched by
detection-engineering-standards.
- Paths by failure domain, not by team or by convenience:
<environment>/<service>/<purpose>. One policy per consuming identity, with read-only
access to its own paths. Nobody —not an application, not a pipeline— reads *.
- Separation of duties: whoever administers the manager should not be able to read the business
secrets in clear text; whoever reads them does not administer policies. Every operation with root
material audited and with dual control.
- Auditing that answers concrete questions: who read what and when, which identity has
never read a secret it has been granted (dead permission → it is withdrawn), and which secret has
not been read in months (deletion candidate). A secret nobody reads is pure risk.
- Alerting on the manager itself: read spikes, a read from a new origin, policy
change, auditing disabled, unplanned sealing/unsealing, and repeated authentication
failures. With no
audit device configured and with a monitored destination, there is no
secrets management: there is a store.
- Verify that the audit destination does not become the leak: real precedent
(HCSEC-2026-09) — GitHub webhook secrets were exposed in base64 in an
HTTP header that ended up in load balancer, proxy and SIEM logs. The manager's audit logs
are as sensitive as its contents.
3.3 Rotation: if it has never been run, it does not exist
- Every credential has an explicit rotation period and an owner. With no owner there is no rotation,
there is intent.
- Rotation is actually executed and on a schedule, not "when it's due". A
documented but never exercised rotation procedure fails exactly on the day of the
incident, which is when it has to be run under pressure and en masse.
- Design for overlap (dual-key / expand-contract): two credentials valid at
once during the transition window. Without overlap, every rotation is a planned
outage, and that is why nobody rotates.
- Programme metric: time-to-rotate-everything — how long it would take you to rotate all
the credentials in a scope. If the answer is "weeks", you have a latent incident,
just like a PKI that cannot reissue within 24 h.
- Rotation is a recovery gate, not just hygiene: the compromise response plan
depends on this number being small.
3.4 Kubernetes: how the secret reaches the pod
Two valid paths, and the choice is documented:
- External Secrets Operator (ESO): syncs from the manager to a Kubernetes
Secret.
Convenient and compatible with everything, but it materialises the secret in etcd (requires encryption at
rest with a KMS provider and strict RBAC — kubernetes-standards). Before adopting it,
know its history, because it changes the risk assessment:
- It is no longer
v0.x: 1.0 GA in Nov 2025, 2.0 in Feb 2026, today 2.8.0. A very
short support window: each minor dies when the next one ships.
v1beta1 is not deprecated: it was REMOVED in v0.17.0 (May 2025). Migration goes
necessarily through v0.16.2, and there is no official migration guide. Added gotcha:
in 0.16.x the webhook converts to v1 even though it serves both, producing permanent drift
in Argo CD if Git stays on v1beta1.
- Its own critical CVEs: CVE-2026-22822 (CVSS 9.3,
getSecretKey retrieved
cross-namespace secrets with the controller's rolebinding; fix 1.2.0),
CVE-2026-34984 (getHostByName in the templating engine → exfiltration via DNS; fix
2.3.0), CVE-2025-55196 (PushSecret without a namespace selector → cluster-wide read;
fix 0.19.2). Hard minimum: 2.3.0+.
- Project health: CNCF Sandbox since 2022 with no promotion; in Jul 2025 the
maintainers paused all releases due to burnout and the CNCF TOC opened a
health review with an archiving checklist; it was resolved with new governance, and
the company backing it (External Secrets Inc.) shut down in Nov 2025. It is usable, but
it goes into your risk register with an owner.
- Secrets Store CSI Driver: mounts the secret as a volume without creating a K8s
Secret
(unless you enable syncing, which cancels out the advantage). Preferable when the threat
model includes "whoever reads etcd or has get secrets must not see this".
- sealed-secrets / SOPS: for GitOps without a manager. They are encryption in the repo, not secrets
management: no rotation, no read auditing, no TTL, no revocation. Valid and KISS
for a homelab and small teams; their limit must be declared, not discovered.
- Vault/OpenBao on K8s: Vault Secrets Operator 1.5.0 (careful, breaking: it removes
spec.appRole.secretIDPath) or Vault Agent Injector (vault-k8s) 1.7.5. HashiCorp does not
recommend one over the other by default, but the load on the manager does differ: VSO is
the lowest (per-node pool with cache), the CSI provider is in between, the Agent Injector the
highest (sidecar per pod).
3.5 Where secrets actually leak
In order of observed frequency, not of drama:
- Repository and Git history (§3.8).
- CI variables and logs:
set -x, debug echo, error output from an HTTP
client. CI masking is best effort: it breaks with transformations (base64,
chunking, escaped JSON) and the payload of the Trivy compromise read the memory of the runner
process precisely to bypass it.
- Container images and layers: an
ARG/ENV or a COPY of a file deleted in a
later layer is still in the image. Use BuildKit's --mount=type=secret
(kubernetes-standards).
- Build artifacts and packaged configuration files.
- Terraform state — in clear text by design (
iac-standards).
- Backups and snapshots: if the backup contains the secret, the backup is the secret. Its
encryption key lives outside the backed-up system (
cryptography-pki-standards,
bcdr-standards).
- Telemetry: URLs with a token in the query string,
Authorization headers, request
bodies, stack traces (observability-standards).
- And the one almost nobody models: the compromised consumer. The Shai-Hulud worm uses
TruffleHog inside the victim's machine to harvest credentials, and its third
wave (malicious
@bitwarden/cli 2026.4.0, ~93 minutes on npm, Apr 2026) directly emptied
AWS Secrets Manager, SSM Parameter Store, GCP Secret Manager and Azure Key
Vault with the process's legitimate identity. No manager protects you from a compromised
consumer: that is why the real defence is short TTLs, least privilege per identity and
detection of anomalous reads — not the vault.
3.6 HA, sealing and the question nobody asks
If the manager goes down, does your application start? Answer it in writing before deploying it, not
during the outage:
- A manager on the critical startup path turns its unavailability into total
unavailability. Mitigations: in-memory cache with TTL and degraded startup with the current
credential; replicas and multi-AZ deployment; and automatic unsealing (auto-unseal against
KMS/HSM) so that a restart does not require a human in the middle of the night.
- Auto-unseal shifts the trust to the KMS: if that KMS goes down or has its permissions withdrawn, the
manager does not open. Document the procedure for manual unsealing with a Shamir quorum,
with the keys held by different people and the procedure rehearsed. A recovery key
nobody has tried to use is a key that does not exist.
- In OpenBao, pay attention to the sealing changes:
stored_shares removed in 2.6.0
and the built-in seals leaving the binary in 2.7.0 towards external plugins.
- Backup of the manager (Raft snapshot) encrypted, with the key outside the manager itself and with
tested restore. It is the only case where restoring badly means losing everything at once.
- Watch its advisories like the kernel's. Verified 2026 precedents: in OpenBao,
CVE-2026-63132 (CVSS 9.1, timing side channel in recovery mode that allowed extracting
the recovery token; fix 2.6.0) and escalation via wildcards in templated policies (fix
2.6.0); in Vault, CVE-2026-5051 (bypass of the audit plugin directory guard),
CVE-2026-5052 (SSRF in the PKI engine's ACME challenge validation) and
CVE-2026-3605 (policy bypass by deleting KVv2 metadata via glob).
3.7 Humans ≠ machines
They are two problems with different solutions and they do not get mixed:
|
Human secrets |
Machine secrets |
| Tool |
Team password manager (shared vaults, MFA, recovery) |
Service secrets manager (API, policies, TTL, auditing) |
| Unit |
Person, with onboarding/offboarding |
Workload identity, with the deployment's lifecycle |
| Goal |
That nobody reuses or shares passwords |
That the credential be dynamic and short-lived |
| Typical failure |
Password shared over chat that outlives the departure of whoever created it |
Static token from three years ago that nobody knows who uses |
Hard rule: a credential a human can read and that a service also uses is a
credential that is already compromised for auditing purposes — you cannot attribute its use.
3.8 Exposure response: rotate first, clean up afterwards
A secret that has been in a repository, in a log or in a chat channel is burned.
Rewriting history does not un-burn it: cloned, cached by the forge, indexed by bots
that sweep GitHub in seconds, and present in forks and in attackers' databases.
Non-negotiable order:
- Rotate/revoke the credential. First. Before investigating how it got there.
- Verify the revocation (that the old one no longer works) and look for use of the exposed
credential in the logs from the moment of exposure — it is a case for
detection-engineering-standards, and if there was use, an incident for
incident-response-forensics-standards.
- Clean up the history (
git filter-repo, git-workflow-standards) and coordinate the
force-push with whoever has clones.
- Fix the cause: why the scan did not catch it earlier, why that static
secret existed, and whether it could have been eliminated through federation.
Reversing the order is forbidden. "I'll delete it from the history and then we'll see" is the response that
turns a leak into a breach.
4. CI gates (they break the build)
- Local pre-commit + CI scan of the diff with
gitleaks/betterleaks — the pre-commit
is convenience, the CI gate is the control, because the local hook is skipped with
--no-verify.
- Full history scan at least periodically (and always when opening a new repo
or importing it): the diff only sees what arrives today.
- Forge push protection enabled: on GitHub, free in public repos, but in
private ones it requires GitHub Secret Protection (billed per active committer) and at
repo/organization level it is disabled by default — enable it explicitly. On
GitLab, Secret Push Protection is Ultimate only (GA in 17.5) and it skips binaries,
files > 1 MiB and pushes of more than 350,000 lines: know its gaps, do not treat it as a
total safety net.
- A finding = broken build + immediate rotation, not a TODO nor a silent exception.
Every suppression (
.gitleaksignore, allowlist) carries a reason, an owner and an expiry.
- Scan of the built image and of the IaC state, not just of the source code: the
secrets appear in layers and in
terraform.tfstate.
- Prohibition of static credentials in the pipeline: a gate that fails if
AWS_ACCESS_KEY_ID, a JSON service account key or a client_secret appears as a CI variable
where OIDC exists (cicd-standards).
- Automated rotation test in the pre-production environment: rotate, check that the
service still works and that the old credential no longer authenticates. Without this, §3.3
is an intention.
- Verification that the secret does not appear in the output: a test that runs the service
startup and fails if a known secret value appears in stdout/stderr or in the structured
log.
5. Specific security
- Least privilege all the way: one identity, one set of secrets, read-only.
The universal anti-pattern is the broad policy "so as not to block the team", which turns
any compromise of any pod into total compromise.
- Never pass secrets as command-line arguments (visible in
ps, in the
shell history and in the process audit logs): file, stdin or environment variable
of the process itself, in that order.
- Never put them in the URL (query string): they end up in access logs, in the
Referer and
in telemetry.
- One secret per service and per environment. Sharing a credential between services destroys
attribution and multiplies the blast radius of every rotation.
- The supply chain of the secrets tooling itself is attack surface, and the
precedent is explicit: the compromise of
tj-actions/changed-files (CVE-2025-30066)
dumped CI secrets into the build logs of more than 23,000 repositories, and the 2026 campaign
against Trivy/Checkmarx stole CI credentials at scale. Consequence: pin by SHA every
action and by digest every image that touches secrets, signature verification, and restricted
egress in the jobs that handle them. Verified Aug 2026: neither gitleaks, nor trufflehog,
nor SOPS, nor ESO, nor sealed-secrets have suffered a supply chain compromise — they have had
their own vulnerabilities (§2.4, §3.4), which is a different thing.
- OIDC federation: the trust policy is the control, and it changed in 2026. GitHub Actions now issues
immutable subject claims (
repo:org@<id>/repo@<id>:ref:...), applied
automatically since 15 Jul 2026 to new repos and to renamed or transferred ones:
trust policies that match by path break or, worse, stop matching what you
thought. In GitLab, CI_JOB_JWT* was removed in 17.0 and the guidance is to trust by
project_id/namespace_id, not by path (which a rename changes). In Azure, issuer,
subject and audience are case-sensitive and there is a limit of 20 federated credentials
per managed identity. Never use broad wildcards in the sub.
- No secrets in telemetry nor in error messages. The "invalid credential" error
does not print the credential, nor its prefix, nor its length.
6. Operability
- Inventory: which secrets exist, who consumes them, when they were last rotated and
who the owner is. Without an inventory there is no possible emergency rotation, and therefore no
compromise response.
- Cache in the consumer with a TTL so as not to turn every request into a call to the manager
(cost, latency and a point of failure per operation). Cache in memory, never on disk in
clear text, and respect the TTL as an upper bound.
- Real cost: AWS Secrets Manager bills ~0.40 USD per secret per month plus calls — a
"one secret per microservice per environment" pattern shows up in the bill and pushes towards the
anti-pattern of grouping everything into one giant shared secret. Design the granularity with
cost and blast radius on the table.
- Observability of the manager: read latency and error rate, active leases, seal
state, and the age of the oldest unrotated secret as a published metric.
- Decommissioning: when a service dies, its secrets and its policies are deleted in the same
PR. Orphaned credentials are the residue with the highest return for an attacker.
7. Sustainability and prohibitions
- Cadence: review the manager's advisories (monthly — it has criticals regularly),
the version of ESO/CSI driver/sealed-secrets (their support window is short), and auditing of
accesses and pruning of permissions and dead secrets, quarterly.
- Governance risks in the register, with an owner and a review date: Vault's BUSL licence
under IBM, the Sandbox level and the SOPS health review in the CNCF, the trajectory
of ESO after the shutdown of the company backing it, and the feature complete status of
gitleaks. None is cause for panic; all are cause for a written plan B.
- Every exception (static secret that cannot be eliminated, long TTL, broad policy) carries
a reason, an owner and an exit date.
FORBIDDEN
- ❌ Storing a static secret where short-lived federated identity would have fit.
- ❌ Secrets in code, in the Git history, in
values.yaml, in *.tfvars, in the state,
in a Dockerfile's ENV/ARG, in image layers, in logs or in chat channels.
- ❌ Secrets as command-line arguments or in a URL's query string.
- ❌ Environment variables as the default injection mechanism (§2.3).
- ❌ Rewriting the history before rotating: rotate first, clean up afterwards. Always.
- ❌ Assuming an exposed secret is safe because "the repo was private" or "it was deleted straight away".
- ❌ Rotation documented but never executed; quorum unsealing procedure
never rehearsed; manager backup without a tested restore.
- ❌ A credential shared between services, between environments, or between a human and a
service.
- ❌ Wildcard policies over the manager, or OIDC trust policies with a wildcard
sub.
- ❌ Secrets manager with no audit device configured and with that log unmonitored.
- ❌ A manager on the critical startup path without having answered what happens when it goes down.
- ❌ Treating
sealed-secrets or SOPS as a substitute for a manager when rotation,
revocation or read auditing is needed.
- ❌ Using
gitleaks-action v2 without knowing its commercial licence and its end of
operation (16 Sep 2026); relying only on the pre-commit hook with no CI gate.
- ❌ Pointing sealed-secrets at
bitnamilegacy/* (frozen image with no patches).
- ❌ ESO below 2.3.0, sealed-secrets below 0.36.0, or any manager with a published critical
CVE left unpatched.
- ❌ Starting something new on
detect-secrets or Nosey Parker (unmaintained / archived).
- ❌ Actions or images by mutable tag in pipelines that handle secrets.
8. Mandatory web verification
Before pinning a version, licence, governance or behaviour, look it up — do not recall it.
Data verified Aug 2026 (the most volatile in the catalogue): Vault 2.0.3, BUSL 1.1 with no
change, Licensor now IBM (acquisition closed 27 Feb 2025), new IBM support cycle
and Community with no LTS; OpenBao 2.6.1, MPL-2.0, OpenSSF/LF Sandbox level;
Infisical 0.162.15; bws 2.1.0; ESO 2.8.0; Secrets Store CSI Driver 1.6.0;
sealed-secrets 0.38.4; Vault Secrets Operator 1.5.0; vault-k8s 1.7.5; SOPS
3.13.3 (CNCF Sandbox, open health issue); age 1.3.1; gitleaks 8.30.1 (feature
complete) and Betterleaks 1.7.3; trufflehog 3.96.0; SPIRE 1.15.2 (SPIFFE/SPIRE
graduated in the CNCF); systemd v261.
- Vault's licence under IBM and OpenBao's real level in the Linux Foundation/OpenSSF:
it is the pair of facts most often misquoted. Read it from the
LICENSE file and from the project's
page, not from a blog.
- Vault↔OpenBao divergence before assuming portability: built-in seals leaving the
binary in 2.7.0,
stored_shares, namespace semantics, replication.
- The manager's advisories (HashiCorp's HCSEC, OpenBao security advisories): both
published criticals in 2026.
- Maintenance status of gitleaks/Betterleaks, SOPS (health issue in the CNCF), ESO
and sealed-secrets before betting the pipeline on any of them.
- Current OIDC claims and trust policies from GitHub/GitLab towards AWS/Azure/GCP: GitHub's immutable
subject claims changed format in 2026 and break existing policies.
- Rotation mechanism and cost of the specific cloud manager: none of the three rotates
everything automatically, and what managed rotation covers changes every quarter.
- systemd credential directives and their minimum version in your distribution before
basing a deployment on them (
SetCredential=/LoadCredential= since 247;
LoadCredentialEncrypted=/SetCredentialEncrypted=/systemd-creds since 250;
ImportCredential= since 254).
- Supply chain compromises of any new tool in the secrets pipeline
before adopting it.
Declared gaps — not verified in this document, verify them yourself before using them:
- OpenBao's exact level in the OpenSSF today (Sandbox according to the project's page; one
reading of the index suggested "Incubation" and no locatable promotion announcement exists), and
the LF Edge entry/exit dates.
- The comparison of Vault features absent in OpenBao (DR/performance replication,
automated snapshots, Sentinel) comes from a secondary source.
- Formal outcome of ESO's incubation application in the CNCF (issue closed, with no
publicly located resolution).
- Existence and version of
ImportCredentialEx= in systemd, and the exact mode/owner
of the files in $CREDENTIALS_DIRECTORY (the documentation was not accessible).
- Exact date of systemd v261.
- Specific CIS or NIST SP 800-190 guidance against environment variables for
secrets: the argument in §2.3 rests on systemd and OWASP, not on NIST/CIS.
- Who nominally maintains the
gitleaks/gitleaks repository today, and the tiers of
gitleaks.io.
- Existence of a Secret Store extension for AKS in the cloud (the located documentation
covers Arc-enabled Kubernetes; for AKS the supported path is the CSI driver add-on).
- Maximum federated credentials per app registration in Entra (the verified limit of 20
is per managed identity).
- Exact CVSS of several 2026 Vault CVEs and CVE-IDs of some HCSEC bulletins.
- Any incident not published via GHSA in SaaS managers (Doppler and equivalents were not
consulted).
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: secrets-management-standards3description: Secrets lifecycle for already-generated credentials. Use when working with HashiCorp Vault or OpenBao (policies, dynamic secrets, seal/unseal, vault or bao CLI), Infisical, Bitwarden Secrets Manager, consuming AWS Secrets Manager, Azure Key Vault or GCP Secret Manager, External Secrets Operator (ExternalSecret, ClusterSecretStore), Secrets Store CSI Driver, sealed-secrets, SOPS with age, systemd LoadCredential= and systemd-creds, gitleaks, betterleaks or trufflehog scans, .gitleaks.toml, responding to a leaked credential, rotation runbooks, or replacing a static credential with short-lived federated identity.4---56# Secrets management standards78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Applies to the **operational lifecycle of already-generated secrets**: deciding on and deploying the13manager, modelling paths and policies, dynamic secrets with TTL, the injection mechanism into14the consumer (file, API, systemd credential, Kubernetes operator), automatic rotation15and testing it, responding to the exposure of a secret, detecting secrets in16code and history, encrypting secrets when they must live in the repository, secrets in17images, container layers, build artifacts, logs and backups, auditing and least18privilege over the manager itself, HA/sealing and behaviour when it goes down, and the separation19between human secrets and machine secrets.2021Triggers: `vault`/`bao` CLI, `vault.hcl`, HCL policies, `bao operator unseal`,22`ExternalSecret`/`ClusterSecretStore`/`PushSecret`, `SecretProviderClass`, `SealedSecret`,23`.sops.yaml`, `sops -e`, `age`/`age-keygen`, `LoadCredential=`/`LoadCredentialEncrypted=`/24`systemd-creds`, `.gitleaks.toml`/`.gitleaksignore`, `gitleaks detect`, `trufflehog git`,25`bws`, `infisical run`, `aws secretsmanager get-secret-value`, `az keyvault secret show`,26`gcloud secrets versions access`, "rotate credential", "leaked secret", "push protection".2728**Guiding principle**: **the best secret is the one that does not exist**. Before storing a29credential, the job is to **eliminate it**: short-lived federated identity (CI OIDC into30the cloud, the provider's workload identity, SPIFFE/SPIRE between services) replaces the31static secret with no custody, no rotation and no leak risk. The secrets manager is for32**what cannot be eliminated**, and its real goal is not to be a vault of eternal33passwords, but to **issue dynamic credentials with a short TTL**. Corollary: an architecture34with many stored secrets is not a well-guarded architecture, it is a badly35designed one.3637**Not applicable**: see `cryptography-pki-standards` (**algorithm choice, key38generation, randomness, PKI and certificate issuance, KMS/HSM as a cryptographic primitive and39envelope encryption — all of that is theirs**; here only the **custody, distribution, operational40rotation and consumption** of already-generated material),41`identity-access-management-standards` (**the preferred alternative to the static secret**: IdP,42OAuth 2.1/OIDC flows, tokens, federation, SPIFFE/SPIRE, PAM/JIT — before storing a secret43you check there whether it can not exist at all), `cicd-standards` (the pipeline, its OIDC, pinning44of actions and the gates that run the scan), `kubernetes-standards` (the `Secret` object,45encryption in etcd, RBAC and admission), `iac-standards` (**the Terraform state contains secrets46in clear text** and `sensitive` variables; handling the state is theirs), `aws-standards` /47`azure-standards` / `gcp-standards` (configuration, IAM and cost of the specific managed48service — here the criteria for use and consumption), `data-platform-standards` (credentials and49encryption at rest of the data engine), `windows-server-ad-standards` (gMSA/dMSA, LAPS and50`krbtgt` rotation: **directory** secrets), `bash-linux-scripting-standards` (the51script that consumes the secret without leaking it through `ps` or the log),52`incident-response-forensics-standards` (**mass credential rotation during a53compromise**: there the incident process, here the mechanism that makes it possible in hours and54not in weeks), `incident-management-standards` (declaration, severity and communication),55`detection-engineering-standards` (**shared and bidirectional boundary**: the telemetry of56this domain —anomalous reads from the manager, a secret detected in a push, use of a57credential from an impossible origin, a secret read after having been revoked— is a58**first-order detection source**; the scanning is mine, the rule that turns the finding59into an alert and its runbook are theirs), `privacy-engineering-standards` (**a personal data item is not60a secret** and is not managed with these tools), `grc-compliance-standards` (the regulatory61control that demands custody and rotation, and its evidence), `bcdr-standards` (custody of recovery62keys **outside** the backed-up system and their role in the continuity plan), and the63language skills —among them `powershell-standards`, which **delegates here** the choice of manager64and secrets scanner and keeps the code criteria: `SecretManagement`/`SecretStore` as65front-end, and the prohibition of `ConvertTo-SecureString -AsPlainText -Force` with a secret written66in the file—, `developer-workstation-standards` (**the choice of secrets manager and scanner67belong here**; **custody on the developer's machine is theirs** —key in hardware, PIN and68mandatory touch, a `credential.helper` that does **not** write in clear text, token outside the shell69history and the `~/.netrc`, and the credential scope a coding agent can reach—),70and `solidity-standards` (the contract is theirs; **custody of the deployment71key and of the `owner`/`upgrader` key belongs here** —HSM or multisig, rotation, threshold—.72Fact that orders the priority: **private key compromise explains more than 25 % of on-chain73thefts and four of the ten largest**; an upgradeable contract shifts all the risk to whoever74holds that key, so **a single hot key is not an acceptable architecture**).7576## 2. Default decisions7778> Data verified Aug 2026. **Verify version, licence and governance on the web before79> committing to a platform (§8)**: this domain had licence, foundation and80> maintenance changes in 2025-2026, and several of them invalidate earlier recommendations.8182### 2.1 The decision hierarchy (in this order, always)83841. **Can the secret not exist?** → OIDC/workload identity federation (CI→cloud),85 SPIFFE/SPIRE (service→service), the provider's managed identity. **With no secret there is no86 custody, no rotation and no leak.**872. **Can it be dynamic?** → the manager **generates** the credential on the fly with a TTL (database88 user, temporary cloud credential, short-lived certificate). The secret exists for89 minutes, not years.903. **Can it be short-lived and revocable?** → token with TTL and effective revocation.914. **Only then**: static secret in the manager, with an owner, policy, auditing and92 tested rotation.9394### 2.2 Manager9596| Need | Default | Justifiable alternative / vetoed |97|---|---|---|98| A single cloud, workloads only in it | **The native manager** (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) | It is the KISS option: IAM already exists, native integration and zero operations. Deploying Vault/OpenBao "just in case" with a single cloud is over-engineering |99| Multi-cloud, on-prem or serious dynamic secrets | **OpenBao 2.6.1** (MPL-2.0, **OpenSSF/Linux Foundation, Sandbox level** since Jun 2025) | **HashiCorp Vault 2.0.3** is still valid and is more mature in replication — but its licence is **BUSL 1.1 since Aug 2023 and has not changed** after the acquisition by **IBM closed (27 Feb 2025)**: today the *Licensor* in the LICENSE file is IBM. It is an **ADR decision with legal review**, not a technical one |100| "Product-grade" manager with DX and UI | **Infisical 0.162.15** (MIT core, `ee/` under a proprietary Enterprise licence) | An honest *open core* model but with key paid features (**dynamic secrets are in the Advanced tier**). Valid for small teams; check what you need that sits in `ee/` before committing |101| You already use Bitwarden for the team | **Bitwarden Secrets Manager** | **Little-known licence warning**: the server is AGPLv3, but the **SDK where the `bws` client lives (`bitwarden/sdk-sm`) is NOT open source** — a proprietary licence that forbids using it with software other than Bitwarden. The 2024 GPL controversy was resolved for the password manager, **not** for Secrets Manager. Its Kubernetes operator is officially installed with `--devel` (pre-release channel): undeclared maturity |102| **Human** secrets (team) | **Password manager** with shared vaults and MFA | **It is not the same problem nor the same tool** (§3.7). Vetoed: using the service secrets manager as the team's password vault, or the other way round |103104**On Vault↔OpenBao compatibility (the most volatile fact in this skill)**: it remains105**practical in the essentials** (API, HCL policies, migration by Raft snapshot), but **the106divergence is real and accelerating** — verify it before assuming portability:107- OpenBao **removed `stored_shares`** (2.6.0) and **is retiring the built-in seals**108 (`awskms`, `azurekeyvault`, `gcpckms`, `ocikms`, `pkcs11`, `alicloudkms`) **in 2.7.0**,109 moving them to external `kms` plugins: it is a deployment change, not a cosmetic one.110- Its **namespaces have different semantics** from those of Vault Enterprise (identity store per111 namespace, no group inheritance; `unsafe_cross_namespace_identity` flag, `false` by default,112 to restore Vault's behaviour).113- In OpenBao's favour: namespaces at no cost (in Vault they are Enterprise), *namespace sealing*,114 distroless and non-root images, transactional storage, plugins via OCI. Verifiable real115 adoption: **GitLab Secrets Manager is built on OpenBao** and **EdgeX 4.0 made it116 its default secret store**, replacing Vault.117- In Vault's favour: DR and performance replication, integrated automated snapshots,118 Sentinel (OpenBao offers CEL). *(Comparison from a secondary source: confirm it.)*119120### 2.3 Injection into the consumer — best to worst121122| Mechanism | When | Note |123|---|---|---|124| **No secret**: federated token obtained at runtime | Whenever the platform allows it | The goal. `identity-access-management-standards` |125| **Manager API from the process**, with in-memory cache and TTL | Application that can be integrated | Allows rotation **without restart**, which is half the value of rotating |126| **File in tmpfs / `$CREDENTIALS_DIRECTORY`** | systemd services, containers | In systemd: **non-*swappable* memory, immutable if privileges allow, accessible only to the service's UID and destroyed on exit** — the best mechanism in the Linux ecosystem |127| **Mounted volume** (CSI driver, K8s secret as a volume) | Kubernetes | It is **refreshed** when the secret changes; environment variables are **not** |128| **Environment variable** | Last defensible resort | See below |129130**Why environment variables are the worst defensible option** — with the131correct argument, because the one usually going around is badly framed:132- **`/proc/PID/environ` is NOT world-readable**: access to it is governed by a133 `PTRACE_MODE_READ_FSCREDS` check (same UID, or root/`CAP_SYS_PTRACE`). Using "anyone can134 read it" as an argument is **incorrect** and discredits you in the technical discussion.135- The arguments that **do** hold: **they are inherited automatically by the whole process136 tree** (any subprocess, including the one that should not see it, and any `curl` you137 launch from there); **there is no per-credential access control** (all or nothing); they appear in138 **dumps and crash traces**; they are readable by any process with the same UID and by root;139 they end up exposed in introspection interfaces (`kubectl describe pod`, `docker inspect`);140 **they cannot be rotated without restarting the process**; and they have size limits and problems with141 binary data. The systemd credentials document itself enumerates exactly these142 defects against its model, where access is checked in the kernel on every use.143- OWASP (Secrets Management Cheat Sheet) is explicit: *"using environment variables is144 therefore not recommended unless the other methods are not possible"*, and forbids Docker145 `ENV`/`ARG` for secrets.146147### 2.4 Supporting tools148149| Area | Default | Critical note |150|---|---|---|151| Secret scanning | **gitleaks 8.30.1** (MIT) as the already-integrated baseline; **Betterleaks 1.7.3** (MIT, *drop-in*, reads `.gitleaks.toml` and `.gitleaksignore`) as the successor | **Fact that changes the recommendation**: gitleaks declared itself **feature complete** — *"future releases will be security patches only"* — and its own README points to Betterleaks, maintained by the same authors. **And its GitHub Action is another matter**: `gitleaks-action` **since v2.0.0 left MIT and requires a commercial licence (`GITLEAKS_LICENSE`) for organizations**; furthermore **v2 stops working on 16 Sep 2026**. Scan with the **binary**, not with the action |152| Deep scanning / live credential verification | **trufflehog 3.96.0** (AGPL-3.0) | Its ability to **verify** whether the credential is still live is what sets it apart. Beware AGPL if you integrate it into a product |153| Alternatives | **Kingfisher** (MongoDB, Rust), **ggshield** (requires SaaS), **Titus** (Praetorian) | **Nosey Parker has been archived since 2026-04-24** (points to Titus) and **Yelp's detect-secrets has gone ~27 months without a release**: do not start anything new on them |154| Encrypting secrets in the repo | **SOPS 3.13.3** (MPL-2.0, CNCF **Sandbox**, `getsops` org) with **age 1.3.1** (BSD-3) or KMS | **Open governance risk**: `cncf/toc#2098` (Mar 2026) declares SOPS *"active but has some health issues"* and is evaluating whether it can be relicensed away from MPL; if not, it contemplates **archiving it or removing it from the CNCF**. It remains the best option, but **with vigilance and a plan B** |155| GitOps alternative on K8s | **sealed-secrets 0.38.4** | **Hard minimum 0.36.0+** (CVE-2026-22728: `/v1/rotate` allowed widening the scope to cluster-wide). **Verified trap**: `bitnamilegacy/sealed-secrets-controller` exists frozen at ~0.31.0 as a side effect of Bitnami's image archiving — repointing there nails you to an unpatched image. Valid alternative registry, signed with cosign: `ghcr.io/bitnami/sealed-secrets-controller` |156| Syncing to Kubernetes | **External Secrets Operator 2.8.0** | See §3.4: it is powerful but has a **history of governance issues and critical CVEs** you must know before adopting it |157| Direct mount in the pod | **Secrets Store CSI Driver 1.6.0** (Kubernetes SIG Auth) | Avoids materialising a K8s `Secret`. Trade-off: **privileged DaemonSet with the kubelet's hostPath**, and its maintainers are publicly asking for hands (low bus factor). In **1.6.0 rotation changed** to the `requiresRepublish: true` model and **the rotation RBAC was removed** |158| Workload identity | **SPIRE 1.15.2** (SPIFFE and SPIRE **graduated in the CNCF** since Aug 2022) | Vault 2.0.0 already accepts **SPIFFE JWT-SVID**; Entra ID documents federation with SPIFFE/SPIRE |159160## 3. Structure and conventions161162### 3.1 Dynamic secrets: the real goal163164- **Database credential generated on the fly with a TTL** (minutes or hours), revoked when165 the lease expires. It eliminates in one go: manual rotation, the secret shared between166 services, the "we don't know who uses this password" and most of the impact of a167 leak.168- **Design requirements people discover late**: the application must **renew or169 reconnect** when the lease expires (a connection pool with an expired credential fails at170 the worst moment); the manager becomes a **dependency on the critical path** (§3.6); and the171 data engine must support creating/deleting users at that rate.172- It also applies to temporary cloud credentials, short-lived certificates and service173 tokens. **A secret with a long TTL is a decision, and is justified in the PR.**174- **Cloud managers do not rotate on their own, except in specific cases** — verify it before175 promising it:176 - **AWS Secrets Manager**: *managed rotation* without Lambda only for Aurora/RDS/DocumentDB/177 Redshift and a set of external integrations; the rest **still requires your Lambda**.178 Since Jul 2026 it emits change notifications to **EventBridge** at no cost: use them to179 detect rotations that do not happen.180 - **Azure Key Vault**: **it has no native rotation of *secrets*** (*keys* do). The181 official pattern is a `SecretNearExpiry` event → Event Grid → Function, with a182 **dual-key** scheme. Also, since Mar 2026 vaults created with the `2026-02-01` API use183 **Azure RBAC by default**, and **the earlier control plane APIs are retired on184 27 Feb 2027**.185 - **GCP Secret Manager**: **it does not rotate**; it emits a **`SECRET_ROTATE`** message to Pub/Sub at186 `next_rotation_time` and you create the new version. Cloud SQL credential rotation187 is in **Preview** (Jul 2026).188189### 3.2 Modelling, policies and auditing of the manager190191- **The manager is an extremely high-value target: its compromise is the organization's worst192 day.** It is treated as Tier 0: segmented network, administrative access separated from193 consumption access, MFA for humans, and its telemetry watched by194 `detection-engineering-standards`.195- **Paths by failure domain**, not by team or by convenience:196 `<environment>/<service>/<purpose>`. One policy per consuming identity, with **read-only197 access to its own paths**. Nobody —not an application, not a pipeline— reads `*`.198- **Separation of duties**: whoever administers the manager should not be able to read the business199 secrets in clear text; whoever reads them does not administer policies. Every operation with root200 material audited and with dual control.201- **Auditing that answers concrete questions**: who read what and when, which identity has202 never read a secret it has been granted (dead permission → it is withdrawn), and which secret has203 not been read in months (deletion candidate). **A secret nobody reads is pure risk.**204- **Alerting on the manager itself**: read spikes, a read from a new origin, policy205 change, auditing disabled, unplanned sealing/unsealing, and **repeated authentication206 failures**. With no `audit device` configured and with a monitored destination, there is no207 secrets management: there is a store.208- **Verify that the audit destination does not become the leak**: real precedent209 (HCSEC-2026-09) — GitHub webhook secrets were exposed in base64 in an210 HTTP header that ended up in load balancer, proxy and SIEM logs. The manager's audit logs211 are as sensitive as its contents.212213### 3.3 Rotation: if it has never been run, it does not exist214215- **Every credential has an explicit rotation period and an owner.** With no owner there is no rotation,216 there is intent.217- **Rotation is actually executed and on a schedule**, not "when it's due". A218 documented but never exercised rotation procedure fails exactly on the day of the219 incident, which is when it has to be run under pressure and en masse.220- **Design for overlap (dual-key / expand-contract)**: two credentials valid at221 once during the transition window. Without overlap, every rotation is a planned222 outage, and that is why nobody rotates.223- **Programme metric**: *time-to-rotate-everything* — how long it would take you to rotate **all**224 the credentials in a scope. If the answer is "weeks", you have a latent incident,225 just like a PKI that cannot reissue within 24 h.226- Rotation is a **recovery gate**, not just hygiene: the compromise response plan227 depends on this number being small.228229### 3.4 Kubernetes: how the secret reaches the pod230231Two valid paths, and the choice is documented:232233- **External Secrets Operator (ESO)**: syncs from the manager to a Kubernetes `Secret`.234 Convenient and compatible with everything, but **it materialises the secret in etcd** (requires encryption at235 rest with a KMS provider and strict RBAC — `kubernetes-standards`). Before adopting it,236 know its history, because it changes the risk assessment:237 - **It is no longer `v0.x`**: 1.0 GA in Nov 2025, 2.0 in Feb 2026, today **2.8.0**. A **very238 short** support window: each minor dies when the next one ships.239 - **`v1beta1` is not deprecated: it was REMOVED in v0.17.0** (May 2025). Migration goes240 necessarily through v0.16.2, and **there is no official migration guide**. Added gotcha:241 in 0.16.x the webhook converts to `v1` even though it serves both, producing **permanent drift242 in Argo CD** if Git stays on `v1beta1`.243 - **Its own critical CVEs**: **CVE-2026-22822** (CVSS 9.3, `getSecretKey` retrieved244 **cross-namespace** secrets with the controller's rolebinding; fix 1.2.0),245 **CVE-2026-34984** (`getHostByName` in the templating engine → exfiltration via DNS; fix246 2.3.0), **CVE-2025-55196** (`PushSecret` without a namespace selector → cluster-wide read;247 fix 0.19.2). **Hard minimum: 2.3.0+.**248 - **Project health**: CNCF **Sandbox** since 2022 with no promotion; in Jul 2025 the249 maintainers **paused all releases** due to *burnout* and the CNCF TOC opened a250 **health review with an archiving checklist**; it was resolved with new governance, and251 the company backing it (*External Secrets Inc.*) **shut down in Nov 2025**. It is usable, but252 it goes into your risk register with an owner.253- **Secrets Store CSI Driver**: mounts the secret as a volume without creating a K8s `Secret`254 (unless you enable syncing, which cancels out the advantage). Preferable when the threat255 model includes "whoever reads etcd or has `get secrets` must not see this".256- **sealed-secrets / SOPS**: for GitOps **without** a manager. They are encryption in the repo, not secrets257 management: no rotation, no read auditing, no TTL, no revocation. Valid and KISS258 for a homelab and small teams; **their limit must be declared**, not discovered.259- **Vault/OpenBao on K8s**: **Vault Secrets Operator 1.5.0** (careful, *breaking*: it removes260 `spec.appRole.secretIDPath`) or **Vault Agent Injector (vault-k8s) 1.7.5**. HashiCorp does not261 recommend one over the other by default, but the **load on the manager does differ**: VSO is262 the lowest (per-node pool with cache), the CSI provider is in between, the **Agent Injector the263 highest** (sidecar per pod).264265### 3.5 Where secrets actually leak266267In order of observed frequency, not of drama:268269- **Repository and Git history** (§3.8).270- **CI variables and logs**: `set -x`, debug `echo`, error output from an HTTP271 client. CI masking is **best effort**: it breaks with transformations (base64,272 chunking, escaped JSON) and **the payload of the Trivy compromise read the memory of the runner273 process precisely to bypass it**.274- **Container images and layers**: an `ARG`/`ENV` or a `COPY` of a file deleted in a275 later layer **is still in the image**. Use BuildKit's `--mount=type=secret`276 (`kubernetes-standards`).277- **Build artifacts and packaged configuration files**.278- **Terraform state** — in clear text by design (`iac-standards`).279- **Backups and snapshots**: if the backup contains the secret, the backup **is** the secret. Its280 encryption key lives outside the backed-up system (`cryptography-pki-standards`,281 `bcdr-standards`).282- **Telemetry**: URLs with a token in the query string, `Authorization` headers, request283 bodies, stack traces (`observability-standards`).284- **And the one almost nobody models: the compromised consumer.** The **Shai-Hulud** worm uses285 **TruffleHog** inside the victim's machine to harvest credentials, and its third286 wave (malicious `@bitwarden/cli` 2026.4.0, ~93 minutes on npm, Apr 2026) **directly emptied287 AWS Secrets Manager, SSM Parameter Store, GCP Secret Manager and Azure Key288 Vault** with the process's legitimate identity. **No manager protects you from a compromised289 consumer**: that is why the real defence is short TTLs, least privilege per identity and290 detection of anomalous reads — not the vault.291292### 3.6 HA, sealing and the question nobody asks293294**If the manager goes down, does your application start?** Answer it in writing before deploying it, not295during the outage:296297- A manager on the critical startup path turns its unavailability into total298 unavailability. Mitigations: **in-memory cache with TTL** and degraded startup with the current299 credential; replicas and multi-AZ deployment; and **automatic unsealing** (auto-unseal against300 KMS/HSM) so that a restart does not require a human in the middle of the night.301- **Auto-unseal shifts the trust to the KMS**: if that KMS goes down or has its permissions withdrawn, the302 manager does not open. Document the procedure for **manual unsealing with a Shamir quorum**,303 with the keys held by different people and **the procedure rehearsed**. A recovery key304 nobody has tried to use is a key that does not exist.305- **In OpenBao, pay attention to the sealing changes**: `stored_shares` removed in 2.6.0306 and the built-in seals **leaving the binary in 2.7.0** towards external plugins.307- Backup of the manager (Raft snapshot) **encrypted, with the key outside the manager itself** and with308 **tested restore**. It is the only case where restoring badly means losing everything at once.309- **Watch its advisories like the kernel's.** Verified 2026 precedents: in OpenBao,310 **CVE-2026-63132** (CVSS 9.1, timing side channel in *recovery mode* that allowed extracting311 the recovery token; fix 2.6.0) and escalation via *wildcards* in templated policies (fix312 2.6.0); in Vault, **CVE-2026-5051** (bypass of the audit plugin directory guard),313 **CVE-2026-5052** (SSRF in the PKI engine's ACME challenge validation) and314 **CVE-2026-3605** (policy bypass by deleting KVv2 metadata via glob).315316### 3.7 Humans ≠ machines317318They are two problems with different solutions and **they do not get mixed**:319320| | Human secrets | Machine secrets |321|---|---|---|322| Tool | Team password manager (shared vaults, MFA, recovery) | Service secrets manager (API, policies, TTL, auditing) |323| Unit | Person, with onboarding/offboarding | Workload identity, with the deployment's lifecycle |324| Goal | That nobody reuses or shares passwords | That the credential be dynamic and short-lived |325| Typical failure | Password shared over chat that outlives the departure of whoever created it | Static token from three years ago that nobody knows who uses |326327Hard rule: **a credential a human can read and that a service also uses is a328credential that is already compromised for auditing purposes** — you cannot attribute its use.329330### 3.8 Exposure response: rotate first, clean up afterwards331332**A secret that has been in a repository, in a log or in a chat channel is burned.**333Rewriting history does not un-burn it: cloned, cached by the forge, indexed by bots334that sweep GitHub in seconds, and present in forks and in attackers' databases.335336**Non-negotiable** order:3371. **Rotate/revoke the credential.** First. Before investigating how it got there.3382. **Verify the revocation** (that the old one no longer works) and **look for use** of the exposed339 credential in the logs from the moment of exposure — it is a case for340 `detection-engineering-standards`, and if there was use, an incident for341 `incident-response-forensics-standards`.3423. **Clean up the history** (`git filter-repo`, `git-workflow-standards`) and coordinate the343 force-push with whoever has clones.3444. **Fix the cause**: why the scan did not catch it earlier, why that static345 secret existed, and whether it could have been eliminated through federation.346347**Reversing the order is forbidden.** "I'll delete it from the history and then we'll see" is the response that348turns a leak into a breach.349350## 4. CI gates (they break the build)3513521. **Local pre-commit + CI scan** of the diff with `gitleaks`/`betterleaks` — the pre-commit353 is convenience, **the CI gate is the control**, because the local hook is skipped with354 `--no-verify`.3552. **Full history scan** at least periodically (and always when opening a new repo356 or importing it): the diff only sees what arrives today.3573. **Forge push protection enabled**: on GitHub, free in public repos, but in358 private ones it requires **GitHub Secret Protection** (billed per *active committer*) and **at359 repo/organization level it is disabled by default** — enable it explicitly. On360 GitLab, **Secret Push Protection is Ultimate only** (GA in 17.5) and **it skips binaries,361 files > 1 MiB and pushes of more than 350,000 lines**: know its gaps, do not treat it as a362 total safety net.3634. **A finding = broken build + immediate rotation**, not a TODO nor a silent exception.364 Every suppression (`.gitleaksignore`, allowlist) carries a reason, an owner and an **expiry**.3655. **Scan of the built image and of the IaC state**, not just of the source code: the366 secrets appear in layers and in `terraform.tfstate`.3676. **Prohibition of static credentials in the pipeline**: a gate that fails if368 `AWS_ACCESS_KEY_ID`, a JSON service account key or a `client_secret` appears as a CI variable369 where OIDC exists (`cicd-standards`).3707. **Automated rotation test** in the pre-production environment: rotate, check that the371 service still works and that the old credential **no longer authenticates**. Without this, §3.3372 is an intention.3738. **Verification that the secret does not appear in the output**: a test that runs the service374 startup and fails if a known secret value appears in stdout/stderr or in the structured375 log.376377## 5. Specific security378379- **Least privilege all the way**: one identity, one set of secrets, read-only.380 The universal anti-pattern is the broad policy "so as not to block the team", which turns381 any compromise of any pod into total compromise.382- **Never pass secrets as command-line arguments** (visible in `ps`, in the383 shell history and in the process audit logs): file, stdin or environment variable384 of the process itself, in that order.385- **Never put them in the URL** (query string): they end up in access logs, in the `Referer` and386 in telemetry.387- **One secret per service and per environment.** Sharing a credential between services destroys388 attribution and multiplies the blast radius of every rotation.389- **The supply chain of the secrets tooling itself is attack surface**, and the390 precedent is explicit: the compromise of `tj-actions/changed-files` (**CVE-2025-30066**)391 dumped CI secrets into the build logs of more than 23,000 repositories, and the 2026 campaign392 against Trivy/Checkmarx stole CI credentials at scale. Consequence: **pin by SHA every393 action and by digest every image** that touches secrets, signature verification, and **restricted394 egress in the jobs that handle them**. Verified Aug 2026: **neither gitleaks, nor trufflehog,395 nor SOPS, nor ESO, nor sealed-secrets have suffered a supply chain compromise** — they have had396 their own vulnerabilities (§2.4, §3.4), which is a different thing.397- **OIDC federation: the trust policy is the control, and it changed in 2026.** GitHub Actions now issues398 **immutable subject claims** (`repo:org@<id>/repo@<id>:ref:...`), applied399 automatically since **15 Jul 2026** to new repos and to renamed or transferred ones:400 trust policies that match by path **break or, worse, stop matching what you401 thought**. In GitLab, `CI_JOB_JWT*` was **removed in 17.0** and the guidance is to trust by402 `project_id`/`namespace_id`, not by path (which a rename changes). In Azure, `issuer`,403 `subject` and `audience` are **case-sensitive** and there is a limit of **20 federated credentials404 per managed identity**. Never use broad wildcards in the `sub`.405- **No secrets in telemetry nor in error messages.** The "invalid credential" error406 does not print the credential, nor its prefix, nor its length.407408## 6. Operability409410- **Inventory**: which secrets exist, who consumes them, when they were last rotated and411 who the owner is. Without an inventory there is no possible emergency rotation, and therefore no412 compromise response.413- **Cache in the consumer with a TTL** so as not to turn every request into a call to the manager414 (cost, latency and a point of failure per operation). Cache in memory, **never on disk in415 clear text**, and respect the TTL as an upper bound.416- **Real cost**: AWS Secrets Manager bills ~0.40 USD per secret per month plus calls — a417 "one secret per microservice per environment" pattern shows up in the bill and pushes towards the418 anti-pattern of grouping everything into one giant shared secret. Design the granularity with419 cost and blast radius on the table.420- **Observability of the manager**: read latency and error rate, active leases, seal421 state, and **the age of the oldest unrotated secret** as a published metric.422- **Decommissioning**: when a service dies, its secrets and its policies are deleted in the same423 PR. Orphaned credentials are the residue with the highest return for an attacker.424425## 7. Sustainability and prohibitions426427- **Cadence**: review the manager's advisories (monthly — it has criticals regularly),428 the version of ESO/CSI driver/sealed-secrets (their support window is short), and **auditing of429 accesses and pruning of permissions and dead secrets, quarterly**.430- **Governance risks in the register, with an owner and a review date**: Vault's BUSL licence431 under IBM, the Sandbox level and the SOPS health review in the CNCF, the trajectory432 of ESO after the shutdown of the company backing it, and the *feature complete* status of433 gitleaks. None is cause for panic; all are cause for a written plan B.434- Every exception (static secret that cannot be eliminated, long TTL, broad policy) carries435 **a reason, an owner and an exit date**.436437**FORBIDDEN**438- ❌ Storing a static secret where **short-lived federated identity** would have fit.439- ❌ Secrets in code, in the Git history, in `values.yaml`, in `*.tfvars`, in the state,440 in a Dockerfile's `ENV`/`ARG`, in image layers, in logs or in chat channels.441- ❌ Secrets as **command-line arguments** or in a URL's query string.442- ❌ **Environment variables as the default injection mechanism** (§2.3).443- ❌ **Rewriting the history before rotating**: rotate first, clean up afterwards. Always.444- ❌ Assuming an exposed secret is safe because "the repo was private" or "it was deleted straight away".445- ❌ Rotation documented but **never executed**; quorum unsealing procedure446 never rehearsed; manager backup without a tested restore.447- ❌ A credential shared between services, between environments, or between a human and a448 service.449- ❌ Wildcard policies over the manager, or OIDC trust policies with a wildcard `sub`.450- ❌ Secrets manager **with no audit device configured** and with that log unmonitored.451- ❌ A manager on the critical startup path **without having answered** what happens when it goes down.452- ❌ Treating `sealed-secrets` or SOPS as a substitute for a manager when rotation,453 revocation or read auditing is needed.454- ❌ Using `gitleaks-action` v2 without knowing its **commercial licence** and its end of455 operation (16 Sep 2026); relying only on the pre-commit hook with no CI gate.456- ❌ Pointing sealed-secrets at `bitnamilegacy/*` (frozen image with no patches).457- ❌ ESO below 2.3.0, sealed-secrets below 0.36.0, or any manager with a published critical458 CVE left unpatched.459- ❌ Starting something new on `detect-secrets` or Nosey Parker (unmaintained / archived).460- ❌ Actions or images by **mutable tag** in pipelines that handle secrets.461462## 8. Mandatory web verification463464Before pinning a version, licence, governance or behaviour, **look it up — do not recall it**.465Data verified Aug 2026 (the most volatile in the catalogue): Vault **2.0.3**, **BUSL 1.1 with no466change**, *Licensor* now **IBM** (acquisition closed 27 Feb 2025), new IBM support cycle467and **Community with no LTS**; OpenBao **2.6.1**, **MPL-2.0**, **OpenSSF/LF Sandbox level**;468Infisical **0.162.15**; `bws` **2.1.0**; ESO **2.8.0**; Secrets Store CSI Driver **1.6.0**;469sealed-secrets **0.38.4**; Vault Secrets Operator **1.5.0**; vault-k8s **1.7.5**; SOPS470**3.13.3** (CNCF Sandbox, open health issue); age **1.3.1**; gitleaks **8.30.1** (feature471complete) and Betterleaks **1.7.3**; trufflehog **3.96.0**; SPIRE **1.15.2** (SPIFFE/SPIRE472graduated in the CNCF); systemd **v261**.4734741. **Vault's licence under IBM** and **OpenBao's real level in the Linux Foundation/OpenSSF**:475 it is the pair of facts most often misquoted. Read it from the `LICENSE` file and from the project's476 page, not from a blog.4772. **Vault↔OpenBao divergence** before assuming portability: built-in seals leaving the478 binary in 2.7.0, `stored_shares`, namespace semantics, replication.4793. **The manager's advisories** (HashiCorp's HCSEC, OpenBao security advisories): both480 published criticals in 2026.4814. **Maintenance status** of gitleaks/Betterleaks, SOPS (health issue in the CNCF), ESO482 and sealed-secrets before betting the pipeline on any of them.4835. **Current OIDC claims and trust policies** from GitHub/GitLab towards AWS/Azure/GCP: GitHub's immutable484 subject claims changed format in 2026 and break existing policies.4856. **Rotation mechanism and cost** of the specific cloud manager: none of the three rotates486 everything automatically, and what managed rotation covers changes every quarter.4877. **systemd credential directives** and their minimum version in your distribution before488 basing a deployment on them (`SetCredential=`/`LoadCredential=` since 247;489 `LoadCredentialEncrypted=`/`SetCredentialEncrypted=`/`systemd-creds` since 250;490 `ImportCredential=` since 254).4918. **Supply chain compromises** of any new tool in the secrets pipeline492 before adopting it.493494**Declared gaps — not verified in this document, verify them yourself before using them**:495- **OpenBao's exact level in the OpenSSF today** (Sandbox according to the project's page; one496 reading of the index suggested "Incubation" and **no locatable promotion announcement exists**), and497 the **LF Edge entry/exit dates**.498- The comparison of **Vault features absent in OpenBao** (DR/performance replication,499 automated snapshots, Sentinel) comes from a **secondary source**.500- **Formal outcome of ESO's incubation application in the CNCF** (issue closed, with no501 publicly located resolution).502- **Existence and version of `ImportCredentialEx=`** in systemd, and the **exact mode/owner503 of the files** in `$CREDENTIALS_DIRECTORY` (the documentation was not accessible).504- **Exact date of systemd v261**.505- **Specific CIS or NIST SP 800-190 guidance** against environment variables for506 secrets: the argument in §2.3 rests on systemd and OWASP, **not** on NIST/CIS.507- **Who nominally maintains** the `gitleaks/gitleaks` repository today, and the tiers of508 gitleaks.io.509- **Existence of a Secret Store extension for AKS in the cloud** (the located documentation510 covers Arc-enabled Kubernetes; for AKS the supported path is the CSI driver add-on).511- **Maximum federated credentials per app registration** in Entra (the verified limit of 20512 is per *managed identity*).513- **Exact CVSS** of several 2026 Vault CVEs and CVE-IDs of some HCSEC bulletins.514- **Any incident not published via GHSA** in SaaS managers (Doppler and equivalents were not515 consulted).516517If the web contradicts this document, **the web wins** — flag the discrepancy.