多租户最危险的漏洞,其实是不会报错。

它只会在某个时刻,把 A 客户的智能体配置悄悄返回给 B 客户。不抛异常,不打日志,功能看起来完全正常——只是数据归属错了。

这就是为什么这几天,我们做了第 4、5……9、10 轮跨租户交叉审查。

一、问题的根源:按名字查

系统里有大量历史代码,查智能体用的是名称,查知识库用的是集合名,查配置用的是编号——全都没有带“属于哪个租户”这个条件。

单租户时代,这完全没问题。SaaS 场景下,这意味着:

操作 单租户世界 多租户世界
按名称查智能体 返回唯一结果 返回第一个同名智能体(可能是别家的)
后台写用量记录 正常计费 可能归到默认账户,账单归属全错
知识库语义召回 命中当前库 可能跨租户串读
审计日志 正确归属 可能漏掉租户信息,或写到错误客户名下

数据库表结构加了租户字段,只是定义了“可以按租户隔离”的可能性。把每一条运行时查询都补上“属于哪家”的条件,才是真正的隔离。

这轮收口了:智能体配置、知识库绑定关系、任务执行记录、人工审核记录、语音消息队列、飞书文件入库链路、知识库召回选择、审核回写链路。十轮交叉审查,最终所有查询覆盖归零。 系统进入数据库行级隔离就绪状态。

二、同名智能体,不再互相干扰

之前有个隐患:数据库表结构已经支持按租户区分,但查询和运行时还在按全局名称处理。实际效果是:客户 A 和客户 B 都建了一个叫“采购助手”的智能体,两边会互相影响对方的配置和执行逻辑。

这次把智能体的列表、保存、权限判断、消息分发、执行参数、初始播种全部切成按租户工作。不同客户现在可以有同名智能体,配置完全隔离,互不干扰。

三、一个反直觉的设计:给全局任务留一扇门

建隔离墙的时候发现了一个悖论。后台有一类任务——记忆整合、强化学习聚合、技能合成——天然需要跨租户的全局视角。如果行级隔离全面开启,这些任务会被锁死在单个客户的数据里,什么有效工作都做不了。

解法是单独建一个全局任务角色,带跨租户通行证——独立连接池、最小权限、默认关闭,需要时显式开启。

这不是在破坏隔离,而是显式承认“有些任务就是全局的”,并且把这个例外做成受控的、可审计的,而不是让它藏在某个忘改的查询里偷偷绕过去。

四、飞书:一个客户,一个独立进程

做飞书多租户接入时,先跑了一轮验证实验,结论是:飞书 SDK 的底层长连接机制,在同一进程里并发多个应用是不可行的。这不是我们的架构问题,是 SDK 的硬约束。

所以采用了进程级隔离:每个客户配置自己的飞书应用,消息接入按客户拉起独立子进程,发消息、下载媒体、卡片回复、流式推送都按客户选凭证。

平台从“一个飞书账号服务所有租户”,升级成了“每个客户自带飞书接入”。

五、平台管理员的安全切换

新加了一个后台能力:平台管理员可以切换成任意客户的视角操作。实现上,后端校验只有平台管理员才能使用这个权限,同时审计日志里同时记录真实操作者和代理的客户身份。

这和前端改个下拉框切换展示不同——那只是界面变化,数据还是全局的。这次是真正以目标客户身份发请求,整条数据路径都走目标客户的隔离上下文,是安全版的身份代理,不是展示层的幻觉。

下午,一个客户来问部署情况,顺口问了一句数据会不会跟别家混在一起。得到的回复是:不会。客户追问如何保证,回答是:这几天我们就在做这个——十轮跨租户交叉审查,把所有按名字查的漏洞都堵上了。

这,是第四十二天。

《从0到1:企业级AI项目迭代日记》记录一个企业级 AI 项目从创意、架构到落地的真实过程。不讲神话,只记录进化。


如果你也在做企业 AI 落地,欢迎留言来聊。或者,把这篇转发给一个正在踩同样坑的朋友。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