One Password for the Whole Family

Supply-chain bugs are supposed to happen to other people, bigger people with build servers: a dependency you didn't write, a build server you don't 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, a set of instructions an agent follows to install my standard auth pattern into whatever repo I point it at. The pattern uses opaque session tokens, never JWT, with platform-native password hashing, a dedicated auth database, and a global request gate that's secure by default with an allowlist. It has viewer and admin roles, a seed admin created on first boot so you can log in to an empty system, login rate limiting, and audit events.

It's a good pattern, and it's 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

It wasn't a security audit that found it. It was 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 in three different apps, all from one origin. That's what a supply-chain bug looks like from the inside: 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's no fallback literal to fall back to.

It was 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, because 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 haven't 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 don't want to pretend the scrub is finished when it isn't. Somebody could dig the old default out of an old commit and learn what my apps used to accept. That's what's left, stated.

The Part That Generalizes

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

The fix isn't "be more careful with templates." I was careful, and the seed step had a comment telling you to rotate the password. The fix is that templates shouldn't 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 what 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'd 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