agent-vault 0.1.0

Zero-trust credential manager for AI agents
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# agent-vault

[![CI](https://github.com/ewimsatt/agent-vault/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/ewimsatt/agent-vault/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

**Zero-trust credential manager for AI agents.**

Give your AI agents access to API keys, database passwords, and other secrets — without ever exposing the raw credentials in plaintext. Secrets are encrypted locally using [age encryption](https://age-encryption.org/) and synced via Git. No server, no SaaS, no third-party trust. The Git repo is just an encrypted blob store.

## Why agent-vault?

AI agents need credentials to call APIs, connect to databases, and interact with services. But giving an agent a raw API key is risky:

- **Keys leak into logs, prompts, and error messages.** Even well-designed agents can accidentally expose credentials.
- **Revoking access is hard.** If you share one key across agents, you can't cut off a single agent without rotating for everyone.
- **There's no audit trail.** You don't know which agent used which key, or when.

agent-vault solves this with a simple model:

1. **Each agent gets its own key.** Grant and revoke access per-agent, per-secret-group.
2. **Secrets stay encrypted at rest.** The Git repo only ever contains ciphertext. Decryption happens in memory, never touching disk.
3. **The owner controls everything.** One master key to rule them all — add agents, manage access, recover from compromise.
4. **Git is the sync layer.** Push, pull, branch, merge — use the Git workflows you already know.

## Quick Start

### Install

```bash
# From source
cargo install --path .

# Or from crates.io
cargo install agent-vault

# macOS (Homebrew)
brew install ewimsatt/tap/agent-vault
```

### Set up a vault

```bash
cd my-project
agent-vault init          # Creates .agent-vault/, generates owner key
agent-vault add-agent ci  # Creates a key for the "ci" agent

# Store a secret
agent-vault set stripe/api-key "sk_live_abc123"

# Grant the agent access to the "stripe" group
agent-vault grant ci stripe

# Retrieve as owner
agent-vault get stripe/api-key

# Retrieve as agent
agent-vault get stripe/api-key --key ~/.agent-vault/agents/ci.key
```

That's it. The agent can now decrypt `stripe/api-key` using its own key. If you revoke access later, the secret is re-encrypted without the agent's key — they lose access immediately.

---

## How It Works

### Trust Model

There are two types of keys:

| Key | Location | Purpose |
|-----|----------|---------|
| **Owner key** | `~/.agent-vault/owner.key` | Master key. Can decrypt everything. Never committed to Git. **Back this up.** |
| **Agent keys** | `~/.agent-vault/agents/<name>.key` | Per-agent keys. Can only decrypt secrets in groups they've been granted access to. |

Each agent also has an **escrow file** committed to the repo — the agent's private key encrypted to the owner's public key. If an agent key is lost, the owner can recover it.

### Encryption

Every secret is encrypted using [age](https://age-encryption.org/) multi-recipient encryption. The recipient list includes:

- The **owner's** public key (always)
- Every **agent** that has been granted access to the secret's group

When you `grant` or `revoke` access, agent-vault re-encrypts all affected secrets with the updated recipient list. When you `remove-agent`, all their secrets are re-encrypted without their key.

### Repository Layout

All vault data lives under `.agent-vault/` in your Git repo:

```
.agent-vault/
├── config.yaml              # Vault configuration
├── owner.pub                # Owner's public key (plaintext, safe to commit)
├── manifest.yaml            # Access control: agents → groups → secrets
├── agents/
│   └── my-agent/
│       ├── public.key       # Agent's public key
│       └── private.key.escrow  # Agent's private key, encrypted to owner
└── secrets/
    └── stripe/
        ├── api-key.enc      # Encrypted secret
        └── api-key.meta     # Plaintext metadata (name, group, timestamps)
```

### Security Guarantees

- Decrypted material is **never written to disk** — memory or stdout only
- A **pre-commit hook** blocks commits containing age private keys, PEM keys, RSA keys, EC keys, and OpenSSH keys
- `.gitignore` blocks `*.key`, `*.pem`, `**/private.*` (but allows `*.escrow`)
- Revocation **re-encrypts** affected secrets and warns about credential rotation
- Private key files get `chmod 600` on Unix

---

## CLI Reference

### Vault Management

| Command | Description |
|---------|-------------|
| `agent-vault init [dir]` | Initialize vault, generate owner keypair, install pre-commit hook |
| `agent-vault check [--json]` | Audit for expiring credentials, orphaned secrets, manifest inconsistencies |

### Agent Management

| Command | Description |
|---------|-------------|
| `agent-vault add-agent <name>` | Create agent keypair with escrow backup |
| `agent-vault remove-agent <name>` | Remove agent, re-encrypt all their secrets, warn about rotation |
| `agent-vault list-agents [--json]` | List agents and their group memberships |
| `agent-vault recover-agent <name>` | Generate new keypair from escrow, re-encrypt secrets |
| `agent-vault restore-agent <name> --to <path>` | Restore original key from escrow to a file |

### Secret Management

| Command | Description |
|---------|-------------|
| `agent-vault set <path> <value> [--agents a,b]` | Encrypt and store a secret |
| `agent-vault set <path> --from-file <file>` | Store secret from file |
| `agent-vault get <path> [--key <path>]` | Pull latest, decrypt, output to stdout |
| `agent-vault list [--group <name>] [--json]` | List secrets with metadata |

### Access Control

| Command | Description |
|---------|-------------|
| `agent-vault grant <agent> <group>` | Grant access, re-encrypt group secrets for the agent |
| `agent-vault revoke <agent> <group>` | Revoke access, re-encrypt group secrets without the agent |

### Utilities

| Command | Description |
|---------|-------------|
| `agent-vault completions <shell>` | Generate shell completions (bash, zsh, fish, powershell) |

### Key Resolution

The `get` command resolves identity keys in this order:

1. `--key <path>` flag
2. `AGENT_VAULT_KEY` environment variable (file path or raw `AGE-SECRET-KEY-...` string)
3. `~/.agent-vault/owner.key` (default)

---

## SDKs

### Python SDK

Read-only SDK for agents to retrieve secrets programmatically. Uses [pyrage](https://pypi.org/project/pyrage/) for age decryption.

```bash
pip install agent-vault
```

```python
from agent_vault import Vault

vault = Vault(
    repo_path="/path/to/vault",
    key_path="~/.agent-vault/agents/my-agent.key",
)

# Get a secret
api_key = vault.get("stripe/api-key")

# List secrets
for secret in vault.list_secrets(group="stripe"):
    print(f"{secret.name}  expires={secret.expires}")

# Works as a context manager
with Vault(repo_path="/path/to/vault") as vault:
    db_url = vault.get("postgres/conn")
```

Supports local paths and remote Git URLs:

```python
vault = Vault(repo_path="https://github.com/myorg/secrets.git")
```

### Node.js / TypeScript SDK

Read-only SDK using the [age-encryption](https://www.npmjs.com/package/age-encryption) package.

```bash
npm install agent-vault
```

```typescript
import { Vault } from "agent-vault";

const vault = new Vault({
  repoPath: "/path/to/vault",
  keyPath: "~/.agent-vault/agents/my-agent.key",
});

const apiKey = await vault.get("stripe/api-key");

const secrets = vault.listSecrets("stripe");
```

### Environment Variable

All SDKs (and the CLI) support the `AGENT_VAULT_KEY` environment variable:

```bash
export AGENT_VAULT_KEY="AGE-SECRET-KEY-1QFNZ..."

# Now no key path needed
python -c "from agent_vault import Vault; print(Vault('/path/to/vault').get('stripe/api-key'))"
```

---

## MCP Server

agent-vault ships an [MCP](https://modelcontextprotocol.io/) server so any MCP-compatible AI agent (Claude, etc.) can retrieve credentials through the standard tool-use protocol. The server holds the agent's private key in memory — the agent process never touches key material.

```bash
pip install 'agent-vault[mcp]'
agent-vault-mcp --repo /path/to/vault --key ~/.agent-vault/agents/my-agent.key
```

### Tools Exposed

| Tool | Description |
|------|-------------|
| `agent_vault_get(secret)` | Decrypt and return a secret |
| `agent_vault_list(group?)` | List available secrets with metadata |

### Claude Desktop Configuration

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "agent-vault": {
      "command": "agent-vault-mcp",
      "args": ["--repo", "/path/to/vault", "--key", "/path/to/agent.key"]
    }
  }
}
```

### Claude Code Configuration

Add to your `.mcp.json` project config:

```json
{
  "mcpServers": {
    "agent-vault": {
      "command": "agent-vault-mcp",
      "args": ["--repo", "/path/to/vault", "--key", "/path/to/agent.key"]
    }
  }
}
```

---

## For AI Agents: Getting Started

If you're an AI agent (or a developer setting up an agent), here's what to do after installation:

### 1. Get your key

Your operator should have run `agent-vault add-agent <your-name>` and given you a key file. Store it at:

```
~/.agent-vault/agents/<your-name>.key
```

Or set the environment variable:

```bash
export AGENT_VAULT_KEY="AGE-SECRET-KEY-1..."
```

### 2. Access secrets

**Python:**
```python
from agent_vault import Vault

vault = Vault(repo_path="/path/to/vault")
api_key = vault.get("stripe/api-key")
```

**TypeScript:**
```typescript
import { Vault } from "agent-vault";
const vault = new Vault({ repoPath: "/path/to/vault" });
const apiKey = await vault.get("stripe/api-key");
```

**CLI:**
```bash
agent-vault get stripe/api-key
```

**MCP (no code needed):** If your operator configured the MCP server, just call the `agent_vault_get` tool with the secret path.

### 3. Handle errors

| Error | Meaning | What to do |
|-------|---------|------------|
| `SecretNotFoundError` | Secret path doesn't exist | Check the path, ask operator to create the secret |
| `NotAuthorizedError` | Your key can't decrypt this secret | Ask operator to grant you access to the group |
| `VaultNotFoundError` | No vault at the repo path, or no key configured | Check repo path and key configuration |

### 4. Best practices

- **Never log or print decrypted secrets.** Access them, use them, discard them.
- **Don't cache secrets in files.** Re-read from the vault each time.
- **Use `auto_pull=True` (the default)** to get the latest secrets on each access.
- **Handle `NotAuthorizedError` gracefully.** Your access may be revoked at any time.

---

## Deployment Examples

### Docker

```dockerfile
FROM python:3.12-slim
RUN pip install agent-vault
COPY . /app
WORKDIR /app
CMD ["python", "agent.py"]
```

```bash
docker run \
  -e AGENT_VAULT_KEY="$(cat ~/.agent-vault/agents/my-agent.key)" \
  -v /path/to/vault:/vault:ro \
  my-agent
```

```python
# agent.py
from agent_vault import Vault

vault = Vault(repo_path="/vault")
api_key = vault.get("stripe/api-key")
```

### GitHub Actions

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install agent-vault
      - run: |
          SECRET=$(python -c "
            from agent_vault import Vault
            v = Vault(repo_path='.', auto_pull=False)
            print(v.get('deploy/token'))
          ")
          echo "::add-mask::$SECRET"
          echo "DEPLOY_TOKEN=$SECRET" >> "$GITHUB_ENV"
        env:
          AGENT_VAULT_KEY: ${{ secrets.AGENT_VAULT_KEY }}
```

### CI/CD with CLI

```bash
export AGENT_VAULT_KEY="$VAULT_KEY"
DB_PASSWORD=$(agent-vault get postgres/password)
API_KEY=$(agent-vault get stripe/api-key)
./deploy.sh
```

---

## Development

### Building from source

```bash
# Rust CLI
cargo build
cargo test

# Python SDK
cd python-sdk
pip install -e '.[dev]'
pytest -v

# Node.js SDK
cd node-sdk
npm install
npm run build
```

### Running all tests

```bash
# Build the CLI first (Python tests use it)
cargo build

# Run everything
cargo test && (cd python-sdk && pytest -v)
```

### Shell Completions

```bash
# Bash
agent-vault completions bash > ~/.local/share/bash-completion/completions/agent-vault

# Zsh
agent-vault completions zsh > ~/.zfunc/_agent-vault

# Fish
agent-vault completions fish > ~/.config/fish/completions/agent-vault.fish
```

---

## License

[MIT](LICENSE)