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 NameFieldsUsed By
Calendarusername, passwordConcert Radar, morning briefing
LastFM API KeyAPI_KEYConcert Radar
Tautulli APIAPI_KEYMorning briefing
Midjourney API KeyAPI_KEYDiary image generation
Forgejo API Token (agent)passwordCI/CD automation
LAN Static Web Host SSH Key for Clawdprivate 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

  1. Credentials only exist in Vaultwarden. Access via rbw at runtime.
  2. Master password lives in a single file (mode 600) — nowhere else.
  3. Never write credentials to memory files, logs, git commits, config files, bashrc, environment exports, shell output, cron payloads, or chat messages.
  4. 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

rbwbw (official)
Speed~10ms30-60s
ArchitectureBackground agent, cached vaultCold start per call
RuntimeNative RustNode.js
SessionAutomaticManual 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 unlock

Common 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 unlock

Usage 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.com

In 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/false

Common issues:

  • “couldn’t connect to server” → Check base_url config
  • “couldn’t find entry” → Item doesn’t exist or needs rbw sync
  • “entry had no password” → Item uses a custom field, use --field or --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
done

Save as ~/.local/bin/pinentry-rbw, make executable, and configure:

chmod +x ~/.local/bin/pinentry-rbw
rbw config set pinentry ~/.local/bin/pinentry-rbw

Security 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:

  1. Scrubbed the file
  2. Force-pushed to rewrite git history
  3. 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:

  1. Full vault access is too broad — subagent only needs 1-2 credentials, not everything
  2. Inline passing is unsafe — credentials in spawn parameters appear in logs, process lists
  3. Environment variables are fragile — subagent environment may not inherit them correctly
  4. 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.church

Why 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
done

Setup 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-subagent

Threat 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:

  1. Update the credential in the subagent’s Vaultwarden vault
  2. rbw sync from the subagent’s profile
  3. 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"
done

Vaultwarden 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