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.
117 lines
4.7 KiB
Markdown
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.
|