深色模式
出口脱敏与敏感库
8 个 MCP 工具的出口在返回给 AI 之前实时跑脱敏规则;命中即替换为 [REDACTED:xxx] 给 AI,明文 AES-GCM 加密入「敏感库」。
本章讲安全模型视角,UI 操作详见 敏感库 (功能页)。
为什么需要出口脱敏
即使你给 AI 配了「凭据永远不暴露」「策略只读」,AI 还能这么做:
AI 调 sftp_read("/etc/mysql/my.cnf")
→ 拿到文件内容(含 root password=...)
→ 把内容写进它的对话上下文
→ 用户看不到,但 LLM 提供商的日志看到了这种「凭据从合法输出里被复述出去」的攻击面,没有出口脱敏就堵不住。
触发点:8 个 MCP 工具
| 工具 | 输出经过脱敏? |
|---|---|
ssh_exec | ✅ |
ssh_exec_script | ✅ |
ssh_exec_multi | ✅(每台都过) |
sftp_read | ✅ |
db_query | ✅(结果集每行) |
tail_log | ✅ |
redis_get | ✅ |
redis_scan | ✅(值部分) |
sftp_list | ❌ 目录列表无明显敏感性 |
system_info / disk_usage 等 | ❌ 内容结构化无凭据 |
人用通道(终端 / SFTP UI / SQL 工作台)不脱敏——给你自己看的。
处理流程
原始输出(含 "mysql_root_password=Sup3rS3cr3t!")
↓
对每行扫描所有启用的规则
↓ 命中 mysql_root_password 规则
找出匹配的子串("Sup3rS3cr3t!")
↓
明文 → AES-256-GCM 加密 → vault.value_enc
↓
入库 sensitive_vault 表,得 vault_id
↓
原文该子串替换为 "[REDACTED:mysql_root_password]"
↓
返回脱敏后的输出给 AI
↓
审计记录 vault_id 指针规则引擎
每条规则 = (name, pattern, kind, enabled):
yaml
- name: mysql_root_password
pattern: '(?i)password\s*[=:]\s*(?P<secret>\S+)'
kind: credential
enabled: true(?P<secret>...) 捕获组指明要替换的部分(其他部分保留)。
kind 分类
| kind | 用途 |
|---|---|
| credential | 密码 / passphrase |
| token | API Key / Bearer Token |
| private_key | PEM / OpenSSH 私钥 |
| url_with_auth | https://user:pass@host |
| generic | 其它疑似敏感 |
内置规则(约 20+ 条)
| 规则 | 命中 |
|---|---|
generic_password_line | password\s*[=:]\s*\S+ |
mysql_root_password | mysql.*password 上下文 |
aws_access_key | AKIA[0-9A-Z]{16} |
aws_secret_key | 40+ 长 base64 + 上下文 |
private_key_pem | -----BEGIN .* PRIVATE KEY----- 整块 |
ssh_authorized_keys | ssh-rsa AAAA… |
bearer_token | Bearer\s+[A-Za-z0-9._-]+ |
api_key_assign | api[_-]?key\s*[=:]\s*\S+ |
url_with_auth | https?://\S+:\S+@\S+ |
| ... |
完整清单见仓库 src-tauri/ssh-core/src/redaction/builtin_rules.yaml。
用户级 YAML 扩展
<app data>/redaction.yaml:
yaml
rules:
- name: company_internal_token
pattern: 'CMP-[A-Z0-9]{32}'
kind: token
enabled: true
- name: prod_db_string
pattern: 'mysql://prod_admin:(?P<secret>\S+)@'
kind: credential
enabled: true修改后下次加载生效(应用启动 / 敏感库 → 重载规则)。
规则测试器
敏感库 页 → 工具栏 测试规则 按钮:
- 粘贴一段测试文本
- 选规则集
- 实时高亮命中位置 + 命中的规则名
- 调试新规则前必跑
自定义规则的安全建议
写新规则时:
| 建议 | 原因 |
|---|---|
用 (?P<secret>...) 命名捕获 | 仅替换敏感子串,保留上下文 |
加边界(\b、行首 ^) | 防止把 "passwordless" 也命中 |
用 (?i) 大小写不敏感 | 实际日志大小写混杂 |
| 加 unit test(用规则测试器) | 防止下次升级失效 |
误报处理
规则误命中时(例如把无关字符串识别成密码):
- 敏感库行 → 标假阳性 → 状态
已丢弃+ 加入误报样本 - 后续规则优化时可参考误报样本
漏报处理
规则漏掉了真的敏感数据:
- 审计行能看到原始输出(如果未命中规则,明文没入库)
- 不可恢复的损失(理论上 AI 已经看到了)
- 唯一对策:补规则
现实
出口脱敏是纵深防御的最后一层,不能替代「AI 不该看的就别给它看」。
- 该用 Readonly 档位的别用 Trusted
- 敏感配置文件别让 AI sftp_read
- 关键库别让 AI db_query
与凭据库的区别
| 维度 | servers / installed_services | sensitive_vault |
|---|---|---|
| 来源 | 用户主动添加 / 一键装服务 | AI 工具输出被动捕获 |
| 用途 | SSH / DB 登录使用 | 后续 Reveal / 转凭据 / 审计 |
| 删除策略 | 软删保留审计期 | TTL 过期自动清 value_enc |
| 加密 | AES-256-GCM | AES-256-GCM(同 DEK) |
与审计的联动
每条敏感库条目:
- 关联审计行 ID(哪次 AI 调用产生的)
- 关联触发的规则名
- 关联服务器
审计页有「跳转敏感库」按钮看完整上下文。
一键转凭据
敏感库 → 行 转凭据 按钮:
- 把敏感库条目转成正式
installed_services凭据 - 适合场景:"AI 帮我装了一个 MySQL,输出里的密码我现在确认要保留"
过期清理
每条 vault 记录有 TTL:
- 默认 30 天
- Sweeper 每小时检查
expires_at - 到期 →
value_enc清空,状态已过期,hash 留审计
设置 可改全局默认 TTL。
与凭据加密共用 DEK
sensitive_vault.value_enc 用的是同一组 DEK(凭据加密):
- 锁定时无法 Reveal
- 重置凭据库时 vault.value_enc 一起清