小岛AIAI 航行日志

小岛 / 岛刊社 / 文章

Codex 写的代码再让 Codex 查,我不太敢全信

小岛AI

OpenAI 悄悄开源了 Codex Security,一天涨了两千星。工具是好工具,但它自己在覆盖率报告里留了 unknown 这一档,扫描还按 token 收钱。要不要接进 CI,代价得先算清楚。

AI 编程开源行业与公司

OpenAI 这次发东西的方式有点怪。

代码先扔上 GitHub,公告一个字没发,结果被 Hacker News 上的人先扒了出来,官方账号才回过神来补了一条推。原话大概是这个意思,我们悄悄放出了开源的 Codex Security CLI,但 Hacker News 在我们来得及在这儿说之前就发现了它。

等我去翻仓库的时候,星标已经从发布当天的一千五涨到三千七,fork 两百多。一天翻了一倍还多。

好家伙,这就是「悄悄」。

东西本身一句话能讲完,一个命令行工具加一个 TypeScript SDK,用来找、验证、修代码里的安全漏洞。装法也简单得没什么好写的。

npm install @openai/codex-security
npx codex-security login
npx codex-security scan .

Apache-2.0 协议,要 Node.js 22 以上、Python 3.10 以上。CI 里不做交互式登录,改成环境变量塞 OPENAI_API_KEY 就行。

到这儿为止,是所有号今天都会写的那一段。我想聊的不在这儿。

我想聊的是,一个天天在你 IDE 里写代码的模型,现在要顺手把审查它自己产物的活也接过去了。以及,这工具的文档里藏着两个字段,坦白得让我有点意外。

静态扫描这块,积怨已久

先说点背景,不然后面那个转折没有重量。

代码安全扫描这行叫 SAST,静态应用安全测试,翻译成人话就是不跑代码,光读源码找漏洞。Semgrep、CodeQL、Bandit,做后端的多少都在流水线里见过其中一个。

这类工具的老毛病是什么,用过的都懂,误报。

它的工作方式是模式匹配,你写了一条规则说「拼接 SQL 字符串的地方标红」,那它就把所有拼字符串的地方都标红,管你那个变量是不是从环境变量里读出来的常量。规则写得松,一次扫描给你吐两千条,团队看两周就麻木了;规则写得紧,真漏洞从指缝里溜走。

我见过太多团队最后的选择是把它挂在流水线上但不阻断,红了就红了,反正没人看。安全部门有报表,研发有清净,各自安好。

这不是工具不行,是模式匹配这条路本身有天花板。一段代码到底危不危险,取决于它周围那一圈东西,谁调它、参数从哪来、有没有中间做过校验。这些信息模式匹配读不到。

Codex Security 的官方说法就是冲着这个来的,它不靠规则命中,靠模型做上下文分析,判断这段代码在它实际所处的环境里会怎么跑,然后给出有意义的发现,还直接产出一个你能 review 的补丁。

流程是三段,扫描、验证、修复。中间那个「验证」我觉得是关键,它是专门用来确认这条发现是不是真的,而不是丢给你一堆疑似让你自己甄别。

坦率讲,这个方向我是认的。误报问题拖了这行十几年,规则派已经卷到头了,换个思路是对的。

然后我在文档里看到了 unknown

上手部分我跳过,直接说文档里让我停了一下的地方。

一次扫描跑完,它会在你本地吐一堆产物文件。findings.json 是漏洞明细带严重级别和修复建议,report.md 是给人看的摘要,results.sarif 是标准格式导出,能直接喂给 GitHub Code Scanning 这类现成的看板。

这些都正常,直到我看到第三个文件,coverage.json。

覆盖率报告,状态分三档,complete、partial、unknown。

意思是这次扫描它自己知道哪些地方看全了,哪些只看了一部分,哪些它自己也不知道有没有看明白。文件里还会列出「未解答的问题」。

我盯着 unknown 这个词看了会儿。

传统扫描器从来不给你这个东西。CodeQL 跑完就是一份命中列表,没命中的地方默认就是干净的,它不会跟你说「这个模块的数据流我没跟下去」。这种沉默不是因为它更强,是因为规则引擎压根没有「我不确定」这个状态,规则匹上就是匹上,没匹上就是没匹上。

模型有。而且 OpenAI 选择把它暴露出来,而不是藏起来假装扫全了。

这是我今天看到最有诚意的一个设计,也是最劝退的一个。

