让 AI 设计权限模型,为什么角色资源动作说不清就一定漏

2026-07-29

数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。

权限漏成筛子,绝大多数不是模型写错了 if 判断,而是你给它的输入里根本没有一个能唯一确定答案的权限模型。 很多人复盘时把锅扣在”AI 不懂业务""模型幻觉”上,然后去加长描述、换更强的模型、上更严的代码评审。这三条都缓解不了根因。根因是:角色、资源、动作这三个维度里,只要有一个维度的边界你自己说不清楚,模型就必须猜;猜出来的东西看着完整、跑得通、测试还是绿的,漏的那条路径要等到线上有人踩才现形。

我的判断是,权限这件事上 AI 的能力边界特别清晰:它能把你定好的规则铺满代码,能帮你穷举组合、找出没覆盖到的格子,但它定不了”这个角色到底能不能删别人的东西”。后者是业务决策,不是代码问题。把这条线划清楚,后面的活才好干。

本篇和站内另外两篇的分工说一下,免得你翻重复的内容:Agent 最小权限设计讲的是给 Agent 本身发多大的通行证(它能读哪些目录、能不能执行 shell),Agent 权限太大惹的祸讲的是权限已经放太宽之后怎么止血;本篇讲的是你让 AI 去写的那套业务权限模型——你的系统里用户之间的权限,AI 参与设计和实现时会在哪里漏、怎么提前拦住。三篇的对象不一样:前两篇的主体是 AI 自己,本篇的主体是你的产品。

一、先分因:漏权限的四种成因长得很像

线上暴出一个越权,第一反应往往是”补一个判断”。补完下周还漏。原因是这四种成因的现象几乎一样,都是”某人做了不该做的事”,但处置动作完全不同。

成因 A:维度缺失。 你的描述里只有角色和动作,没有资源实例。“编辑可以删除文章”——删谁的文章?自己的、本部门的、全站的?模型会挑一个最省事的实现,通常是”只要角色是编辑就放行”。这类漏洞的特征是:同角色的两个账号互相能操作对方的数据。

成因 B:动作粒度太粗。 “管理订单”这四个字底下藏着查看、改地址、改金额、退款、取消、导出六七个动作,风险完全不一个量级。模型会把它们打成一个权限点。特征是:一个低风险动作放行后,高风险动作跟着放行了。

成因 C:资源层级没穿透。 资源本身有归属链——租户下有项目,项目下有文档,文档下有评论。你校验了文档,没校验文档属不属于当前租户。特征是:换个 ID 就能访问别家数据,接口返回 200 而不是 403。

成因 D:入口不齐。 模型在你指的那个接口上写对了,但同一份数据还有导出接口、搜索接口、批量接口、消息推送、后台管理页。特征是:主路径怎么试都对,旁路一试就穿。这一类在 AI 参与的改动里格外容易出,机制上不难理解:你贴给它的往往只有当前那个文件,它看不见的入口就不会去改,而它给出的回复读起来像是把这件事整体做完了。

先把现象归到成因,再动手。归错了就是白改。

二、判别表:从现象定位到处置

现象大概率成因怎么验证处置动作
同角色两个账号能互改对方数据维度缺失,只判角色没判归属建两个同角色测试账号,用 A 的令牌带 B 的资源 ID 打接口,看是否返回 200在权限判断里补”资源归属于谁”的入参,规则改成角色加归属的二元判断
只读账号能触发写操作动作粒度太粗,读写打成一个权限点拿只读账号的令牌直接打写接口(POST/PUT/DELETE),只要有一条不是被拒,读写就是同一个权限点按动作拆分权限点,写操作单独立项,默认不继承读权限
换一个 ID 就能看到别的租户数据资源层级没穿透用租户 A 的令牌请求租户 B 的资源 ID,返回 200 且响应体里是 B 的数据,就是穿透缺失在数据访问层强制带租户条件,而不是在业务层逐处判断
主接口拦住了,导出/搜索能拿到入口不齐全局搜索该数据表或实体名的所有引用点,逐个看有没有过权限判断把权限判断下沉到统一入口,禁止业务层各写各的
升级角色后旧权限没收回角色是叠加而非替换改一次角色,然后打印该用户实际生效的权限点集合做前后对比明确角色是覆盖式还是叠加式,写进规则文档并让实现按这一条来
接口返回 401 但业务上应是 403认证与授权混在一起用一个已登录但无权的账号请求,看返回码分开处理:身份不明返回 401,身份明确但无权返回 403,不要用 401 兜底
前端隐藏了按钮,直接调接口能成只做了展示层控制用命令行直接打接口,绕过前端后端补齐判断,前端隐藏只当体验优化,不算权限

