
就这两天我决定把半年来用 AI 做代码审查的翻车记录整理出来。不是标题党是真的在同一条线上连续踩了三次坑AI 帮我标记了几十个“疑似问题”真正被攻击者利用的深层漏洞一个都没拦住反而有三处漏洞是我后来靠人工威胁建模翻出来的。我一开始也迷信“把代码丢给大模型它总能找出你没发现的安全漏洞”现在我只能说它像个精力旺盛但不懂业务规则的实习生你给它喂什么视角它就看到什么世界。这篇内容我会还原三次翻车的完整现场然后给出我现在固定使用的 5 个深层安全漏洞排查方向以及一套人机协作的实际执行方式。打算把 AI 引入代码审查但还没完全建立信任体系的团队或者正在准备安全排查清单的个人开发者读这一篇应该够省很多试错时间。1. 三次翻车AI代码审查为什么会漏掉真正的洞先说结论AI 代码审查翻车通常不是因为它“笨”而是因为它缺少三样东西——运行时上下文、完整调用链、业务语义。下面三次事故分别对应三种典型失败模式。1.1 翻车现场一AI把“坏味道”当成漏洞漏掉真实数据流第一次翻车出现在一个内部管理后台。我把一整个模块的代码喂给 AI 审查工作流预期它能找出“越权访问”这类问题。结果 AI 输出了一个长长的清单里面最醒目的是几个“硬编码密钥”和“SQL 查询拼接字符串”的警告。这些确实存在团队按要求修掉了但它们都不是最要命的。那段时间暴露在公网的一个GET /api/admin/order/{orderId}接口OrderService 里直接按orderId查库返回既没有判断当前登录用户是否是订单归属者也没有判断他是否具备管理员角色。外部攻击者只要遍历订单号就能把所有用户订单的收货地址、手机号、支付信息全拉出来。这就是典型的对象级越权。AI 为什么没发现因为单看 OrderService 的几个方法它看不出这些方法会被路由直接暴露到公网也看不出上层没有加鉴权拦截器。它会认为“查询后返回数据”是合法行为顶多提示“缺少异常处理”不会把“路径参数 - 方法入参 - 数据库查询 - 响应返回”整条数据流串起来判断信任边界。我后来复盘这类失败的核心原因是模型在训练阶段见过大量的“写法坏味道”但缺少“该业务对象当前处于什么权限上下文”的信息。喂给它一个方法它只能回答局部风险只有把完整的路由注册信息、鉴权策略和调用入口一起给进去它才有机会推断越权链路。所以现在的经验是第一原则单文件审查只适合查语法级问题想查深层漏洞必须补上下文。1.2 翻车现场二单文件审查导致状态机盲区第二次翻车更隐蔽。有个积分兑换系统业务规则是“订单取消后积分回退但如果已经发货不允许取消”。代码里面cancelOrder方法写得很干净先判断order.status是否为“已发货”再决定能否取消。AI 这一轮没有报任何高风险项因为局部逻辑确实没有语法错误也没有危险函数。问题出在另一个方法上adminUpdateOrder允许运营人员修改订单状态而前端和接口层没有对状态流转做完整限制。攻击者拿到普通用户权限后直接调用这个接口把订单从“已发货”改成“待支付”再走一次取消流程就能把已经拿到的商品的积分反复回退套利。这种跨方法的业务逻辑漏洞AI 几乎是完全失明的。它只审查了OrderService.cancelOrder这一个文件里的状态判断却不知道系统还有别的地方能跳过正常流程修改状态。这也是 AI 代码审查工具的通病绝大多数模型对“状态机”没有建模能力。它们能理解一个函数里的if条件但对“谁在什么时间把状态改成什么值”这种跨请求的时序关系缺乏感知。那次之后我再也不用“一次丢一个文件”的方式做审查改成按“业务事件流”组织从用户入口到状态变更点再到资金/积分变动全部连起来让 AI 看。这一步效果立竿见影后续抓出来的业务逻辑漏洞明显变多。1.3 翻车现场三AI修复建议本身变成了新漏洞第三次翻车最让我哭笑不得AI 识别的漏洞是真的但它给出的“修复方案”是错的。当时发现某个 Python 服务在解析用户上传的配置时直接把字符串丢进eval()。AI 正确报出了“代码注入风险”并建议用ast.literal_eval()替代。从安全角度来说这个建议方向没问题但ast.literal_eval()只支持数字、字符串、元组、列表、字典等字面量不支持集合、切片等操作。团队照着提示改完确实挡住了eval()的任意代码执行。但没过多久新问题来了合法配置里包含了一串带格式的时间表达式literal_eval解析不了为了兼容旧数据团队又加了一段“如果解析失败就退回 eval 试试”的逻辑。这一退理论上回到了原始漏洞而且因为混在正常流程里静态扫描根看不出问题。后来安全测试工具对这个端点做了一轮恶意 payload 探测直接命中整个修复形同虚设。这件事给我的教训是AI 给的修复建议必须当作“人工工单”而不是“最终方案”。每次 AI 修复后要追问三件事这次修改是否覆盖了所有入口它为了兼容原有行为是不是加了新的兜底逻辑兜底逻辑本身可不可信如果 AI 的回答含糊那就去读 diff别偷懒。2. 深层安全漏洞的五个排查方向三次翻车之后我开始做一件事把 AI 审查从“查危险函数”重新定位成“查深层漏洞”。现在固定使用五个检查维度它们不是从扫描器里抄规则集来的而是从真实攻击路径里一层层剥出来的。2.1 越权与对象级授权缺失这是当前 API 类应用里最容易被忽略的一类风险也是我和 AI 协作时最先会手动核查的部分。对象级越权的本质是用户可以直接操作某个具体资源比如订单、发票、私有项目但后端没有判断这个资源到底属于谁。排查时我会先扫一遍所有“通过 ID 操作资源”的接口规则很简单路径里出现{id}或查询参数里出现id就要确认服务端有没有把当前登录用户带进查询条件看授权校验发生在哪个层级是统一拦截器里做了还是 Controller 里做了还是干脆没做敏感资源哪怕是内部接口也要假设会被外部访问到否则“内网可越权”一样是事故。如果依赖 AI记得在 prompt 里给出路由映射和鉴权策略否则它默认“有 ID 就能查”。我第一次翻车就是漏了这一步。2.2 数据过度公开很多团队做接口时图省事直接把数据库实体序列化后返回给前端。成员列表接口返回了User实体的全部字段里面包含手机号、身份证号、内部角色编码。前端不用这些字段不代表它们不存在。只要一次抓包隐私数据就全被拖走。这个现象现在很常见但静态扫描规则很难把它当成漏洞来报因为代码本身没有“危险函数”很多 AI 审查也只会轻描淡写地说“注意敏感信息”。建议排查时做两件事逐个接口核对响应 JSON看返回字段是不是和前端实际需要的字段一一对应生产代码里必须用 DTO数据传输对象或 ViewModel禁止直接把 ORM 实体类返回给外部。我现在会让 AI 专门做“DTO 与实体对比审查”把接口返回对象定义丢给它让它标注哪些字段从未被实际使用再人工确认。这一步能把泄露面压下来很多。2.3 不安全反序列化与类型混淆如果是 Java、Python、PHP 这类语言的项目反序列化是必须单列的排查项。ObjectInputStream.readObject()、pickle.loads()、unserialize()这些方法一旦接收了用户可控的序列化输入就可能触发恶意对象构造链最终变成远程命令执行。AI 会对这类函数名敏感但容易错过两个变种第一入口不是直接调用反序列化而是接收 Base64 字符串解码后才传入反序列化函数模型只看后半段容易误判“数据来自内部”第二项目中引入了通用型序列化组件攻击者可以控制类型名触发任意类构造。类型混淆不一定是pickle或 Java 原生序列化才有的问题现在很多 JSON 反序列化框架也支持多态类型一旦允许用户指定class、type之类的字段风险等级立刻提升。排查思路搜索所有readObject、loads、unserialize、fromJson的调用点追一下输入来源。凡是从 HTTP 请求体、文件上传、消息队列里取出来的数据都默认不可信必须做类型允许列表校验或字段白名单校验。2.4 模板注入与表达式求值边界这个方向 AI 尤其容易漏。很多团队一看到eval()就会紧张但模板引擎里的动态表达式比如 Java 的 SpEL、Python 的 Jinja2、JavaScript 的模板字符串拼接危险程度一点都不低。攻击者在姓名、地址、备注这类输入框里填{{7*7}}如果后台又把这个输入直接交给了模板引擎渲染那7*7就会被执行成 49。再进一步恶意表达式可以读取环境变量、加载类、执行命令。手工排查时我会把全项目搜索范围从“eval 和 exec”扩到所有模板引擎的调用点例如render_template_string、TemplateEngine.process、ExpressionParser.parseExpression。然后逐个确认模板内容本身是不是固定的有没有任何用户输入被拼进模板字符串如果想让 AI 参与不能只给一个模板文件要给它输入字段来源。因为 AI 判断“模板是否安全”高度依赖它能否看到没有被转义的用户输入缺少这个信息它很容易说“没问题”。2.5 关键操作的审计与可追踪性缺失严格说这不是传统意义上的代码漏洞但它决定安全漏洞发生之后你能不能扛得住。很多系统对“修改订单状态、调整余额、导出数据、删除配置”这些关键操作完全没有审计日志攻击发生时你连对方从哪进来、动了什么东西都不知道只能靠猜。排查方法不复杂列出所有“高危写操作”例如资金变动、权限变更、状态回退、数据导出然后看每个操作后面有没有记录操作者、操作时间、业务单据号、请求 ID。没有记录的就是要补的洞。AI 对这块的钝感很强因为日志通常写在业务代码之外的拦截器或切面里单看业务方法它不会觉得“没打日志”是安全问题。我更倾向于把“审计日志缺失”写进人工安全验收清单每次发版前对着清单逐项确认。3. 实操指南把AI审查从“找茬”变成“找洞”现在我不再让 AI 漫无目的地看代码而是把它当作一个“有经验的初级工程师”来分配任务。要点是明确给它角色、入口、信任边界和输出格式。3.1 给AI喂上下文调用链、输入入口和信任边界我目前的实践是每次让 AI 审查一个接口或模块前先拼一段上下文请审查以下代码背景如下 - 入口POST /api/v1/orders/{orderId}/cancel - 鉴权Controller做了登录校验但未校验订单归属 - 数据流request body - OrderService.cancel - OrderRepository.update - 信任边界orderId 和 request body 均完全不可信 - 关注点是否存在对象级越权、状态机绕过、金额/积分计算异常这样喂完之后AI 会明显更聚焦。它不再到处找“未捕获异常”“魔法数字”之类的表面问题而是顺着你指出的数据流去做漏洞推演。如果项目里已经有 API 路由注册文件我也会把路由表导出后一起贴进去相当于把全局调用关系直接给它。3.2 用威胁建模引导AI而不是让它自由发挥“请帮我看看有什么漏洞”是最低效的提示词因为模型会把你往常见 CWE 列表里带结果就是给你一堆泛泛而谈的建议。我建议引入威胁模型明确告诉 AI“可能攻击者是谁、已经有什么权限、他要做什么事”。举个例子威胁模型 1. 匿名攻击者只能访问公开接口 2. 普通登录用户可以访问自己的订单和自己的资料 3. 普通用户尝试访问管理员接口、篡改订单归属、提交负数金额优惠。 请基于以上威胁场景定位代码中可被利用的安全漏洞。这样 AI 会主动检查越权、IDOR、金额逻辑比泛泛提示要深入得多。这也是三次翻车后我觉得最值得改变的一点。3.3 人工复核的检查清单我的习惯是无论 AI 交上来多少条结果最终铺以这份检查清单逐条人工确认防止“看起来修了其实没修”每个受保护资源接口是否在业务方法内部校验资源归属批量查询接口是否通过用户维度过滤所有响应体的字段数量是否与前端实际消费的字段一致反序列化入口是否有类型/字段白名单模板引擎渲染的内容是否为固定模板用户输入是否经过转义状态变更是否可以按正常业务顺序触发是否存在绕过正常流程的路径资金/积分变动是否有幂等机制是否受并发条件影响高危操作是否有审计记录请求标识是否可以贯穿调用链这份清单不靠 AI 自动出结果它更像是你的验收底稿。AI 说“没问题”不代表这些点没问题你必须自己点过一遍。4. 常见问题与避坑实录4.1 为什么AI报了漏洞但测试环境却复现不了这个是问得最多的问题。AI 报“这里存在 SQL 注入”你拿测试数据打一下没反应你就觉得它误报。实际上多数情况是上下文不匹配比如某条代码路径需要前置条件才能走到或者数据库连接、ORM 配置做了安全处理恰好让输入无法闭合语句。按我的经验AI 报出来的东西要分三类对待一类是真实漏洞一类是潜在风险代码一类是纯误报。分不清楚就先把现场代码、调用链、数据样例全部收集好再逐条人工验证。别因为一条误报就否定整个 AI 审查流程也别因为一条“看起来能跑”的结果就盲目上生产。4.2 AI经常误报怎么办误报率高的一个重要原因是提示词里缺少业务规则。你在上下文里写清楚“订单金额不允许为负数积分只能在待支付状态退回”AI 就能大幅减少“这个数据结构可能会被攻击者传负数”这类无依据猜疑。还有一个实用做法先让 AI 输出“它是如何走到这条攻击路径的”再让它输出“漏洞等级”。如果攻击路径讲不通那这条结果基本可以直接降级处理。我自己会固定维护一个“已知可接受风险清单”每次 AI 审查完后把命中这些规则的输出直接过滤掉减少噪音。4.3 我现在的验收习惯最后分享一个我现在使用的习惯AI 出的每一条漏洞都要求它至少给出“证据链”和“复现步骤”而不是一句话结论。证据链包括从入口参数开始的一步一步数据流变化复现步骤则要具体到请求方法、路径、参数和预期响应。如果 AI 给不出来我就让它继续挖上下文或者我自己来补链路。这样处理完后AI 的结论通常是可靠的至少可以当作高优先级人工复核目标。说实话AI 代码审查的价值不在于“替代人”而在于把你从大量低级重复劳动里解放出来。它负责快速扩大覆盖面你负责把攻击思维注入到审查流程里。这套打法定下来之后我们后续上线版本的漏洞漏检率明显降下来了越权类和数据过度公开类问题基本在测试阶段就被拦截住。如果你也正在被 AI 审查的误报和漏报折磨不妨按上面五个方向重新组织一次审查应该会有不一样的结果。