有诚意,是因为它把不确定性明明白白摆在你面前,你至少知道自己的防线上哪块是虚的。

劝退,是因为你现在要面对一个以前不用面对的问题。绿灯不再等于安全,绿灯只等于「它扫过的那部分安全」。你得多看一个文件,才知道这次绿灯有几分成色。

很多团队接安全工具的心理预期是「装上,然后不用想了」。这工具从设计上就不让你这么干。

扫过的地方是清楚的,剩下那片是它自己标出来的未知区

我是真的觉得这一点值得单拎出来说,因为它不只是一个字段的事。你手里所有让模型帮你干活的东西,其实都有这个 unknown,只是绝大多数没写出来。

扫一次要多少钱

第二个让我坐直的细节是 --max-cost。

这个参数用来设单次扫描的花费上限,防止模型用量失控。文档里还有个 --dry-run,本地校验输入,明确说了不触发 API 调用。

反过来读就是,正式扫描是要调模型的,是要花钱的,而且花多少事先不太好说,得给个天花板兜着。

这是 SAST 这行以前没有的成本模型。CodeQL 你想跑一万次也就是那台 runner 的电费,扫描成本约等于零,所以大家的习惯是每次 push 都全量扫一遍,反正不心疼。

现在这个习惯得改。

你要是把 scan . 挂在每个 PR 上全仓库跑,一个中等规模的仓库、一个活跃的团队,那个账单曲线自己想。

好在工具方也想到了,它给了四种扫描粒度,我觉得这几个才是真正决定你能不能用起来的东西。

标准扫描是全仓库分析,适合季度体检,不适合天天跑。 路径扫描只扫指定的服务或者包,改哪扫哪。 diff 扫描扫两个 git revision 之间的变更,这个是给 CI 准备的。 工作区扫描扫暂存区和工作区的改动,这个是给你提交前准备的。

还有一个 deep 模式做更宽的架构级审查,那个明显更贵,属于「重要版本发布前跑一次」的量级。

所以真要接进流水线,我的判断是这样的。

每个 PR 走 diff 扫描,只看这次改了什么,成本可控。全量扫描做成定时任务,一周或者一个月一次,跑在没人加班的时段。deep 模式留给大版本。

它还提供了 install-hook,装一个 pre-commit 钩子,高危发现直接卡住提交。这个我建议慎用,本地开发被一个网络请求卡住 commit 的体验,试过的应该都记得当时的心情。真要装,也是团队讨论过之后统一装,别一个人默默给自己上刑。

另外有个参数我觉得被低估了,--knowledge-base,可以把架构文档喂进去当上下文。

这个东西是模型驱动的扫描器才有的能力。你告诉它「我们这套服务前面有一层统一鉴权网关,所有请求进来都过了 JWT 校验」,它对内层服务那些看起来没做鉴权的接口的判断就完全不一样了。规则引擎理解不了这句话,模型能。

想少收一半误报,这个参数值得花半天写文档。

出题的和判卷的,是一家人

铺垫到这儿,说我最纠结的那一层。

你现在的日常大概率是这样的,用 Codex 或者别的什么让模型帮你写一坨代码,扫两眼觉得没大毛病,合了。

现在 OpenAI 告诉你,这坨代码的安全审查,也可以交给它。

同一家公司,同一个模型家族,写的是它,查的也是它。

我不是在暗示什么阴谋,OpenAI 没有动机故意放过自己写的漏洞,这生意不划算。我担心的是另一个东西,盲区是共享的。

模型的安全意识来自训练数据。它在生成代码的时候没意识到某类问题,是因为这类模式在它的认知里就不算问题。那么在审查的时候,同样的认知会让它同样看不见。

这跟人是一个道理。你写完一个函数自己 review 一百遍,都不如同事扫一眼有用,不是因为你不认真,是因为你脑子里那套假设在写的时候和读的时候是同一套。代码审查这个制度存在的全部意义,就是引入一双不共享你假设的眼睛。

现在这双眼睛,跟写代码那双是同一副瞳孔。

两副镜片一模一样,连看不见的那块也一模一样

说实话我也不确定这个担心有多严重,我没有数据支撑,这更多是一个工程直觉。而且我得承认反过来的论证也成立,甚至可能更有力。

正因为它是写 AI 代码的那个模型,它可能反而最懂 AI 生成的代码会在哪儿出问题。模型写代码有非常固定的坏习惯,错误处理糊弄、边界条件想当然、把示例里的硬编码直接留在生产代码里。这些毛病一个熟悉自己产物分布的模型,理论上抓得比通用规则准。

