Files
agent-credential-broker/README.md
T
me be87900794 agent-credential-broker: fork of vaultwarden-agent-broker + OpenBao backend
Mechanical rename (package agent_credential_broker; entry points
agent-broker{,-mcp,-plugin}; env AGENT_BROKER_*; paths agent-broker;
agent-browser credential provider 'broker'; MCP server name agent-broker).

New backend 'openbao': stdlib-only HTTP client for OpenBao/Vault KV v2
(approle login or token file/env; metadata-only ref index; lazy re-login
on 401/403; token renewal). bitwarden-cli backend kept as transition.
2026-10-14 13:27:33 -06:00

117 lines
4.7 KiB
Markdown

# agent-credential-broker
Capability layer between AI agents and a credential vault: **agents use
credentials, they can never read them.**
Forked from the Vaultwarden-era `vaultwarden-agent-broker` (same socket
protocol, MCP surface, policy model and adapters; renamed package,
binaries, env prefix and paths). The design rationale for the backend
split lives in `backlog/02_password_manager/broker-design.md`.
## Backends
| Backend | Status | Auth | Notes |
|---|---|---|---|
| `openbao` | target backend | AppRole (`role_id`/`secret_id` files, 0600) or direct token (0600 file / `AGENT_BROKER_OPENBAO_TOKEN`) | KV v2 at `<mount>/data/<prefix>/<ref>`; the ref index is built **only** from `metadata/*`, so unlock never transits plaintext |
| `bitwarden-cli` | transition | `bw` subprocess, API key + master password | unchanged; kept for the migration window, will be dropped with the old vault |
Select with `vault_backend` in the config JSON.
## Item contract (OpenBao)
```
secret/data/agent/<ref> {username, password, totp, uris, notes, ...}
secret/metadata/agent/<ref> custom_metadata:
agent.ref, agent.adapters, agent.allowed_urls,
agent.allowed_hosts, agent.auth_type,
agent.confirmation, agent.*_selector,
agent.ssh_user, agent.ssh_key_path,
agent.cli_profile, agent.insecure_http
label (display name), uris (csv or JSON list)
```
`agent.ref` is the stable, unique, exact-match reference. Every
`agent.*` field and its effect is documented in
`agent_credential_broker/policy.py`. Paths may nest
(`agent/<sub>/<ref>`) — the item id is the path relative to the prefix.
Minimal AppRole policy:
```hcl
path "secret/data/agent/*" { capabilities = ["read"] }
path "secret/metadata/agent/*" { capabilities = ["read", "list"] }
```
## Quick start
```bash
pip3 install --user -e .
# 1. credentials dir + AppRole files (or openbao-token.txt)
install -d -m 700 ~/.config/devin/credentials
# openbao-role-id.txt / openbao-secret-id.txt (600)
# 2. config -- ~/.config/agent-broker/config.json (or AGENT_BROKER_CONFIG)
# {
# "vault_backend": "openbao",
# "openbao_addr": "http://127.0.0.1:18200",
# "openbao_mount": "secret",
# "openbao_prefix": "agent",
# "openbao_role_id_file": "~/.config/devin/credentials/openbao-role-id.txt",
# "openbao_secret_id_file": "~/.config/devin/credentials/openbao-secret-id.txt",
# "openbao_ca_cert": "/path/ca.pem", # optional, https only
# "openbao_tls_skip_verify": false, # dev only
# "openbao_namespace": null, # -> X-Vault-Namespace
# "unlock_ttl_seconds": 3600
# }
# 3. daemon (unix socket only)
agent-broker daemon & # or: make service-install
# 4. unlock -- openbao ignores the password value (non-interactive by
# design, the 0600 files are the credential); enter anything or set
# master_password_file for unattended unlock
agent-broker unlock
# 5. refs only
agent-broker list-refs
agent-broker status
agent-broker lock
```
## Interfaces
| Consumer | Entry point |
|---------------------|--------------------------------------------------------------------|
| Agent (MCP) | `agent-broker-mcp` (stdio, server name `agent-broker`) |
| agent-browser | `--credential-provider broker` -> `agent-broker-plugin` |
| Local human | `agent-broker` CLI (daemon / status / unlock / lock / confirm) |
## Threat model (unchanged)
- Unix socket only (`$XDG_RUNTIME_DIR/agent-broker/broker.sock`, 0600,
dir 0700), same-UID enforced via `SO_PEERCRED`. No TCP listener.
- Decrypted items exist only in daemon RAM, wiped after each use
(`wipe_item`), auto-locked after `unlock_ttl_seconds`.
- The ref index holds metadata only (id/type/name/uris/`agent.*`
fields). On OpenBao it is built from `metadata/*` endpoints — no
plaintext transit at all.
- The agent-facing surface (MCP tools, socket methods) is a static
allowlist; a startup assert forbids any tool named like a secret
reader (`get_*`, `reveal`, `dump`, ...).
- Adapters inject secrets via child env / askpass-on-tmpfs / the
agent-browser plugin — never argv — and every byte that crosses the
boundary is sanitized (`sanitize.py`).
- Same-UID local malware remains out of scope: it can read the 0600
credential files directly. The boundary is the agent, not the host.
## Tests
```bash
make test # or: python -m pytest tests/ -x -q
make lint
```
All tests are offline fixtures (`tests/fakebw.py`, `tests/fake_bao.py`)
— no real vault, no real credentials.