这张表的用法是:先复现,再对号,最后才写代码。跳过验证那一列直接改代码,是这类问题反复出现的主要原因。

有一处容易验歪,单独说一下:跨租户请求到底该回 403 还是 404,没有唯一正确答案。回 403 的好处是语义诚实、排障快;回 404 的好处是不泄露”这个 ID 存在”,能挡住靠遍历 ID 探测别家数据量的行为。**真正的漏洞信号只有一个——返回 200 并且响应体里是别人的数据。**看到 404 别急着当成 bug 去改成 403,先确认是”查不到”还是”故意藏”。要注意的是同一套接口里两种码混着用:存在的资源回 403、不存在的回 404,等于把资源存在性当侧信道送了出去。选哪种都行,全站固定一种,并写进规则文件,让 AI 实现时有据可依。

三、三要素怎么写才让 AI 不用猜

模型不是不会写权限,是你没给它可判定的输入。判断标准很简单:**把你的描述给一个不了解业务的人看,他能不能对每一个组合给出唯一答案。**能,AI 就能写对;不能,AI 一定会猜。

角色这一维,要写清三件事:角色列表是封闭的还是可扩展的;一个用户能不能同时持有多个角色;角色之间是继承关系还是平行关系。这三件事任何一件没说,模型都会按最常见的那种假设来实现,而最常见的那种通常是”单角色、平行、封闭”,跟大多数真实系统对不上。

资源这一维,要写清归属链。不要只写资源名,要写它挂在谁下面。比如”评论挂在文档下,文档挂在项目下,项目挂在租户下”。这条链写出来,模型才知道校验要穿透几层。没写的话,它只会校验最近的那一层。

动作这一维,要按风险拆而不是按接口拆。同一个接口里改标题和改状态可能是两个风险等级。我的做法是先列动作,标上”错了会怎样”,凡是标注涉及钱、涉及删除、涉及对外可见的,一律单独立权限点。

写出来的东西长这样(这是给你自己和给 AI 看的规则说明,不是代码):

角色:owner / admin / member / viewer,单用户可持多角色,取并集,不继承
资源:tenant > project > document > comment,四层,校验必须穿透到 tenant
动作:read / update_meta / update_content / delete / export / transfer_owner
      其中 delete、export、transfer_owner 为高风险,需单独权限点
默认:未显式授予即拒绝

最后那句”未显式授予即拒绝”看着废话,但少了它,模型在遇到没覆盖的组合时会倾向于放行——因为放行不会让测试失败,拒绝会。这是一个很实际的行为偏差,写死它比事后查便宜得多。

关于这类规则文件怎么组织、放在哪里让工具稳定读到,可以参考CLAUDE.md 怎么写Cursor Rules 最佳实践里的做法,思路是一样的:把不该让模型猜的东西固化成文本,而不是每次对话重讲一遍。

四、给 AI 派活的正确顺序与验收动作

顺序错了,后面都白干。我用的顺序是四步。

第一步,让它做矩阵而不是写代码。 把上面的三要素给它,要求输出一张”角色 × 动作”的表格,每一格填允许、拒绝、或”需要资源归属条件”。这一步的价值在于逼出你自己没想清楚的格子。凡是它填得犹豫、或者同一格在不同轮次答案不一致的,就是你的规则有歧义,回去补规则,别急着往下走。