OpenAI 自己的定位里就有这层意思,他们说这工具是给「大规模采用 AI 编码助手」的组织准备的,是为了更紧的反馈回路。翻译一下就是,我们知道我们的模型在你们仓库里生产了多少代码,所以我们来管管。

两种可能都存在,而且现在没人有数据判定哪个占上风。

我的结论很没劲,但我觉得是对的,把它当第二双眼睛,不要当唯一一双。

你现有的 Semgrep 或者 CodeQL 别撤,规则引擎有一个模型没有的优点,它的失败模式是确定的、可预测的、跟模型不相关的。两套东西的盲区不重合,这才是价值所在。Codex Security 支持导出 SARIF 格式,本来就是为了跟现有流水线并存,不是为了取代谁。

至于人工审查,那更是一点都不能撤。

这一轮大家都在卷刹车

再往外退一步看,会发现这不是一件孤立的事。

同一周,Google 给 Gemini API 的 Managed Agents 加了一个叫 environment hooks 的东西。官方博客写得很清楚,你可以在工具调用前后跑自定义脚本,配置放在 .agents/hooks.json 里,脚本可以直接拒绝这次操作,并且把拒绝的理由塞回模型的上下文里让它知道自己为啥被拦了。

同一批更新里还有 max_total_tokens 预算上限、可以查可以删的沙箱环境 API、定时触发。

你把这些和 Codex Security 的 --max-cost、coverage.json、pre-commit 钩子摆在一起看,会发现两家做的是同一件事。

都在给 agent 装刹车、装仪表盘、装预算表。

前两年这些平台比的是模型多能干,上下文窗口多大,跑分多高。今年下半年这一轮更新,比的东西明显换了,approval 模式怎么设计、任务中断了能不能续、成本怎么封顶、出了事怎么审计、你到底敢不敢让它自己跑完。

厉害了,这行终于开始聊工程了。

这个转向背后的现实很朴素。模型已经强到能自己干活,但企业不敢放,卡在信任上。而信任这个东西不是靠模型再强 10% 换来的,是靠可观测、可干预、可回滚这些无聊的工程能力换来的。

我给 AI 搭脚手架的那点日常,说到底也就是在干这个。让模型能干活的那层工程,八成的代码都不是在增强能力,是在兜住它可能翻的车。

所以看到这一轮平台竞争终于卷到控制面,我是有点高兴的。

那到底要不要装

回到最实际的问题。

如果你的团队现在大量用 AI 写代码,而安全审查还停留在「有个扫描器挂着但没人看报表」的状态,我觉得这东西值得花一个下午试。Apache-2.0,本地跑,--dry-run 先不花钱验证一遍配置,试错成本很低。

上手路径我建议这样,先对一个你最熟的服务跑一次路径扫描,不要一上来全仓扫。跑完先别看 findings.json,先打开 coverage.json,看它说自己看全了多少。这一步能让你对这工具的实际能力形成一个校准,比看多少条发现有用得多。

然后拿一两条它报的高危,人工去核一下是不是真的。核完你心里就有数了。

至于要不要接进 CI,我的建议是先接 diff 扫描,不阻断,跑两周攒数据。两周后拉出来看误报率和成本,再决定要不要升级成阻断。

千万别在还不知道它误报率的时候就设成 required check,那大概率会以整个团队集体绕过它收场,最后比不装还糟。

还有一条,扫描是要把代码送出去的。你们公司对源码出网有规定的话,这件事得先过合规,别自己偷偷装。这不是危言耸听,是很多团队真正卡住的地方。

最后

其实我挺喜欢 OpenAI 这次的姿态。

他们自己在推文里写的是「这是一次早期发布,我们在听你们的反馈」,而且是先扔出来才想起来发公告。文档里也老老实实留了 unknown 这一档。

这跟发布会上那套「全面提升、显著增强」的话术是两种东西。后者听着舒服但你不知道该信几分,前者难看但你知道边界在哪。

我一直觉得,工具告诉你它做不到什么,比告诉你它能做到什么更值钱。因为前者你能围着它做设计,后者你只能围着它做祈祷。

Codex Security 至少给了你一张标注了未探明海域的海图。

至于那片 unknown 里到底有没有礁石,那还得你自己开船进去看。

小心点开。