Services/Vaultwarden
As of: 2026-07-05 — migrated from `services/VAULTWARDEN.md`
Vaultwarden
Self-hosted Bitwarden-compatible password manager. The human-facing credential store. (OpenBao is the machine-facing one — see services/OPENBAO.md.)
Container & Endpoints
| Item | Value |
|---|---|
| Container | vaultwarden
|
| Compose | /library/vaultwarden-app/
|
| Public URL | https://vault.wilsoz.com (via VPS nginx → Tailscale → stronghold)
|
| Internal port | 8080 |
| Auth | OAuth2 via Authentik (provider PK 10) |
| Master folder | "Stronghold Infrastructure" (all infrastructure credentials) |
Claude Code Integration
Claude reads Vaultwarden via the bitwarden MCP server (configured at the user level — works in every Claude session on this machine).
Files
| File | Mode | Purpose |
|---|---|---|
~/.config/claude/vaultwarden.env
|
600 | BW_URL, BW_CLIENTID, BW_CLIENTSECRET, BW_EMAIL, BW_PASSWORD |
~/.config/claude/bw-mcp-start.sh
|
700 | Wrapper that unlocks vault and execs the MCP server |
~/.config/claude/bw-get.py
|
700 | One-off shell helper (bw-get.py "Item Name" [field])
|
~/.claude.json → mcpServers.bitwarden
|
— | Registers the MCP with Claude Code |
/snap/bin/bw
|
— | Bitwarden CLI (snap-installed, official) |
/home/mnw/.nvm/versions/node/v22.12.0/lib/node_modules/@bitwarden/mcp-server/
|
— | MCP server (npm-installed globally) |
How the wrapper works
- Reads
vaultwarden.envvia Python (avoids shell quoting issues with special chars in BW_PASSWORD). - Refuses to start if
BW_URL != https://vault.wilsoz.com— guard against accidentally authing to the wrong server. - Runs
bw config server(idempotent) so the CLI is pointed at the self-hosted instance. - Logs in via
--apikey(uses BW_CLIENTID/BW_CLIENTSECRET) if not already logged in. - Unlocks with
bw unlock --passwordenv BW_PASSWORD --rawand exportsBW_SESSION. - Execs
node <path>/dist/index.js(not themcp-server-bitwardensymlink — see Known Bugs below).
Available MCP tools
58 tools, covering: lock, sync, status, list, get, generate, create_item, create_folder, edit_item, edit_folder, delete, plus organization/group/policy/send management. Used like any other MCP — e.g., "list items matching voip" or "create a new item named X".
Asking the User for Credentials
When you need a credential that isn't in Vaultwarden yet (e.g., a new third-party API key the user just got), never ask the user to paste it in chat. Instead:
Pattern: "Save it to Vaultwarden, then I'll fetch it"
Ask the user to do this:
- Please add it to Vaultwarden:
- 1. Open https://vault.wilsoz.com
- 2. New Item → Folder: Stronghold Infrastructure
- 3. Name:
<service-name>(e.g.,voip.ms) - 4. Add fields:
- -
username= … - -
password= … - - Custom fields for anything else (e.g.,
api-key,did) - 5. Save, then tell me "done"
- I'll fetch it from Vaultwarden via the MCP — you never need to paste secrets here.
Then on your side: <syntaxhighlight lang="text"> [use bitwarden MCP] list items matching "<service-name>" [use bitwarden MCP] get item <id> [use vault MCP] write secret/<service-name> with the retrieved fields </syntaxhighlight>
Why this matters
- Chat transcripts are logged. Secrets in chat = secrets in logs forever.
The user has explicitly said: never store or surface credentials, use Vaultwarden + OpenBao.*
- If you accidentally print a secret (e.g., via
bash -x,set -x,cat env-file, or echoing a fetched variable) — stop, tell the user immediately, and ask them to rotate that credential.
Hard-Won Gotchas
1. mcp-server-bitwarden symlink does not work (v2026.2.0)
The npm package installs a symlink at …/bin/mcp-server-bitwarden → …/dist/index.js. The script at the end of index.js checks process.argv[1].endsWith('index.js') to decide whether to start the server. The symlink path doesn't end in index.js, so the server never starts — it just exits silently with code 0.
Workaround: invoke via node with the full path:
<syntaxhighlight lang="bash">
exec node "$(npm root -g)/@bitwarden/mcp-server/dist/index.js"
</syntaxhighlight>
2. Never use bash -x or set -x near source vaultwarden.env
Bash tracing prints every variable assignment to stderr, including BW_PASSWORD. This will leak the master password into your terminal scrollback, into logs, and into Claude conversation transcripts. Use targeted echo debugging or python3 parsing instead.
3. bw login defaults to vault.bitwarden.com
If the bw CLI's local config gets reset (~/.config/Bitwarden CLI/data.json deleted), it will default to the public Bitwarden cloud. Running bw login against the wrong server sends your credentials to Bitwarden, Inc. Always run bw config server "$BW_URL" first. The wrapper script enforces this with a refuse-to-start guard.
4. bw login vs bw unlock
bw login= authenticate the device to the server (once per device, persisted indata.json).bw unlock= decrypt the local vault cache (once per session, returns BW_SESSION).- The MCP server needs both. The wrapper handles login idempotently; unlock runs on every start.
5. bw login with email + password fails on Vaultwarden
Vaultwarden / Bitwarden self-hosted requires bw login --apikey (client credentials) rather than bw login email password. The email/password flow expects 2FA prompts and an interactive TTY.
Rotating BW_PASSWORD
If the master password leaks (e.g., bash -x mistake):
- In Vaultwarden web UI: Account Settings → Change Master Password.
- On stronghold: Update
~/.config/claude/vaultwarden.env:
```bash # Edit the line: BW_PASSWORD='new-password-here' chmod 600 ~/.config/claude/vaultwarden.env # verify mode still 600 ```
- Re-login to bw:
```bash bw logout set -a; source ~/.config/claude/vaultwarden.env; set +a bw config server "$BW_URL" bw login --apikey ```
- Next Claude session will pick up the new password from the env file automatically.
The BW_CLIENTSECRET (API key) is a separate value — only rotate it if it leaks. Rotating it requires regenerating in Account Settings → Security → Keys → View API Key.
Related Docs
services/OPENBAO.md— the machine-facing companion storeservices/OPENBAO.agent.md— agent-focused decision tree for credential access