Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français
Lire en français

Why static secrets are a problem

By updated

Improve this page

A static secret is a credential created once, stored somewhere and reused for months: a database password, an API key, a cloud access token. This lesson is for the DevOps engineer who inherits dozens of these secrets and wants to understand why they are a problem before choosing an alternative. The flaw is not cryptographic: it is the lifetime, the copies and the lack of identity that cost dear.

A measured example shows it. A GitHub ghp_ token committed and then removed in the next commit is invisible to gitleaks 8.30.1 in dir mode, and found in git mode in the history, with an exit code of 1. The file is gone, the secret is not: as long as it is not revoked, it remains usable.

What you will learn

  • Define a static secret and its five usage properties
  • Explain why lifetime, copying and sharing create the risk
  • Recognise six symptoms of immature management in your organisation
  • Measure that a secret removed from a file survives in Git history
  • Choose a path: vault, dynamic secrets, then workload identity
  • Place this approach in requirement SOCLE-SEC-ROT-2 (in French), which favours short-lived identities

What is a static secret

A static secret is a fixed value that exists before it is used, stays valid for a long time and only changes when a human decides to change it. The term is the opposite of the dynamic secret, generated on demand with a short expiry, and of workload identity, which removes the shared secret. The table sums up the five properties that characterise it; each is harmless on its own, it is their combination that creates the risk.

CharacteristicDescription
Pre-existenceThe secret exists before it is used
Persistent storageIt is stored somewhere (file, variable, vault)
Frequent sharingOften reused by several components
Long lifetimeValid for months or years
Manual rotationChanged rarely, often by hand

Concrete examples

Three forms come up in almost every team. The values below are examples: the AWS ones are the illustration keys published by the official documentation, which give access to nothing.

Database password:

Terminal window
# Defined once in a configuration file
DB_PASSWORD="SuperSecretPassword123!"

This password is created at installation, stored in a .env file or a vault, and reused for the whole life of the application. It is sometimes shared between several applications that access the same database.

External service API key:

Terminal window
# Generated on the provider's portal
STRIPE_API_KEY="sk_live_abc123xyz789..."

This API key is generated once, stored in the CI/CD secrets or the vault, and used by every environment that calls the provider. Many providers offer no alternative to this model.

Cloud access keys:

Terminal window
# AWS access keys created manually (example values from the AWS docs)
AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

These access keys are created in the IAM console, stored in the pipeline and used for months. The AWS documentation itself calls them an account security risk and recommends temporary credentials (IAM roles) instead. It also limits a user to two access keys at a time, which caps rotation without downtime.

Why static secrets are a problem

A long, random password remains cryptographically strong for years. It is its usage properties that cause the problem: its lifetime, its tendency to get duplicated and the fact that it carries no identity. The six angles that follow go from the most visible to the most structural.

Lifetime that is too long

The longer a secret exists, the more chances it has to leak. It is discovered by an attacker, copied to an unprotected place, shared with people who no longer need it, or forgotten in an old system. Every month of validity adds a window of exploitation.

The consequence is mechanical: a secret stolen six months ago may still be active today. A token that expires in one hour, by contrast, is worth almost nothing an hour after the theft. The lifetime is therefore the first lever, ahead of even the quality of storage.

Duplication across several layers

A static secret tends to spread. The diagram follows a common path: the value leaves the vault, is copied into a Kubernetes Secret, injected as an environment variable, then leaks into the startup logs or a debug dump. The last two copies are in red: nobody decided on them.

Spread of a static secret: the original secret, in a vault, is copied into a Kubernetes Secret, injected as an environment variable, then leaks into the startup logs and is captured in a debug dump.copyinjectionleakcaptureOriginal secret(vault)KubernetesSecretEnvironmentvariableStartuplogsDebugdump
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Each copy is an extra attack surface, and above all one more place to clean up on the day of a revocation. The vault protects the original; it protects none of the copies that come from it.

Difficulty of complete revocation

When a static secret is compromised, revocation takes four steps that must all succeed. The first is the hardest, because it requires an up-to-date inventory of the consumers, which most teams do not have.

  1. Identify every place where the secret is used
  2. Generate a new secret
  3. Deploy this new secret everywhere
  4. Check that the old one no longer works

In practice, the operation is rarely complete. An old script, a forgotten cron job or a copy in a backup keeps using the old value. And revocation depends on the provider: a password changed in a file is not necessarily changed in the database.

A secret removed from the code stays in history

