
前阵子我干了一件挺“自虐”的事把公司一个老项目里的 20 个核心代码模块分别丢给 Claude Code 和 Codex CLI 做安全审计让它们各自输出风险结论和修复建议。结果很有意思——两个主流 AI 编程助手按理说都读过海量代码却在大部分模块上各说各话。20 个模块里它们只在 5 个上给出了方向一致的结论剩下的全是分歧、误报、或者干脆一方报了另一方当没看见。这个实验不算严谨的学术测评但确实能反映一件事拿 AI 做代码审计能提效但千万别把它的结论当“权威判决”。这篇文章我会把完整过程、对比结果、分歧原因、以及我最后沉淀下来的混合审计工作流都写出来适合所有在用 Claude、Codex 辅助写代码或者准备把 AI 引入代码安全审查流程的同学。1. 实验设定为什么同时让两个 AI 审同一批代码1.1 想验证的并不是“谁更强”先说清楚我这次不是想证明 Claude 和 Codex 谁更聪明。代码审计这种活儿最终看的不是模型的“智商”而是它的上下文理解能力、对漏洞模式的敏感度、以及输出结果的稳定性。同样的模块同样的需求描述两个模型分别审一遍如果结论高度一致说明问题大概率真实存在如果分歧很大反而值得警惕——这里面既有模型本身的原因也有我提问方式的原因。另外我平时的工作流里其实两个工具都在用。Claude Code 更擅长理解全局逻辑适合让它梳理跨文件的调用链Codex 对具体代码片段执行得比较快修 bug 和补测试的时候很顺手。既然每天都在用那就干脆做个对照实验看看它们在“审计”这种高压场景下到底靠不靠谱。1.2 环境准备与工具安装实验环境其实不复杂核心就三件事Node.js 运行时、Claude Code、Codex CLI。我用的 Node.js 版本是 20 LTS这个别太老两个 CLI 工具对 Node 版本都有要求低于 18 大概率装不上。然后装 Claude Codenpm install -g anthropic-ai/claude-code装完跑一下claude --version确认版本号正常。Codex 也是走 npm虽然它经常被误解成只有官网那个网页版其实官方早就提供了 CLInpm install -g openai/codex登录这块稍微啰嗦两句。Claude Code 首次启动会让你走 OAuth 流程需要在终端里打开链接授权Codex CLI 默认要求配置 API Key首次运行跟着提示把 Key 填进去就行。我说的是官方标准渠道至于网络层面怎么处理你自己想办法我这边只讲工具本身。提示两套 CLI 的配置目录不在一块。Claude Code 在~/.claude/Codex 在~/.codex/后面如果要改模型、改代理、调超时时间别找错地方。1.3 抽取哪 20 个模块怎么保证公平项目是我之前维护过的一个微服务仓库Java 写的里面有认证、支付、文件上传、用户管理、消息推送等典型模块。我从中挑了 20 个相对独立的模块每个模块的文件数量控制在 3 到 10 个之间避免文件太多导致上下文被截断。公平性上我做了三件事。第一同一个模块的代码严格按照相同顺序喂给两个工具如果先贴主文件再贴依赖文件那两边都这么贴顺序不能变。第二提示词除了工具名不同其余措辞完全一致不搞“针对 Codex 优化一下”这种事。第三两个工具都只允许读取我指定的目录不让它们自己满仓库乱翻防止一个工具运气好翻到了关键配置文件另一个没翻到。当然我也知道这样会漏掉一些只有全局搜索才能发现的问题所以后面又补了一轮完整仓库级别的审计这部分放到第五节讲。2. 审计提示词与输出协议设计2.1 提示词模板让两个 AI 说同一种“语言”要让两个工具的结论能对比第一件事就是让它们按同一套格式输出。我设计的提示词长这样你是一名资深代码审计工程师。请对以下代码模块进行安全审计。 要求 1. 逐文件阅读代码重点关注注入、认证绕过、越权、敏感信息泄露、不安全反序列化、竞态条件、错误处理缺失等问题。 2. 每个问题按“模块名-严重级别-问题类型-位置-修复建议”的格式输出。 3. 严重级别只能是 CRITICAL / HIGH / MEDIUM / LOW / INFO 五档。 4. 如果你认为某个文件没有安全问题请明确说“通过”不要强行凑数。 5. 最后总结该模块的整体风险评级和理由不超过 200 字。 以下是代码文件 [粘贴代码]这个模板看着简单其实埋了几个关键约束。“逐文件阅读”是为了逼它别只看一眼就下结论“严重级别五档”是为了后续能统计比对“没有安全问题就说通过”是为了减少“没事找事”式的误报。实测下来如果不加第 4 条两个工具都会倾向于编造一些 LOW 级别的问题来“体现工作量”这很烦人。2.2 风险等级判定标准光有等级还不够两个模型对“HIGH”的理解可能完全不一样。所以在正式审计前我先给了一套判定标准让它们照这个尺度来级别判定条件处理时限建议CRITICAL可直接被外部利用导致 RCE、未授权访问全量数据、资金损失立即修复HIGH需要特定条件才能利用但利用后影响面大如越权操作他人数据、SQL 注入1 周内修复MEDIUM存在安全隐患但利用门槛较高如日志泄露敏感字段、过期依赖存在已知 CVE1 个月内修复LOW代码质量问题或潜在风险但当前无法被利用下个迭代处理INFO建议性优化不影响安全自行决定这套标准直接贴在提示词后面。没有这个Claude 和 Codex 对同一个危险函数的判定可能差出两个级别去后面就没法比了。2.3 为什么还要让它输出结构化结果除了让人读的总结我还让两个工具额外输出一份 JSON 结构的问题清单字段包括module、severity、type、location、suggestion。这样我可以写个小脚本把两边的结果解析出来按模块和问题类型做自动比对省得人工一条条对。{ module: UserAuth, issues: [ { severity: HIGH, type: secret_leak, location: UserAuth.java:47, suggestion: 将硬编码的 JWT 密钥替换为环境变量注入 } ] }这一步强烈建议别省。20 个模块每个模块平均 4 个问题两边加起来就是 160 条记录纯靠眼睛看会疯。后面第五节我会放出这个比对脚本的思路。3. 20 个模块的审计结果全记录3.1 整体结果一览先看总表这是整个实验的核心数据。模块名Claude 结论Codex 结论结论方向UserAuthHIGHJWT 硬编码密钥HIGHJWT 硬编码密钥一致PaymentOrderHIGH金额精度丢失MEDIUM金额比较使用 double不一致FileUploadCRITICAL任意文件上传HIGH文件类型校验绕过部分一致SmsSender通过通过一致TokenRefreshMEDIUM刷新逻辑未绑定设备通过不一致UserProfileLOW日志泄露手机号MEDIUM日志泄露 PII一致但级别不同CouponDistCRITICAL并发超发HIGH缺少数据库唯一约束不一致PushNotify通过LOW第三方 API Key 硬编码不一致ExportReportMEDIUMCSV 注入通过不一致PasswordResetCRITICAL越权重置密码HIGH校验不严部分一致CacheLayerMEDIUM缓存穿透无防护通过不一致RateLimiter通过通过一致PayCallbackCRITICAL回调签名未校验HIGH签名校验逻辑可绕过部分一致OrderQueryINFO分页参数未上限通过不一致AdminLoginHIGH登录无失败锁定HIGH登录无失败锁定一致ConfigCenterMEDIUM明文存储敏感配置HIGH明文存储敏感配置一致但级别不同LogCollection通过通过一致DataMigrateMEDIUM批量操作无事务LOW操作无回滚机制部分一致WebhookNotifyHIGH重试机制可被滥用通过不一致SearchEngineLOW错误信息泄露堆栈MEDIUM异常响应含内部调用链一致但级别不同这里“一致”不只是问题类型一样级别也在同一档“部分一致”是两边都发现了问题但对严重程度的判断有明显差异“不一致”是一方报了问题另一方直接说“通过”。统计一下完全一致 5 个部分一致 4 个完全不一致 11 个。这数据相当扎心——一致率只有 25%。就算把“部分一致”也算作“都发现了问题”也只有 9 个模块两边同时有动静剩下的 55% 的模块里至少有一个 AI 处于“瞎了”的状态。3.2 达成共识的 5 个模块有什么共性先说那 5 个完全一致的模块UserAuth、SmsSender、RateLimiter、AdminLogin、LogCollection。这些模块有一个很明显的特征——问题要么非常明显要么真的没有。UserAuth 和 AdminLogin 属于前者JWT 密钥写死在代码里、登录接口没有失败锁定这两个问题在公开代码库里出现过成千上万次模型训练数据里全是这种案例属于“看一眼就知道有问题”的类型。SmsSender、RateLimiter、LogCollection 则属于后者本身代码就很短没有外部交互没有敏感操作两边的结论都是“通过”也正常。这给了我一个启发AI 审计在“典型漏洞识别”和“干净代码确认”这两件事上确实稳定但在需要结合业务上下文做判断的地方就很不靠谱了。3.3 分歧最大的几个反面案例挨个说几个印象深刻的。PaymentOrder这个模块问题在于金额比较用了浮点数。Claude 直接标 HIGH理由是金额精度丢失可能导致支付金额计算错误Codex 标 MEDIUM理由是虽然用了 double但实际业务场景中金额差异很小利用门槛高。两边说的都有道理但修复优先级直接差了一档。人工复核后的结论这是支付核心链路哪怕当前利用难度低也必须马上修Claude 的判断更贴近真实业务风险。PushNotifyCodex 报了一个硬编码的第三方推送 API KeyClaude 全程说“通过”。我把代码贴回来仔细看了一遍Codex 是对的那个 Key 就藏在配置类的一个私有常量里。Claude 为什么会漏我怀疑是它只关注了业务逻辑方法没有去细看配置类。这个话题后面还影响到我调整提示词。CouponDist优惠券发放的并发问题两边都发现了但给出的优先级完全相反。Claude 认为并发超发是 CRITICALCodex 认为只要数据库加上唯一约束就能兜底所以是 HIGH。真实情况是这个接口没有加锁也没有唯一索引并发请求确实能超发但生产环境流量不大短期没有直接损失。人工复核后我定级为 HIGHClaude 有点夸大了。WebhookNotify这个最离谱。Webhook 重试机制没有做幂等攻击者可以伪造重复请求触发多次业务操作。Claude 报 HIGHCodex 直接“通过”。我第一反应是 Codex 漏看了重新喂了一遍代码后它才承认“确实存在风险”。这说明 AI 在第一次阅读时漏掉跨方法状态判断的概率不算低。3.4 最可怕的情况两个 AI 都漏了比分歧更值得警惕的是“集体失明”。FileUpload 这个模块Claude 和 Codex 都报了风险但都只盯住了文件类型校验绕过人工复核时我发现真正致命的问题根本不在这——上传接口虽然校验了扩展名但文件名在存入对象存储前没有做路径归一化处理攻击者构造../文件名可以直接覆盖其他用户的文件。这个问题两个 AI 都没看到因为它们只看单文件没有去梳理存储层的调用链。还有一个类似的案例是 PasswordReset。两边都报了“越权重置密码”的风险但没注意到重置令牌存在 Redis 里的过期时间被配置成了 7 天远超设计预期。这个属于“配置漂移”类问题不看部署配置根本发现不了。这给我提了个醒AI 审计本质上是“静态分析 海量经验模式匹配”它看不到运行时的真实状态也看不到配置文件和代码之间的隐性关联。所以它的定位只能是“辅助”不能替代人工复核。4. 分歧从哪来模型特性与上下文陷阱4.1 训练语料决定了“知识边界”Claude 和 Codex 背后的训练数据来源、代码语料占比、漏洞样本的覆盖范围都不一样这不是什么秘密。Claude 在长文本理解和跨文件关联上明显更强所以它对“支付金额精度”“回调签名校验”这种需要结合业务流程判断的问题更敏感Codex 在短代码片段上的执行能力更利索对“硬编码 Key”“类型校验绕过”这种局部模式识别更快。两个模型的“敏感方向”不同结论自然就容易错位。这个现象其实跟团队里两个水平差不多的工程师做 code review 一样A 整天处理支付系统B 天天搞安全巡检你让他们看同一段代码关注点肯定不一样。AI 的“经验”取决于它被训练时看了什么这个没法苛求但使用者得心里有数。4.2 上下文窗口长文件是重灾区实验中一个很明显的规律文件越多、越长两个 AI 的结论就越容易出问题。我把其中几个模块的代码文件平均行数做了个统计凡是平均单文件超过 300 行、且模块总文件数超过 8 个的要么出现一方漏报要么出现结论太泛、没有具体到问题行号。原因不复杂。上下文窗口虽然大但模型对长文本不同位置的注意力权重不是均匀的越靠后的内容越容易“记不住”。这就导致一个结果你贴了 10 个文件进去模型认认真真分析了前 5 个后面 5 个就开始敷衍了甚至直接忽略。碰到这种情况我现在的处理方式是拆。一个模块如果文件太多就按依赖关系拆成“核心逻辑”“对外接口”“数据存储”三个批次分开审计每个批次控制在 4 个文件以内。批次之间通过前一批次的审计结论做衔接让 AI 每次都轻装上阵。4.3 提示词措辞会直接改变结论我拿 UserAuth 模块做了个小测试。第一轮提示词是“请审计这个模块的安全问题”Claude 报了 3 个问题Codex 报了 5 个。第二轮我把提示词改成“请审计这个模块重点关注敏感信息泄露和认证绕过”两边报的问题数量都变了而且涉及的具体漏洞类型也往提示词的方向偏。这说明 AI 审计的“召回率”和“精确率”跟提示词强相关。提示词给的范围太窄会漏报太宽会大量误报。平衡点是把审计重点列出来但同时明确“如果发现其他类型的问题也请指出”留给它自由发挥的空间。4.4 达成共识的模块有什么共同特征回头看那 5 个一致结论的模块除了“问题明显”和“真的干净”还有一个共性模块职责都非常单一。UserAuth 只做认证RateLimiter 只做限流没有掺杂其他业务逻辑。这种模块AI 理解起来不费劲不太需要跨模块推演结论自然稳定。反过来看分歧严重的 WebhookNotify、PushNotify、CouponDist几乎都是“既要处理外部请求又要操作业务数据还要调第三方服务”的复合型模块。这种模块的审计需要人拿着时序图慢慢捋AI 让人省心的地方是能快速筛出可疑点但你要完全交给它被坑的概率是 75%。5. 实操一套可复用的混合审计流程5.1 先跑静态扫描再用 AI 做深度解读经历过这次实验后我现在已经不看 AI 的“原始结论”了而是用一套混合流程。第一步永远是静态分析工具。Java 项目我习惯用 Semgrep 加 SpotBugs先全量扫仓库拿到带行号的候选问题。这一步的好处是快、全、可重复缺点是一堆误报。第二步才轮到 AI——把静态扫描命中的文件按照模块打包连同样本问题一起丢给 Claude Code 或 Codex让它们判断“这些被工具怀疑的地方是否真实可利用”。这套组合拳的效果立竿见影。AI 相当于给静态分析结果做了一层“人工复判”把那些“看起来危险但实际不可达”的路由过滤掉再把误报率压下来。同时因为静态扫描已经标了行号AI 不用满文件乱翻上下文截断的问题也缓解了不少。5.2 并行审计与差异比对脚本两个工具并行审计后差异比对这一步我直接用脚本跑不靠肉眼。思路很简单把两边输出的 JSON 格式问题清单喂进一个 Python 脚本按“模块名—问题类型”做键比对两边是否命中相同问题再按严重级别做差值。import json claude json.load(open(claude_result.json)) codex json.load(open(codex_result.json)) claude_map {} for item in claude[issues]: key (item[module], item[type]) claude_map[key] item codex_map {} for item in codex[issues]: key (item[module], item[type]) codex_map[key] item common set(claude_map) set(codex_map) only_claude set(claude_map) - set(codex_map) only_codex set(codex_map) - set(claude_map) print(两边都命中:, len(common)) print(只有 Claude 命中:, len(only_claude)) print(只有 Codex 命中:, len(only_codex))输出结果后我先看common这部分是两边都认可的问题优先修复再看only_claude和only_codex这些是分歧点交给人工复核。有一说一这个流程跑下来人工需要盯的点位比我预想中少很多大概只有总数的三成。5.3 人工复核优先级排序人工复核是躲不掉的环节但可以排序优化。第一优先级永远是两边都命中的 CRITICAL/HIGH 问题。这类问题基本不是误报直接安排修复。第二优先级是单边命中但指向敏感操作模块的问题比如支付、鉴权、越权接口哪怕只有一个 AI 报了也要认真看一遍。第三优先级才是其余分歧项。对于分歧项的判断我有个土办法看这个问题是否依赖“真实业务状态”。如果它只在特定并发量下才出问题或者需要复杂调用链才能触发那就得提高警惕因为 AI 恰恰在这些地方最弱如果只是代码风格或依赖版本类建议直接进待办列表就行。5.4 审计报告模板最后产出的审计报告我是按这个结构写的模块名称 - 整体结论建议重新Review / 可上线 / 需修复后合入 - 风险问题清单级别-位置-问题描述-修复建议 - 与 AI 审计结果的比对说明为什么采纳/不采纳某条结论 - 回归验证方案报告不追求长追求可执行。每条问题必须能对应到一个具体文件和一个具体修复动作否则就不算“修复完成”。AI 生成的建议里像“建议对参数进行严格校验”这种正确的废话我一律会打回重写直到它能说出“校验哪些参数、用什么规则校验、放在哪一层校验”。6. 常见坑与避坑经验6.1 工具安装与运行环境的坑Claude Code 和 Codex CLI 都要求 Node.js 18我一开始在公司的老服务器上跑Node 还是 14npm install 直接报错。升级 Node 后没问题但注意不要乱用 nvm 切版本切到一半两个 CLI 的全局命令会互相踩。Codex CLI 有时会提示“无法解析 endpoint请检查代{点}理配置”——这是我见过的报错里频率最高的一条通常是运行环境里的网络代理设置没对上。我不展开讲怎么配代理我只说一句如果你在公司内网先确认全局代理和 Codex 的配置文件里指向一致不然请求会打到奇怪的地方去。还有一个常见情况是claude : 无法将“claude”项识别为 cmdlet、函数、脚本文件或可运行程序的名称这通常是 npm 全局 bin 目录没加到系统 PATH不是工具坏了把npm prefix -g的输出路径加进 PATH 即可。6.2 让 AI 自己翻文件 vs 手动贴代码结果差很多实验里我发现一个使用习惯上的坑Claude Code 和 Codex CLI 都支持读取本地文件你让它“审计src/main/java/com/example/Auth.java”它会自己去翻。这很省事但结果有时反而不如“把文件内容直接贴进提示词”来得稳。原因是我贴代码时其实做了预处理——我把非核心依赖、DTO 类、冗余 import 手动静音了。AI 自己翻文件时会被这些无关代码分散注意力导致该看的方法体没看仔细。所以我现在的做法是先用脚本把模块里最核心的 3 到 4 个文件提取出来代码做一次瘦身再喂给 AI。瘦身不是删逻辑是删掉跟审计目标无关的样板代码。6.3 两个 AI 结论打架时怎么办别试图让它们“当场辩论”我试过结果很离谱——Claude 会为了迎合你上一个问题而修改自己的初始判断也就是所谓的不稳定性。正确做法是把两边结论摘出来列成对比表然后通过人工补测来判公平。怎么补测最常见的是写个一次性单测或者 curl 脚本构造攻击流量打一下。比如 PaymentOrder 那个金额精度问题我直接传了两个差距极小的 double 值进去根本不需要讨论谁的 AI 更聪明实验结果自然会说话。能复现的一定存在不能复现的再查是不是其他约束在兜底。6.4 我的最终建议先说结论AI 代码审计必须用但不能裸用它最大的价值是帮你把静态扫描的海量候选问题快速收敛成 20% 左右的高危点省的是“找问题”的过程“验证问题”这个环节你永远缺不了。我现在已经把这个流程固化了一个新模块合入前先跑 Semgrep 扫描再让 Claud Code 和 Codex 并行审计比对差异后人工复核高危点最后附上审计报告合入。这个流程跑下来我个人的体会是花在人工复核上的时间已经比最初直接人肉看代码少了四成以上但安全性一点没打折扣。最后再分享一个小技巧审计完的模块我会保留当时的提示词和 JSON 结论放到仓库的docs/security-audit/目录下。下次改代码回归审计时直接复用同一个提示词跑一遍对比新旧报告AI 是不是遗漏了新变更引入的问题一眼就能看出来。这个习惯帮我避免过好几次改 A 改出 B 的意外事故值得你也试试。