第二步,人来审这张矩阵。 这是全流程里唯一不能省的人工环节。审的重点不是”填得对不对”,是”有没有格子是你不敢拍板的”。不敢拍板的格子说明是业务决策没做,这时候找产品或业务方确认,不要让模型替你决定。

第三步,才让它按矩阵实现。 实现阶段要求它把权限判断集中在一处,而不是散在各个接口里。散着写的版本人工没法验收,集中写的版本你扫一眼就知道有没有覆盖全。

第四步,验收动作。 不要问模型”你写对了吗”,它会说对。用这几条客观检查:

  • 把矩阵里每一格转成一条测试用例,包括拒绝的格子。只测放行不测拒绝,等于没测。
  • 全局搜索资源实体名,把所有引用点列出来,逐个确认经过了权限判断。这一步用命令行更靠谱:
# 列出所有引用了该实体的文件,人工逐个过一遍入口
grep -rn "Document" --include="*.ts" --include="*.js" src/ \
  | grep -v -e '\.test\.' -e '\.spec\.' -e '/__tests__/'

排除测试文件时别图省事写成 grep -v test,那会把 latestattestation 这类正常路径一起吞掉,普查就漏了。宁可过滤条件写长一点,也不要让一个入口悄悄消失在管道里。

  • 拿一个低权限账号的令牌,对高风险接口逐个打一遍,确认返回的是拒绝而不是 200 或 500:
curl -i -X DELETE "https://api.example.com/v1/documents/123" \
  -H "Authorization: Bearer $LOW_PRIV_TOKEN"

这条命令请在测试环境跑,并且用一个删掉也无所谓的资源 ID。你验的正是”权限判断可能失效”这件事,一旦真失效,这一发就是真删除。

返回 500 也算失败,但先去看一眼服务端日志:如果异常栈落在权限判断或数据加载那一段,说明它在拿不到无权数据时崩了,而不是干净地拒绝;如果是数据库连不上之类的无关故障,那属于环境问题,重跑再判。

  • 让另一个会话或另一个模型只拿规则矩阵去审你实现的代码,不给它原始对话。这一招的作用是切断它对自己产出的偏袒。类似的对抗式检查在Agent 对手验证里有更系统的展开。

这四步走完,剩下的漏基本都属于”业务规则本身定错了”,那不是 AI 的问题。

五、什么情况下别再折腾

工程上比”怎么修”更值钱的是”什么时候停手”。以下几条是我的止损线。

第一条:同一个越权点修了三轮还在漏,停止打补丁,回到矩阵。 反复漏说明成因归错了,你在治现象。这时候继续让 AI 改代码只会把判断逻辑摊得更散,越改越难验收。正确动作是回退这几轮的改动,从第三节重新走一遍三要素。

第二条:如果权限判断已经散落在超过十来个地方,别再逐处补,改架构。 逐处补的成本是线性增长的,而且你永远不知道漏没漏。把判断收敛到统一入口(中间件、数据访问层、或者一个显式的授权服务),一次性的改造成本高,但从此可验收。判断依据很简单:你能不能在五分钟内向别人证明”所有入口都过了权限判断”。不能,就是该改架构了。

第三条:涉及合规、审计、资金的权限,AI 出的方案只当草稿,必须人来定稿。 这不是不信任模型,是责任归属问题——出事的时候没人能拿模型的输出当依据。这类场景里 AI 的合理用法是帮你列穷举、找遗漏,不是帮你拍板。

第四条:线上已经在漏数据,先止血再谈设计。 止血手段是关闭入口或把该角色的权限收到最小,而不是在线上热修判断逻辑。回滚点提前想好:如果这次改动引发误拒(本该有权的人被拦了),你要能在几分钟内回到上一版。误拒比误放行好,但长时间误拒也是事故。

第五条:如果你发现自己在跟模型反复解释同一条业务规则,换条路。 说明这条规则本身表述有问题,或者你的系统里存在互相矛盾的规则。这时候该做的是把规则重写清楚,而不是换更贵的模型。这一类”越修越乱”的循环在AI 修不好陷入死循环里讲得更细,判断信号是一致的:同一个问题超过三轮没有实质进展就该换方法。

