Pakkit.net
← Back to blog

Security

A Default Credential Is a Backdoor You Shipped

admin/admin, a placeholder secret, a "change this before production" comment nobody acts on — these aren't conveniences, they're backdoors with a delayed fuse, because temporary defaults have a way of becoming permanent.

  • Security
  • Secrets
  • Operations
  • Reliability

Every so often I open a tool’s config and find the same thing: a default username and password, admin / admin, with a friendly comment saying “change these before deploying anywhere real.” I’ve written that comment myself. The problem is that the comment is a hope, not a control, and hopes don’t survive contact with a busy deploy. A default credential isn’t a convenience you’ll tidy up later — it’s a backdoor you shipped, armed, with the fuse set to “whenever someone forgets.”

Temporary defaults are permanent by default

The whole danger is a mismatch between intent and behavior. You intend the default to be a placeholder for local development, replaced before anything real. But nothing enforces the replacement, so the actual behavior is that the default works — and working software rarely gets revisited. The system came up, the demo went fine, everyone moved on, and the “change me later” credential is now the production credential, indistinguishable from one someone chose on purpose.

“Change this before production” is a sticky note on a loaded gun. The gun doesn’t read sticky notes.

I’ve seen the same shape in more than one form: a dashboard shipping with admin/admin, a health-check probe passing the literal word secret as its shared secret because someone was testing and never swapped it, a config template with real-looking placeholder passwords that got copied into a real deployment. Each was “obviously temporary.” Each was one busy day away from being permanent.

A placeholder that reaches production is worse than nothing

It’s tempting to think a default credential is better than no security at all. It’s usually worse, for two reasons. First, defaults are public — the vendor’s docs, a README, a thousand other installs all use the same admin/admin, so it’s the very first thing any scan or attacker tries. A default password is a password everyone already knows. Second, it creates a false sense of protection: there’s a login screen, there’s a password field, so it feels secured, and nobody looks harder. A wide-open service at least looks wide open. A default-credentialed one looks locked while holding the key under the mat.

The same goes for placeholder secrets in general. A shared secret set to secret, a signing key of changeme, an API token of xxx — if it reaches a running system, it’s not a weakened secret, it’s the absence of one wearing a costume.

Make the default fail closed

The fix is to stop relying on humans to remember and make the system refuse to run insecurely. Instead of shipping a working default, ship a state that forces a real choice:

  • No default password — require one at setup. The service won’t start, or won’t leave a locked “you must set a password” state, until a real credential is provided. Can’t-run beats runs-insecurely.
  • Force rotation on first use. If you must ship a default to bootstrap, make first login mandatory-reset, so the default can’t survive past setup even if someone tries to keep it.
  • Fail fast on placeholders. If a secret is required, refuse to start when it’s empty or obviously a placeholder, with a loud error — the opposite of silently accepting changeme. I like this so much I now build it in: the safe path is the one that needs a real secret to even boot.
  • Never commit real-looking placeholders. A config template should carry empty values or obvious non-secrets, so nobody can copy it to production and accidentally ship a “working” fake. (This is the neighbor of keeping secrets out of git — don’t commit the real ones, and don’t commit fakes that could be mistaken for real.)

The theme is fail-closed: when the operator hasn’t made a security decision yet, the system’s default posture should be stopped, not open.

Own the whole credential lifecycle

Underneath this is the same discipline as any machine identity: a credential isn’t done when it’s created, it’s owned from birth to retirement — which is the argument in automation needs an identity and why environment variables are not a vault. A default credential is just the birth defect version: an identity that was never really chosen, so nobody feels responsible for it, so nobody rotates or retires it. Give every credential a deliberate origin — a real value, chosen at setup, stored properly — and the “oops, still on the default” incident stops being possible.

So when I see admin/admin with a “change me” comment now, I don’t treat it as a to-do. I treat it as a bug: the system is willing to run insecurely, and willingness is the whole problem. Make it unwilling. If you’ve inherited a fleet where the defaults quietly became the credentials, I’d like to hear how you dug out.