用 Codex 做 Java 存量商城代码审计:从 SQL 注入到硬编码密钥的风险排查实践
上个月我一次性接手了 6 个 Java 商城项目的遗留代码库说实话打开仓库列表那一刻头皮有点发麻。这批项目的代码量在 8 万到 30 万行之间涉及订单、支付、商品、会员、后台权限这些电商标配模块而且不是同一套脚手架生成的单体、微服务、不同权限框架混着来。时间紧又没有足够人手做一次全面代码走查所以我做了个实验让 Codex 以只读模式把 6 个项目全部“扫”一遍用同一套审查口径输出风险报告然后我再逐份核验。这篇文章我会直接说清楚三件事我的实测环境和方法、Codex 真正能替技术负责人省时间的几个场景以及它给出的那些看着专业、实际需要人兜底的误报和“废话结论”。如果你也打算用 AI 编程工具做存量代码巡检这篇文章里的命令、提示词和核验流程都可以直接抄作业。1. Codex 的定位纠正它先是一个“项目读者”然后才是写代码的人1.1 为什么我选它而不是人海战术很多团队做代码质量盘点第一反应是拉人开会、分模块看代码。但 6 个项目、几十万行代码人力走查的问题很明显成本高、口径不一致、看完后面忘前面。静态扫描工具像 SonarQube、SpotBugs 我也用过它们对空指针、资源未关闭、循环依赖这类确定性规则很敏感但对业务语义几乎没有感知更不会帮你梳理“哪些接口会改到订单状态”这种跨层问题。Codex 不一样。它的强项不是规则完备性而是语义检索和自然语言问答。你可以把它理解成一个读过整个仓库、阅读速度极快、还能按你的提问逻辑整理答案的初级顾问。它会翻 Controller、Service、Mapper XML尝试理解调用关系然后把结论连同文件路径一起摆到你面前。对技术负责人来说这部分恰恰是最耗时间的体力活——找证据链、确认影响面、判断风险等级。1.2 这 6 个商城的实际构成为了让大家对后面的结论有体感我先交代一下这次扫描对象的画像。6 个项目不是同一个公司同一个年代的产物技术栈和架构有明显代差项目编号架构形态核心框架代码量估主要模块备注M1单体应用Spring Boot 2.7 MyBatis-Plus MySQL8 万行B2C 订单支付商品线上运行中M2单体应用Spring Boot 3.1 JPA PostgreSQL12 万行B2C 会员营销新团队维护M3单体应用Spring Boot 2.x MyBatis Spring Security15 万行B2C 复杂促销历史包袱重M4微服务Spring Cloud Alibaba Nacos MyBatis30 万行多商户订单支付服务数量多M5微服务Spring Cloud Gateway MyBatis25 万行多商户商品库存网关链路长M6单体应用Spring Boot Sa-Token10 万行B2C 分销返佣权益逻辑复杂这些数字只是我根据仓库规模和模块数量做的粗估不是精确统计。目的是让大家明白我们面对的是真正意义上的存量系统有跑了好几年的老代码也有刚重构了一半的新模块不是教学项目那种“全项目只有一个 Controller”的情况。1.3 扫描前的安全设定用 Codex 扫代码库第一步不是写提示词而是先想清楚权限边界。我统一使用只读沙箱模式运行配置文件里把批准策略设成不允许任何写操作明确告诉它“不要修改任何文件只输出报告”。这样即使它产生修改意图也不会真的动到仓库里的代码。另外项目里有数据库口令和支付回调密钥这种敏感信息我提前确认了报告输出只保存在本地工作目录避免外泄。2. 6 个仓库喂给 Codex 的实际流程安装、配置与三种扫描姿势2.1 最小可用环境Codex 现在的形态是 CLI 工具安装路径有不少我这边用的是常规的全局安装方式装完以后在终端执行登录流程生成对应的工作目录配置。整个过程不算复杂但有一个容易被忽略的点登录之后一定要确认 CLI 环境能正常访问 API 服务否则后续所有命令都会卡在请求阶段表现就是长时间无响应或者直接报连接错误。初始化配置后Codex 会在目录里生成配置文件全局配置放在用户目录项目配置可以放在仓库根目录。我会在全局配置里写好默认模型和审批策略在具体项目里用项目级配置覆盖避免每个仓库都重复设置。刚开始用的时候我踩过一个坑直接把网上抄来的配置整段粘进去结果字段和当前版本对不上启动时一直提示有未识别的配置项。后来我把配置项逐个对照文档确认了一遍才恢复正常。2.2 我踩过的配置坑模型名不对直接跑不起来有几次我用codex exec跑扫描任务刚发起请求就报错提示指定的模型在当前接入状态下不可用。这个报错非常容易误导人因为它看起来像是权限问题实际上就是 codex.json 里手写的模型名不在可用列表内。我当时的处理方式很简单打开配置确认当前可用的模型标识然后把我填的那个模型名替换掉再重启会话。如果你用的是官方标准渠道直接选默认模型即可如果走了兼容网关接其他模型服务要格外确认模型路由名称和实际模型标识能对上否则就会复现我这个问题。遇到这类报错不要反复重试同样的命令先花五分钟查配置文件多半能定位。2.3 三次扫描试出来的最佳姿势整仓扫描、分域扫描、聚合复盘第一批扫描我图省事把 6 个项目的仓库根目录全部塞进一个会话里希望 Codex 一次性给出横向对比报告。结果很不理想它给出的建议全是“订单接口需要考虑幂等”“支付回调需要验签”这类哪套代码都适用的泛泛之谈没有路径、没有行号、没有具体到某个项目的独有问题。原因我认为是上下文窗口被外层目录结构消耗太多模型对每个仓库的实际代码“记忆”都被稀释了最后只能靠通用常识补全答案。第二批我改成“一仓一会话”单独进入每个仓库跑扫描。效果好很多但遇到 M4、M5 这种代码量大的微服务项目一次提问的输出还是会偏向框架层面对单点问题的深度不够。于是第三批我采用分域扫描把项目按订单域、支付域、商品域、会员域拆开每个会话只负责一个业务域。并行开几个会话同时跑每个会话十分钟左右出报告再把报告合并。走完这轮之后我的结论是整仓扫描只适合代码量 10 万行以内的小项目大项目必须分域而且要明确告诉 Codex 关注哪些模块目录否则它会平均用力输出没有焦点。2.4 固定提示词才能横向对比我做的是 6 个项目的横向健康度盘点所以所有项目都用同一份提示词模板保证口径一致。提示词里我明确指定了六类问题SQL 注入与拼接、未鉴权的后台接口、分布式环境下缺少幂等或并发控制的高风险点、支付退款订单状态流转中的一致性问题、硬编码密钥、事务内远程调用。同时要求输出格式按风险等级分组每条都要有文件路径、行号、调用链证据、修改建议。如果某类问题不存在必须明确写“未发现”防止它为了凑数硬编结论。这份提示词我贴在下面可以直接复制去改你是资深 Java 代码审查员。请以只读方式扫描当前仓库。 重点检查六类问题 1. SQL 注入与拼接MyBatis ${}、JDBC Statement 拼接 2. 未鉴权/越权的后台接口入口 3. 分布式环境下缺少幂等或并发控制的高风险点 4. 支付/退款/订单状态流转中的一致性问题 5. 硬编码密钥、Token、数据库连接信息 6. 事务内出现远程调用或长时间 IO 输出格式按风险等级分组每条给出文件路径、行号、 调用链证据、风险说明、修改建议。 如果某类问题在仓库中不存在请明确说未发现。 不要修改任何文件。用同一份提示词跑完 6 个项目之后我才敢把它们的风险报告放在一起比对。如果每个项目用不同的提问方式得到的结果只能算是零散线索谈不上横向体检。3. 实测中最能省时间的三个场景检索、链路、横向对比3.1 高危项检索直接带“证据链”第一类让我觉得值得写进工具链的是带证据链的高危项检索。传统做法是用 grep 或 IDE 全局搜索关键词比如搜${找 MyBatis 拼接或者搜Runtime找命令执行入口。这招有效但搜完只是拿到一坨匹配行接下来还得人肉确认哪些真正是用户可控输入哪些只是框架内部写法。Codex 在这件事上比我预想的好。它能够从 Mapper XML 里的一个${keyword}一直追溯到 Controller 的请求参数入口中间把 Service 层的调用方法也串起来输出一条相对完整的证据链。我拿其中一个单体商城试过它标记了一个商品搜索接口的 SQL 注入候选点定位到ProductController#search方法、ProductService#searchProduct方法最后落到ProductMapper.xml第 78 行的${keyword}。我用编辑器逐个文件验证确实存在这条链路而且该接口没有做参数白名单。这种“从可疑点到用户入口”的追溯放在以前我需要自己看至少三四个文件现在几轮问答就完成了。还有一次它在一个多商户项目里发现了工具类中的命令执行调用并且标记为“命令注入候选”。我核对后发现该接口确实缺少鉴权只是目前仅在可信内网调用。这类问题不会被日常功能测试暴露但一旦后续把这个接口暴露到公网后果会很严重。这种基于上下文的风险提示纯静态扫描工具给不了需要有一定“理解”能力的模型才提得出来。3.2 跨层问题的快速问答第二类场景是“结构式问答”。我经常在技术评审前需要快速回答一个问题哪些入口会修改订单状态人工做法是搜状态字段的 setter然后一层层往回找调用方到处跳文件非常折磨人。我对 Codex 提了同样的问题它在十几秒内给出了入口清单用户取消订单、超时自动关单、后台人工改单、支付回调更新、售后退款回滚状态。我再逐一抽查了几个入口基本都能对应到具体方法。这个能力对技术负责人特别实用因为做方案评审时最怕遗漏影响面。你要评估一个改动是否安全前提是知道它会波及哪些调用方。Codex 不能替你判断业务上是否合理但它能快速把“涉及同一状态字段的逻辑都摆出来”让人的判断建立在完整的影响面之上而不是靠记忆和跳代码。还有个衍生用法与其直接问“有没有 bug”不如问“这段逻辑被哪些地方复用”。有一次它帮我发现了订单超时关闭任务和一个用户主动取消接口共用同一段库存回补逻辑但超时任务走的是异步线程池上下文里拿不到用户信息导致库存回补记录缺失操作人。这个结论单独看任何一处代码都不会觉得有问题但两边对照之后问题就清清楚楚了。3.3 六套代码的“实现方式普查”第三个场景是我自己实验出来的叫“实现方式普查”。同样是购物车合并逻辑M1 用的是 Redis 临时 Cart 表加定时刷新M2 用的是数据库关联表加事务合并M3 压根没做合并直接粗暴覆盖。我让 Codex 分别在每个仓库里总结该模块的实现方式然后汇总成一张对照表。这种横向对比本质上不是找 bug而是帮助技术负责人判断团队的技术一致性。维护 6 套项目最痛苦的地方在于同一个需求在 A 项目里实现得很优雅在 B 项目里可能是残缺版。有了 Codex 做“语义层面”的检索你可以快速知道哪些项目之间存在重复建设哪些实现是可以用到其他项目里的“最佳实践”。我在其中一轮扫描里发现M4 的支付回调幂等设计明显优于其他项目后面就可以拿它作为标准实现参考给其他项目补课。3.4 使用 Codex 在这种场景下需要注意的问题虽然 Codex 在检索链路和横向对比上帮我节省了不少时间但在实际使用中也需要注意几个问题。首先它对调用链的判断是基于语义理解的推断不是精确的符号分析所以输出结论只能作为线索不能直接作为证据。其次在大型仓库中它的“记忆”会随着上下文变长而变得模糊尤其是跨文件引用特别多的场景下定位会逐渐失真。最后prompt 的质量直接决定输出质量。用大白话问“这个项目有没有问题”得到的就是一段正确的废话问“请找出所有未鉴权的后台接口并给出入口路径”得到的就是可执行的清单。4. 翻车现场那些我核验过的误报和看起来专业的废话4.1 最典型的误报看不到外层拦截器就断定缺并发控制有一次 Codex 标记了某个订单取消接口“并发控制不足”理由是方法体内没有加锁、没有乐观锁版本号。我顺着它给的路径去核对结果发现接口外面套了一层基于 Redis 的分布式锁拦截器锁的 key 是从请求上下文里取的跟订单业务方法根本不在同一个类里。Codex 只读了 Service 实现类的方法体没有识别到 AOP 切面这层逻辑于是给出了一个从局部看“对”、从全局看“错”的结论。这件事给我们提了个醒Codex 的调用链是“语义启发式拼接”不是编译器的调用图分析。它对横切逻辑拦截器、切面、注解的感知尤其弱。以后凡是涉及并发控制的结论我都会先确认有没有 AOP 层面的处理再做定论。4.2 把配置映射当成硬编码业务规则另一个高频误报是把“字典映射”当成“业务规则硬编码”。它在一个项目里看到MapString, String paymentChannelMap里面放了几个支付渠道代码和显示名的对应关系马上标记为“渠道规则硬编码后续变更需要发版”。但实际上这个 Map 只是字符串常量映射真正的渠道启停规则配置在数据库表里由运营后台维护。Codex 没有执行数据库初始化逻辑所以误以为这个 Map 是业务规则源头。这类误报单独看每条都很有道理组合起来会浪费你大量核验时间。我最后养成一个习惯凡是 Codex 报的“业务规则”类问题必须先回答一个问题——“这些数据有没有数据库来源”如果有多半是误报如果确实写死在代码里还要跟着版本走那才是真问题。4.3 上下文窗口不够时输出会退化成“正确的废话”当我试图用一次会话扫完一个 30 万行的微服务项目时Codex 的结论开始变得“安全且无用”建议订单接口加幂等、建议支付回调做验签、建议对管理端接口增加权限校验。这些话单独看全对但没有文件路径、没有行号、没有说明具体哪个服务哪个接口有问题。它们本质上是模型在用通用常识填补上下文缺失。这里我有一个判断标准一条结论如果连“文件路径 行号 具体场景”都没有就默认当作无效输出不进入风险清单。让 Codex 补上证据链如果它给不出具体定位说明它并没有真正分析到那个深度只是泛泛而谈。大项目一定要拆域扫描就是在和这个弱点做对抗。4.4 高成本重构建议要按“需求变更”来审而不是按“AI 建议”来审Codex 在报告里偶尔会附赠“架构级建议”比如某个老模块已经有大量状态字段和 if-else 分支它建议引入状态机模式或者直接改为事件驱动架构。从代码设计角度看你说不出它哪里错但落到实际项目里这种建议几乎等于一次全面重构涉及数据迁移、接口兼容、团队学习成本、回归测试范围根本不是一次代码巡检能覆盖的。我对这类建议的处理方式是不给它单独排优先级而是把它当作“提案”放进需求池用正常的排期评审逻辑去评估。如果一个重构建议不能解决当前明确存在的线上问题也没有对应的人力和测试资源那它再“优雅”也要排队。AI 对“该不该做”没有判断力它只能提示“哪里可能有问题”代价和收益得人来算。5. 拿到扫描报告后的 24 小时技术负责人的处理流程5.1 先把问题分成三色清单扫描报告到我手里之后我不会直接按输出顺序分发处理。第一步永远是分级阻断项、隐患项、风格项。风险级别判定标准处理时效示例阻断项可被外部利用、会导致资金损失或数据泄露当天修复SQL 注入入口、硬编码密钥隐患项特定条件触发、影响一致性或可用性排入迭代幂等缺失、事务内远程调用风格项影响维护性和一致性无直接事故风险慢慢治理重复代码、命名混乱阻断项我会直接拉人开会确定修复方案代码量通常不大比如把${}改成#{}、把硬编码密钥挪到配置中心隐患项回到 JIRA 按迭代排风格项不进本期迭代只记录到技术债清单。这个分类动作本身不复杂但没有报告支撑时你连“哪里有阻断项”都不知道更别说分级了。5.2 只信带路径带行号的结论并且抽验 20%我在核验过程中执行了一个相对笨的办法每个项目抽查 20% 的“证据链条目”打开对应文件确认行号和上下文是否属实。抽查结果直接影响我后续对 Codex 报告的信任系数。如果命中率高之后的结论可以直接分发到对应负责人做二次确认如果连续几个条目都对不上整份报告就降级为“线索池”需要人工逐条核实。这个抽验动作看起来费时间但非常值得。因为你给团队下发问题清单时如果清单里混着误报团队的信任成本会被消耗掉之后你提出的每个结论都要打折扣。宁可自己先花半小时抽查也不要让团队在无效 bug 上浪费时间。5.3 把 Codex 变成周常和合入前审查工具扫描完静态代码之后我开始把 Codex 纳入日常工作流。每周五下午我会让它在当周改动过的目录上跑一次增量审查重点看是否有新增的高危模式新写的 SQL 用了拼接、新加的接口没走鉴权注解、新提交的代码把密钥写进常量类。这类增量审查比全量扫描快得多因为输入范围明确上下文压力小结论也更具体。另一个高频场景是合入前审查。分支合并到主干之前我会用同样口径的提示词跑一遍分支和主干的差异代码重点盯事务边界和远程调用。人工 review 通常关注业务正确性Codex 可以作为规则检查的补充把一些固定模式问题提前拦下来。跑完之后把结论贴在 PR 评论里让开发者在合入前看一遍效率比事后追责高很多。5.4 哪些事现在还不该交给 Codex说了这么多能用 Codex 的场景也得划清楚边界。第一不要让它直接改业务代码。它在只读模式下做审查是一回事让它自主改代码又是另一回事尤其存量项目里“代码能跑”比“代码好看”重要得多。第二不要用它的建议替代架构评审。一个系统要不要引入消息队列、要不要拆服务必须基于业务增速、团队规模和运维成本来判断AI 给不了这些答案。第三不要让它的“重构冲动”污染你迭代节奏。技术负责人最核心的职责是在正确的时间做正确强度的改造而不是把所有优雅方案都堆在一个迭代里。5.5 扫描报告的分发与跟进机制报告出来后我会把阻断项拆成一条条工单绑定到对应仓库的负责人并在周会上过一遍进度。隐患项我只更新到一个风险看板标注“发现时间、涉及模块、建议处理版本”不强制绑定排期。这样做的原因是防止“一次性大扫除”变成失控工程阻断项拖不起必须立刻做隐患项可以有序消化。真正让存量项目健康度改善的不是某一次扫描的爆发力而是持续把小问题按固定节奏清掉的稳定性。跑完这 6 个项目的体检之后我最大的感受不是“AI 要取代技术负责人了”而是它把最枯燥的那部分工作——翻代码、查调用链、找证据——压缩到了几十分钟。真正省下来的不是思考而是找线索的时间。技术负责人一天只有那么多精力把这些精力从“翻找”里解放出来用在风险判断、资源协调和排期优先级上这才是工具的正确用法。最后再分享一个我养成的习惯不要在同一个会话里反复问不相关的问题Codex 的输出是依赖上下文的前面的对话会把后面的结论带偏每个主题单独开一个会话长会话里它会慢慢“忘记”仓库细节开始用常识补位。实测这样做的准确率明显高于把 6 个项目塞进一个会话硬聊。这个工具适合当一个随时能喊来干活的分析员但当它开始说“看起来都很标准”“建议关注通用最佳实践”这类话的时候你就该新开一个会话了。