六、避坑清单

坑一:用自然语言长描述代替权限矩阵。 为什么会踩:写描述比画表格快,而且看起来说得挺全。但自然语言天然允许省略,你说”管理员可以管理用户”的时候,脑子里其实有一堆例外,只是没写。 怎么避:任何权限需求,最终交付物必须是一张离散的表,行是角色,列是动作,格子里只有三种值。表格逼你填满每一格,描述不会。

坑二:把前端的按钮隐藏当成权限。 为什么会踩:AI 生成的前端代码通常会顺手加上条件渲染,看起来效果立竿见影——低权限账号登录后确实看不到那个按钮,测试也就过了。 怎么避:验收权限一律绕过前端,直接用命令行打接口。前端隐藏是体验优化,跟安全无关。

坑三:只给模型一个文件的上下文就让它改权限。 为什么会踩:上下文给多了慢又贵,习惯性只贴当前文件。但权限是横切关注点,判断散在多个地方。 怎么避:改权限前先做一次入口普查,把该资源的所有访问路径列成清单再动手。大仓库上下文不够的问题和处理思路可以看大仓库上下文不足

坑四:把认证当授权用。 为什么会踩:登录态检查是现成的中间件,加一行就有;权限判断要写业务逻辑。图省事就变成”登录了就能干”。 怎么避:在代码层面把两件事分开命名、分开位置。身份不明返回 401,有身份无权限返回 403,看到代码里用 401 兜底一切就是信号。

坑五:给测试账号发了超额权限,然后用它跑所有测试。 为什么会踩:一个万能账号跑测试最省事,不用为每个用例准备身份。 怎么避:权限相关的测试必须用最小权限账号,而且要有专门的”拒绝用例”。全绿但从来没有一条断言是”应该被拒绝”,这套测试对权限是没有覆盖的。这类假绿的更多形态可见测试假通过

坑六:默认放行。 为什么会踩:开发期为了跑通流程临时放开,后面忘了收。而且模型在规则没覆盖时倾向于放行,因为拒绝更容易让流程跑不下去。 怎么避:把”未显式授予即拒绝”写进规则文件,并且在权限判断的最后一个分支里显式返回拒绝,而不是让它自然落到放行。代码评审时专门扫这一个点。

坑七:多角色叠加时没定合并规则。 为什么会踩:单角色的系统好想,多角色是后来才加的,加的时候没人明确”取并集还是取交集”。 怎么避:在规则文件里写死一条,并且写一条测试用例覆盖”同时持有高权限和低权限角色”的情况。不写死,两个人实现出来的行为会不一样。

坑八:把权限规则藏在数据库配置里,代码里看不出来。 为什么会踩:做成可配置的显得灵活,运营还能自己改。 怎么避:可配置可以,但要有一个能一键导出当前生效规则的手段,让评审时能拿到实际值。不然你评审的是代码,线上跑的是另一套。

收束

权限模型这活的关键不在写代码那一步。你把角色、资源、动作三个维度定成一张任何人看了都能给出唯一答案的表,AI 就能把它稳稳铺进代码里;定不清,换什么模型都是猜。省下的那点定义时间,会在线上以事故的形式还回来。

上线前过一遍这七条自检:

  1. 有没有一张角色 × 动作的离散矩阵,每一格都填了值?
  2. 资源的归属链写出来了吗,校验穿透到最外层了吗?
  3. 高风险动作(删除、导出、转移、涉及资金)是不是单独的权限点?
  4. 拒绝用例写了吗,占比是不是接近放行用例?
  5. 用低权限令牌直接打接口,返回的是规则里定好的那个拒绝码(403 或 404,全站统一),而不是 200 或 500 吗?
  6. 该资源的所有入口(含导出、搜索、批量、后台)都过了同一处判断吗?
  7. 规则里有没有一条明确的”未显式授予即拒绝”,代码最后一个分支落到拒绝吗?

七条里有一条答不上来,就别急着发。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。