yottayoshida/llm-key-ring
GitHub: yottayoshida/llm-key-ring
一款基于 macOS Keychain 的 LLM API 密钥安全管理 CLI 工具,通过加密存储、TTY 守护、内存清零等多重防护机制,解决明文 .env 文件和 AI 代理窃取密钥的安全隐患。
Stars: 13 | Forks: 1
# LLM Key Ring (`lkr`)
[](https://github.com/yottayoshida/llm-key-ring/actions/workflows/ci.yml)
[](https://crates.io/crates/lkr-cli)
[](https://docs.rs/lkr-core)
一个通过 macOS Keychain 安全管理 LLM API Key 的 CLI 工具。告别 `.env` 文件中的明文密钥。
```
$ lkr set openai:prod
Enter API key for openai:prod: ****
Stored openai:prod (kind: runtime)
$ lkr get openai:prod
Copied to clipboard (auto-clears in 30s)
sk-p...3xYz (runtime)
$ lkr exec -- python script.py # Keys injected as env vars (safest)
$ lkr gen .env.example -o .env # Generate config from template
```
## 为什么?
| 问题 | `lkr` 解决方案 |
|---------|---------------|
| API Key 存放在 `.env` 文件中 | 加密存储在 macOS Keychain |
| 密钥通过 Shell 历史记录泄露 | 交互式提示输入(绝不使用 CLI 参数) |
| AI 代理通过管道提取密钥 | TTY 守护拦截所有非 TTY 的 `get` 访问 (v0.2.0) |
| 密钥在剪贴板中残留 | 30 秒后自动清除 |
| 密钥在进程内存中残留 | `zeroize` 在 drop 时擦除内存 |
## 安装
```
# 从源码
git clone https://github.com/yottayoshida/llm-key-ring.git
cd llm-key-ring
cargo install --path crates/lkr-cli
```
需要 Rust 1.85+ 和 macOS(使用原生 Keychain)。
## 使用方法
### 存储密钥
```
lkr set openai:prod # Interactive prompt (recommended)
pbpaste | lkr set openai:prod # From clipboard (avoids hidden-input confusion)
```
密钥名称使用 `provider:label` 格式(例如 `openai:prod`、`anthropic:main`)。
### 获取密钥
```
lkr get openai:prod # Masked display + clipboard (30s auto-clear)
lkr get openai:prod --show # Show raw value in terminal
lkr get openai:prod --json # JSON output (masked value; safe in non-TTY)
lkr get openai:prod --plain # Raw value only (blocked in non-interactive env)
lkr get openai:prod --force-plain # Raw value even in non-interactive (use with caution)
```
### 列出密钥
```
lkr list # Runtime keys only
lkr list --all # Include admin keys
lkr list --json # JSON output
```
### 以环境变量形式运行命令(推荐)
```
lkr exec -- python script.py # Inject all runtime keys
lkr exec -k openai:prod -- curl ... # Inject specific keys only
lkr exec -k openai:prod -k anthropic:main -- node app.js
lkr exec --verbose -- python script.py # Show injected env var names
```
密钥会映射到常规的环境变量名(例如 `openai:prod` → `OPENAI_API_KEY`)并注入到子进程中。只有 `runtime` 密钥会被注入 —— `admin` 密钥在设计上被排除。**密钥永远不会出现在 stdout、文件或剪贴板中** —— 这是将 secrets 传递给程序的最安全方式。尽可能优先使用 `exec` 而不是 `gen`。
### 从模板生成配置
当目标程序需要配置文件且无法接受环境变量时,使用 `gen`。
在非交互式环境中,需要使用 `--force` (v0.2.0):
```
lkr gen .env.example # → .env (auto-derived output path)
lkr gen .env.example -o .env.local # Explicit output path
lkr gen config.json.template # Works with JSON templates too
```
**`.env.example` 格式** —— 密钥通过精确的环境变量名自动解析:
```
OPENAI_API_KEY=your-key-here # ← resolved from openai:* in Keychain
ANTHROPIC_API_KEY= # ← resolved from anthropic:*
```
**JSON 模板格式** —— 使用显式的 `{{lkr:provider:label}}` 占位符:
```
{
"openai_key": "{{lkr:openai:prod}}",
"anthropic_key": "{{lkr:anthropic:main}}"
}
```
生成的文件以 `0600` 权限写入。如果输出文件不在 `.gitignore` 中,会显示警告。
当同一 provider 存在多个运行时密钥时(例如 `openai:prod` 和 `openai:stg`),将使用按字母排序的第一个密钥。警告会列出备选项。使用 `{{lkr:provider:label}}` 占位符进行显式控制。
### 迁移密钥 (v0.1.x → v0.2.0)
```
lkr migrate --dry-run # Preview what would change
lkr migrate # Apply: adds sync protection + lock protection
```
为所有现有密钥添加 `kSecAttrSynchronizable: false`(无 iCloud 同步)和 `kSecAttrAccessibleWhenUnlocked`(设备锁定时阻止访问)。可以安全地多次运行(幂等)。
### 删除密钥
```
lkr rm openai:prod # With confirmation prompt
lkr rm openai:prod --force # Skip confirmation
```
### 检查 API 使用成本
```
lkr usage openai # Single provider
lkr usage # All providers with admin keys
lkr usage --json # JSON output
```
需要使用 `--kind admin` 注册的 **Admin API key**:
```
lkr set openai:admin --kind admin
```
### 全局标志
```
lkr --json # JSON output (all commands)
lkr --help
lkr --version
```
## 支持的 Provider
`lkr gen` 自动解析这些 provider 的密钥:
| Provider | 环境变量 | 密钥名示例 |
|----------|-------------|-----------------|
| OpenAI | `OPENAI_API_KEY` | `openai:prod` |
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic:main` |
| Google | `GOOGLE_API_KEY` | `google:dev` |
| Mistral | `MISTRAL_API_KEY` | `mistral:api` |
| Cohere | `COHERE_API_KEY` | `cohere:prod` |
| Groq | `GROQ_API_KEY` | `groq:prod` |
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek:api` |
| xAI | `XAI_API_KEY` | `xai:prod` |
| 更多... | | |
任何 `provider:label` 名称都适用于 `set`/`get`/`rm`。上面的 provider 列表用于 `lkr gen` 中的自动解析。
## 安全性
### 威胁模型
查看 [docs/SECURITY.md](docs/SECURITY.md) 了解完整的威胁模型。主要保护措施:
| 威胁 | 缓解措施 |
|--------|-----------|
| 明文密钥文件 | 密钥存储在 macOS Keychain(静态加密) |
| Shell 历史记录暴露 | `lkr set` 从提示符读取,绝不从 CLI 参数读取 |
| 剪贴板残留 | 通过 SHA-256 哈希比较在 30 秒后自动清除 |
| 终端背后窥视 | 默认遮盖显示 (`sk-p...3xYz`) |
| **AI 代理窃取** | **TTY 守护拦截所有非 TTY 的 `get`/`gen` 访问 (v0.2.0)** |
| 内存取证 | `zeroize::Zeroizing` 在 drop 时将内存清零 |
| 模板中的 Admin 密钥 | `lkr gen` 只解析 `runtime` 密钥 |
| 意外 git 提交 | 对生成的文件进行 `.gitignore` 覆盖检查 |
### Agent IDE 攻击防护 (v0.2.0)
AI 编程助手(Cursor、Copilot、Claude Code 等)可能通过提示词注入被诱骗运行窃取 secrets 的命令。`lkr` v0.2.0 全面阻止了此类攻击:
```
# 非 TTY:ALL 默认阻止访问 (退出代码 2)
echo | lkr get openai:prod # ← Blocked
echo | lkr get openai:prod --show # ← Blocked
echo | lkr get openai:prod --plain # ← Blocked
# 非 TTY 中允许的替代方案:
echo | lkr get openai:prod --json # ← Pass (masked value only)
echo | lkr get openai:prod --force-plain # ← Pass (explicit override)
# gen 在非 TTY 中也被阻止:
echo | lkr gen .env.example # ← Blocked
echo | lkr gen .env.example --force # ← Pass (explicit override)
# exec 始终有效 (最安全的路径 — 密钥永远不会出现在 stdout 中):
lkr exec -- python script.py
```
## 架构
```
llm-key-ring/
├── crates/
│ ├── lkr-core/ # Library: KeyStore trait, Keychain, templates, usage API
│ └── lkr-cli/ # Binary: clap CLI (set/get/list/rm/gen/usage/exec/migrate)
├── docs/
│ └── SECURITY.md # Threat model
├── LICENSE-MIT
└── LICENSE-APACHE
```
所有业务逻辑位于 `lkr-core`。CLI 只是一个薄封装。
### 平台支持
目前仅支持 macOS(通过 `security-framework` / `security-framework-sys` 直接 FFI 使用原生 Keychain)。`KeyStore` trait 抽象旨在为未来支持其他后端(Linux `libsecret`、Windows Credential Manager)。
### Keychain 存储
| 字段 | 值 |
|-------|-------|
| Service | `com.llm-key-ring` |
| Account | `{provider}:{label}` |
| Password | `{"value":"sk-...","kind":"runtime"}` |
| Synchronizable | `false` (v0.2.0+, 无 iCloud 同步) |
| Accessible | `WhenUnlocked` (v0.2.0+) |
## 从 v0.1.x 升级
### v0.2.0 中的破坏性变更
1. **`lkr get` 在非交互式环境中被阻止** (退出码 2)。
- 之前只有 `--plain`/`--show` 被阻止;现在裸的 `get` 也会被阻止。
- 使用 `--json`(遮盖值)或 `--force-plain`(原始值)来覆盖此行为。
- 建议:切换到 `lkr exec` 用于自动化。
2. **`lkr gen` 在非交互式环境中被阻止** (退出码 2)。
- 在使用 `lkr gen` 的 CI/CD 脚本中添加 `--force`。
3. **`lkr exec` stderr 输出变更**:
- TTY:默认静默(以前是详细模式)。使用 `--verbose` 查看注入的密钥。
- 非 TTY:1 行警告。
### 迁移步骤
```
# 1. 更新 lkr
cargo install --path crates/lkr-cli --force
# 2. 迁移现有密钥 (添加 iCloud 同步保护 + 锁定保护)
lkr migrate --dry-run # Preview
lkr migrate # Apply
# 3. 更新 CI/CD 脚本 (如适用)
# 之前:lkr get openai:prod --plain | ...
# 之后:lkr exec -- ...
# 或者:lkr get openai:prod --force-plain | ...
# 或者:lkr gen .env.example --force
```
## 退出码
| 代码 | 含义 |
|------|---------|
| 0 | 成功 |
| 1 | 常规错误(未找到密钥、Keychain 错误等) |
| 2 | TTY 守护违规(非交互式环境被阻止) |
## 路线图
| 版本 | 主题 | 主要变更 |
|---------|-------|-------------|
| **v0.2.1** (当前) | 安全加固 | Keychain 属性加固、全面的 TTY 守护、`lkr migrate`、ACL 调查 |
| **v0.3.0** | DX 改进 | `lkr init` (.env 导入)、shell 补全、Homebrew tap |
| v0.4.0 | MCP Server | IDE 集成以实现安全密钥访问 |
## 开发
```
# 构建
cargo build
# 测试
cargo test
# Clippy
cargo clippy -- -D warnings
# 不安装直接运行
cargo run --bin lkr -- list
```
## 许可证
根据您的选择,双重许可于 [MIT](LICENSE-MIT) 或 [Apache-2.0](LICENSE-APACHE)。
标签:AI安全, Chat Copilot, DLP, JSONLines, LLM API, macOS Keychain, Rust, SecOps, StruQ, TTY检测, 云安全架构, 内存安全, 剪贴板安全, 加密存储, 可视化界面, 威胁情报, 安全, 密钥轮换, 开发者工具, 敏感数据保护, 数据防泄露, 环境变量, 网络流量审计, 超时处理, 进程注入防护, 通知系统, 通知系统, 通知系统, 零信任