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.
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.
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.
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.
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.
.env (mode 600) and private dotfiles, all git-ignored; commit a
.env.example instead.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.