内容
# 代理保险箱
> 给你的 AI 代理提供使用秘密的能力,而无需让它看到秘密。
[](https://github.com/mvplab-ai/agent-vault/actions/workflows/ci.yml)
[](LICENSE)
[](https://www.python.org/downloads/)
<p align="center">
<img src="docs/demo.gif" alt="agent-vault demo: init the vault, add a secret at a hidden prompt, list without values, the whole vault encrypted on disk, a tool's output scrubbed of the secret, and an audit log" width="900">
</p>
代理保险箱是为 AI 代理提供的一个本地凭证保险箱。你的代理通过名称(例如 `stripe_key`)引用一个秘密,从不看到其值:保险箱在模型的上下文之外注入真正的凭证,强制执行每个秘密的宿主规则,清除代理读取回的秘密值,并写入一个追加审计日志。
把它想象成一个 **酒店前台** —— 代理是一个客人,可以要求打开门,但从不持有钥匙。
```text
AI 代理 ──"使用保险箱:stripe_key 在 api.stripe.com 上"──▶ 代理保险箱
(仅看到名称) │
├─ 注入真实值
├─ 检查宿主允许列表
├─ 清除响应
└─ 追加到审计日志
```
## 目录
- [为什么](#为什么)
- [功能](#功能)
- [安装](#安装)
- [快速入门](#快速入门)
- [工作原理](#工作原理)
- [代理设置(MCP)](#代理设置-mcp)
- [安全模型](#安全模型)
- [审计日志](#审计日志)
- [环境变量](#环境变量)
- [项目布局](#项目布局)
- [贡献](#贡献)
- [安全策略](#安全策略)
- [许可证](#许可证)
## 为什么
要完成实际工作,代理需要凭证 —— Stripe 密钥、GitHub令牌、数据库密码。将这些秘密粘贴到代理的上下文中是很危险的:
- 秘密最终出现在模型的对话中,该对话被发送到提供商并经常存储在日志和记录中。
- 网页或文档中的提示注入有效载荷可能会欺骗代理提取密钥。
- 一旦秘密出现在聊天记录中,您就无法取消公开它。
代理保险箱将真实值保存在您机器上的一个锁定盒中。代理通过名称操作秘密,并且一个正常的进程 —— 在模型之外 —— 插入真实值,完成操作,清除值从代理看到的任何内容中,并记录它。
## 功能
- **按名称引用。** 代理使用 `stripe_key`;它们从不接收值。
- **两个门,一个核心。** 一个用于代理原生 API 调用的 MCP 服务器和一个用于任何 CLI 工具的 `run` 命令包装器。
- **每个秘密的宿主允许列表。** 一个秘密只能发送到您批准的宿主 —— 一个被劫持的代理无法将您的密钥 `POST` 到 `evil.com`。
- **输出清除。** 秘密值(及其 base64 / percent-encoded 形式)在代理看到它们之前被替换为 `{{vault:NAME}}`。
- **整个保险箱的认证加密。** 值*和*元数据(包括每个秘密的允许列表)被加密并使用 Fernet 进行 MAC 验证,因此磁盘篡改被检测和拒绝。
- **追加审计日志。** 每次注入、请求和拒绝都被记录 —— 名称和宿主,从不值。
- **仅限本地。** 没有云,没有同步,没有遥测。主密钥存储在您的 OS 密钥链中。
## 安装
```bash
uv tool install agent-vault
# 或
pipx install agent-vault
```
对于此存储库克隆的开发:
```bash
uv sync
```
## 快速入门
```bash
# 创建保险箱并将主密钥存储在您的 OS 密钥链中。
agent-vault init
# 添加一个秘密。您将被提示输入带有隐藏输入的值;
# --host 限制秘密可以发送到的宿主。
agent-vault add stripe_key --host api.stripe.com
# 列出秘密名称和元数据 —— 从不值。
agent-vault list
# 运行带有注入为环境变量的秘密的工具。
agent-vault run --env GITHUB_TOKEN -- gh pr list
# 或将秘密插入命令参数中,带有 {{vault:NAME}}
# 占位符。值在执行前被替换,输出被清除。
agent-vault run -- curl -H "Authorization: Bearer {{vault:stripe_key}}" https://api.stripe.com/v1/charges
```
`{{vault:NAME}}` 占位符中的命令被替换为真实值,就在子进程被执行之前,并且所有存储的秘密值在子进程的标准输出和标准错误被返回给您(或您的代理)之前被清除为 `{{vault:NAME}}`。`agent-vault run` 以子进程的退出代码退出。
## 工作原理
代理保险箱是一个 **核心**,通过两个薄前端暴露,因此它涵盖了代理实际做的两件事 —— 调用 API 和运行 shell 工具。
```text
┌───────────────── 代理保险箱 ─────────────────┐
MCP 工具 ───▶│ server.py ┐ │
│ ├─▶ 核心:存储 · 策略 · │
shell ───▶│ runner.py ┘ 清除 · 审计 · 密钥 │
(agent-vault run) │ │
└──────────────────────┬────────────────────────┘
▼
~/.agent-vault/vault.json (一个 Fernet 认证的 blob)
~/.agent-vault/audit.jsonl (仅追加,0600)
OS 密钥链 (主密钥)
```
- **`store`** —— 加载/保存加密保险箱,添加/获取/删除秘密。
- **`keys`** —— 来自 OS 密钥链(或 `AGENT_VAULT_KEY` 用于 CI)的密钥管理。
- **`policy`** —— 宿主允许列表检查(精确 + 单级通配符)。
- **`scrub`** —— 用占位符替换秘密值(及其编码形式)。
- **`audit`** —— 追加审计日志的 JSONL 记录。
## 代理设置(MCP)
代理保险箱附带一个 MCP 服务器,因此代理可以在不处理其值的情况下使用秘密。将它添加到您的代理的 MCP 配置(对于 Claude Code,在 `.mcp.json` 或您的 MCP 设置中):
```json
{"mcpServers": {"agent-vault": {"command": "agent-vault", "args": ["mcp"]}}}
```
服务器公开两个工具:`vault_list_secrets`,它返回秘密名称和元数据(从不值),以及 `vault_http_request`,它通过在服务器端注入命名秘密,强制执行秘密的宿主允许列表,并在返回之前清除响应。
## 安全模型
代理保险箱提供五个保证:
1. **秘密值从不出现在代理可见的输出中。** MCP 响应和 `agent-vault run` 输出被清除:每个存储的值在代理看到它之前被替换为其 `{{vault:NAME}}` 占位符。MCP 响应清除还涵盖值的 base64 和 percent-encoded 形式,因此反射的 `basic` 身份验证或查询凭证被捕获。包装的 `run` 子进程 **不** 在其环境中继承 `AGENT_VAULT_KEY` 主密钥。
2. **MCP HTTP 上的每个秘密宿主允许列表。** 每个秘密都有一个 `allowed_hosts` 列表。注入提示的代理无法将 `stripe_key` 泄漏到 `evil.com` 通过 `vault_http_request` —— 不允许的宿主在请求被发出之前被拒绝,代理提供的路由标头(`Host`,`:authority`,`forwarded`,以及 `X-Forwarded-Host/For/Proto/Port/Server`,`X-Real-IP`,`X-Original-Host`,`X-Host` 系列)被拒绝,因此它们无法重定向允许的请求,并且请求中出现原始秘密值本身(在 `method`/`url`/`headers`/`body` 中)被拒绝。
3. **每个使用和每个拒绝尝试都被审计记录。** 注入、HTTP 请求和策略拒绝都被记录(名称和宿主,从不值)。审计日志是链接安全的,fsync 耐用的,并且容忍损坏的行。
4. **仅限本地,整个保险箱在休息时经过身份验证。** 没有云服务,同步或遥测。整个保险箱文件 —— 秘密值*和*元数据,包括每个秘密的 `allowed_hosts` —— 被加密并使用 Fernet 进行身份验证,因此磁盘篡改被检测和拒绝。主密钥存储在您的 OS 密钥链中(带有 `AGENT_VAULT_KEY` 环境变量后备用于无头使用)。保险箱写入是原子的(临时文件 + `fsync` + 重命名),针对并发写入锁定,并且链接安全。
5. **诚实的限制,记录而不是隐藏:**
- `agent-vault run` **没有宿主策略**,并且按照设计将真实秘密交给子进程(这是子进程如何进行身份验证的)。一旦进程合法地持有秘密,它就可以用它做任何事情 —— 转换它,转发它或存储它 —— 并且没有多少输出清除可以防止这种情况。`run` 将根据您的要求将秘密注入到任何命令中。宿主允许列表仅适用于 MCP HTTP 请求。
- 清除是 **尽力而为** 的,并且按字面意思匹配秘密值(加上它们的 base64/percent-encoded 形式在 MCP 响应上)。如果工具以其他方式转换秘密(哈希,反转,分割),转换后的形式不被识别或清除。`run` 输出清除另外是 **基于行的**:跨行边界的秘密,或包含换行符的秘密,不被清除。
- 替换的 `{{vault:NAME}}` 值出现在 **子命令的 argv** 中,在同一台机器上的 `ps` 和其他进程列表中可见,子进程的整个生命周期中。优先使用环境中的 `--env NAME` 注入。
- 身份验证检测篡改但 **不回滚**。可以覆盖 `vault.json` 的攻击者无法伪造新的策略,但可以恢复 *以前有效的* 整个文件的副本(恢复加固的允许列表或轮换的秘密)。使用文件系统权限保护保险箱目录;反回滚超出了此本地文件设计的范围。
- **机密性假设代理被限制在保险箱的工具中。** 代理保险箱保护秘密值免受只能通过 `vault_http_request` 和 `run` 访问它们的代理的影响。它 **不** 防御具有任意代码执行权限的进程,该进程以您的用户身份运行,可以从您的 OS 密钥链(受 OS 自身提示的约束)或环境中的 `AGENT_VAULT_KEY` 中读取主密钥,并直接解密整个保险箱,没有清除,允许列表或审计记录。如果您将 Agent Vault 与具有 shell 能力的代理配对,保险箱提高了门槛并为您提供了工具介导使用的审计跟踪,但它不是沙盒,无法容纳已经具有相同用户代码执行的代理。
退出代码遵循 Click 的约定:`0` 表示成功,`1` 表示用户或验证错误,`2` 表示用法错误。策略拒绝作为结构化的 MCP 错误出现,而不是退出代码;`agent-vault run` 不更改子命令的退出代码。
发现漏洞?请阅读 [SECURITY.md](SECURITY.md) —— 不要公开安全报告的问题。
## 审计日志
审计日志位于 `~/.agent-vault/audit.jsonl`(或 `$AGENT_VAULT_HOME/audit.jsonl`)。它是仅追加的,以 `0600` 模式写入,并存储为每行一个 JSON 对象。每个条目记录一个时间戳、事件类型(`add`、`remove`、`run_inject`、`http_request` 或 `denied`)、秘密名称、目标(命令或主机)、操作是否允许以及一个简短的详细信息字符串。它从不包含秘密值。
```bash
agent-vault audit --tail 20
```
## 环境变量
- `AGENT_VAULT_HOME` —— 覆盖保险箱目录(默认为 `~/.agent-vault`)。对于每个项目或测试中使用单独的保险箱很有用。
- `AGENT_VAULT_KEY` —— 一个 base64 Fernet 密钥,在没有密钥链的无头或 CI 环境中使用。
## 项目布局
```text
agent_vault/
cli.py # Typer CLI:init / add / list / remove / audit / run / mcp
runner.py # `agent-vault run` —— 占位符 + 环境注入,输出清除
server.py # MCP 服务器:vault_list_secrets,vault_http_request
core/
store.py # 加密保险箱存储(整个文件经过身份验证)
keys.py # 主密钥管理(密钥链 + AGENT_VAULT_KEY 后备)
policy.py # 宿主允许列表检查
scrub.py # 用 {{vault:NAME}} 替换秘密值
audit.py # 仅追加审计日志
tests/ # pytest 套件(单元 + CLI + runner + MCP)
docs/ # 设计规范和实施计划
```
## 贡献
欢迎贡献。从 [CONTRIBUTING.md](CONTRIBUTING.md) 开始,了解开发设置、测试工作流以及(高于通常标准的)安全敏感核心更改的门槛。通过参与,您同意 [行为准则](CODE_OF_CONDUCT.md)。
## 安全策略
请私下报告漏洞,如 [SECURITY.md](SECURITY.md) 中所述,而不是通过公开的问题。
## 许可证
麻省理工学院。参见 [LICENSE](LICENSE)。