AI Agent 落地实践 · 第 1 篇

2026-09-21 · 阿良 · 苏州畅达软件

给 AI Agent 做权限隔离:一次「绕过 RBAC」的架构修复

AI Agent权限设计多租户架构决策fail-closed

我们在做一个面向医疗场景的 AI 助手:医护人员在企业微信里跟它对话,Web 端是管理后台。 产品跑通之后,我们回头把权限体系完整梳理了一遍,结果发现——最危险的那条路径,恰恰是最后加进去的那个入口。

这篇复盘那次架构修复:企业微信入口直连智能体引擎,绕过了整套权限体系。 做 AI Agent 落地时,团队 90% 的精力花在"怎么让它更聪明",剩下 10% 才想起来问一句: "它凭什么有权限做这些事?"


一、问题的起点

我们有一个面向医疗场景的 AI 助手产品:医护人员在企业微信里跟它对话,Web 端是管理后台(管理用户绑定、权限配置、数据查看)。

最初的部署拓扑是这样的:

                Cloudflare
                     │
                  Nginx
        ┌────────────┼─────────────┐
        │            │             │
    /api/*      /wecom-app        /*
    :4021         :18789         :4020
    NestJS     智能体引擎        Next.js
  (RBAC 在此)                (管理后台)

看起来挺清晰。但当我们把权限体系完整梳理一遍之后,发现一个致命问题:

/wecom-app 这条路径直连智能体引擎,完全绕过了 NestJS 的 RBAC 体系和数据范围控制。

具体意味着什么:

  • 一个只应看到本科室患者的账号,通过企微入口可以问出全院数据
  • 智能体根本不知道"现在是谁在问",自然也无法施加任何与身份相关的权限
  • 整套在 Web 侧精心建好的 RBAC + DataScope,在企业微信这条路上一步都没走

这不是某个函数的疏漏,是架构层面少了一层


二、为什么会变成这样

复盘下来,这个漏洞是"自然演进"的产物,不是谁写错了代码。

演进路径

第 1 步  先做 Web 平台,权限体系围绕 Web 请求建立
第 2 步  接入智能体引擎,它自带一套 Runtime 和对话入口
第 3 步  为了减少改造,直接在 Nginx 上给智能体开一条独立路径
第 4 步  把这条路径暴露给企业微信 —— 于是身份在入口处就丢了

每一步都合理,合起来就是个洞。

根因

智能体引擎是一个"独立系统",它有自己的一套身份概念(或者说没有身份概念)。

当请求直接打到它身上时:

  • 它不知道调用者是谁
  • 它不知道调用者能看什么数据
  • 它更不知道"这次工具调用该不该被允许"

而这些恰恰是需要在业务侧才能回答的问题——因为权限规则是由业务定义的,不是由智能体框架定义的。


三、核心思路:把「决策」和「执行」拆开

修复的核心,是引入一个在安全架构里很经典、但在 AI Agent 场景里经常被忽略的划分:

PDP  Policy Decision Point   策略决策点 —— 判断"能不能做"
PEP  Policy Enforcement Point 策略执行点 —— 执行"允许的操作"

我们把这条原则固化成了产品的一条不变量

平台是 PDP;智能体引擎只执行。签名校验 fail-closed。

翻译成架构语言:

职责 绝对不做的事
平台(NestJS) 识别身份、判定数据范围、决定这次调用是否放行
智能体引擎 拿到已授权的指令,执行对话/工具调用 不判断权限(它没有判断依据)
传输层 签名校验,验不过就拒绝 不放行任何未签名请求

修复后的请求路径

企业微信用户发消息
        │
        ▼
   平台(PDP)
   ├─ 解析身份:这个人是谁、属于哪个租户
   ├─ 施加数据范围:他能看哪些数据
   ├─ 判断动作:这次工具调用是否被允许
   └─ 签名后转发 ──────────► 智能体引擎(PEP)
                                  │
                             只执行,不决策

关键点:智能体引擎永远拿不到"未经过平台判定的原始请求"。

这样一来:

  • 权限逻辑只有一份(在平台),不会出现"Web 侧严、企微侧松"
  • 智能体引擎即使被替换(换成另一个框架),权限体系不用重做
  • 新增入口(比如钉钉、飞书)时,只要走平台,权限自动生效

四、数据层的隔离:为什么不共用一张库

权限收口只解决了"谁能发起调用"。但还有一个问题:智能体引擎自己的运行态数据,该和业务数据放一起吗?

我们的结论是:独立数据库,但部署在同一个 PostgreSQL 实例上。

PostgreSQL 实例(同一台服务器)
├── agent_core        ← 智能体引擎独占
│   ├── 工作流定义
│   ├── Agent 状态
│   └── 工具调用记录
│
└── followup_web      ← 业务平台独占
    ├── 业务主数据
    ├── 任务与记录
    ├── 组织与人员
    └── 审计日志

交互方式:API 协议,绝不跨库直读对方表

为什么不共用同一个 database

四条理由,按重要性排序:

① Schema 主权冲突

智能体引擎升级时可能自动执行 migration。如果和应用表混在同一个 database,升级动作有可能误删或冲突——这是"数据被第三方工具改坏"的典型场景

② 安全边界必须能差异化

业务数据需要字段级加密(AES-256),而智能体的运行态数据(工作流定义、执行日志)不需要这个级别。混在一起,加密策略就无法差异化落地。

③ 可替换性

未来如果要更换智能体引擎(不同框架的能力差异很大),只需要切换 agent_core 这一个库,业务数据完全不动。

④ 备份策略不同

数据类型 备份要求
业务数据(随访记录等) 30 天增量备份 + 日志归档
智能体运行态数据 7 天即可

为什么不干脆拆成两台服务器

这是个阶段性问题,不是原则问题:

  1. 早期阶段两台 PG 服务器是过度投入,运维复杂度不划算
  2. 同一实例内的不同 database 已经有权限边界——agent_core 的数据库用户无权访问业务库的表
  3. 两个系统之间没有跨库事务需求(通过 API 异步通信)

换句话说:隔离的粒度先做到"库级",等业务量上来再做到"实例级"。但"永不跨库直读"这条从第一天就守住。

唯一的数据交换通道

业务平台 → POST /api/mcp/call          → 智能体(发起操作)
智能体   → POST /api/webhooks/callback → 业务平台(回写结果)

业务平台永远不 SELECT 智能体库的表,智能体也永远不直接 INSERT 业务库的记录。

代价是清晰的:无法用一条 SQL 做跨域 JOIN。但这就是设计意图——要跨域,就得走接口,走接口就会留下权限校验和审计记录。


五、传输层:签名必须 fail-closed

那条"唯一通道"如果签名能被绕过,前面所有隔离都白做。

我们的做法是两条:

① 签名校验 fail-closed

签名缺失、无效、或密钥未配置 → 一律拒绝,不存在"校验失败就放行"的降级路径。

这一条看似显然,但在工程实践中特别容易破功:开发期为了方便会加"如果没配密钥就跳过校验",然后这个分支就跟着上线了。所以我们在生产环境做了硬约束:

生产环境(NODE_ENV=production)下,
若签名密钥缺失、为空、或等于文档化的开发默认值 → 拒绝签发与校验

② 签名密钥按租户隔离

多租户场景下,所有租户共用一个签名密钥是个隐患:一个租户的密钥泄漏,等于所有租户的通道都可伪造。

所以每个租户可以配置专用签名密钥,验签方根据请求头里的租户标识选择对应密钥:

每租户专用密钥(缺失时回退全局密钥)
  → 签名方与验签方共用同一套解析逻辑
  → 密钥只存在环境变量里,不入库

这里有个细节值得单独说:"密钥不入库"是一条明令禁止项。 密钥一旦落库,数据库备份、只读副本、审计查询……每一个环节都成了泄漏面。


六、顺带修掉的几个"同源问题"

排查这个漏洞时,我们发现它其实是一类问题的表现。同一批修完的还有:

问题 修法
查询条件字段可被客户端任意指定 字段白名单,白名单外一律拒绝
生产可能回退到开发用密钥 生产环境硬性 fail-closed
敏感操作没有审计记录 登录成功/失败/改密/刷新/登出全部落审计事件
部署样例里带真实密钥 样例文件只允许占位值,进 CI 校验
边缘代理暴露版本信息 server_tokens off + HSTS + 安全响应头基线

它们共享同一个设计原则:

安全相关的分支,失败时要走向"拒绝",不能走向"放行"。


七、沉淀成四条可复用的判断

做完这次修复,我们把经验收敛成四条判断。它们和具体技术栈无关,可以直接迁移到任何 Agent 项目:

① Agent 不该拥有权限判断能力

它是个执行者。权限判断必须发生在业务侧,因为只有业务侧才知道"谁能看什么"。

如果你发现权限逻辑被写进了 Agent 的 prompt 或工具定义里——那就是走错了方向。

② 每个新入口都要回答"身份从哪来"

每加一个入口(Web、企微、钉钉、API),都要能回答:

这个入口进来的请求,身份在哪里被解析?
数据范围在哪里被施加?

回答不出来的入口,就是新的越权通道。

③ 隔离粒度可以分阶段,但"永不直读"不能妥协

库级隔离 → 实例级隔离可以随业务量升级;但"两个系统之间永不跨库直读"必须从第一天守住——因为这条一旦破,后面所有权限设计都会漏。

④ 安全分支要 fail-closed,且要在生产环境强制

"没配密钥就跳过校验"这类便利分支,必须在生产环境被硬性拒绝,不能只靠开发者自觉。


八、一句话总结

AI Agent 的能力上限由模型决定,但它的权限边界必须由架构决定。

大多数关于 Agent 的讨论都在讲"怎么让它做更多事",而落地到真实业务系统时,更难也更重要的其实是——怎么确保它做不了不该做的事。


作者:阿良 · 苏州畅达软件 —— 后续会写多租户数据隔离与 Agent 操作审计的具体实现,有类似场景欢迎交流。

准备开始您的数字化项目?

留下需求,我们会在一个工作日内与您联系。

联系我们