Keeping secrets

API keys, tunnel tokens and passwords are the one kind of data we never want on a web page, in a log or in git. Here is how we store them, how code reads them, and why Bitwarden sits behind an adapter instead of being wired in everywhere. This page describes the approach only; it contains no secrets.

1. One door for every secret

Every app and script asks for a secret by name through one small Ruby module, and never knows where it came from:

Secrets.fetch("NGROK_AUTHTOKEN")

Behind that call sits one class per provider, each with the same two methods: fetch(name) and names. An environment variable picks which one runs. That is the same adapter shape Rails uses for storage, mail and jobs, and it means changing secret managers is a configuration change, not a rewrite.

2. Default: a .env file per project, kept out of git

Each project keeps its secrets in its own .env file, plain KEY=value lines that every tool already understands, and other private config in dotfiles beside it. They are readable only by their owner (the adapter refuses a .env other users could read) and git-ignored everywhere, so they never reach a repository. What does get committed is a .env.example listing the names with empty values. There is no service to run, nothing to unlock at boot, and real environment variables still win when you need to override one.

3. Second provider: Bitwarden Secrets Manager

Bitwarden's Secrets Manager is built for machines rather than people. A machine account gets access to a chosen set of secrets, and its CLI, bws, lists them or injects them into a command's environment. The free plan covers what we need: unlimited secrets, two users, three projects and three machine accounts.

The trade-off is hosting. Bitwarden runs it for us; running Secrets Manager ourselves needs their own server and an Enterprise plan. That is fine behind an adapter: if Bitwarden changes terms, we switch back to .env files, or to whatever comes next, with one environment variable.

4. Why not Vaultwarden?

Vaultwarden is an excellent open source, self-hosted server for the Bitwarden password manager, and it is a good home for passwords people type. It does not implement the Secrets Manager API, so bws can't talk to it. Using it for unattended services would mean storing a vault's master password on disk to unlock it at boot, which is the problem we were trying to solve. It stays an option for people's passwords, not for machines.

5. The rules

These rules are section 13 of AGENTS.md, which every coding agent here follows.


Provided as is, for information only, with no warranty and no liability. Not legal or professional advice. Security and radio material is for authorized, lawful use only. Full disclaimer.

Visits are logged on our own server: IP address, browser and device details, and pages viewed. We use them to understand traffic and spot attacks, and never share or sell them.