AI代码审查漏报怎么办?用4类风险做人工复核清单

编程狮 2026-09-18 17:13:15 浏览数 (19)
反馈

AI 代码审查漏报不能靠“再审一遍”解决。更可靠的做法是按数据与权限、状态与并发、异常与资源、测试与回滚四类风险复核;每类都用一个失败输入或并发场景验证,而不是只读 AI 给出的评论。

AI 审查漏报用4类风险复核

本文用“优惠券核销”改动做示例:代码表面只有十几行,AI 可能发现空值,却漏掉越权、重复核销和吞异常。你会得到一份可以贴进 Pull Request 的 AI 代码审查清单。它适合普通业务仓库,不替代安全团队对高风险系统的专业审计。

一、先接受一个事实:AI 审查不是完备证明

GitHub 在 Copilot代码审查说明 中明确提醒:工具不能保证发现所有问题,可能犯错,反馈必须由人验证。文档还列出某些不会被审查的文件类型,例如依赖管理文件、日志与 SVG。看见“无问题”并不代表整个变更面已经覆盖。

AI 代码审查的优势是快速扫描常见模式、解释 diff、建议测试;弱点是看不见未提供的业务规则,也可能缺少运行时状态、权限模型和外部系统语义。你需要把它定位为第一遍筛查,而不是合并许可。

AI 审查能做什么 AI 审查不能做什么
扫描常见语法与模式问题 保证发现所有缺陷
解释 diff、生成测试建议 替代业务规则确认
提示空值、类型、风格问题 验证运行时并发状态
快速覆盖大量文件 理解完整权限模型与外部系统语义
给出修改方向 替代安全审计与发布回滚设计

如果团队刚开始引入这类工具,可以先用 AI编程技能教程 统一提示与验证方式,再把高风险项固化为仓库规则。

二、四类风险复核总览

风险类别 核心问题 验证方式
数据与权限 调用者能否读取和修改这条记录? 用他人 ID、不存在 ID、过期状态测试
状态与并发 两个请求同时执行,最终状态是什么? 并发发起两次相同请求,只允许一条成功
异常与资源 数据库抛错时,日志、连接和返回码怎样? 模拟超时、唯一约束、连接失败
测试与回滚 哪些测试阻止回归,发布失败怎样恢复? 检查测试集、迁移脚本、回滚开关

一句话:AI 标出可疑点,人用失败输入和并发场景验证四类风险。

三、案例:看似正常的核销函数藏着哪些问题

假设本次改动新增下面的函数:

export async function redeemCoupon(db, couponId, userId) {
  // 按主键查询优惠券,但没有校验归属
  const coupon = await db.coupons.findById(couponId);

  // 只判断是否存在
  if (!coupon) return { ok: false, reason: 'not_found' };

  // 只判断是否已使用,但没有原子性保证
  if (coupon.used) return { ok: false, reason: 'used' };

  try {
    // 直接更新为已使用,条件只有 couponId
    await db.coupons.update(couponId, { used: true, usedBy: userId });
    return { ok: true };
  } catch {
    // 吞掉所有异常,没有日志、没有错误类型
    return { ok: false, reason: 'failed' };
  }
}

单次运行时它很像正确代码:查记录、判状态、更新、返回结果。但它存在四个问题:

  1. 越权风险:没有验证优惠券属于谁;
  2. 并发风险:两个请求可同时读到 used=false
  3. 异常风险:异常被压成统一字符串,日志和告警拿不到原因;
  4. 测试风险:测试若只覆盖成功路径,就不会暴露这些问题。

复核前先收集最小业务约束:优惠券归属规则、是否允许管理员代操作、数据库是否支持条件更新、失败是否可重试、核销成功后有哪些下游动作。AI 没拿到这些事实时,不应期待它猜出正确答案。

四、第一类:数据与权限风险

数据与权限复核要问“调用者能否读取和修改这条记录”。findById(couponId) 只按主键查询,没有把 userId 放进条件。攻击者只要猜到别人的券号,就可能代为核销。

更稳的查询要把租户、所有者或可见范围放进数据库条件,而不是查出后再靠 UI 隐藏。

// 把 ownerId 和 status 放进查询条件,避免越权
const coupon = await db.coupons.findOne({
  id: couponId,
  ownerId: userId,   // 必须是当前用户拥有的券
  status: 'active'   // 只允许有效状态的券
});

还要测试不存在、属于别人、已过期、已禁用四种状态。权限测试应从不可信输入开始,不要只用管理员账号。

检查项 失败输入 预期结果
券不存在 随机 couponId 返回 not_found
券属于别人 他人 couponId 返回无权限或 not_found
券已过期 status=expired 拒绝核销
券已禁用 status=disabled 拒绝核销
管理员代操作 管理员 userId 按业务规则单独授权

这只是示意接口,真实字段要以仓库模型为准。进行 AI 代码安全复核时,可把权限规则写进仓库级技能或指令;Codex插件与技能说明 能帮助理解规则如何被复用,但规则内容仍需团队给出。

五、第二类:状态与并发风险

“先读 used,再写 used=true”存在检查与写入分离。两个请求可能同时通过判断,随后都返回成功。正确方向是把条件和更新放进一个原子操作,让数据库只允许一方把 active 改为 used。

