- if (!ALLOWED_ROLES.includes(user.role)) return deny(req);
+ if (BANNED_ROLES.includes(user.role)) return deny(req);
提交信息:简化权限判断,可读性更好。
确实更好读。短一行,语义更直白。单测全绿——fixture 里就俩角色,一个 admin 一个 user,都不在黑名单上。评审的人扫两秒,approve。
从这一刻起,任何不在 BANNED_ROLES 里的角色都能过。包括下季度新加的那个角色,包括迁移脚本里写错的大小写,包括上游服务传过来的一串空字符串。
没有攻击者。没有 prompt injection,没有投毒的依赖,没有哪份文档里藏着写给模型看的指令。就是把一段碰权限判断的代码交给模型重构,模型交回来的东西更干净,你放行了。
换成攻击者的思路:我要进你的后台,干嘛费劲绕一个 allowlist?等你把它改成 denylist 就行了,我什么都不用做。
到处都是"好人"在放口子
大家都在讨论 AI 写代码的安全问题,眼睛都盯着坏人。可绝大多数越权是好人用最正当的理由写进去的——"可读性更好"这五个字,比大部分 payload 都好使。
dev.to 上隔一阵就冒出来一篇"AI 是不是已经比多数开发者强"。最近那篇几天攒了 190 多个 reaction、150 多条评论,核心论点:写代码从来不是开发工作里最有价值的部分,AI 写得又快又干净,那开发者剩下的可辩护价值是什么。
TheBitForge 回应说这论点只对了一半。我也觉得只对了一半,但另一半不是重点。
IDC 的数字是开发者一天里真正敲代码的时间占 16%。剩下 84% 花在澄清一个还没想清的需求、做取舍、拍架构、在 deadline 面前判断哪里可以妥协。我不去论证那 84% 是不是更值钱,那种论证谁都能赢,也永远没结论。我说个更窄的:AI 在那 84% 里替你省掉的判断,就是这场辩论里没人算的那笔账。而它已经开始出现在 review 环节上了。
那 84% 里有一块是纯安全假设。AI 不知道你上季度改过两次结账流程,也不知道某个"快速修复"会撞上一个只存在于你脑子里的缓存层——权限在那里被缓存了 30 秒,所以撤销成员关系之后还有人能读 30 秒。这两件事听起来是业务知识,其实一半是安全约束。模型不会问,你也不会在代码里把它写出来,它写在你脑子里。理解债清的就是这个账户的余额。
1.7 倍我不在乎,2.7 倍要看它落在哪
有组数字被反复转:AI 生成代码引入 bug 的概率大概是手写的 1.7 倍,引入 XSS 的接近 2.7 倍。
1.7 倍我基本不在乎。bug 是噪音,是响的,linter、类型检查、单测拦掉大部分,剩下的在 code review 里也显眼——它会让东西崩,崩了你就得看,躲不掉。
2.7 倍我在乎,但它不是一个能平均摊销的数。得除以:你交给 AI 写的代码里,有多少条路径真的把用户可控的输入渲染出去。内部 CRUD 表单上,2.7 倍跟 1 倍没区别。导出接口上,它就是把用户输入拼进模板的那一下,2.7 倍等于事故。
第一次看到这数字我也顺手转发了,配文大概是"AI 写的代码 XSS 风险高两倍多"。后来把触发条件摊开看:这个倍数只在渲染用户输入的路径上吃满,而那些路径你一旦单拎出来人工过一遍,倍数就掉回去了。所以我现在的判级不是"别让 AI 写前端",是"渲染用户输入的模板、所有手工拼接字符串的地方,不管谁写的,都单独排一队"。判重和判轻都是失职,这账不能靠转发数字来结。
漏过来的那批,长得像对的
1.7 倍这个数没说 bug 的质地。语法错误、拼错变量、类型对不上,这些是噪音。真正漏过来那部分有个共同特征:它们在测试环境下是对的。
off-by-one 在 fixture 数据里对。空值边界在 fixture 里根本不存在。竞态只在生产负载下露头。而竞态一旦撞上权限检查,就是 TOCTOU:
if (await isMember(userId, teamId)) {
await somethingAsync() // 任何一次跨进程或跨网络的等待
return readTeamFile(teamId) // 这一刻,成员关系可能已经被撤销
}
单线程测试环境永远复现不出来,它需要一个"刚好在这个窗口里把权限撤销掉"的时序。这类洞我打靶的时候最喜欢找。手上还压着一个没到 disclosure 窗口的,跟这篇没关系——离题了,收:模型学过的是"看起来对",而看起来对的代码和真的对的代码,在语法层面长得一模一样。对它来说,生成一段读起来像教科书的实现,和生成一段权限正确的实现,是同一种任务。模型输出的是概率上最像正确答案的东西。它没有"我不确定"这个通道。
理解债就是安全债
3.1% 的开发者说自己会不检查就信 AI 输出——这个数低得可疑,它是自我报告。配另一个数看才有意思:45.2% 的人说,调试 AI 生成的代码比自己从头写还费时间。
两个数放一起,说明的不是"大家很谨慎",是检查这件事正在赔本。当审查一段代码的成本超过重写它的成本,人自然退到"跑通就行"。而"跑通就行"落在信任边界上,就是不审。
再叠一个:2026 年 1 月那份研究,被动接受 AI 生成代码的开发者理解测试得分 50%,手写的是 67%。差 17 个百分点。还有那个工程师,合并了 Claude 写的功能,三天后解释不清这功能到底怎么工作。
这三件事指向同一个结论。审一段你自己写不出来的代码,你签的不是"我检查过了",是"我信了"。橡皮图章不是懒,是成本核算的必然结果。
回到开头那条 diff。要发现它,唯一需要的是知道这里本来该是白名单。这个知识没法靠对比两个版本获得——两版长得太像,短的那版还更像样。它只能从读过、写过、踩过里来。自信的代码→全绿的测试→approve→三天后没人说得清→下次谁都不知道这里本来是白名单。链条上每一环单独看都合理,合起来就是那道口子。
理解债在安全语境里就是这个意思:你丢掉的不是打字速度,是发现"这里被人翻过"的能力。
拆弹清单
- 碰信任边界的那几十行,自己写。权限判断、鉴权中间件、支付回调的签名校验、导出接口的参数过滤。CRUD、配置、脚手架、样板单测随便交给 AI,那部分它确实比我快,我不跟它抢。
- 凡是 diff 里动过 if 方向的、翻转过比较运算符的、改过默认分支的、把名单从白名单换成黑名单的——单独拎出来看,谁写的都一样。这类改动短、干净、像整理,正好是眼睛会滑过去的地方。
- 把 AI 当红队用,别当验收用。让它对着你的设计找"这里怎么绕过去",输出当成待验证的线索。红队说没找到,不构成没漏洞。
- 合并一周后随手抽一段 AI 写的代码,问自己"它做错了会怎样"。答不上来的那段,就是下次打靶的靶心。
别慌,这不是要你把 AI 从流程里踢出去。我自己几乎每个交付项目都在用,WooCommerce 那堆 hook 签名我早就不背了。要收回来的只是那几十行碰权限的代码,和点 approve 的那个瞬间。
AI 没有变得比开发者强。它变得比那些不再看代码的开发者强。
排雷记录:橡皮图章不是态度问题,是成本问题——把该审的范围缩到几十行,比让所有人"提高警惕"有用得多。
