hermes-vaultwarden
Hermes Agent plugin: pull env vars from a Vaultwarden (or Bitwarden
Password Manager) vault item's custom fields at process startup, via the
bw CLI.
Vaultwarden implements the Bitwarden Password Manager API — not
Secrets Manager — so Hermes' bundled secrets.bitwarden source (which
uses the bws CLI) does not work with self-hosted Vaultwarden
instances. This plugin bridges that gap as an external
SecretSource plugin
(successor to in-tree PR
#42300,
reworked for the pluggable interface introduced in
#59498).
Install
git clone https://github.com/hansipie/hermes-vaultwarden ~/Code/hermes-vaultwarden
ln -s ~/Code/hermes-vaultwarden ~/.hermes/plugins/vaultwarden
User plugins are opt-in — add the plugin to plugins.enabled in
~/.hermes/config.yaml (the setup wizard does this for you):
plugins:
enabled:
- vaultwarden
Install the bw CLI (not auto-installed): npm install -g @bitwarden/cli,
snap, or the native binary from https://bitwarden.com/help/cli/.
Setup
bw config server https://your-vaultwarden.example.com
bw login
export BW_SESSION=$(bw unlock --raw)
hermes vaultwarden setup
The wizard verifies bw, stores the session token in ~/.hermes/.env,
lets you pick the vault item, optionally maps login.username /
login.password / notes to env vars, test-fetches it, and enables
everything.
Secrets are read from one vault item's custom fields — name each
field after the env var it should set (e.g. OPENROUTER_API_KEY).
The item's structural login.username / login.password / notes
are not custom fields and are ignored unless you opt in — either
answer the wizard's prompts (blank = skip) or pass
--username-env / --password-env / --notes-env for non-interactive
runs (see Config below). Re-running setup and leaving a binding blank
clears it from config.yaml.
Commands
| Command | What it does |
|---|---|
hermes vaultwarden setup |
Interactive wizard (flags: --session, --item-name, --server-url, --username-env, --password-env, --notes-env for non-TTY) |
hermes vaultwarden status |
Config, binary, server, session presence |
hermes vaultwarden sync |
bw sync + fresh fetch, dry-run table; --apply exports into the current process |
hermes vaultwarden disable |
Flips secrets.vaultwarden.enabled to false and clears the disk cache |
Config (secrets.vaultwarden.* in config.yaml)
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Master switch |
session_env |
BW_SESSION |
Env var holding the bw unlock --raw token |
item_name |
— | Vault item whose custom fields become env vars |
username_env |
— | Env var for login.username; unset = not exported |
password_env |
— | Env var for login.password; unset = not exported |
notes_env |
— | Env var for the item's notes; unset = not exported |
override_existing |
false |
Overwrite vars already set by .env/shell (never another secret source) |
cache_ttl_seconds |
300 |
TTL for both cache layers; 0 disables caching entirely |
binary_path |
— | Pin an exact bw binary path |
timeout_seconds |
120 |
Orchestrator wall-clock budget around the fetch |
Config keys are identical to the old in-tree PR #42300 branch — an existing config keeps working unchanged.
Behaviour & caveats
- Bulk source: all custom fields of the item are offered implicitly.
Explicit mapped bindings (e.g. 1Password
env:entries) outrank them on contested vars; conflicts are warned, never silently clobbered. - Login/notes bindings:
username_env/password_env/notes_envare opt-in — there's no default name to guess a structural field into, so unset means not exported. If a binding's target name also matches a custom field, the login/notes value wins and a warning is emitted. - The session token env var is protected — a vault field named
BW_SESSIONcan never overwrite the credential used to reach the vault. - Timing: plugin secret sources are discovered after the very
first env load of the process that discovers them. They apply to every
Hermes process spawned afterwards (gateway children, cron sessions,
subagents). For secrets the first interactive process itself needs,
use
hermes vaultwarden sync --applyor keep them in.env. - Failures (missing binary, expired session, unknown item) never block startup — they surface as one-line warnings with a machine-readable error kind.
- Cache: in-process dict +
~/.hermes/cache/vaultwarden_cache.json(atomic writes, mode0600, keyed on a session fingerprint — the token itself is never written). A stalebw_cache.jsonfrom the old in-tree branch is dead weight and safe to delete.
Tests
HERMES_AGENT_SRC=~/Code/hermes-agent \
uv run --with pytest --with rich --with pyyaml pytest tests/ -v
HERMES_AGENT_SRC must point at a hermes-agent checkout that includes
the SecretSource interface (post-#59498). The suite is hermetic (no
real vault, subprocess mocked) and includes the upstream
SecretSourceConformance kit.