One Password for the Whole Family

Supply-chain bugs are supposed to happen to other people. Bigger people, with build servers. A dependency you did not write, a build server you do not control, a maintainer who got phished. This one happened inside my own tooling, to my own apps, and I published it myself.

The Pattern

I build a lot of small apps, and most of them need the same login. So at some point I stopped writing it by hand and wrote a skill instead. It is a set of instructions an agent follows to install my standard auth pattern into whatever repo I point it at. Opaque session tokens, never JWT. Platform-native password hashing. A dedicated auth database. A global request gate that is secure by default with an allowlist. Viewer and admin roles. A seed admin created on first boot so you can log in to an empty system. Login rate limiting. Audit events.

It is a good pattern. It has been applied to most of the apps I run. The skill's source lives in a public mirror of my agent harness, because I think the harness is worth sharing.

The seed-admin step had a fallback. If the environment variables that set the first admin's login were missing, use this literal password so first boot still works.

One literal. In a public repository. Telling every repo built from it to accept that literal as the admin password until someone changed it.

How It Was Found

Not by a security audit. By a routine ship-and-unstick pass across all my repos, the kind of sweep whose job is to commit stranded work and clear small advisories. One line in its report, under Security, said a default seed credential was being published in the public repo. The default for the whole family was readable by anyone.

A separate security sweep, running on its own, found the same literal still sitting in one app. It put the consequence plainly: anyone with repo access who beats the real admin to first login takes over the account. A third pass found an app whose README printed the literal verbatim, with only a comment nudging the reader to rotate it.

Three independent finds, three different apps, one origin. That is what a supply-chain bug looks like from the inside. It is not one vulnerability. It is one template, and the number of vulnerabilities is however many times you applied it.

The Fix

The pattern now builds a password out of 24 bytes of cryptographically secure randomness and prints it once to the console, on the boot that creates the account. You have to change it after the first login. There is no fallback literal to fall back to.

Verified by smoke test, not by reading. First boot on an empty database printed one line and that password logged in. The old literal returned 401. A reboot against an already-seeded database printed nothing. The live apps were unaffected. They already had a seeded admin, so the seed step does nothing there.

Fixed in the live harness, fixed in the public mirror, pushed.

What I Left

The literal stays in git history. Rewriting that is a separate decision, and I have not made it. The password is rotated everywhere it was ever used, so what sits in the history is a curiosity rather than a working credential. But I do not want to pretend the scrub is finished when it is not. Somebody could dig the old default out of an old commit and learn what my apps used to accept. That is what is left, stated.

The Part That Generalizes

If you use an agent to apply the same pattern across many repos, you have built a supply chain, and you are the upstream. Every default in the template is a default in every downstream. Every weak choice is a weak choice times the number of times the template ran.

The fix is not "be more careful with templates." I was careful. The seed step had a comment telling you to rotate the password. The fix is that templates should not carry secrets-shaped defaults at all. A template that needs a credential should generate one, print it once, and force its replacement. The convenience of a known default is exactly the thing an attacker wants, and a template is the one place where that convenience multiplies.

I got lucky. The apps are small, the audience is smaller, and the sweep that caught it runs weekly. If I had been shipping this pattern to clients instead of to myself, the story would be about them.

-- Justin Higgins. Software Engineer, Midwest. Published his own supply-chain bug and rotated it before writing this.


Companion pieces: An Autonomous Prompt Is a Permissions Document - what a template is allowed to do. Your Rate Limiter Is Counting the Wrong IP - the same auth hub, a different mundane bug.

Reactions, disagreements, war stories: jchigg2000.dev@gmail.com