AI Agent 调用 API 时如何保护密钥不泄露?Cloak 零信任凭证代理实战
当你给 Claude Code 或 Cursor 配置一个 Stripe 密钥让它帮忙测试支付流程时,你有没有想过一个问题:这个密钥现在在模型手里。
不只是模型——它在模型上下文里、在 API Provider 的日志里、在任何能读到模型输出的人手里。一次提示注入攻击,密钥就能被提取出来远走高飞。
问题的本质:Agent 需要密钥,但模型不该看到它
传统做法很简单:把 API 密钥放到环境变量、.env 文件或配置文件中,Agent 读取后直接使用。这在人机交互时代没问题——人是密钥的最终使用者。
但到了 AI Agent 时代,情况变了:
- 密钥在模型上下文中:Agent 读取
.env后,密钥明文就进了 LLM 的上下文窗口 - 密钥在 Provider 日志中:大多数 LLM API Provider 会记录请求内容用于调试和监控
- 密钥在提示注入的射程内:如果 Agent 访问了一个恶意网站,该网站可能通过提示注入提取上下文中的密钥
- 密钥在工具调用结果中:Agent 调用
read_file(".env")后,密钥以明文形式出现在工具调用参数中
这不是理论问题。通过 Agent 的提示注入提取 API 密钥已有多起实际报告案例。OpenAI、Anthropic 的安全文档也都将凭证管理列为 Agent 安全的核心关注点。
传统方案为什么不够
| 方案 | 风险 |
|---|---|
环境变量 .env | Agent 的 read_file 工具能直接读取,密钥进入模型上下文 |
| 手动输入 | 每次都要人工操作,抵消了自动化的价值 |
| 短期 Token | 仍然需要初始凭证来生成 Token,没有消除根因 |
| 专用 Secret 管理服务 (Vault) | 设计用于服务器间通讯,不对接 MCP 协议 |
我们需要的是一个让 Agent 能使用但不看到密钥的方案——这就是零信任凭证代理的核心思路。
Cloak:本地加密保险库 + MCP 凭证代理
Cloak 是一个 Rust 编写的开源项目(Apache-2.0),核心设计只有一句话:把密钥放在 Agent 永远看不到的地方,Agent 只能通过代理请求来使用它们。
它由三个组件构成:
cloakCLI:你用来添加和管理密钥的命令行工具cloakd守护进程:在后台运行,持有解密后的密钥,执行所有特权操作cloak-mcpMCP 服务端:你的 AI 客户端(Claude Desktop、Claude Code、Cursor 等)通过 MCP 协议连接的桥接层
关键设计原则:MCP 服务端没有任何密钥的读取权限,密钥永远不离开守护进程的进程空间。 MCP 服务端只是一个”类型化的有线格式适配器”——它转发请求,但看不见密钥值。
安装与配置
macOS 和 Linux 上一条命令安装:
brew install cloakward/cloak/cloak cloak setup
cloak setup 会自动安装并启动守护进程、初始化加密保险库,并检测你系统上已安装的 AI 客户端(Claude Desktop、Claude Code、Cursor、Windsurf、Zed、Continue.dev、Codex),自动配置 MCP 连接。
初始化保险库后,系统会生成一个 24 词的恢复种子,需要手写保存到离线位置——这是你丢失密码后恢复密钥的唯一方式。
添加密钥
cloak add OPENAI_API_KEY # 在隐藏提示中输入密钥 cloak add STRIPE_SECRET_KEY # 添加 Stripe 密钥 cloak list # 只显示名称,从不显示值
配置访问策略(默认拒绝)
Cloak 采用 default-deny 策略——每个密钥默认无法访问任何主机。需要明确授权:
cloak allow OPENAI_API_KEY api.openai.com cloak allow STRIPE_SECRET_KEY api.stripe.com cloak policy # 查看每个密钥的访问权限
这些策略即时生效,不需要重启守护进程。
实际使用效果
配置完成后,你的 Agent 就可以使用密钥了——但它只通过名称来引用,真正的密钥值从未进入模型上下文。
以下是一个真实示例。你告诉 Agent:
“test my checkout: create a $50 Stripe PaymentIntent with pm_card_visa and confirm it succeeded.”
Agent 调用 proxy_authenticated_http_request 工具,Cloak 在守护进程中附加 STRIPE_SECRET_KEY,向 Stripe API 发送请求,然后只返回结果给模型:
proxy_authenticated_http_request → POST https://api.stripe.com/v1/payment_intents
Status 200
{
"id": "pi_3ThFkTKCZ65x2cgg0rzmsrj3",
"amount": 5000,
"amount_received": 5000,
"currency": "usd",
"livemode": false
}
一笔 50 美元的扣款成功执行了。但那个授权了这笔交易的 STRIPE_SECRET_KEY(可以退款所有订单并清空账户余额的凭证)从未出现在模型收到的任何数据中。
六种 MCP 工具详解
Cloak 向模型暴露了正好 6 个 MCP 工具,每个都遵循同一个不变量:没有工具返回原始存储的密钥值。
| 工具 | 功能 | 返回值 |
|---|---|---|
list_secret_names | 列出保险库中的密钥名称和元数据 | 仅名称、种类、标签,无值 |
get_secret_metadata | 查询单个密钥的元数据 | 创建时间、标签、版本,无值 |
sign_request | 对请求计算认证签名头 | 计算的认证头信息 |
proxy_authenticated_http_request | 代理带认证的 HTTP 请求 | 上游状态码、响应头和正文 |
mint_short_lived_token | 生成短期派生凭证 | 派生凭证 + 过期时间 |
query_audit | 查询审计日志 | 审计条目,无值 |
其中 proxy_authenticated_http_request 是最常用的工具——它相当于一个”可信代理”,让模型能调用需要认证的 API,但密钥本身留在受信任的守护进程中。
与传统凭证管理方案的区别
Cloak 的设计与传统的 Secret 管理服务(如 HashiCorp Vault、AWS Secrets Manager)有几个关键区别:
- 本地优先:不需要云服务、不需要账户、没有遥测。密钥在本地 SQLite 保险库中,使用 AEAD 加密
- MCP 原生:直接对接 Model Context Protocol,AI 客户端无需额外 SDK
- default-deny 策略:每个密钥默认无法访问任何主机,必须显式授权
- 不可读设计:MCP 服务端没有密钥读取能力,这是架构层面的保证
安全保障措施
Cloak 的威胁模型是诚实且有限的:
- 保护范围:阻止长期密钥从 Agent 上下文中泄漏
- SLSA L3 认证:发布版本经过 cosign 签名和 SLSA L3 认证
- macOS 公证:macOS 版本经过 Apple 公证
- 局限性:不防御被攻陷的主机、root 级别的攻击,也不能使被劫持的 Agent 变得无害(派生凭证仍然会被 Agent 使用)
总结
当你的 AI Agent 开始接触真正的 API —— Stripe、GitHub、AWS —— 密钥管理就从一个”基础设施问题”变成了”安全问题”。Cloak 用一种简洁的架构解决了这个问题:本地加密保险库存储密钥,MCP 代理作为可信中介处理认证请求,模型只通过名称引用密钥而永远看不到值。
对于安全敏感的 AI Agent 工作流,这不应该是一个可选项——它应该是默认配置。
相关链接: