website/docs/user-guide/secrets/bitwarden.md
Pull API keys from Bitwarden Secrets Manager at process startup instead of storing them in plaintext inside ~/.hermes/.env. One bootstrap secret (a machine-account access token) replaces N per-provider keys, and rotating a credential becomes a single change in the Bitwarden web app.
~/.hermes/.env as BWS_ACCESS_TOKEN.hermes (or the gateway, or a cron job) starts, after ~/.hermes/.env has loaded, Hermes calls bws secret list <project_id> and sets the returned keys into os.environ.override_existing: false in config if you want .env to win instead.The bws binary is auto-downloaded into ~/.hermes/bin/ on first use — no apt, no brew, no sudo.
Bitwarden Secrets Manager is designed for non-interactive workloads: machine accounts can't be 2FA-gated because there's no human in the loop. The access token is the credential. Anyone with it can read every secret the machine account has access to, so treat it like a high-value bearer token — store it in .env (not config.yaml), and revoke + regenerate from the Bitwarden web app if it ever leaks.
You set up the machine account in the web app, where your normal 2FA applies. After that the token is autonomous.
In the Bitwarden web app (or vault.bitwarden.eu for EU accounts):
OPENROUTER_API_KEY, ANTHROPIC_API_KEY, etc.0.). Bitwarden cannot retrieve it again — keep the copy.Secrets Manager is included on the Bitwarden free tier with limits; no paid plan needed to try this.
hermes secrets bitwarden setup
It will:
bws v2.0.0 into ~/.hermes/bin/bws.~/.hermes/.env as BWS_ACCESS_TOKEN.config.yaml as secrets.bitwarden.server_url and passed to bws as BWS_SERVER_URL.config.yaml as secrets.bitwarden.project_id.secrets.bitwarden.enabled: true.Non-interactive setup is also supported via flags:
hermes secrets bitwarden setup \
--access-token "$BWS_ACCESS_TOKEN" \
--server-url https://vault.bitwarden.eu \
--project-id <project-uuid>
hermes secrets bitwarden status
From now on, every hermes invocation pulls fresh secrets at startup. You'll see a one-line summary in stderr the first time secrets are applied in a process.
| Command | What it does |
|---|---|
hermes secrets bitwarden setup | Interactive wizard (install binary, prompt for token, pick project, test fetch) |
hermes secrets bitwarden status | Show config + binary version + token presence |
hermes secrets bitwarden sync | Dry-run: pull secrets now and show what would be applied |
hermes secrets bitwarden sync --apply | Pull and export into the current shell's environment |
hermes secrets bitwarden install | Just download the pinned bws binary (no auth required) |
hermes secrets bitwarden disable | Flip enabled: false; leaves token + project id in place |
Defaults in ~/.hermes/config.yaml:
secrets:
bitwarden:
enabled: false
access_token_env: BWS_ACCESS_TOKEN
project_id: ""
server_url: ""
cache_ttl_seconds: 300
override_existing: true
auto_install: true
| Key | Default | What it does |
|---|---|---|
enabled | false | Master switch. When false, Bitwarden is never contacted. |
access_token_env | BWS_ACCESS_TOKEN | Env var name that holds the bootstrap token. Change this if you already use BWS_ACCESS_TOKEN for something else. |
project_id | "" | UUID of the project to sync from. |
server_url | "" | Bitwarden region or self-hosted endpoint. Empty = bws default (US Cloud, https://vault.bitwarden.com). Set to https://vault.bitwarden.eu for EU Cloud, or your own URL for self-hosted. Plumbed into the bws subprocess as BWS_SERVER_URL. |
cache_ttl_seconds | 300 | How long an in-process fetch result is reused. Set to 0 to disable caching. Cache is per-process; new hermes invocations start fresh. |
override_existing | true | When true, Bitwarden values overwrite anything already in env (so rotation in the web app actually takes effect). Flip to false if you want .env / shell exports to win locally. |
auto_install | true | When true, bws is auto-downloaded into ~/.hermes/bin/ on first use. |
Bitwarden never blocks Hermes startup. If anything goes wrong, you'll see a one-line warning in stderr and Hermes continues with whatever credentials .env already had:
| Symptom | Cause | Fix |
|---|---|---|
BWS_ACCESS_TOKEN is not set | Enabled in config but token cleared from .env | Re-run hermes secrets bitwarden setup |
bws exited 1: invalid access token | Token revoked or wrong | Generate a new token, re-run setup |
[400 Bad Request] {"error":"invalid_client"} | Token is for a Bitwarden region other than the one bws is calling (e.g. EU token hitting the US identity endpoint) | Re-run setup and pick the right region, or set secrets.bitwarden.server_url to https://vault.bitwarden.eu (or your self-hosted URL) |
bws timed out | Network blocked or Bitwarden API slow | Check connectivity to api.bitwarden.com (or your server_url) |
bws binary not available | auto_install: false and bws not on PATH | Install manually from github.com/bitwarden/sdk-sm/releases or flip auto_install back on |
Checksum mismatch | Download corrupted or tampered | Re-run, will retry; if it persists, file an issue |
BWS_ACCESS_TOKEN) is itself sensitive — anyone with it can read every secret the machine account has access to. Treat it the same as any other API key.override_existing: true. If you store BWS_ACCESS_TOKEN as a secret inside the project, it's silently skipped during apply.bws binary download is verified against the published SHA-256 checksum from the same GitHub release. Mismatch aborts the install.bws v2.0.0 at time of writing) is updated through PRs to this repo — Hermes does not auto-upgrade bws to "latest" because upstream release shapes can change.~/.hermes/.env is fine. You're trading one credential for another and adding a network dependency at startup.api.bitwarden.com.The good case for this is multi-machine fleets, shared dev boxes, gateway VPSes, or any setup where you want centralized rotation and revocation across multiple Hermes installations.