几十年来,静态应用安全测试 (SAST) 一直是安全团队扩展代码审查能力最有效的方式之一。
但在构建 Codex Security 时,我们做了一个深思熟虑的设计决策:我们并没有从导入静态分析报告并要求智能体进行漏洞分级开始。我们让系统直接从代码仓库本身开始分析,包括架构、信任边界以及预期行为。在需要人工介入之前,系统会先对发现的问题进行校验。
原因很简单:很多最难发现的漏洞,并不只是数据流问题。当代码看似执行了安全检查,但该检查实际上并不能保证系统所依赖的安全性属性时,漏洞就会发生。换句话说,挑战不仅仅是追踪数据在程序中如何流动,而是确定代码中的防御机制是否真的有效。
SAST 通常被描述为清晰的流水线:识别不可信输入的源头 (Source),追踪数据在程序中的流向,并标记那些未经过滤处理 (Sanitization) 就到达敏感汇聚点 (Sink) 的案例。这是一个优雅的模型,且涵盖了许多真实的漏洞。
但在实践中,为了在大规模代码库上保持可计算性 — 特别是在存在间接调用、动态分派、回调、反射以及严重依赖框架控制流的真实代码库中 — SAST 必须进行近似处理。这些近似处理并非 SAST 的缺陷,而是试图在不执行代码的情况下对代码进行静态推理的必然现实。
但这并不是 Codex Security 不从 SAST 报告开始的原因。
更深层次的问题在于:当你成功追踪了从源头到汇聚点的路径后,会发生什么。
即便静态分析正确追踪了跨越多个函数和层级的输入,它仍需回答那个真正决定漏洞是否存在的问题:
以一个常见模式为例:代码在渲染不可信内容之前调用了类似 sanitize_html() 的函数。静态分析器可以看到过滤器 (sanitizer) 运行了,但它通常无法确定,在面对特定的渲染上下文、模板引擎、编码行为以及后续的下游转换时,该过滤器是否足以保障安全。
这就是棘手之处。问题不仅仅在于数据是否到达了汇聚点,而是在于代码中的检查是否真的按照系统假设的方式约束了该值。
换句话说:“代码调用了过滤器”和“系统是安全的”之间存在巨大的鸿沟。
这里有一个在真实系统中屡见不鲜的模式。
一个 Web 应用程序接收一个 JSON 负载,提取 redirect_url,通过白名单正则表达式对其进行校验,接着进行 URL 解码,最后将结果传递给重定向处理程序。
一份经典的“源头到汇聚点”(Source-to-sink) 报告可以这样描述该流程:
不可信输入 → 正则检查 → URL 解码 → 重定向
但真正的问题不在于检查是否存在,而在于在后续的转换操作之后,该检查是否仍然能够约束该值。
如果正则校验运行在解码之前,那么它是否真的按照重定向处理程序解释的方式,约束了解码后的 URL?
要回答这个问题,就必须对整个转换链进行推理:正则表达式实际允许哪些输入,解码与标准化处理会产生什么结果,URL 解析在各种边缘情况下的行为,以及重定向逻辑最终如何解释和确定 Scheme 与 Authority。
在实践中,许多重要的漏洞都呈现出这种特征:操作顺序错误、部分标准化处理、解析歧义 (Parsing Ambiguity),以及校验与解释之间的不匹配。数据流是清晰可见的,问题在于这些约束在后续转换过程中能否持续生效。
这不仅仅是一个理论模式。在 CVE-2024-29041(在新窗口中打开) 中,Express 曾曝出一个开放重定向漏洞,由于重定向目标被编码及随后被解释的方式,畸形 URL 可以绕过常见的白名单实现。数据流非常直观,而那个更难回答,且决定了漏洞是否真正存在的问题是:在经过转换链之后,校验是否依然有效。
Codex Security 的构建围绕一个简单的目标:通过呈现带有更强证据的问题来减少漏洞筛选和分流成本。在产品中,这意味着利用特定仓库的上下文(包括威胁模型),并在呈现问题之前,先在隔离环境中校验高信号 (High-signal) 问题。
当 Codex Security 遇到一个看起来像是“校验”或“过滤”的边界时,它不会把它当作“已完成的安全检查”。它会尝试理解代码试图保证什么 — 然后尝试证伪该保证。
在实践中,这通常表现为以下方式的结合:
- 结合完整的代码仓库上下文阅读相关代码路径: 就像安全研究员所做的那样,寻找意图与实现之间的不匹配。这包括代码注释,但模型并不一定会相信注释 — 因此,在代码上方加上 //Halvar says: this is not a bug 并不会误导它(如果真的存在漏洞的话)。
- 将问题缩小到最小可验证单元: 例如,围绕单个输入的转换流水线。这样你就可以在不受系统其他部分干扰的情况下对其进行推理。从这个意义上说,Codex Security 会提取极小的代码单元,并为它们编写微型模糊测试工具 (Micro-fuzzer)。
- 分析约束在多次转换中的传播情况: 而不是独立对待每个检查。在适当的情况下,这可以被形式化为一个可满足性问题 (Satisfiability question)。换句话说,我们为模型提供了带有 z3-solver 的 Python 环境,它擅长在需要时像人类一样使用该工具,来解决特别复杂的输入约束问题。这在查看非标准架构上的整数溢出或类似漏洞时尤其有用。
- 尽可能在沙箱校验环境中执行假设: 旨在区分“这可能是一个问题”与“这确实是一个问题”。最有说服力的方式,是在调试模式下编译代码并运行完整的端到端 PoC。
这就是关键的转变:系统不再满足于“已存在检查”,而是致力于“校验不变性 (Invariant) 是否成立,并给出相应的证据”。并且,模型会为该任务选择最合适的工具。
一个合理的反应是:为什么不两者兼顾?先从 SAST 报告开始,然后利用智能体 (Agent) 进行更深层次的推理。
在某些情况下,预计算的发现确实是有帮助的 — 特别是对于范围狭窄的已知漏洞类别。但对于一个旨在结合上下文发现并校验漏洞的智能体来说,从 SAST 报告开始会引发三种可预测的失败模式。
首先,它会导致过早缩小范围。一份发现列表本质上是现有工具已经探查过的地图。如果你将其视为起点,系统就会产生偏见,在相同代码区域投入过多分析精力、使用相同的思维定式,从而漏掉那些不符合该工具“世界观”的问题类别。
其次,它会引入难以消除的隐含判断。许多 SAST 的发现都包含关于过滤、校验或信任边界的假设。如果这些假设是错误的 — 或者仅仅是不完整的 — 将它们喂入推理循环,会使智能体的角色从“调查”转变为“确认或排除”,这并非我们想要智能体做的事。
最后,这会增加评估推理系统的难度。如果流水线从 SAST 输出开始,就很难区分哪些是智能体通过自身分析发现的,哪些是从其他工具继承而来的。如果你想准确衡量系统的能力(这是系统随时间不断改进的必要条件),这种区分至关重要。
因此,我们构建 Codex Security 的初衷是让它从安全研究的起点开始:从代码和系统意图出发,并在打扰人工之前,利用校验手段来提高置信度标准。
SAST 工具在它们的设计初衷上表现卓越:执行安全编码标准、捕获直观的源头到汇聚点 (Source-to-sink) 问题,以及在大规模场景下以可预测的权衡来检测已知模式。它们可以成为纵深防御体系中的强力组成部分。
这篇文章缩小焦点,探讨为什么一个旨在推导行为并校验发现结果的智能体 (Agent),不应该在开展工作时被锚定在一份静态的发现列表上。
此外,纯粹的“源头到汇聚点”思维还存在一个相关的局限性,值得点出:并非每个漏洞都是数据流问题。许多真实的失效是状态和不变性 (Invariant) 问题 — 例如工作流绕过、权限缺失以及“系统处于错误状态”的漏洞。对于这些类型的漏洞,受污染的值 (Tainted value) 并不一定会到达某个单一的“危险汇聚点”。风险在于程序中那些被默认始终成立的前提。
我们预见安全工具生态系统将持续改进:静态分析、模糊测试 (Fuzzing)、运行时防护 (Runtime guard) 以及智能体工作流都将各司其职。
我们希望 Codex Security 擅长的是对安全团队而言成本最高的部分:把“看起来可疑”的问题,转变为“确认存在的漏洞”,并说明其触发方式,以及给出符合系统设计意图的修复方案。
如果你想了解更多关于 Codex Security 如何扫描代码仓库、校验发现结果并提出修复方案的信息,请参阅我们的文档(在新窗口中打开)。


