21 KiB
AI-Native Secret Manager 设计文档
面向 AI Agent(Cursor / OpenCode)和开发者的轻量级 Secret 管理工具。通过 CLI 提供安全的环境变量注入,secret 在使用阶段永远不进入 LLM 上下文。
背景与动机
当前痛点
在使用 AI 编码助手(Cursor、OpenCode)进行服务器运维、部署等操作时,经常需要提供敏感凭证:
# 当前做法:明文存储 + 聊天中粘贴
~/.../ricnsmart/config.toml ← 明文 TOML,iCloud 同步
~/.../refining/config.toml ← 包含 AK/SK、密码、API Key
~/.../*/keys/*.pem ← SSH 私钥
聊天中直接粘贴 AccessKey ← 明文进入 LLM context → 发送到云端 API
核心安全问题:Secret 明文反复进入 LLM 的上下文窗口,被发送到 Anthropic/OpenAI 等远程服务器,并留存在聊天历史中。
设计目标
- 使用阶段 Secret 不进入 LLM 上下文:AI Agent 只知道环境变量名(
$VAR_NAME),不知道值 - 跨设备、跨平台:macOS / Windows / Linux 多台电脑共享同一套 secrets
- 面向 AI Agent:AI 通过 CLI 进行 CRUD 和环境变量注入
- 人性化管理:Web UI 提供可视化的 secret 管理界面
- 轻量安全:客户端加密(CLI + WASM),Server 端永远不接触敏感明文
市场定位
| 工具 | 定位 | AI Agent 支持 | 客户端加密 | 自托管 |
|---|---|---|---|---|
| HashiCorp Vault | 企业级 Secret 管理 | 无 | 否 | 是 |
| Infisical | 开发团队 Secret 管理 | 无 | 有 | 是 |
| 1Password MCP | 消费级密码管理 + AI | 有(SaaS) | 有 | 否 |
| SOPS / age | 文件加密 | 无 | 是 | - |
| 本项目 | AI Agent 的 Secret 注入 | 核心功能 | 是 | 是 |
架构
整体架构
Rust crypto crate(加密核心)
┌──────────────────────────┐
│ AES-256-GCM / Argon2id │
│ DEK 管理 / 信封加密 │
└──────┬──────────┬─────────┘
│ │
编译为 native │ │ 编译为 WASM
│ │
┌────────────────────┐ │ ┌────┴───────────────┐
│ CLI (Rust 跨平台) │◄──────┘ │ Web UI (浏览器) │
│ + OS Keychain │ │ + WASM 加密模块 │
│ macOS / Win / Linux│ │ + wasm-bindgen │
└─────────┬──────────┘ └─────────┬──────────┘
│ HTTPS │ HTTPS
└──────────────┬───────────────────┘
│
┌─────────┴──────────┐
│ Remote Server │
│ (Rust / Axum) │
│ ├─ HTTPS API │
│ └─ 托管 Web UI + │
│ WASM 静态文件 │
└─────────┬──────────┘
│
┌─────────┴──────────┐
│ PostgreSQL 18 │
└────────────────────┘
组件
| 组件 | 职责 | 技术选型 |
|---|---|---|
| crypto | 加密核心(AES-256-GCM + Argon2id + 信封加密) | Rust crate,编译为 native + wasm32 |
| server | HTTPS API、数据库 CRUD、认证、托管静态文件 | Rust / Axum |
| cli | 客户端加解密、OS Keychain、环境变量注入、CRUD | Rust / clap |
| web (WASM) | 浏览器端加解密绑定 | Rust / wasm-bindgen |
| web-ui | 可视化 Secret 管理界面 | HTML / JS / CSS |
| PostgreSQL 18 | 持久化存储(密文 + 明文元数据) | 云端 PG |
Cargo Workspace 结构
secrets/
├── crates/
│ ├── crypto/ ← 加密核心(编译目标: native + wasm32)
│ ├── server/ ← Axum HTTPS Server
│ ├── cli/ ← CLI 客户端(链接 crypto native)
│ └── web/ ← WASM 绑定(wasm-bindgen,链接 crypto wasm)
├── web-ui/ ← 前端代码(调用 WASM 模块)
└── Cargo.toml ← workspace 根配置
核心设计
1. 安全模型:分层暴露策略
Secret 在不同生命周期阶段有不同的暴露等级:
| 阶段 | Secret 暴露情况 | 说明 |
|---|---|---|
| 创建 | 可能经过 LLM | 用户通过聊天告知 AI,或 AI 执行 secrets set 命令。一次性暴露,可接受 |
| 使用 | 不经过 LLM | AI 只引用 $VAR_NAME,本地 shell 展开后执行。反复使用,始终安全 |
| generate | 完全不经过 LLM | CLI 本地生成随机密码 → 本地加密 → 密文发送 Server。值不经过 AI |
核心价值:一个 secret 可能被使用几十上百次,只有创建时可能暴露一次,之后的所有使用都通过环境变量引用,安全。
工作流示例:
# 创建阶段(一次性,secret 可能经过 LLM)
AI Agent: secrets set ricnsmart/aliyun.ak "LTAI5t..."
# 使用阶段(反复使用,secret 不经过 LLM)
AI Agent: eval $(secrets inject ricnsmart --export)
AI Agent: ssh -i "$RICNSMART_SSH_KEY" "$RICNSMART_SSH_USER@$RICNSMART_HOST_PRIMARY" "..."
↑ AI 只写变量名,本地 shell 展开后执行
# 生成阶段(secret 完全不经过 LLM)
AI Agent: secrets generate ricnsmart/db.password --length 32
→ CLI 本地生成 → 本地加密 → 密文存 Server
→ 返回: "已生成并存储,环境变量名: RICNSMART_DB_PASSWORD"
SSH 远程命令场景
环境变量注入天然支持 SSH 远程命令场景:
eval $(secrets inject ricnsmart --export)
ssh user@server "mysql -u root -p'$DB_PASSWORD' -e 'CREATE DATABASE app'"
执行过程:
eval将 secrets 注入当前 shell 环境变量- AI 写命令只引用
$DB_PASSWORD(变量名) - 本地 shell 展开
$DB_PASSWORD为真实值 - 展开后的命令通过 SSH 加密通道传输到远程执行
- AI 始终只看到变量名,从不看到实际值
2. 加密模型:客户端加密 + 混合存储
采用混合加密模型:用户决定每条 secret 是否加密。敏感数据客户端加密后存密文,非敏感数据明文存储以支持搜索。
敏感数据:信封加密
secret_value
→ AES-256-GCM(DEK) → 密文 → 存入 PostgreSQL (encrypted_value)
DEK (Data Encryption Key)
→ AES-256-GCM(Master Key) → 加密 DEK → 存入 PostgreSQL (encrypted_dek)
Master Key
→ Argon2id(password, salt) → 派生 → 存入 OS Keychain
每条加密的 secret 使用独立的 DEK,限制单个密钥泄露的影响范围。
PostgreSQL 只存密文和加密后的 DEK,Server 被攻破也无法获取明文。
非敏感数据:明文存储
用户选择不加密的 secret(如 URL、用户名等)以明文存储,支持服务端搜索和索引。
元数据:JSONB 灵活字段
每条 secret 附带 JSONB 元数据字段,始终为明文,用于描述、标签、备注等。支持 GIN 索引高效搜索。
3. 跨设备 Master Key 同步
采用密码派生方案,跨平台支持:
# 新设备初始化(只需一次)
secrets init --server "https://secrets.example.com"
# 输入 master password
# → 从 Server 获取 Argon2id salt
# → 本地派生 Master Key
# → 存入 OS Keychain
# 之后自动从 Keychain 获取,无需再次输入
所有设备使用相同密码,派生出相同的 Master Key。Argon2id salt 存储在 Server 端 PostgreSQL 中。
| 平台 | Keychain 实现 |
|---|---|
| macOS | Keychain Services |
| Windows | Credential Manager |
| Linux | libsecret (Secret Service API) |
Rust keyring crate 统一封装三个平台。
4. Web UI 浏览器端加密(Rust WASM)
Web UI 通过 Rust 编译为 WebAssembly 实现浏览器端加密,与 CLI 共享完全相同的加密逻辑:
用户打开 Web UI
→ 输入 master password
→ WASM 模块中 Argon2id 派生 master key(salt 从 Server API 获取)
→ master key 仅存于浏览器内存
创建敏感 secret:
→ WASM 加密 → 密文通过 HTTPS API 发送到 Server
查看敏感 secret:
→ Server 返回密文 → WASM 解密 → 浏览器中显示
页面关闭:
→ master key 从内存消失
Server 端全程不接触明文,无论是 CLI 还是 Web UI 访问,安全模型一致。
使用 WASM 而非 Web Crypto API 的原因:
- Web Crypto API 不支持 Argon2id(只有 PBKDF2)
- WASM 直接复用 CLI 的 Rust 加密代码,保证一致性
- Rust 内存安全 + 成熟加密 crate,优于 JS 实现
5. 环境变量命名规则
project: ricnsmart, key: aliyun.access_key_id
→ 环境变量: RICNSMART_ALIYUN_ACCESS_KEY_ID
project: refining, key: grafana.password
→ 环境变量: REFINING_GRAFANA_PASSWORD
规则: upper(project) + "_" + upper(key.replace(".", "_"))
Server API
认证
API Token 认证。首次 secrets init 时生成 Token,存入客户端 OS Keychain。所有 API 请求通过 Authorization: Bearer <token> 头认证。
接口列表
# 配置
GET /api/config/:key 获取配置(如 argon2_salt)
POST /api/config 设置配置
# 项目
GET /api/projects 列出所有项目
POST /api/projects 创建项目
DELETE /api/projects/:id 删除项目
# Secrets
GET /api/secrets 列出 secrets(支持过滤,不含加密值)
?project=ricnsmart
&pattern=ssh.*
&kind=file
POST /api/secrets 创建 secret(接收密文或明文)
GET /api/secrets/:id 获取 secret 详情(含加密值密文,客户端解密)
PUT /api/secrets/:id 更新 secret
DELETE /api/secrets/:id 删除 secret
# 静态文件
GET / Web UI 入口
GET /assets/* Web UI 静态资源 + WASM 文件
请求/响应示例
创建加密 secret:
POST /api/secrets
{
"project": "ricnsmart",
"key": "aliyun.access_key_id",
"is_encrypted": true,
"kind": "text",
"encrypted_value": "<base64 密文>",
"encrypted_dek": "<base64 加密后的 DEK>",
"nonce": "<base64>",
"dek_nonce": "<base64>",
"metadata": {
"description": "阿里云主账号 AccessKey",
"tags": ["cloud", "aliyun", "production"],
"env_var": "RICNSMART_ALIYUN_ACCESS_KEY_ID"
}
}
创建非加密 secret:
POST /api/secrets
{
"project": "ricnsmart",
"key": "gitea.url",
"is_encrypted": false,
"kind": "text",
"plaintext_value": "https://gitea.refining.dev",
"metadata": {
"description": "Gitea 服务地址",
"tags": ["service", "gitea"]
}
}
列出 secrets 响应(不含加密值):
[
{
"id": "uuid-...",
"project": "ricnsmart",
"key": "aliyun.access_key_id",
"is_encrypted": true,
"kind": "text",
"metadata": {
"description": "阿里云主账号 AccessKey",
"tags": ["cloud", "aliyun", "production"]
},
"created_at": "2025-03-06T10:00:00Z",
"updated_at": "2025-03-06T10:00:00Z"
}
]
数据模型
PostgreSQL 18 Schema
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT UNIQUE NOT NULL, -- "ricnsmart", "refining"
description TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE secrets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
key TEXT NOT NULL, -- "aliyun.access_key_id"
-- 敏感数据(客户端加密后存储)
encrypted_value BYTEA, -- AES-256-GCM 密文
encrypted_dek BYTEA, -- 加密后的 DEK
nonce BYTEA, -- GCM nonce
dek_nonce BYTEA, -- DEK 加密的 nonce
-- 非敏感数据(明文存储,可搜索)
plaintext_value TEXT, -- 用户选择不加密时存储明文
-- 灵活元数据(明文 JSONB,可搜索)
metadata JSONB DEFAULT '{}', -- 描述、标签、备注等
is_encrypted BOOLEAN NOT NULL DEFAULT true,
kind TEXT NOT NULL DEFAULT 'text', -- "text", "ssh-key", "json", "file"
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
UNIQUE(project_id, key)
);
-- JSONB GIN 索引,支持高效搜索
CREATE INDEX idx_secrets_metadata ON secrets USING GIN (metadata);
-- 按 kind 过滤
CREATE INDEX idx_secrets_kind ON secrets (kind);
-- 全局配置(Argon2id salt 等)
CREATE TABLE config (
key TEXT PRIMARY KEY,
value BYTEA NOT NULL
);
-- 审计日志
CREATE TABLE audit_log (
id BIGSERIAL PRIMARY KEY,
secret_id UUID REFERENCES secrets(id) ON DELETE SET NULL,
action TEXT NOT NULL, -- "read", "write", "delete", "inject", "generate"
device TEXT, -- 设备标识
detail TEXT, -- 额外信息
created_at TIMESTAMPTZ DEFAULT now()
);
从现有 TOML 配置迁移的映射
# ricnsmart/config.toml
[[servers]] → ricnsmart/server.tianchu-primary (kind=json, encrypted)
hostname = "..." encrypted_value: {...}
public_ip = "8.153.204.96" metadata: {"description": "天储主服务器"}
[services.gitea] → ricnsmart/gitea.url (kind=text, 不加密)
url = "https://gitea.refining.dev" plaintext_value: "https://gitea.refining.dev"
ricnsmart/gitea.token (kind=text, 加密)
token = "92a627..." encrypted_value: <密文>
# SSH 密钥 → ricnsmart/ssh-key.ricnsmart-sh (kind=file, 加密)
~/.../keys/ricnsmart-sh.pem encrypted_value: <密文>
metadata: {"original_path": "ricnsmart-sh.pem"}
CLI 命令设计
CLI 是 AI Agent 和人共用的主要接口。
# ===== 初始化 =====
secrets init --server "https://secrets.example.com"
# 输入 master password → 从 Server 获取 salt → Argon2id 派生 Master Key
# → Master Key + API Token 存入 OS Keychain
# ===== 项目管理 =====
secrets project list # 列出所有项目
secrets project create ricnsmart # 创建项目
secrets project delete ricnsmart # 删除项目(需确认)
# ===== Secret CRUD =====
secrets set ricnsmart/aliyun.ak "LTAI5t..." # 创建(默认加密)
secrets set ricnsmart/gitea.url "https://..." --no-encrypt # 创建(不加密)
secrets set ricnsmart/ssh.key --file ~/.ssh/ricnsmart.pem # 从文件创建(加密)
secrets set ricnsmart/server.primary --json '{"host":...}' # JSON 类型(加密)
secrets get ricnsmart/aliyun.ak # 获取值(本地解密,输出到 stdout)
secrets list # 列出所有(不显示值)
secrets list ricnsmart/ # 按项目过滤
secrets list --pattern "ssh.*" # 模糊匹配 key
secrets list --kind file # 按类型过滤
secrets delete ricnsmart/aliyun.ak # 删除
# ===== 生成随机密码 =====
secrets generate ricnsmart/db.password --length 32 # 默认字符集
secrets generate ricnsmart/api.key --length 64 --charset hex # 指定字符集
# → 本地生成 → 本地加密 → 密文存 Server
# → 输出: "已生成并存储,环境变量名: RICNSMART_DB_PASSWORD"
# ===== 环境变量注入 =====
# macOS / Linux (bash/zsh)
eval $(secrets inject ricnsmart --export)
# Windows PowerShell
secrets inject ricnsmart --export-ps | Invoke-Expression
# 通用方式(所有平台,启动子进程)
secrets inject ricnsmart -- ./app
# 选择性注入
secrets inject ricnsmart --keys "aliyun.*,ssh.*" --export
# ===== 导入 =====
secrets import config.toml --project ricnsmart # 从 TOML 导入
secrets import config.toml --project ricnsmart --dry-run # 预览
安全注意事项
Secret 生命周期
创建: CLI set / generate → 本地加密 → HTTPS → Server 存密文到 PG
Web UI 输入 → WASM 加密 → HTTPS → Server 存密文到 PG
读取: CLI inject → HTTPS 拉取密文 → 本地解密 → 注入环境变量
Web UI 查看 → HTTPS 拉取密文 → WASM 解密 → 浏览器显示
使用: AI 写 shell 命令引用 $VAR → 本地 shell 展开为真实值 → 执行
清理: 进程退出后环境变量消失,Web UI 关闭后 master key 消失
威胁模型
| 威胁 | 防御措施 |
|---|---|
| PostgreSQL 被攻破 | 客户端加密,PG 只有密文,Server 不持有 Master Key |
| Server 被攻破 | 敏感数据为客户端密文,非敏感数据暴露但不含凭证 |
| 设备丢失 | Master Key 在 OS Keychain(需要设备密码/生物识别) |
| LLM 提供商窥探 | 使用阶段 secret 不进入 LLM context,只有变量名 |
| 命令输出泄露 secret | 可选输出脱敏(Phase 2,替换已知 secret 值为 ***) |
| Master Password 弱 | Argon2id 高计算成本(推荐:m=64MB, t=3, p=4) |
| 传输层攻击 | HTTPS/TLS 加密传输 |
| WASM 模块被篡改 | Server 托管 WASM,可通过 SRI (Subresource Integrity) 校验 |
不防御的场景
- 设备上的 root 权限攻击者(可直接读取进程环境变量或内存)
- 用户主动将 secret 粘贴到聊天中(行为问题,非技术问题)
- AI 通过
echo $VAR读取环境变量值并返回到 LLM 上下文(已知弱点,Phase 2 输出脱敏可缓解)
技术选型
| 组件 | 选择 | 理由 |
|---|---|---|
| 语言 | Rust | 单二进制分发、加密库成熟、性能好、编译为 native + WASM |
| 加密 | aes-gcm crate |
AEAD,业界标准 |
| 密钥派生 | argon2 crate |
抗 GPU/ASIC,OWASP 推荐,支持 WASM 编译 |
| OS Keychain | keyring crate |
跨平台(macOS Keychain / Windows Credential Manager / Linux libsecret) |
| 数据库 | PostgreSQL 18 + sqlx crate |
async、编译期 SQL 检查、JSONB 原生支持 |
| HTTP Server | axum crate |
Rust 生态主流、tokio 驱动、tower 中间件 |
| WASM 绑定 | wasm-bindgen + wasm-pack |
Rust → WASM 标准工具链 |
| CLI 框架 | clap crate |
Rust 生态标准 |
| 序列化 | serde + serde_json |
JSON 处理标准库 |
开发计划
Phase 1: MVP(Server + CLI)
- Cargo workspace 脚手架(
crypto+server+cli) cryptocrate:AES-256-GCM + Argon2id + 信封加密- PostgreSQL 18 schema + 迁移脚本
servercrate:Axum HTTPS API(CRUD + config + 认证)clicrate:init / set / get / list / delete / generate / inject- 跨平台 OS Keychain 集成(macOS / Windows / Linux)
- 跨平台 inject 命令(bash/zsh + PowerShell)
secrets import config.toml从现有配置导入
Phase 2: Web UI + 增强
webcrate:crypto 编译为 WASM + wasm-bindgen 绑定- Web UI 前端:CRUD 管理界面 + WASM 浏览器端加解密
- Server 托管 Web UI + WASM 静态文件
- TOTP 支持(存储 TOTP seed,生成一次性验证码)
- 输出脱敏(可选,替换已知 secret 值为
***) - 审计日志
Phase 3: 扩展
- MCP Server(可选 AI 增强,stdio 模式)
- 模板渲染(
secrets template config.tpl > config.yaml) - 团队共享(多用户 + RBAC)
- Secret 轮换提醒
- CI/CD 集成(Gitea Actions 中使用)
参考
- MCP 协议规范
- Infisical — 开源 Secret Manager
- age — 文件加密工具
- SOPS — Mozilla 的加密文件编辑器
- Rust
aes-gcm— AEAD 加密 - Rust
argon2— 密码哈希 - Rust
keyring— 跨平台 Keychain - Rust
axum— HTTP Server 框架 - wasm-bindgen — Rust WASM 绑定
- wasm-pack — Rust WASM 打包工具