Hardcoded default credential in a variable placeholder
ID |
placeholder_default_credential |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Vendor |
- |
Family |
Password |
Description
Configuration files often read credentials from the environment through a variable placeholder, and many placeholder syntaxes allow a fallback value to be embedded when the variable is not set. That fallback is a credential hardcoded in the repository, even though the surrounding line looks like a safe indirection.
This detector reports the fallback literal of a placeholder assigned to a credential-like key. Both families of separator are covered:
-
Spring, Quarkus and Micronaut property placeholders:
${VARIABLE:default}. -
POSIX shell,
.envfiles and Docker Compose interpolation:${VARIABLE:-default},${VARIABLE-default}and${VARIABLE:=default}.
A placeholder with no fallback (${VARIABLE}) is a plain indirection and is not reported. Neither is the
error-message form ${VARIABLE:?message}, which aborts when the variable is missing instead of supplying a
value, nor a fallback that is itself a placeholder (${VARIABLE:${OTHER}}).
Security
The fallback silently becomes the effective credential whenever the environment variable is absent, which is
exactly what happens in a hurried deployment, a new environment, a local run promoted to staging, or a
container started without its env file. Nothing fails and nothing is logged, so a well-known default such as
admin123 or changeit ends up protecting a real service.
Because the value lives in the repository, it is readable by everyone with access to the source and to every copy of the history, and defaults are frequently shared verbatim across projects and documentation.
Examples
spring.datasource.password=${DB_PASS:s3cr3tPass99} (1)
spring.mail.password=${MAIL_PASSWORD} (2)
| 1 | The default s3cr3tPass99 applies whenever DB_PASS is not exported. |
| 2 | No default: the value can only come from the environment, so it is not reported. |
services:
api:
environment:
- DB_PASSWORD=${DB_PASSWORD:-composeDefault1} (1)
- API_TOKEN=${API_TOKEN:?token is required} (2)
| 1 | Compose and shell use :- for the default value. |
| 2 | The :? form raises an error when unset, so there is no hardcoded credential. |
Mitigation / Fix
Remove the fallback and let the application fail fast when the credential is not provided:
spring.datasource.password=${DB_PASS}
For Docker Compose and shell scripts, the :? form turns a missing credential into an explicit startup
error, which is preferable to a silent default:
DB_PASSWORD=${DB_PASSWORD:?DB_PASSWORD must be set}
Then provision the value from the environment, a secrets manager or a vault, and rotate any credential that was previously committed as a default, since it must be considered compromised.
Keeping a non-sensitive default is acceptable for values that are not credentials, such as ports, hostnames or log levels.