-- 原子更新:只有满足 id、owner_id、status 条件时才更新
UPDATE coupons
SET status = 'used',
    used_by = :user_id,
    used_at = CURRENT_TIMESTAMP
WHERE id = :coupon_id
  AND owner_id = :user_id     -- 防止越权
  AND status = 'active';      -- 防止重复核销

应用层检查受影响行数:

  • 等于 1:核销成功;
  • 等于 0:重新查询并区分不存在、无权限或已核销。

并发测试至少同时发起两次相同请求,预期只能一条成功。

场景 旧写法结果 原子更新结果
两个请求同时核销 都可能成功 只有一条成功
券已核销 第二次返回 used 受影响行数为 0
券属于别人 可能越权成功 条件不匹配,更新失败
数据库超时 统一返回 failed 可区分超时并记录日志

若下游还要发积分或消息,需要事务、幂等键或 outbox,而不是把两个动作简单串起来。

状态复核还要画出允许的迁移,例如 active → usedactive → expired。任何能从 used 回到 active 的入口都要单独授权和留审计记录。

六、第三类:异常与资源风险

catch 会丢掉数据库错误、超时和唯一约束信息。对外可以返回稳定错误码,对内必须保留异常类型、请求标识和关键业务键;日志不能写完整令牌或隐私数据。

try {
  await db.coupons.update(couponId, { used: true, usedBy: userId });
  return { ok: true };
} catch (error) {
  // 对外返回稳定错误码,对内记录异常类型和请求标识
  logger.error('coupon_redeem_failed', {
    couponId,
    userId,
    errorType: error.name,
    requestId
  });
  return { ok: false, reason: 'internal_error' };
}

连接、文件和锁要确认在异常路径释放,重试也要限制次数并只针对可恢复错误。

检查项 常见问题 修复方向
异常类型 空 catch 吞掉所有错误 记录 error.name 和关键业务键
日志内容 写入完整令牌或隐私数据 脱敏,只记录业务键
连接释放 异常路径未释放连接 使用 finally 或连接池管理
重试策略 无限重试或重试不可恢复错误 限制次数,只重试超时等可恢复错误
对外错误码 直接暴露数据库错误 返回稳定业务错误码

七、第四类:测试与回滚风险

测试与回滚复核关注“怎样证明修复有效,以及失败时怎样撤回”。

最低测试集包括:

测试用例 验证目标
合法核销 正常路径成功
他人优惠券 越权被拒绝
已核销 重复核销被拒绝
两请求并发 只有一条成功
数据库超时 异常被记录,返回稳定错误码
迁移字段非空 旧数据兼容,回滚脚本可执行

迁移字段若新增非空约束,还要验证旧数据和回滚脚本。配置开关的默认值、灰度范围与监控指标也应在 PR 中写明。

回滚检查项 要求
数据库迁移 有回滚脚本,旧数据可兼容
配置开关 默认值安全,可快速关闭
灰度范围 明确首批用户与监控指标
监控告警 核销失败率、异常类型可观测
发布失败 有明确恢复步骤和责任人

八、把四类风险变成 PR 复核清单

可以把四类复核转成四条追问:

  1. 哪个失败输入能证明权限条件生效?
  2. 两个请求同时执行,最终状态是什么?
  3. 数据库抛错时,日志、连接和返回码分别怎样?
  4. 哪些测试阻止问题回归,发布失败怎样恢复?

如果 AI 只给出风格建议,而四条都没有证据,AI 代码审查漏报风险仍然很高。

复核项 需要留下的证据
数据与权限 他人 ID、不存在 ID、过期状态的测试结果
状态与并发 并发请求日志或测试断言
异常与资源 错误日志样例、返回码、连接释放确认
测试与回滚 测试清单、迁移回滚脚本、灰度开关
PR 结论 代码、测试、监控、回滚闭环

AI 代码审查四类风险复核

总结

AI 审查适合做第一遍扫描,却不能替代业务知识、并发验证和回滚设计。面对短小 diff,也要分别检查数据权限、状态并发、异常资源、测试回滚,因为严重缺陷往往不在语法层。

实操时先让 AI 标出可疑点,再由人把四类风险映射到失败输入和测试命令。最终合并依据应是代码、测试、监控与回滚都能闭环,而不是评论区出现一句“看起来没问题”。

延伸学习

  1. 需要在本地配置 Agent 时,先查 Codex安装指南
  2. 想把复核步骤接进命令行,可对照 CodeBuddy CLI参考
  3. 选工具前可阅读 AI编程助手横向对比,但审查责任不因产品变化而转移。

常见问题

Q:AI 审查没有评论,可以直接合并吗?

A:不可以。没有评论可能表示没有发现,也可能表示文件未覆盖或上下文不足。至少执行项目测试,并按四类风险完成一次人工复核。

Q:四类风险每次都要全部检查吗?

A:都要过一遍,但深度随变更调整。纯文案变更可很轻;涉及权限、资金、库存、并发或数据库迁移时,每类都应留下证据。

Q:怎样减少同一种漏报反复出现?

A:把业务不变量写成自动测试,把通用审查点写进仓库指令或技能,把高风险文件设置责任人。文字提醒只能辅助,测试才会持续阻断回归。

Q:AI 建议的修复也要复核吗?

A:要。修复建议仍是代码变更,可能引入性能、兼容或权限问题。应用后重新看 diff、运行测试,并确认改动没有超出原问题范围。

0 人点赞