Flox secrets plugins
Intro
I was the PM and designer for our pod at Flox, and I led this project.
We shipped Flox's experimental [plugins] manifest section about a week after I framed it.
By day 17, we had also shipped four secret-store plugins.
The feature began as a prototype I built with Claude agents, which engineering reworked.
Flox defines reproducible developer environments in a shared manifest.toml.
Secrets don't belong there, yet our docs modeled vars.PGPASSWORD = "super-secret".
Teams needed a safe way to declare secrets.
I framed the problem, built the first prototype, and wrote three of the four plugins. I also wrote the plugins concept page and reworked the secrets page.
Team and collaboration
An engineer reworked my prototype for production and wrote the 1Password plugin.
My pod teammate ran the prior-art research, built plugin CI and publishing, and fixed the post-launch [vars] blocker.
Another colleague handled testing and review.
A fourth colleague's earlier profile.d loading fix made the design possible.
Stakeholders included engineers at spec review, community users, and marketing.
Work flowed from prototype to production:
- I framed the problem with Claude agents and took the framing to a team review.
- I built a working prototype, and the engineer reworked it with Claude for production.
- I presented at spec review and took new plugins to the community users who asked for them.
Hand-rolled secrets problem
Our docs offered a hand-rolled workaround: an activation hook that fetches each secret. It was hard to discover, different for every store, and silent about which secrets an environment needed.
The open question was how much Flox should own. There were three paths:
- Flox could hold secrets itself, encrypted in the repo (SOPS or age).
- It could build each store's provider into the binary, as the prior-art research recommended.
- It could own only the declaration and let packages fetch.
My July 21 sketch took the last path.
It was a [plugins.1password] table of secret references, fetched by the store's package.
Framing with Claude agents
On July 21, I framed the project with Claude agents in an internal planning repo.
At the team review that day, I asked how secrets would stay out of flox containerize images.
My pod teammate questioned the planned leak linter and CI guarantees.
We cut all three, and I recorded each cut.
References, not values
I decided this feature would carry references, never values, even though every user now needs an external store. Holding values means owning key management, rotation, revocation, audit, and breach blast radius.
Here's a Vault declaration from our secrets docs:
[plugins.vault]
GH_TOKEN = "secret/github-work#token" A generic [plugins] section, not [secrets]
My pod teammate, the rest of our pod, and I chose one generic section, so a new integration is just a package. We accepted that nothing validates plugin data before activation, and that failing scripts are hard to attribute.
A prototype the team could try
The night of the team review, I built a working prototype through the planning repo's Claude agents. The goal was a design the team could try, not just read. Its 10 commits added schema, helpers, a demo plugin, tests, docs, and a walkthrough. The PR said it wasn't meant to merge as-is. It also named its own shortcut, an in-place schema edit. Claude's PR review flagged that shortcut too.
Handoff to engineering
The engineer's first review asked for hard errors where my helper silently returned nothing. He reworked the branch with Claude (a proper v1.14.0 schema, a fail-closed helper, and new tests). He merged it 45 hours after framing began. My data model and merge logic shipped essentially unchanged, but the commits are his. At handoff, the rework dropped my demo plugin, my walkthrough, and one helper.
We left a few loose ends. Our merge commit claimed a version bump that hadn't happened. A version-guard test was skipped on purpose until the engineer re-armed it eight days later. The next-day release slipped to the following week.
Plugins the community asked for
On July 29, I presented at spec review, then turned its open questions into decision issues. Community members had asked for Vault, Infisical, and OpenBao. So I wrote those plugins with Claude Code and soft-launched them to those users. Not every store got a live end-to-end test before release.
Each plugin owns its failure policy
We left fail-fast to each plugin. Core Flox still blocks activation when a plugin's table is missing. My plugins just warn on a missing secret. The cost is no Flox-level "required secrets" contract.
Real use exposed an ordering problem
Early real-world use showed that plugins couldn't read [vars] and that debugging a plugin was opaque.
Package profile.d scripts ran before [vars] was set.
I'd written that order down twice, but I didn't see it as a blocker until then.
My pod teammate's fix shipped in Flox 1.15.0.
The debugging section I'd asked for in the docs on August 4 didn't merge until August 26.
The plugins docs now show how to trace which plugin scripts ran:
Our docs kept the bad example
Fixing our super-secret docs was in scope, but it still hasn't happened.
That's awkward for a secrets project.
Outcome
Flox 1.14.0 shipped the experimental [plugins] section about a week after framing.
By day 17, we (and by we, I mean my pod teammate) had published all four plugins to the public Flox catalog.
I wrote three of them.
Here's the Vault plugin's install command from our docs:
$ flox install flox/plugin-vault Our secrets docs now list which stores have a Flox plugin:
Flox 1.15.0 let plugins read [vars], 18 days after they went public.
Nothing measures adoption.