Files
secrets/ai-secrets-manager-design.md

21 KiB
Raw Blame History

AI-Native Secret Manager 设计文档

面向 AI AgentCursor / OpenCode和开发者的轻量级 Secret 管理工具。通过 CLI 提供安全的环境变量注入secret 在使用阶段永远不进入 LLM 上下文。

背景与动机

当前痛点

在使用 AI 编码助手Cursor、OpenCode进行服务器运维、部署等操作时经常需要提供敏感凭证

# 当前做法:明文存储 + 聊天中粘贴
~/.../ricnsmart/config.toml     ← 明文 TOMLiCloud 同步
~/.../refining/config.toml      ← 包含 AK/SK、密码、API Key
~/.../*/keys/*.pem              ← SSH 私钥
聊天中直接粘贴 AccessKey          ← 明文进入 LLM context → 发送到云端 API

核心安全问题Secret 明文反复进入 LLM 的上下文窗口,被发送到 Anthropic/OpenAI 等远程服务器,并留存在聊天历史中。

设计目标

  1. 使用阶段 Secret 不进入 LLM 上下文AI Agent 只知道环境变量名($VAR_NAME),不知道值
  2. 跨设备、跨平台macOS / Windows / Linux 多台电脑共享同一套 secrets
  3. 面向 AI AgentAI 通过 CLI 进行 CRUD 和环境变量注入
  4. 人性化管理Web UI 提供可视化的 secret 管理界面
  5. 轻量安全客户端加密CLI + WASMServer 端永远不接触敏感明文

市场定位

工具 定位 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'"

执行过程:

  1. eval 将 secrets 注入当前 shell 环境变量
  2. AI 写命令只引用 $DB_PASSWORD(变量名)
  3. 本地 shell 展开 $DB_PASSWORD 为真实值
  4. 展开后的命令通过 SSH 加密通道传输到远程执行
  5. 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 只存密文和加密后的 DEKServer 被攻破也无法获取明文。

非敏感数据:明文存储

用户选择不加密的 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 keysalt 从 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/ASICOWASP 推荐,支持 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: MVPServer + CLI

  • Cargo workspace 脚手架(crypto + server + cli
  • crypto crateAES-256-GCM + Argon2id + 信封加密
  • PostgreSQL 18 schema + 迁移脚本
  • server crateAxum HTTPS APICRUD + config + 认证)
  • cli crateinit / set / get / list / delete / generate / inject
  • 跨平台 OS Keychain 集成macOS / Windows / Linux
  • 跨平台 inject 命令bash/zsh + PowerShell
  • secrets import config.toml 从现有配置导入

Phase 2: Web UI + 增强

  • web cratecrypto 编译为 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 中使用)

参考