ADR-0001:业务数据与 AI 数据双库分离
- 状态:已采纳
- 日期:2026-09-15
- 决策者:毕设作者
- 相关文档:
docs/05-数据库设计.md、docs/adr/0002-mcp-gateway-as-sole-egress.md、CONTEXT.md
背景
Agent 由大模型驱动,存在两类固有风险:
- 幻觉:模型可能生成看似合理但错误的查询意图;
- 提示注入:Agent 读取的任何不可信内容(故障手册文本、设备日志、工具返回值)都可能夹带指令,诱导 Agent 去读取与其任务无关的数据。
如果 Agent 与业务系统共用一套数据库连接,那么一次成功的提示注入就可以让 Agent 直接查询 user_account(账号与密码哈希)、audit_log(审计日志)、work_order(工单)等敏感表。这类越权读取在日志上表现为"一条合法的 SQL",事后很难与正常查询区分。
决策
将数据存储拆分为两个在账号、进程、凭证上完全分离的库:
- business-db:用户账号、RBAC 权限、设备档案、遥测时序数据、运维工单、安全审计日志、Agent 分析报告。
- ai-db:故障知识库的文档切片与向量索引、Agent 会话短期记忆。
两者的权限边界:
| 角色 | business-db | ai-db |
|---|---|---|
| Web 前端(经业务接口服务) | 完整增删改查 | 无权限 |
| 业务接口服务 | 完整增删改查 | 无权限 |
| MCP 安全网关 | 受限读写(按工具契约的字段白名单) | 只读(向量检索) |
| Agent | 无账号,零权限 | 无账号,零权限(经网关间接读取) |
| 边缘终端 | 无(只经 MQTT 上报,由网关写入) | 无 |
关键点:Agent 侧不部署 ai-db 的客户端,也不持有 ai-db 的连接串。Agent 想检索知识库,调用的是网关的 kb.search 工具,由网关去连 ai-db。
理由
- 边界隔离:Agent 与业务敏感数据之间不存在网络通路,也就不存在"绕过应用层逻辑的越权读取"。攻击面从"模型是否听话"变成"网关的工具契约是否写对"——后者是可测试、可审计的确定性代码。
- 故障域隔离:Agent 或向量库崩溃、被拖垮、需要重建索引时,business-db 与业务服务不受影响。工业场景下"业务系统必须能独立正常工作"是硬要求。
- 可解释的答辩叙事:数据访问路径被收敛成一张有限的工具清单,每个工具的参数与返回字段都可以逐条列举,安全论证可被验证。
备选方案与拒绝理由
| 方案 | 为什么没选 |
|---|---|
| 单库 + 给 Agent 分配只读账号 | 只读只约束读写动作,不约束访问范围。Agent 仍可 SELECT * FROM user_account。 |
| 单库 + 视图(view)隔离 | 视图仍需给 Agent 账号,且视图权限配置散落在 DDL 里,易被后续迁移破坏;返回字段也无法按调用上下文动态裁剪。 |
| 单库 + 行级安全策略(RLS) | PostgreSQL RLS 能限制行,但难以表达"按 Agent 当前任务动态决定返回哪些列";且原型期用 SQLite 无此能力。 |
| Agent 直连 ai-db | ai-db 本身不含敏感数据,风险较低,但会让"Agent 的对外访问必须全部经网关"这条不变量出现例外,例外一多边界就失效。 |
后果
正面
- 越权读取业务数据在架构上不可达,而不是"靠策略拦住"。
- 审计日志的完整性有保障:Agent 无法篡改或删除
audit_log(它没有账号)。 - 后续可以安全地把 ai-db 换成任意向量库,不影响业务侧。
负面 / 代价
- 多一层网关转发,读遥测数据多一次网络跳转与序列化开销。
- 需要为每个数据需求定义 MCP 工具契约,前期设计成本高于"直接写 SQL"。
- 网关成为单点,必须做好进程守护(PM2)与故障降级设计。
落地期演进
原型期采用逻辑隔离(两套独立 SQLite 文件、独立进程、独立账号配置),落地期升级为两台物理实例(甚至不同主机/网段),本 ADR 的结论不变,只是隔离强度提高。