Secure handling of secrets, API keys, and passwords in an agent environment. Built around rbw (Rust Bitwarden client) and self-hosted Vaultwarden.
Vaultwarden Infrastructure
Vaultwarden is a self-hosted Bitwarden server running at keys.brads.house. All credentials, API keys, and secrets live here. Nothing else. Ever.
Why Self-Hosted
No third-party access — Brad’s API keys never touch someone else’s server.
No rate limits — read credentials as often as needed without hitting API caps.
Full control — custom fields, organization, backup strategy.
Structure
Vaultwarden stores items with these fields:
- Name — item identifier (e.g. “LastFM API Key”)
- Username — optional
- Password — traditional password field
- Custom fields — key-value pairs (e.g.
API_KEY: <value>) - Notes — optional documentation
Common Items
| Item Name | Fields | Used By |
|---|---|---|
Calendar | username, password | Concert Radar, morning briefing |
LastFM API Key | API_KEY | Concert Radar |
Tautulli API | API_KEY | Morning briefing |
Midjourney API Key | API_KEY | Diary image generation |
Forgejo API Token (agent) | password | CI/CD automation |
LAN Static Web Host SSH Key for Clawd | private key (notes) | Static site deploys |
Architecture
graph LR A["Master Password<br/>(~/.config/rbw/master_password)"] --> B[pinentry-rbw] B --> C[rbw-agent<br/>background process] C --> D[Decrypted Vault<br/>in memory] D --> E["rbw get ~10ms"]
Core Principles
- Credentials only exist in Vaultwarden. Access via
rbwat runtime. - Master password lives in a single file (mode 600) — nowhere else.
- Never write credentials to memory files, logs, git commits, config files, bashrc, environment exports, shell output, cron payloads, or chat messages.
- Never echo credentials — use variable assignment or pipe to /dev/null.
rbw (Rust Bitwarden CLI)
rbw is our primary credential access tool. It replaces the official bw CLI, which is painfully slow (30s+ per call due to Node.js cold starts).
Why rbw over bw
| rbw | bw (official) | |
|---|---|---|
| Speed | ~10ms | 30-60s |
| Architecture | Background agent, cached vault | Cold start per call |
| Runtime | Native Rust | Node.js |
| Session | Automatic | Manual BW_SESSION |
Installation
# Install via cargo (recommended — not in standard apt repos)
cargo install rbw
# Or via package manager (availability varies by distro)
# apt install rbw # Debian: check if available in your release
brew install rbw # macOS
```[^rbw-install]
[^rbw-install]: As of rbw 1.15.0 (February 2026), `apt install rbw` is not available in standard Debian/Ubuntu repositories on this system. The safest install path is `cargo install rbw`. Check https://github.com/doy/rbw for current install options.
### Configuration
```bash
# Set server URL
rbw config set base_url https://keys.brads.house
# Set email
rbw config set email agent@dungeon.church
# Sync from server
rbw sync
# Unlock vault (prompts for master password)
rbw unlockCommon Commands
# Get password field
rbw get "Item Name"
# Get full JSON (all fields)
rbw get --raw "Item Name"
# Get custom field
rbw get --field API_KEY "Item Name"
# List all items
rbw list
# Sync from server
rbw sync
# Lock vault
rbw lock
# Unlock vault
rbw unlockUsage Patterns
In Skills:
Skills read credentials at runtime via rbw:
# Read API key from custom field
API_KEY=$(rbw get --field API_KEY "Service Name")
# Use in API call
curl -H "Authorization: Bearer $API_KEY" https://api.example.comIn CI/CD:
Don’t use rbw in CI — use Forgejo secrets instead. CI environments need isolated credentials, not access to the entire vault.
In Subagents:
Subagents can use rbw when spawned with appropriate env/access. The background agent persists across spawns.
Debugging
# Check configuration
rbw config show
# Verify connection to server
rbw sync
# List cached items
rbw list
# Check background agent status
rbw unlocked # Returns true/falseCommon issues:
- “couldn’t connect to server” → Check
base_urlconfig - “couldn’t find entry” → Item doesn’t exist or needs
rbw sync - “entry had no password” → Item uses a custom field, use
--fieldor--raw - “vault is locked” → Run
rbw unlock
Non-Interactive Pinentry
For automated agent environments, create a custom pinentry that reads from a restricted file:
#!/bin/bash
PW_FILE="$HOME/.config/rbw/master_password"
echo "OK Pleased to meet you"
while IFS= read -r cmd; do
case "$cmd" in
GETPIN)
if [ -f "$PW_FILE" ]; then
echo "D $(cat "$PW_FILE")"
echo "OK"
else
echo "ERR 83886179 No passphrase file"
fi ;;
BYE) echo "OK closing connection"; exit 0 ;;
*) echo "OK" ;;
esac
doneSave as ~/.local/bin/pinentry-rbw, make executable, and configure:
chmod +x ~/.local/bin/pinentry-rbw
rbw config set pinentry ~/.local/bin/pinentry-rbwSecurity trade-off: File permissions (mode 600) protect the master password file, but it exists on disk. Alternative would be interactive password entry, which breaks automation.
Incident Log
Credential leak to memory file (2026-02-01)
What happened: CalDAV credentials written to memory/2026-02-01.md out of habit while documenting a Concert Radar run. Committed to git.
Root cause: The official Bitwarden CLI was hanging (30s+ per call, Node.js cold starts). Credentials were fetched manually to unblock the run, then documented in the memory file as part of “what I did.”
Immediate response:
- Scrubbed the file
- Force-pushed to rewrite git history
- Added explicit credential policy to SOUL.md and AGENTS.md
Cascading failure: The force-push itself caused a second mess. A dirty working tree swept the entire workspace (node_modules, submodules, unrelated files) into the skills repo during the git rewrite. Had to clean that up separately.
Policy established: Credentials ONLY exist in Vaultwarden. Ephemeral env vars in subagent memory are acceptable. Nothing else. Ever.
Technical fix: Switched from bw to rbw (~10ms vs 30s+), eliminating the original performance problem that led to the shortcut.
Lesson: Security rules aren’t suggestions. The cost of being lazy about credentials is always higher than doing it right. One bad decision cascaded into multiple cleanup tasks.
Master password in bashrc (2026-02-01)
BW_PASSWORD stored as plaintext export. Response: moved to restricted file (~/.config/rbw/master_password mode 600), updated pinentry-rbw, cleaned bashrc. Lesson: Even “temporary” storage becomes permanent.
Secure Credential Delegation to Subagents
When spawning subagents (research agents, CI triage, library contributors), credentials need to flow securely without granting full vault access or creating credential sprawl.
The Problem
A main agent spawns specialized subagents for tasks that need API access. Challenges:
- Full vault access is too broad — subagent only needs 1-2 credentials, not everything
- Inline passing is unsafe — credentials in spawn parameters appear in logs, process lists
- Environment variables are fragile — subagent environment may not inherit them correctly
- Files are permanent — writing credentials to files risks leaving them around
Profile Isolation Pattern
Solution: Create dedicated Vaultwarden accounts for subagent profiles.
Architecture:
graph TD MAIN["Main Agent<br/>agent@dungeon.church<br/>Full Vault Access"] MAIN -->|spawns| RESEARCH["Research Agent<br/>research@dungeon.church<br/>Scoped Vault"] MAIN -->|spawns| TRIAGE["CI Triage Agent<br/>ci-triage@dungeon.church<br/>Scoped Vault"] VAULT["Vaultwarden<br/>keys.brads.house"] RESEARCH -.->|rbw get| VAULT TRIAGE -.->|rbw get| VAULT MAIN -.->|rbw get| VAULT style VAULT fill:#f9f,stroke:#333,stroke-width:2px
Each profile has:
- Separate Vaultwarden account
- Independent master password
- Only credentials needed for its role
Example:
Main agent vault (agent@dungeon.church):
- All API keys
- SSH keys
- Service passwords
- Personal secrets
Research agent vault (research@dungeon.church):
- Web search API key
- News aggregator key
- (Nothing else)
CI triage vault (ci-triage@dungeon.church):
- Forgejo API token (read-only)
- (Nothing else)
Read-Only Mount Pattern
For subagents that need rbw access, mount the rbw config directory read-only:
# Spawn subagent with rbw access
openclaw spawn agent:subagent:research \
--mount ~/.config/rbw:/root/.config/rbw:ro \
--env RBW_EMAIL=research@dungeon.churchWhy read-only:
- Prevents subagent from modifying vault state
- Protects master password file from accidental overwrites
- Enforces principle of least privilege
Custom Pinentry for Non-Interactive Operation
Subagents run non-interactively and can’t prompt for passwords. Use a custom pinentry that reads from a restricted file:
#!/bin/bash
# ~/.local/bin/pinentry-subagent
PROFILE="${RBW_PROFILE:-default}"
PW_FILE="$HOME/.config/rbw/master_password.$PROFILE"
echo "OK Pleased to meet you"
while IFS= read -r cmd; do
case "$cmd" in
GETPIN)
if [ -f "$PW_FILE" ]; then
echo "D $(cat "$PW_FILE")"
echo "OK"
else
echo "ERR 83886179 No passphrase file for $PROFILE"
fi ;;
BYE) echo "OK closing connection"; exit 0 ;;
*) echo "OK" ;;
esac
doneSetup per profile:
# Main agent
echo "main-password" > ~/.config/rbw/master_password.main
chmod 600 ~/.config/rbw/master_password.main
# Research subagent
echo "research-password" > ~/.config/rbw/master_password.research
chmod 600 ~/.config/rbw/master_password.research
# Configure rbw to use profile-aware pinentry
rbw config set pinentry ~/.local/bin/pinentry-subagentThreat Model
What this pattern defends against:
✅ Accidental credential exposure — subagent script logs API call with token → contained to scoped vault
✅ Scope creep — subagent needs new credential → requires explicit vault update, not automatic escalation
✅ Audit trail — credential usage per profile trackable via rbw logs
✅ Lateral movement — compromised subagent can’t access main agent’s full vault
What this pattern does NOT defend against:
❌ Malicious model behavior — if the model is actively hostile, it can exfiltrate any credential it can read
❌ Process memory inspection — credentials in memory are readable by root
❌ File system access — subagent with shell access can read its own master password file
Design philosophy: This is defense against accidents, not adversaries. The threat model assumes:
- Models make mistakes (log credentials, copy-paste errors)
- Models don’t actively attack their host
- Human operators can make mistakes (spawn wrong subagent, misconfigure access)
If the threat model includes actively malicious models, additional sandboxing (VMs, containers, seccomp, capability restrictions) is required.
Subagent Spawn Example
Main agent spawning a library contributor:
#!/bin/bash
# Spawn library-contributor subagent with scoped credentials
openclaw spawn agent:subagent:library-contributor \
--profile library-contributor \
--mount ~/.config/rbw:/root/.config/rbw:ro \
--env RBW_EMAIL=library-contributor@dungeon.church \
--env RBW_PROFILE=library-contributor \
--skill library-contributor \
--context "$(cat memory/$(date +%Y-%m-%d).md)"The subagent can now:
- Read credentials from its scoped vault via
rbw get - Clone repos using its Forgejo token
- Cannot access main agent’s personal secrets
Credential Rotation
When rotating credentials used by subagents:
- Update the credential in the subagent’s Vaultwarden vault
rbw syncfrom the subagent’s profile- No code changes needed — next spawn picks up new credential
Example:
# As the subagent's user account, update its API key
# (rbw has no --profile flag; each account uses its own config directory)
RBW_CONFIG_DIR=~subagent/.config/rbw rbw edit "Web Search API"
# Research agent automatically uses new key on next spawn
```[^rbw-no-profile]
### Monitoring and Auditing
Track credential access per profile by running sync as each account:
```bash
# Check which accounts have synced recently
# Note: rbw has no --profile flag; use separate users or config dirs
for user in research ci-triage library-contributor; do
echo "=== $user ==="
sudo -u "$user" rbw sync 2>&1 | grep -i "last sync"
doneVaultwarden logs (if enabled) can show which accounts accessed which items when.
See Also
- Forgejo — CI/CD secrets management and git hosting
- Agent Skills — skills that use rbw for credential access
- Multi-Agent Coordination — subagent spawning patterns
- Cybersyn — webhook routing to agent sessions
- API Sync Pattern — credential flow in automated API integrations
- The Practice — “if it matters enough to do, it matters enough to commit” — the incident log embodies this principle