Deleting the line that held the secret does not make it disappear: Git keeps every version of every file. The following measurement replays the case with gitleaks 8.30.1, the secrets detector seen in the module on code. A GitHub ghp_ token is committed in deploy.env, then the file is deleted in the next commit.

Terminal window
$ gitleaks dir /r --no-banner
INF no leaks found
$ gitleaks git /r --no-banner --redact -v
Finding: REDACTED
Secret: REDACTED
RuleID: github-pat
Entropy: 5.121928
File: deploy.env
Line: 1
Commit: d400395794befbc7039f5fdad842ea1aa9f0f197
...
INF 1 commits scanned.
WRN leaks found: 1
$ echo $?
1

The dir mode only reads the files present and sees nothing; the git mode walks the history and finds the token, with an exit code of 1 that is enough to fail a job. Anyone who clones the repository can do the same with git log -p. The only real protection is revoking the token, covered in the lesson on the compromised secret.

Poor fit for ephemeral environments

A static secret assumes a stable environment: the application starts once and runs for a long time, the secret is configured at deployment, rotation happens during planned maintenance. This model holds on a server that lives three years.

It breaks when dozens of Kubernetes pods are created per minute, when every serverless function needs access, or when the infrastructure is rebuilt every day. Handing out a fixed value by hand to workloads that live a few minutes amounts to giving each of them a permanent key for temporary use.

Impossible precise attribution

When several services share the same credential, the database logs see only one user. The diagram shows three services that all connect as app_user: if a suspicious query appears, nothing says which one sent it. Service C, in red, can be compromised without the database knowing.

Three services A, B and C connect to the same database with the same app_user user; faced with a suspicious query, the database cannot tell which service sent it.user: app_useruser: app_useruser: app_userService AService BService CDatabaseWhich service madethis request?
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

OWASP puts it this way in its Secrets Management Cheat Sheet: services that share the same secrets make identifying the source of a compromise difficult. One credential per service restores attribution, and lets you revoke a single service without cutting off the other two.

Symptoms of poor maturity

The following six situations occur in most organisations that are starting out. Each one is recognised by a configuration snippet and fixed by a precise measure. The values shown are deliberate counter-examples.

Symptom 1: database password shared between applications

The same password is copied into two manifests: on the database side, nothing distinguishes the two applications any more, and compromising one means compromising the other.

# ❌ Application 1
env:
- name: DB_PASSWORD
value: "shared-password-123"
# ❌ Application 2 (same secret)
env:
- name: DB_PASSWORD
value: "shared-password-123"

The fix is a distinct credential per application, ideally dynamic: an engine such as Vault or OpenBao can create a database user on demand, with a bounded lifetime.

Symptom 2: admin token used "for convenience"

An admin token ends up in a deployment script because creating a restricted token took a few more minutes. The script then has all the rights of the account that created it.

Terminal window
# ❌ Deployment script
export GITHUB_TOKEN="ghp_AdminTokenWithFullAccess..."

The fix is a token with minimal permissions, such as a GitHub fine-grained PAT limited to one repository and one duration, read-only if possible. In a pipeline, OIDC even removes this token, as the lesson on secrets in CI/CD shows.

Symptom 3: secret copied between Kubernetes and workstations

The same password lives in two places that have neither the same lifecycle nor the same level of protection: a cluster object and a .env file on a workstation.

Terminal window
# The secret is in Kubernetes
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
# ❌ But also in a .env for local development
cat .env
DB_PASSWORD=same-password-here

The fix is a single source of truth (the vault) and injection at startup of the process, with distinct credentials for local development.

Symptom 4: manual annual rotation

Rotation is treated as a background task, with no binding deadline and no automation. It is therefore systematically postponed by emergencies, and the ticket ends up moving from one quarter to the next.

Ticket: "Annual secrets rotation"
Assignee: Ops team
Priority: Low
Status: Postponed (3rd time)

A painful rotation will not get done. The fix goes through automated rotation, or better through dynamic secrets that expire on their own and make planned rotation unnecessary.

Symptom 5: old secret never revoked after an incident

The incident did trigger the creation of a new secret, but the old one was never revoked on the provider side. It remains usable, and nobody watches it any more since the incident is closed.

History:
- January: suspected leak of the API token
- January: new token generated
- March: old token still active (forgotten)
- June: old token used by an attacker

The fix is a written revocation procedure that ends with a check: try to use the old secret and observe the failure. Without this test, revocation is only an intention.

