TL;DR:AI驱动的代码安全检测新方案
基于模式匹配的代码扫描工具(例如 Semgrep、Snyk、CodeQL)确实功能强大,但其检测能力几乎完全依赖规则库。换句话说,规则库中收录的漏洞类型能够被准确捕获,而规则未曾覆盖的漏洞则会被遗漏。在实际开发过程中,真正的安全缺陷往往表现为"结构变体"——source 与 sink 之间可能相隔多层函数调用,污点数据可能经过重命名或变形,甚至某些 CVE 的漏洞模式只需稍作伪装,规则扫描器就可能完全无视。

mythos-agent 项目的核心目标,是为传统静态应用安全测试(SAST)体系注入 AI 能力。其设计思路非常清晰:在规则扫描的基础上,引入大语言模型(LLM)进行"假设生成",再对这些假设进行系统化验证——简而言之就是"大胆假设,小心求证",专门用于填补规则库无法覆盖的检测盲区。
本文将深入探讨这套检测流水线的设计原理,展示它能够发现哪些传统规则扫描器无能为力的安全缺陷,同时也会坦诚分析它在哪些场景下仍然存在局限性(这部分内容集中在第六章,如果希望快速了解可以直接跳转)。
npx mythos-agent scan # 纯规则扫描,不需要 API key
npx mythos-agent hunt # 完整 AI 假设 + 验证流水线
一、为什么传统扫描器会漏掉结构变体
基于模式匹配的扫描器,其规则集本质上是一组有限的 (sink, source, condition) 三元组。然而,人类在代码审计时所采用的心智模型要灵活得多——审查者会注意到某段代码对同一行数据先读取后写入却未加锁,会发现某个处理器将用户传入的路径直接拼接到配置根路径上但未处理符号链接,会怀疑某个 eval 函数接收了从三层函数之外经过 stringify 处理的值。
用一个具体例子就能说明问题。Semgrep 默认的 TypeScript 规则集能够检测到下面这种写法:
app.get('/run', (req, res) => {
eval(req.query.code); // 会被标记:eval() 接受了请求输入
});
但下面这个变体,虽然本质上与上述写法属于同一类安全缺陷,规则扫描器却无能为力:
function normalise(input: unknown) {
return String(input).trim();
}
function buildPayload(raw: string) {
return normalise(raw);
}
app.get('/run', (req, res) => {
const payload = buildPayload(req.query.code as string);
new Function(payload)(); // 抓不到:sink 不是 eval,source 在两层函数外
});
规则在寻找的是字面形式的 eval(<污点>)。但现实中的安全漏洞往往是 <任意动态执行 sink>(<污点,可能已变形,可能已改名>)。当然,可以针对这个变体再编写一条 Semgrep 规则——但问题在于,只能为"已经想到的"变体编写规则。而与 eval 行为相似的 sink,其集合是开放的,无法穷举。
二、思路:让 LLM 给每个函数"提假设"
mythos-agent 的流水线划分为四个阶段:
Recon → Hypothesize → Analyze → Exploit(默认关闭)
其中最为核心的阶段是 Hypothesize。针对解析器提取出的每一个函数,一个配置了特定提示词(prompt)的 LLM agent 会生成针对该段代码的具体安全假设——不是简单地给出一个笼统的 CWE 分类标签,而是对该函数做出非常明确的声明。
需要特别说明的是,这些假设并非最终呈现给用户的输出结果,而是作为下一阶段的输入数据。
三、Analyze 阶段:给假设打分
另一个 analyzer agent 会携带前一阶段生成的假设,重新审视该函数,依据控制流、输入可达性、sink 特征等维度来判断假设是否真实成立。每条发现结果附带一个 [0, 1] 区间的置信度分数;使用 --severity high 参数时,仅输出置信度超过设定阈值的结果。
将这两个阶段分开设计,这一点至关重要。Hypothesize 阶段允许"大胆猜测",允许产生大量不一定成立的假设——生成假设的成本很低,后续的 analyzer 会负责筛选过滤。而 Analyze 阶段则被要求趋于保守审慎。
如果将这两个步骤合并到同一个 prompt 中,就会退化为"模型既出题又判卷",实际测试表明,这样输出的结果大多是看似合理但实际上并不存在的误报。拆分为两个独立的 agent 之后,--severity high 这类阈值过滤机制才真正发挥了作用。
从一个测试仓库上运行得到的实际输出示例:
✗ src/api/transfer.ts:38
[HIGH, conf 0.88]
Hypothesis: 对 balance 的读-改-写没加行锁,并发请求下可能双花。
Evidence: line 42 读 balance,line 51 写 balance - amount; 作用域内没有 FOR UPDATE / 事务隔离。
Suggested: BEGIN ... SELECT ... FOR UPDATE ... COMMIT, 或 SERIALIZABLE 隔离级别。
四、Structural Variant Analysis:找 CVE 的"亲戚"
给定一个参考 CVE(来自 NVD 或用户提供的补丁),variant analyzer 会在目标代码库中搜索 AST 形态相似的代码区块,然后对输入和 sink 进行语义角色匹配。这一思路与 Google Project Zero 公开的 Big Sleep 技术报告中描述的方法类似,只不过是在一个开源 TypeScript 工具链中实现的。
这个功能真正解决的场景是:"A 模块中的 X 漏洞已经被修复,代码库中是否还存在与修复前 A 模块结构相似的代码位置?" 仅靠 git diff 加正则表达式是无法发现这类变体的,因为变体可以更改变量名、调整语句顺序、甚至将逻辑拆分到辅助函数中。
五、目前是什么样
- 43 个安全扫描分类(其中 15 个已正式上线,28 个处于实验阶段):覆盖范围包括 SQL 注入、SSRF、路径穿越、命令注入、XSS,以及 JWT 算法混淆、session 处理、竞态条件、加密误用、密钥泄露,还有 IaC 配置错误、供应链安全、AI/LLM 安全、API 安全、云配置、零信任架构、隐私/GDPR 合规、GraphQL、WebSocket、CORS、OAuth、SSTI 等多个领域,覆盖面相当广泛。
- 329+ 条内置检测规则,支持 8 种目标编程语言(TypeScript、JavaScript、Python、Go、Java、PHP、C/C++、Rust)。规则采用可组合设计——"SQL 注入"并非一条简单的正则表达式,而是由 N 条更细粒度的规则组合而成(包括字符串拼接到 sink、带污点插值的模板字面量、使用 raw-SQL 转义接口的查询构建器等)。
- 输出格式:支持 SARIF 2.1.0(可直接用于 GitHub Code Scanning)、HTML 报告、JSON(可管道传输给下游工具)。
- 后端支持:兼容 Claude、GPT-4o、Ollama 以及任意 OpenAI 兼容的端点。纯规则模式完全离线运行,无需任何 API key——Hypothesize 阶段为可选项,可根据需要开启。
- 签名与安全:发布产物使用 Sigstore (cosign) 进行签名,每次发布附带 CycloneDX SBOM 清单。
六、坦白讲,还有这些坑没填好
假设驱动的扫描方案当然不是免费的午餐,以下是当前已知的几项局限性:
- 动态类型语言(Python、JavaScript)比静态类型语言的误报率更高。 类型信息是 analyzer 阶段的重要判断依据,缺乏类型信息时置信度整体偏低,高置信阈值过滤掉的内容也相应增多。
- 跨第三方依赖的污点追踪存在信号丢失。 当污点流入没有源代码的第三方依赖时,Hypothesize 阶段只能依据公共 API 进行推测,容易产生过度生成的假设。
- 运行成本问题。 对整个代码仓库运行假设生成,使用 Claude 或 GPT-4o 时成本不低。
--severity high过滤能在一定程度上控制成本;增量扫描(仅扫描发生变更的文件)效果更为显著。建议在 CI 流水线中将扫描范围限定为差异扫描(diff-only)。
七、三条命令先跑起来
最低使用门槛,无需安装配置,不需要 API key(仅使用模式匹配模式):
npx mythos-agent quick # 10 秒快速体检
npx mythos-agent scan # 完整规则扫描
npx mythos-agent hunt # AI 假设 + 验证(需要 LLM 端点)
npx mythos-agent fix --apply # 对高置信 finding 自动生成并应用 patch
本项目采用 MIT 开源协议,v4.0.0 版本已于今日正式发布。如果您手头有希望测试假设生成效果的代码库(公开代码或经过脱敏处理的代码片段均可),欢迎提交 issue——当前最需要收集的反馈是 analyzer 产出的意外误报案例,这对于优化 prompt 非常有帮助。
问题
- "先出假设再验证"这套两阶段设计思路,你们在类似的系统中是否遇到过什么坑?哪些类型的假设特别容易出现偏差?
- 对于动态类型语言的 AST 形态归一化处理,你们有哪些实践经验?是否有可靠的方法在 Python 或 JavaScript 上稳定计算结构相似度?
- 除了 GitHub Code Scanning 之外,你们还见过哪些下游消费者能够完整渲染 SARIF 2.1.0 的所有字段?哪些工具会静默忽略部分字段?
