用 variant-analysis 做变体分析:找到一个 Bug,再把同类搜干净

前言

安全审计里有一件很常见的事:你在某个文件里确认了一个漏洞,修完之后还是不放心——同样的写法会不会在别的模块里再出现一次?漏洞很少只活在被你点开的那一行。同一套开发习惯、一次复制粘贴、一次只改了一处的修复,都会让同一个根因在仓库里留下好几处变体。

手工做这件事通常停在原来那个文件,或者随手写一条 grep。字面量能对上的能找到,换了变量名、换了 API、换了语言的就漏掉。Trail of Bits 把这套安全工程师日常在做的「变体分析」写成了 Agent Skill:variant-analysis。当前插件版本写在元数据里是 2.0.1,作者是 Axel Mierczuk,许可证是 CC BY-SA 4.0。它不负责帮你从零挖漏洞,专门解决「已经找到一个,仓库里还有没有同类」这个问题。

variant-analysis 是什么

variant-analysis 是 Trail of Bits 技能市场(trailofbits/skills)里的一个插件,目录在 plugins/variant-analysis/。它给 Agent 一套可重复的方法:先抽出根因,再写成只命中已知实例的模式,然后一次只放宽一个条件去搜全库,最后做分诊并写出报告。

官方对它的定位很窄,适合这些情况:

  • 已经确认了一个漏洞或逻辑错误,要在整仓里找相似实例
  • 要把已知实例泛化成 CodeQL / Semgrep 规则,覆盖同一类模式
  • 初次发现问题之后,做一次有方法的代码审计,而不是再扫一遍「感觉可疑」的代码
  • 分析同一个根因在不同代码路径上会以什么样子出现

官方也写了不该用它的场景:还没有已知 Bug、只是做一般代码评审、要写修复建议、或者连代码还没看懂。前面两种分别该用同市场里的 audit-context-building 或领域审计 Skill;写修复建议用 issue-writer

Skill 本体是通用的 SKILL.md 格式,Claude Code、Codex、Cursor 这类支持 Agent Skills 的工具都可以加载。插件另外带了一个工作流 /variant-analysis:variants,这是 Claude Code 插件命令,用来在大仓库里并行扫多个扩展轴。

核心方法:五步,而不是一条超级正则

插件把一次变体搜查拆成五步,每一步有对应的策略文档,放在 skills/variant-analysis/references/ 里。Agent 走到哪一步,就读哪一份。

1、先理解原来那处问题。 要抽出的是「为什么错」,不是「这行代码在干什么」。官方建议先问四个问题:危险操作是什么(eval()、拼 SQL、鉴权判断)、什么数据让它危险、缺了哪一层保护、什么上下文让它成立。然后写成一句根因陈述:

This vulnerability exists because [UNTRUSTED DATA] reaches [DANGEROUS OPERATION] without [REQUIRED PROTECTION].

例如:「用户输入到达 eval() 时没有经过净化」。没有数据流的逻辑 Bug 就改写被破坏的不变量,比如「未登录调用者必须返回 False,但两边 ID 都是 null 时返回了 True」。这句话本身就是后面的搜索模式。同时列出变体可能藏在哪些方向:语义相近的标识符、同一种错误的其他写法、空值与边界情况。

2、先写一条只命中已知实例的模式。 用 ripgrep 对原代码做精确匹配,确认它打中、而且只打中那一处。一条匹配数为零的模式说明你对 Bug 的理解不对,后面所有搜索都是在错误代码上校准。

3–4、一次只泛化一个元素。 从精确匹配往模式家族上爬,每改一处就重新跑、把新增命中全部看完。假阳性超过大约一半就停,回退,换另一条抽象路径。不要一次把变量名、函数名、参数位置全换成通配符——噪声来了,你不知道是哪一步引入的。

5、分诊。 判断候选是不是真的同类问题,并带上严重程度。只看片段不够,要读周围函数、调用方、相关值的类型,专门去找「为什么它其实是安全的」:前面的守卫、净化、参数化 API、类型约束。今天没有调用方也不等于否定:无保护的代码仍然是发现,只是严重程度可以标低。

五步之后要写成报告,失败过的模式也要留下,并给出一条能进 CI 的规则,防止修好一处、同类再长回来。报告模板在 resources/variant-report-template.md

工具怎么选

Skill 不绑定单一扫描器。官方给的选择很直接:

  • 快速摸底用 ripgrep:零配置,适合侦察
  • 简单结构模式用 Semgrep:语法容易写,不需要能编译的工程,残缺代码也能跑
  • 要跟踪值有没有从源到汇,用 Semgrep taintCodeQL
  • 跨函数、要做过程间分析,用 CodeQL
  • 代码编译不过时,继续用 Semgrep,不要一上来就上 CodeQL

推荐顺序是:ripgrep 侦察 → Semgrep 迭代模式 → CodeQL 做深层数据流。官方把「我只用 CodeQL」写成反模式:前面那些快的一轮,是在告诉你 CodeQL 该瞄准哪里。

仓库里还带了现成模板。CodeQL 在 resources/codeql/,Semgrep 在 resources/semgrep/,都覆盖 Python、JavaScript、Java、Go、C++。

抽象阶梯:从原句爬到安全属性

模式有不同抽象层级。官方用一段 SQL 拼接作为校准例子。

原始代码:

query = "SELECT * FROM users WHERE id=" + request.args.get('id')

Level 0 是字面匹配,用来证明你理解的就是这一处:

rg 'SELECT \* FROM users WHERE id=" \+ request\.args\.get'

这一层匹配数应当是 1,假阳性为 0。它不是搜索,是校准点。

Level 1 把变量名换成元变量,用来找复制粘贴变体:

pattern: $QUERY = "SELECT * FROM users WHERE id=" + $INPUT

Level 2 放宽结构,例如「任意字符串拼接,并且用在 cursor.execute 里」,命中会到十几、几十条,假阳性开始出现。

Level 3 抽象到安全属性本身,用污点模式:源是 request.args.get / request.form.get,汇是 cursor.execute。覆盖最全,假阳性也最高,必须分诊。

目标不同,停在哪一层也不同:验证某次修复用 Level 0,找复制粘贴用 Level 1,审一个组件用 Level 2,做整仓安全评估才爬到 Level 3。变体搜查里,假阳性超过大约 50% 就该停;如果规则要进 CI 拦截提交,官方给的可接受假阳性更严,大约要低于 5%。

搜查范围必须是整个仓库根目录,不要只搜发现原 Bug 的那个模块。官方把「只在 api/handlers/ 里搜、漏掉 utils/auth.py」写成最常见的失败原因。

安装与启用

Trail of Bits 把这套东西同时做成了 Claude Code 插件和一份可移植的 Skill。安装方式按工具分开看。

1、Claude Code(官方市场)

先加市场,再装插件:

/plugin marketplace add trailofbits/skills

然后可以用菜单选,或者直接装这个插件:

/plugin install trailofbits/skills/plugins/variant-analysis

装上之后可以使用工作流命令 /variant-analysis:variants。参数按 JSON 对象传,不要写成一段散文。bug 必填,建议带上 文件:行号root 默认当前目录;lang 是主语言,用来选工具;out 是报告路径,默认 variant-analysis-report.md

{"bug": "api/auth.py:42 把布尔值和 token 字符串做相等比较", "root": ".", "lang": "python", "out": "variant-analysis-report.md"}

插件 README 写过:主语言源文件大约不到 40 个(排除 vendored 和测试夹具)时,工作流会收窄、只扫一轮,因为这个规模下并行展开没有收益。代码库大、根因表现又多时,它会按扩展轴起并行子代理,循环到不再发现新东西为止。

2、Codex

官方 README 写明 Codex 可以直接加载 Claude 插件市场:

codex plugin marketplace add trailofbits/skills
codex plugin list
codex plugin add variant-analysis@trailofbits

3、Cursor 以及其他支持 SKILL.md 的工具

把完整 Skill 目录放到工具会扫描的位置,不要只拷一份 SKILL.mdreferences/resources/ 是五步策略和规则模板,缺了 Agent 只能靠主文件里的摘要工作。Cursor 会从项目级 .cursor/skills/.agents/skills/,以及用户级 ~/.cursor/skills/~/.agents/skills/ 加载;为了兼容,它也会读 .claude/skills/.codex/skills/

项目内目录结构类似:

.cursor/skills/variant-analysis/
  SKILL.md
  references/
  resources/

officialskills.sh 给出的安装命令是:

npx skills add https://github.com/trailofbits/skills --skill variant-analysis

装好后,在对话里直接问「还有没有同类」就会触发;也可以用 /variant-analysis 显式调用。/variant-analysis:variants 这条带命名空间的工作流是 Claude Code 插件命令,Cursor 里一般不会出现同名斜杠命令。

典型用法

官方给过一个鉴权判断的例子,流程可以直接照着跑。

假设在 api/auth.py:42 看到:

if user.isAuthenticated == request.token:
    return allow_access()

根因不是「这行写得难看」,而是:布尔值和字符串比较恒为假,逻辑被写反了。

先做 Level 0 校准:

rg "user.isAuthenticated == request.token"

确认只有原处一命中之后,把对象换成元变量:

pattern: $USER.isAuthenticated == $INPUT

如果代码库里鉴权属性不止这一个,再沿「语义相近的标识符」往外扩,把 isActiveisVerified 等实际存在的名字加进去,而不是凭空列一份词典。每扩一次都要重新跑、把新命中看完。

对话里不必先写规则。Skill 的触发描述里写了,像「are there others like this?」「is this the same bug?」这种问法就会用它。你可以这样说:

刚才在 api/auth.py:42 确认 user.isAuthenticated == request.token 这个判断是错的。
请按 variant-analysis 做一次变体搜查:先写根因陈述,再用精确模式校准,
然后一次只放宽一个条件搜整个仓库,最后按模板写出报告。

大仓库、在 Claude Code 里已经装了插件时,改用工作流,让它按扩展轴并行扫。

报告至少要留下这些内容:根因陈述、试过的模式(层级、工具、命中数、真阳性、假阳性)、已确认变体(位置、严重程度、状态)、以及按原因归组的假阳性。最后用命中最多变体的那条模式,整理成可以放进 CI 的 Semgrep 或 CodeQL 规则。

适用场景与注意事项

适合已经有一个锚点的人:安全工程师修完一处漏洞要扫回归,开发者刚合入 CVE 补丁想看 fork 出来的路径有没有漏网,或者要把已知 XSS / 注入写成仓库级规则。它不替代第一次审计,也不替代修复方案的撰写。

官方把搜查失败的原因写得很具体,这几条值得对着自查:

  1. 范围太窄:只搜原 Bug 所在模块。变体出现在别的目录才是常态。
  2. 模式太死:只搜原来那个属性名。isAuthenticated 出问题,isActive / isAdmin / isVerified 往往是同一类检查。
  3. 只追一种漏洞形态:根因是「条件为假时却放行」,它还可能表现为空值相等绕过、文档与实现相反、条件写反。
  4. 只测快乐路径:没拿 null、空集合、未登录用户去压。两边都是 None== 为真,是授权绕过里很常见的一种。
  5. 泛化太快:一次改多个元素,噪声无法归因。

分诊时还有两个官方强调的边角。一是文档和实现相反:函数名或 docstring 写着 DENY,返回值却在授权成功时给了 True,这种函数的每个调用点都可能是发现。二是不要把「当前没有调用方」直接判成误报,标低严重程度即可。

相关 Skill 也在同一个市场里:做深层过程间分析可以配合 codeql,简单模式配合 semgrep,处理扫描结果配合 sarif-parsing。代码还没看懂时,先用 audit-context-building

小结

variant-analysis 做的事情很单一:你已经有一个 Bug,它帮你按根因把整仓同类找出来,并把搜查过程写成可复查的报告和 CI 规则。方法本身并不新,安全工程师一直在做;写成 Skill 之后,开发者也可以在修完一处问题后,让 Agent 按同一套步骤把漏网的复制粘贴和平行实现扫一遍。

官方地址:

  • Skill:https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis/skills/variant-analysis
  • 插件说明:https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis
  • 技能市场:https://github.com/trailofbits/skills
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