How it shows up in Python
A secret in source usually starts as a shortcut meant to be temporary:
import stripe
stripe.api_key = "sk_live_9Qk2Wd7fRt...Z"
def charge(token, amount):
return stripe.Charge.create(amount=amount, currency="usd", source=token)
The key is now in the repository history, on every developer's laptop, in CI logs and in backups. Anyone who reaches any of those has it, and revoking it means editing code and redeploying. The same applies to a database password in settings.py, a cloud access key in a script, a JWT signing secret as a module constant, or a private key pasted into a file. The fix is to read the secret from the environment or a secrets manager at runtime, so it lives outside the code:
import os
import stripe
stripe.api_key = os.environ["STRIPE_API_KEY"]
def charge(token, amount):
return stripe.Charge.create(amount=amount, currency="usd", source=token)
In a container or a cloud deploy, inject the value through the platform's secret store (AWS Secrets Manager, GCP Secret Manager, a Kubernetes secret, a Vault lookup) rather than an environment variable baked into an image. Keep .env files out of version control, and if a secret was ever committed, rotate it: removing it from the latest commit does not remove it from history or from anyone who already pulled.
Why it is hard to see
Dedicated secret scanners exist and are worth running, but they work on text at rest and have two gaps es-python's runtime view fills:
- High-entropy strings that are not provider-shaped. A scanner keyed on known prefixes (
sk_live_,AKIA,ghp_) has no match for a bespoke token or internal signing key outside public patterns. - Whether the secret is actually used, and where it goes. A scanner reports that a string looks like a secret. It cannot say whether that value then travels into an outbound request or a log line, which is what turns a hardcoded secret into a leak.
A runtime view sees the second half: the constant being read, carried through the program, and reaching a place it should not.
What the es-python event looks like
With the hardcoded source on, es-python marks credential-shaped constants in your source as they are compiled, by two routes: provider-key patterns (cloud keys, tokens, PEM private-key headers, JWTs), and a name-and-shape rule for values assigned to credential-named fields that are long enough, high-entropy and not obvious placeholders. The finding does not fire where the secret is written; it fires where the marked value is used, so you see the leak, not just the literal.
Three sink labels carry it. credential exfiltration is a hardcoded secret reaching the network, tagged credential leak and graded Critical. credential logging is one reaching a log record. secret disclosure covers a secret reaching other disclosing sinks. The source itself carries the hardcoded secret impact tag. Two honest limits: a secret whose module is already compiled to a cached .pyc is not re-scanned, and a passphrase-style secret with spaces in it is skipped by the shape filter on purpose. Under a Raise rule, the send or log raises a RuntimeError.
How to fix and prevent it
- Read every secret from the environment or a secrets manager at runtime; keep none in source.
- Inject secrets through the platform's secret store in production, not an environment variable baked into an image.
- Keep
.envand key files out of version control, and add a pre-commit secret scanner. - Rotate any secret that was ever committed; deleting it from the latest commit is not enough.
- Turn on the hardcoded source and watch for
credential exfiltrationandcredential loggingto catch a secret that is not only present but actually leaving the program.
Classification
Hardcoded credentials are CWE-798, Use of Hard-coded Credentials, and the password case is CWE-259, Use of Hard-coded Password. OWASP maps both to A07:2025 Authentication Failures in the Top 10 for 2025.
Questions people ask
The questions that come up on forums and in reviews about this class, each answered in a few sentences.
Where should secrets live instead of source code?
In the environment the program runs in, loaded at startup from a secrets manager, a vault or an environment variable set by the deployment, never in a file that is committed. A local .env file kept out of version control is acceptable for development as long as production never reads it. Rotate any secret that has ever been committed, because a rewritten history does not reach the clones and backups that already have it.
Is a secret in a private repository still a problem?
Yes: a private repository is readable by every present and past collaborator, every CI runner, every laptop with a clone and every backup, and a secret copied into it cannot be rotated without a code change. Scanners that read source find the obvious patterns and are worth running in CI. es-python adds the moment a hardcoded secret is actually used, where it reaches the network, a log line or another disclosing sink.
How does es-python report hardcoded credentials?
With the hardcoded source on, it marks credential-shaped constants as your source is compiled, by provider-key patterns and by a name-and-shape rule, and reports where the marked value is used: credential exfiltration when it reaches the network, credential logging when it reaches a log record, secret disclosure for other disclosing sinks. The finding carries the hardcoded secret impact tag. A module already compiled to a cached .pyc is not rescanned, and a passphrase with spaces in it is skipped on purpose.