Symptom 6: same credential in development and production

The same credential goes through all three environments, which aligns the security level of production with that of the least protected development workstation.

Terminal window
# ❌ .env.dev
DB_PASSWORD="password123"
# ❌ .env.staging
DB_PASSWORD="password123"
# ❌ .env.prod
DB_PASSWORD="password123"

The fix is a distinct secret per environment, generated automatically, with separate access rights: a developer has no reason to be able to read the production secret.

The real impact: compromise scenarios

The three chains described here are common, not extreme. They share the same tipping point: the damage does not come from the initial theft, it comes from the fact that the stolen secret is still valid when the attacker decides to use it.

Scenario 1: the API key in Git history

A developer commits an API key to test, then removes it in the next commit. As measured above with gitleaks, Git history keeps the value. A year later, an attacker who scans public repositories finds it.

The impact fits in one sentence: the key is still active, because nobody revoked or replaced it. Removing the file gave an impression of a fix without changing anything about the validity of the secret.

Scenario 2: the shared password

Three microservices use the same PostgreSQL password. A vulnerability in the least critical service lets an attacker read the process's environment variables.

The attacker now has access to the database shared by the three services, including the data of the other two. The weakest service sets the security level of the whole database.

Scenario 3: the impossible rotation

A team wants to rotate a critical password, used by five applications in production, two cron jobs, three maintenance scripts and a third-party integration. Coordinating the simultaneous change of eleven consumers seems too risky, and the rotation is postponed.

The secret stays unchanged for three years. It is not a lack of will: it is the coupling between one secret and its consumers that makes rotation dangerous. Dynamic secrets remove this coupling, since each consumer receives its own value.

What to do: the alternatives

No organisation removes its static secrets in one go: some third-party integrations only accept a long-lived key. Progress happens in three stages, first reducing the risk carried by existing secrets before changing model. The following lessons of this module detail each stage.

Short term: manage static secrets better

Even without being able to remove static secrets, five steps greatly reduce the risk. The first, centralisation, conditions the other four: you cannot trace or rotate a secret whose location you do not know.

  1. Centralise in a vault (OpenBao, Vault, Infisical)
  2. Separate by environment (development, staging, production)
  3. Limit permissions to the strict minimum
  4. Trace all access
  5. Plan regular rotations, automated if possible

Medium term: adopt dynamic secrets

The most critical secrets can become dynamic: generated on demand, specific to each consumer and expired automatically. Three forms are common: database credentials created by a secrets engine, short-lived cloud tokens, and automatically renewed certificates.

The gain is twofold. Rotation disappears as a task, since each value expires on its own, and attribution comes back, since each consumer has its own value. The lesson on the secret lifecycle shows how the TTL (time to live) replaces planned rotation.

Long term: workload identity

For machine-to-machine exchanges, the next stage removes the shared secret in favour of a workload identity (the identity of a running program). Three families exist: SPIFFE/SPIRE for internal cryptographic identities, the cloud providers' OIDC federation (GCP, AWS, Azure) for access to cloud APIs, and mTLS between services.

This step raises a prior question, that of secret zero: who gives the application the first secret that opens the vault? The two lessons that follow the one on the lifecycle answer it, and show why attestation by the platform breaks the loop.

Assessment checklist

These six questions measure your current maturity. An answer far from the right-hand column points to the symptom to tackle first; tested revocation is the one teams tick least often.

QuestionIdeal answer
How long do your secrets stay valid?Hours or days, not months
Is a secret shared between several services?No, one per service
Can you rotate a secret in less than an hour?Yes, in an automated way
Do you know which services use each secret?Yes, with certainty
Do environments have distinct secrets?Yes, always
Is secret revocation tested regularly?Yes, in drills

Key points

  • A static secret is cryptographically strong; its lifetime, its copies and its sharing are what create the risk.
  • Each copy (Kubernetes Secret, environment variable, logs, dump) is one more place to clean up on the day of a revocation.
  • A secret removed from the code stays in Git history: gitleaks 8.30.1 finds it in git mode, not in dir mode.
  • A credential shared between services makes attribution impossible and prevents revoking a single consumer.
  • A manual, painful rotation will not get done: automation or dynamic secrets.
  • The path goes from the vault to dynamic secrets, then to workload identity.
  • The evidence expected by SOCLE-SEC-ROT-2 is the use of short-lived or ephemeral identities (OIDC) favoured over long-lived secrets.

Knowledge check

Check that you have the essentials of this lesson. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn