Adjoint-uk/llm-secrets
GitHub: Adjoint-uk/llm-secrets
围绕能力委派构建的 AI agent 专用 secrets 管理器,通过 macaroon 签名 token 实现最小权限、有限时长的凭据委派,架构层面杜绝明文泄漏。
Stars: 1 | Forks: 0
# llm-secrets
[](https://github.com/adjoint-uk/llm-secrets/actions/workflows/ci.yml)
[](LICENSE)
**首个围绕能力委派为 AI 编码 agent 构建的 secrets 管理器。**
你不是将凭据直接*给*你的 AI agent,而是将你身份的一部分*委派*给它——限制在一个 secret、一个 repo、一个 branch、五分钟内——使用一个衰减过的、已签名的 bearer token。agent 只能使用你授予它的内容。它无法扩大授权。它无法提权。
## 问题所在
AI 编码 agent(Claude Code、Cursor、Copilot、Aider)运行时具有你的完全身份。它们可以读取你的 `.env` 文件、你的 shell history、你的 `~/.aws/credentials`,以及你文件系统上的任何内容。你已经在使用的 secrets 管理器并不是为此构建的——它们都有一个 `get` 命令,会将明文打印到 stdout,这在 LLM 环境中是一种泄漏,会被持久化到 transcript、training data 和 provider 日志中。告诉 agent“*请不要这样做*”只是一种请求,而不是安全。
这个利基市场中现有的答案都没有抓住重点:
- **`.env` 文件、`sops`、`doppler run`** — 将原始 secret 输出到 stdout。一旦 agent 读取它们,游戏就结束了。
- **Proxy 守护进程(`agentsecrets`、`agent-secrets`)** — 位于 agent 和 secret 存储之间并尝试进行过滤。这是对结构性问题的行为补丁。
- **荣誉系统** — “*agent,请不要看 .env*”。这不是安全。
## 解决方案 — 能力委派
`llm-secrets` 借鉴了一种将云计算从“带密码的 VM”转变为“带 IAM 的 workload identity”的模式:**请求者不携带任何 secret 材料,只携带一个收窄的授权。**
开发者铸造一个 **macaroon** ——一个已签名的 bearer token——其作用域恰好是 agent 执行此任务所需的内容:
```
# Mint:限定于单个 secret,5分钟,仅在此 branch 上,仅用于 Claude Code
M=$(llms macaroon mint \
--secret db_password \
--ttl 5m \
--branch main \
--agent claude-code)
# 将其交给 agent — 它会继承你身份的一部分,而不是全部
LLM_SECRETS_MACAROON=$M claude
```
agent 现在持有一个在*密码学上*无法被扩大的 token。每个 caveat(`secret`、`branch`、`agent`、`expires_at` 等)都由 HMAC-SHA256 链强制执行。移除或替换 caveat 会使签名失效。agent 能做的*少于*你能做的,绝不会更多——而撤销只需一条命令(`llms revoke-all` 会删除每个 session 的 HMAC root key,在 O(1) 时间内使每个派生的 macaroon 失效)。
这与 Tailscale 和 Fly.io 用于 service auth 的模式相同——只是首次被应用于 AI 编码 agent。
## 构建基础
在 macaroon 层之下,`llm-secrets` 也是一个构建良好的 workload identity 工具:
- 使用 `age`(X25519 + ChaCha20-Poly1305)进行静态**加密存储**。
- **已签名的 session** — 每次操作都锚定在一个防篡改、有明确时间界限的声明上,声明了*谁/在哪里/做什么/何时做*,agent 类型会从环境中自动检测(Claude Code、Cursor、Copilot、Aider、Continue、Windsurf)。
- **Allow-list 策略文件** — 在 repo 根目录放置一个 `.llm-secrets-policy.yaml`;未提及的 key 将被拒绝;deny 规则会优先短路 allow 规则。
- **Append-only 审计日志** — 每次访问 secret 都会记录完整的声明集。
- **MCP server 模式** — `llms mcp` 向兼容 MCP 的 AI agent 暴露 CLI 的一个*严格子集*。**没有任何返回明文的 MCP 工具。** 模型从根本上无法请求明文。
- **没有 `get` 命令。** 明文只能通过 `exec --inject`(进入子进程的环境变量)或 `peek`(故意做有损掩码)离开二进制程序。这是*架构层面的强制*——可以在 50 行源码中验证——而不是一种约定。
macaroon 层是最前沿的。其他一切都是使最前沿变得有意义的基础。
## 安装说明
```
cargo install llm-secrets
```
或者从源码构建:
```
git clone https://github.com/adjoint-uk/llm-secrets.git
cd llm-secrets
cargo build --release
```
## 快速开始
### 一次性设置
```
cargo install llm-secrets
llms init # generates an age identity, creates the encrypted store
llms session-start --ttl 8h # mints your root macaroon
echo "$DB_PASSWORD" | llms set db_password --stdin
```
### 简便方法 — profile(v2.1+)
**profile** 是一个 TOML 配方,它将 secret 和 env 映射分组在一个名称下。一次编辑,到处使用。profile 是 config;它在被使用时生成的 macaroon 仍然是不可伪造的、有明确时间限制的 token。
```
mkdir -p ~/.config/llm-secrets
cat > ~/.config/llm-secrets/profiles.toml <<'EOF'
[db]
secrets = ["db_password"]
ttl = "5m"
[db.env]
DB_PASS = "db_password"
EOF
```
然后:
```
# 你直接:
llms profile exec db -- psql -U admin mydb
# 或者将一个 minted token 交给 agent:
eval "$(llms profile mint db)"
claude # inherits LLM_SECRETS_MACAROON, narrowed to db_password for 5 minutes
```
编辑 profile 是免费的——不需要重新铸造,也不需要重新分发 token。这个 TOML 是非机密的 config:窃取它只会得到一串名称列表,没有任何访问权限。macaroon 是唯一携带权限的东西,并且它总是短暂的且受上下文限制的。
### 手动方法 — 直接使用 CLI / 铸造 macaroon
用于一次性脚本,或者当你不需要 profile 时:
```
# 直接 CLI(你,在自己的 terminal 中):
llms exec --inject DB_PASS=db_password -- psql -U admin mydb
# 手动 mint 一个 macaroon 并将其交给 agent:
M=$(llms macaroon mint \
--secret db_password \
--ttl 5m \
--branch main \
--agent claude-code)
LLM_SECRETS_MACAROON=$M claude
```
在接下来的 5 分钟内,agent 可以通过 `llms exec` 使用 `db_password`,但只能在这个 branch 上,且只能作为 `claude-code`。它无法读取任何其他 secret。它无法延长 TTL。它无法移除 caveat。每次访问都会记录在审计日志中。
当你完成时——或者如果发现任何异常——`llms revoke-all` 会删除 macaroon root key 并在 O(1) 时间内使所有派生的 token 失效。`llms revoke-all --rotate` 还会使用一个新的 age key 对磁盘上的存储进行重新加密。
## 命令
### 核心 (v0.2)
| 命令 | 描述 | 对 LLM 安全 |
|---------|-------------|----------|
| `llms init` | 使用 age 加密初始化 secrets 存储 | 是 |
| `llms list` | 列出 secret key(仅名称) | 是 |
| `llms peek ` | 某个值的掩码预览 | 是 |
| `llms set ` | 通过隐藏输入存储 secret | 是 |
| `llms delete ` | 移除一个 secret | 是 |
| `llms exec --inject VAR=key -- cmd` | 注入 secret 后运行命令 | 是 |
| `llms status` | 检查存储和依赖项 | 是 |
### Agent 身份 (v0.3)
| 命令 | 描述 |
|---------|-------------|
| `llms session-start` | 通过 attestation 启动已认证的 session |
| `llms session-info` | 显示当前 session 身份 |
### 时间强制执行 (v0.4)
| 命令 | 描述 |
|---------|-------------|
| `llms lease --ttl 5m` | 请求有明确时间限制的 secret 访问权限 |
| `llms leases` | 列出活跃的 lease |
| `llms audit` | 查看访问审计日志 |
| `llms revoke-all [--rotate]` | 紧急熔断开关(`--rotate` 会重新加密存储) |
### 能力委派 — macaroon (v2.0)
| 命令 | 描述 |
|---------|-------------|
| `llms macaroon mint --secret --ttl [--branch --agent ]` | 铸造一个委派的 bearer token |
| `llms macaroon inspect [--macaroon ]` | 美化打印 macaroon(纯解析) |
| `llms macaroon verify [--macaroon ] [--key ]` | 根据当前上下文验证签名和 caveat |
### Profile — 配方层 (v2.1)
| 命令 | 描述 |
|---------|-------------|
| `llms profile list` | 列出 `~/.config/llm-secrets/profiles.toml` 中的 profile |
| `llms profile show ` | 显示 secret、env 映射、ttl 和 caveat |
| `llms profile mint [--ttl ]` | 铸造 macaroon 并打印 `export LLM_SECRETS_MACAROON=…` |
| `llms profile exec [--ttl ] -- ` | 铸造 + 执行,并注入该 profile 的 env 变量 |
| `llms exec --profile -- ` | `profile exec` 的别名 |
## 策略文件
在你的 repo 中创建 `.llm-secrets-policy.yaml` 以按身份控制访问权限:
```
secrets:
db_password:
allow:
- repo: adjoint-uk/billing
branch: [main, develop]
user: cptfinch
max_ttl: 10m
deny:
- branch: "*" # deny by default
stripe_key:
allow:
- repo: adjoint-uk/billing
user: cptfinch
max_ttl: 5m
```
如果没有策略文件,所有的 secret 都是可访问的(向后兼容)。
## 对比
| | llm-secrets | agentsecrets | agent-secrets | sops / doppler |
|---|---|---|---|---|
| **强制执行** | 架构层面(无 get 命令) | Proxy 拦截 | Lease 过期 | 无 |
| **身份模型** | Workload attestation | 无 | 无 | 无 |
| **策略** | 声明式 YAML | Domain allowlist | 无 | IAM 策略 |
| **访问模型** | 有明确时间限制的 lease | 持久化 Proxy | Session lease | 静态 |
| **语言** | Rust | Go | Go | Go |
| **依赖项** | 单个二进制文件 | Go + OS keychain | Go + age | 多个 |
## 安全模型
- **静态加密**:age (X25519 + ChaCha20-Poly1305)
- **Session 身份**:带有 attestation 的 Ed25519 临时密钥对
- **无原始输出**:secret 只进入子进程环境,绝不进入 stdout
- **时间限制**:lease 过期,熔断开关撤销所有权限
- **审计跟踪**:记录每次访问的 append-only JSONL 日志
## 文档
- [使用指南](docs/USAGE.md) — 每个命令的示例
- [安全模型](docs/SECURITY-MODEL.md) — 威胁模型 + 架构保证
- [ADR](docs/adr/) — 设计决策,包括为什么没有 `get` 命令
## 状态
**v2.1 — 已发布。** 新增了 **TOML profile** ——将 secret 和 env 映射分组的命名配方——因此从许多工具中使用多个 secret 不再需要 wrapper-script 样板代码。Profile 是 config;它们在被使用时生成的 macaroon 仍然是不可伪造的、有明确时间限制的 token。设计详情见 [ADR 0008](docs/adr/0008-toml-profiles.md),完整说明见 [CHANGELOG](CHANGELOG.md)。
基于 **v2.0**,即 macaroon 合并版本构建:session 和委派的 token 是**相同的原语**——两者都是 macaroon。读取操作总是通过已验证的 token 进行。详见 [ADR 0007](docs/adr/0007-macaroon-merge.md)。
## 贡献
有关开发设置和指南,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 许可证
MIT — 详见 [LICENSE](LICENSE)。
标签:AI代理, Macaroon令牌, Python安全, 可视化界面, 能力委派, 身份与访问控制, 通知系统, 零信任架构