尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

双AI对账式代码审计:Claude Code与Codex在5个模块中的争议与共识

双AI对账式代码审计:Claude Code与Codex在5个模块中的争议与共识 前一阵子我给自己布置了一个挺枯燥的任务把代码库里十几个长期没人认真审过的模块做一次“底朝天”式复查。复查的方式有点特别——我没有只靠肉眼去扫而是把五个最有代表性的模块抽出来同时丢给 Claude Code 和 Codex 去审计然后我坐在电脑前一条一条地把它们报出来的问题做人工复核。结果有点刺激五份模块代码两款主流 AI 助手最终只在日志配置模块上给出了完全一致的结论。准确说它们只在 1 个模块上达成共识其余要么互相补充要么完全对不上。这篇就记录这次“双 AI 对账式审计”的完整过程包括实验设计、原始提示词、逐模块的结论对比以及我从中总结出来的一套可用流程。1. 为什么这次审计非得拉上两个 AI 当壮丁1.1 代码审计的旧方案已经不太够用了大服务进入维护期之后安全审计往往处于“重要但没人排期”的状态。靠人一行行读代码一个模块几百行还凑合一旦涉及跨模块调用链、历史配置和第三方 SDK效率就会急剧下降。静态扫描工具能抓一部分固定模式比如硬编码密码、明显的注入拼接但误报率感人真正要花时间的反而是去辨别噪音。我需要的不是另一个“报错机器”而是一个能快速理解业务上下文、还能用自然语言解释“为什么这里有问题”的辅助工具。AI 审计刚好能补上这个位置。Claude Code 和 Codex 都能直接面对整个文件分析区别在于它们的报告风格和风险敏感度差异非常大。这次实验之前我本以为两个模型会给出八九成相似的结论结果现实给了我一巴掌。1.2 我选 Claude 和 Codex 的理由选这两款工具没有太多玄学。Claude Code 和 Codex 都是可以直接对着代码库做多文件分析的对话式 AI我的诉求有三个能让我把整个文件内容放进去而不是只贴一个函数片段能结合上下文做数据流分析而不是只看语法当我不认同某个结论时能来回追问。两个工具都满足这三点但风格差异非常明显。Claude Code 更像一个先通读全文、再写总结报告的安全顾问Codex 则更倾向沿着关键函数一条路追到底遇到可疑控制流就停下来标记。这两种风格的差异在后面的实验结果里被放得很大。1.3 先摆明态度这不是一场评测提前说明这不是对两款产品的横向评测样本只有 5 个模块且它们来自我加工过的脱敏代码结论不能推广到所有场景。我的目标是回答一个问题当两个 AI 对同一份代码各自给出结论判断不一致时差异通常出在哪个环节如果这个答案能帮我把“人工复核”的工作量降下来那实验就算值了。2. 实验设计模块选择、提示词与判定标准2.1 五个模块怎么挑出来的五个模块刻意选了不同类型模块类型行数典型风险点A字符串工具库约 120 行动态执行、正则、类型转换B文件上传处理约 200 行用户输入进入文件系统C异步任务队列约 280 行并发共享状态D日志与配置模块约 80 行敏感数据落日志E三方 SDK 调用封装约 150 行外部接口契约每个模块控制在 80 到 300 行因为太大模型容易丢失早期上下文太小又看不出分析能力。模块里的问题故意埋得深浅不均不是为了考谁满分而是为了观察两个模型对不同风险设计的敏感度。2.2 同一份审计提示词原样贴出我把提示词完全一致地发给两个 AI不换说法也不加额外暗示只贴模块代码你是一名资深安全审计工程师。下面是一段代码模块请按如下要求输出审计结果 1. 只报告你确认存在且能给出证据链的问题不要罗列代码风格建议 2. 每条问题必须包含所在文件/函数/行号、数据来源与流向、触发条件、可能影响 3. 按严重程度打标签严重 / 中等 / 轻微 / 无法确认 4. 如果某个环节你认为整体合理请直接写“未发现问题”不要凑数 5. 最后用三句话总结这个模块最值得优先处理的地方。设计这份提示词的几个细节我得解释一下。第一句角色设定是必要的“资深安全审计工程师”这顶帽子会明显影响输出结构去掉它报告会松散很多。第 2 条是最关键的一条没有“证据链”这个硬约束大多数时候你会收到一堆“看起来可能有风险”的废话。第 4 条是为了压制凑数行为可即便这样后面你依然会看到两款 AI 凑数的欲望比我预想得强。2.3 判定“问题成立”的复核标准我没有直接拿 AI 的结论当最终答案而是把两个模型报出的所有问题整理成清单逐条人工复核。复核时只问三个问题问题是否真实存在回到上下文里能否找到确切的那行代码、那条数据流触发条件是否可控需要什么样的输入、依赖、环境才能触发修复成本与危害描述是否匹配AI 有没有把轻微问题夸大成严重漏洞或者反过来。我按四档给每条问题定性严重、中等、轻微、误报。最终“达成共识”的定义也定得很严格两个 AI 都报同一个问题点且危害等级判断一致才算共识只提到同一类现象但等级不同不算。3. 逐模块对账5 份代码、2 份报告、1 轮人工复核3.1 模块 A字符串工具库一次高误报现场模块 A 是一组字符串工具包括安全过滤、数字转换、正则判断共 120 行。里面确实藏了一个真问题parse_condition函数用eval去执行配置项里读到的表达式字符串。def parse_condition(expr: str) - bool: if not isinstance(expr, str) or not expr: return False # 配置来源可能是后台管理接口人工录入时可能被篡改 return bool(eval(expr, {__builtins__: {}}, {}))Claude 只报了这一个分析很短但准确。Codex 报了 5 个结果其中 4 个是误报。它把一个“如果对象存在就返回 True”的写法当成逻辑漏洞又把字符串与字节串拼接的场景描述成“潜在敏感数据暴露”——实际那个函数是格式工具根本碰不到敏感信息。逐条复核后真正需要修的确实只有eval这一个点。这一刻我意识到AI 审计的高“召回”往往伴随更高的“噪音”关键不在于模型报得多不多而在于它给出的证据链能不能说服你。单纯的报告数量在审计场景里没有任何意义。3.2 模块 B文件上传处理关键漏洞与擦肩而过模块 B 处理文件上传固定保存到本地上传目录白名单校验扩展名。问题出在文件名处理save_upload直接拼接用户传入的filename到目标路径没有做任何净化。def save_upload(file_obj, save_dir: str) - str: filename file_obj.filename target os.path.join(save_dir, filename) # 如果 filename 包含路径分隔符或 target 可能跳出 save_dir data file_obj.read() with open(target, wb) as f: f.write(data) return targetClaude 明确指出了这个路径逃逸风险并且说明了数据流filename来自用户请求直接进入os.path.join没有经过basename或随机名改写。Codex 反而报了一个“没有大小限制”——代码里明明有MAX_SIZE判断又报“没有进行文件类型检查”实属无中生有。真正危险的文件路径覆盖问题Codex 完全没提。为什么会漏我猜它的注意力被“扩展名校验是否可靠”这种显眼话题拉着走反而忽略了更基础的文件路径处理。修复方式其实很朴素import uuid filename os.path.basename(file_obj.filename.replace(\\, /)) target os.path.join(save_dir, f{uuid.uuid4().hex}_{filename})两条审计路线对风险优先级的排序差异在这个模块里体现得淋漓尽致。3.3 模块 C异步任务队列两边各自抓了一半模块 C 是异步队列消费逻辑从数据库拉任务、统计计数、调用外部接口。里面有全局计数器自增也有共享连接对象在协程间复用。_counter 0 async def process(record): global _counter _counter 1 # 并发下不是原子操作 conn await pool.acquire() ... await pool.release(conn) # 连接在多个协程间共享复用Claude 报的是“协程并发下共享连接对象可能产生交叉读写”建议按任务独立创建或使用连接池Codex 报的是“全局计数器自增不是原子操作”建议换成原子计数器。两个结论都成立但都不完整。真正的统一诊断是这个模块缺少对“共享可变状态”的统一隔离策略计数器、连接、统计缓存全都裸奔在同一进程里。如果只跟一个 AI 聊我很容易按它给的单一方向改完就收工另一个问题会在生产环境里等着我。双 AI 对账在这里第一次体现出“互相当第二双眼睛”的意义。这句话平时听得多了但真踩一次才会信。3.4 模块 D日志与配置模块唯一达成共识的 1 个模块 D 负责加载配置并生成日志只有 80 行。它的关键问题非常显眼启动日志里直接打印了从配置读取的数据库密码明文。logger.info( connecting to db %s:%s user%s password%s, db.host, db.port, db.user, db.password, )Claude 和 Codex 都把这个点放到了最高优先级给出的修复方向也完全一致不要在日志里输出敏感字段改成从环境变量读取配置加载完成后立刻丢弃明文。这是全程唯一一次两边意见完全对齐。不过我不愿意把这种共识解读成“两个 AI 很可靠”。因为在同一个模块里还有一个catch everything and log的吞异常问题这两款 AI 不约而同地忽略了。被训练语料反复标注过的“经典安全问题”环节AI 的表现会异常稳定一旦问题不在经典模板里它们照样集体失明。3.5 模块 ESDK 调用封装AI 知识停滞后的一地鸡毛模块 E 是一个支付 SDK 的封装包含签名、回调验签、订单查询。Claude 一眼看出某个字段拼接顺序不符合官方文档推荐的规范建议调整Codex 则认为该字段拼接顺序在当前版本中已经被官方接受不需要改。我去查了一圈最新文档后发现两边说的其实都对只是对不同版本正确。这个 SDK 在 AI 训练数据截止之后更新过接口约定新版本修正了签名算法旧版本则保留旧格式。两个模型都被自己训练语料里的“当时版本”束缚住了。结论很明确涉及外部 SDK 时AI 的知识截止日期是一件要命的事。你可以把它当“风险提示器”但最终契约必须查当前版本文档绝不能拿模型的知识当合同。你让一个大语言模型回忆某个 SDK 两三年前的用法它可能头头是道但你要它判断今天线上这个版本该怎么写它和你看文档的速度一样慢。4. 分歧根源拆解从 20 个问题到 6 个真问题4.1 阅读代码的方式全局扫描与调用链追踪从结果反推两款 AI 在阅读代码时的策略确实不一样。Claude Code 的报告更像先做全局扫描找出“哪些危险操作出现次数多”再回头确认优先级Codex 则更倾向沿着关键函数的调用链一路推演从入口到出口遇到可疑控制流就停下来报告。这两种策略决定了它们误报的形状不同。全局扫描容易在“看似调用风险函数、其实数据根本不可达”的地方犯错调用链追踪则容易在“入口链路很长”时丢掉一些不在主链上的分支。了解这一点对我后续使用很有帮助——当一款 AI 报的问题不合常识我会尝试把它当作“另一种视角”而不是单纯当成错误删掉。4.2 知识截止日期一款模型被“过期的正确答案”带偏模块 E 的分歧是最典型的“知识裂缝”。训练语料里一个 API 的行为和参数如果已经过时模型会非常自信地给出错误建议。Claude 对 SDK 新版本的解读停留在旧版规范Codex 虽然表现出“新版本可能已经接受这种写法”的直觉但说不出具体依据。两者唯一的共同点都无法访问最新版官方文档。所以凡是涉及版本敏感的外部接口我会把 AI 结论的优先级调低把官方文档的优先级调高。这不是某一个模型的缺陷所有语言模型都会面临同样的知识保鲜问题。4.3 置信度取向宁可漏报还是宁可误报我这次把两个模型的全部输出按条整理成了表维度包括报告总条数、成立条数、证据链完整度。五个模块的小样本里结论比较有意思模块Claude 报告成立/总数Codex 报告成立/总数是否共识A 字符串工具库1 / 11 / 5否B 文件上传处理2 / 40 / 2否C 异步任务队列1 / 11 / 1否问题点不同D 日志与配置模块1 / 11 / 1是E SDK 调用封装0 / 30 / 1否合计5 / 103 / 101 个模块共识20 条原始问题人工复核后真正成立的真问题只有 6 条。Claude 偏“少而准”Codex 偏“多而杂”。这更像置信度校准策略不同不是谁智商更高。放进真实流程里“少而准”适合处理高风险模块能帮你快速对齐注意力“多而杂”适合做全面盘点能防止漏网之鱼。理想方案永远不是二选一而是把两者叠加。4.4 双 AI 合并的意义交集定风险并集提召回上面这张表就是“双 AI 对账”最直观的价值证明。20 条原始问题里合并去重后需要人工逐条复核的只有 6 条。如果单看 Claude会觉得结论挺靠谱但会漏掉 Codex 独报的计数器问题如果单看 Codex会被一半多的误报淹没同时还会漏掉文件上传模块里的路径逃逸。两者叠加后复杂问题被互补覆盖而唯一达成共识的模块 D 让我可以放心地把大部分注意力集中在最严重的泄露风险上。这才是双 AI 审计的正确用法交集用来定风险优先级并集用来提高召回率人工大脑单独负责最后一公里的判断。5. 把 AI 审计接进日常流程的实操建议5.1 提示词里必须写清的三件事基于这次实验我现在做类似审计时会固定让 AI 回答三件事哪里有问题、为什么这是问题、怎么验证这是问题。角色设定与输出格式必须写明不然报告就会变成散文。我目前最常用的模板是你是一名资深安全审计工程师。以下代码模块请按严格顺序输出 1. 可疑问题清单按严重程度排序 2. 每个问题的证据链文件/函数/行号、数据来源与流向、触发条件、影响范围 3. 每个问题的修复建议精确到需要改哪一行 4. 最后给出一句话总结这个模块现在能不能上线。5.2 让 AI 给“证据链”而不是给“意见”我给自己定了一条铁律凡是能给出文件、函数、行号、数据来源、触发条件的先标记为高优先级凡是只说“可能存在风险”“建议加强校验”这种话的一律标记为待定。AI 最大的迷惑性在于它可以用非常专业的语气说出根本无法复现的风险描述。你顺着它找过去发现那行代码压根不存在或者数据流根本到不了它说的位置。逼它给证据链是筛选掉这类噪音最有效的手段。如果它给不出那就当它没说过。5.3 双 AI 报告合并去重的表格法实际操作中我不会只依赖聊天窗口而是把两份报告复制到一张表里。按模块分 section每条问题一行列包括来源、位置、问题描述、预期等级、我的初步判断。合并时先把两个 AI 报的同一位置问题打上“交集”标签剩余条目全部进入“待复核”。这个流程很笨但很有效。它最大的价值是把“AI 说了什么”强制转换为“我认为什么”。在做完第三步之后我对每个模块的真实风险状态已经心里有数不再需要反复翻聊天记录。5.4 人工复核的最小闭环我的复核流程固定六步确认问题真实存在回到源码找到对应位置分析触发条件需要什么输入、依赖、权限判断影响范围评估是线上阻断还是低频偶发写出最小复现用例至少在本地跑一次实施修复尽量做最小改动补一条回归测试防止同样的问题再次出现。这六步无法外包给 AI至少现在不行。不过有一个小技巧很值修复完成后把改动后的 diff 重新发给任意一个 AI让它检查“修复是否引入新问题”。这个第二轮检查在本次实验里帮我抓到过一次修复不完整——我当时只处理了文件路径逃逸却漏掉了对file_obj.filename可能为None的判空Codex 在复查时补上了这点。5.5 什么样的模块适合交给 AI 当第一道过滤从这 5 个模块的表现看AI 适合处理单一职责、文件数少、逻辑独立、外部依赖不强的模块。这类代码它读得懂、上下文塞得下报告质量也最高。反过来涉及跨服务调用链、复杂状态机、大量历史配置的全局性审计AI 的结论往往会含糊因为它很难在有限上下文里维护完整全局模型。所以我现在使用 AI 审计的原则很简单它做第一道过滤器专门负责把“看起来要重点盯”的名单缩小最耗精力的真正判断永远放在最后一道留给人来做。最后再分享一个这次实验里印象最深的小细节。那个唯一达成共识的日志模块修复起来其实只有三行去掉日志里的password字段改成从环境变量读配置时直接脱敏。可就这么一个问题如果我此前只跟着 Codex 报的“文件包含检查”去翻模块 A或者只跟着 Claude 报的“eval 风险”去翻模块 B可能要迟很久才会注意到它。把两个 AI 放在同一张桌子上让它们的报告互相“对质”已经成了我现在做模块审计的默认起手式。你也可以试试但请记住它们负责把痕迹找出来拿主意这件事还得我们自己来。
返回列表