关于 graph-evolution
graph-evolution 是基于 Trailmark 的代码图演变分析技能,面向比较代码快照并审查安全相关变化的团队。它结合原生结构差异和预分析子图差异,发现文本差异不易呈现的攻击路径、污点与权限变化。
主要功能
- 01构建并预分析两个代码快照
- 02比较节点、边与入口点
- 03检测子图成员变化
- 04分析污点传播与爆炸半径
- 05生成安全导向的差异报告
适用场景
- 01比较两个 Git 引用的结构变化
- 02审计一段提交中的安全演变
- 03发布前检查攻击面与权限边界变化
- 04定位复杂度或污点传播增加的函数
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill graph-evolution安装后在 Claude Code 中输入/graph-evolution确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlassets/trail-of-bits-mark.svgreferences/evolution-metrics.mdreport-format.mdscripts/graph_diff.py详细介绍
Graph Evolution
在两个源代码快照上构建 Trailmark 代码图,并计算结构差异。揭示文本级差异可能遗漏的安全相关变化:新的攻击路径、复杂度变化、影响范围扩大、污点传播变化,以及权限边界调整。
适用场景
- 比较两个 git 引用,了解结构发生了哪些变化
- 审计一段提交范围内与安全相关的演变
- 检测代码变更带来的新攻击路径
- 找出影响范围或复杂度悄然增长的函数
- 识别重构前后的污点传播变化
- 发布前进行结构比较(标签与标签之间,或分支与分支之间)
不适用场景
- 逐行代码审查(使用
differential-review进行文本差异分析) - 单快照分析(直接使用
trailmark技能) - 根据单个快照生成图表(使用
diagramming-code技能) - 变异测试分类处理(使用
genotoxic技能)
应当避免的错误认知
| 错误认知 | 错误原因 | 必须采取的操作 |
|---|---|---|
| “我们只需要结构差异,跳过预分析” | 没有预分析,就会遗漏污点变化、影响范围扩大和权限边界变化 | 对两个快照都运行 engine.preanalysis() |
| “文本差异涵盖了所有变化” | 文本差异会遗漏新的攻击路径、传递性复杂度变化和子图成员变化 | 使用结构差异作为文本差异的补充 |
| “只有新增节点才重要” | 移除安全函数和变化的权限边界同样危险 | 审查删除和修改,不要只审查新增项 |
| “可以忽略低严重性结构变化” | INFO 级变化(如死代码移除)可能掩盖安全检查的移除 | 对每项变化进行分类,并检查被移除的功能是否有替代实现 |
| “一个快照的图就足以进行比较” | 单快照分析无法检测演变——需要变更前后的两个快照 | 始终构建并导出两个图 |
| “工具未安装,我会手动比较” | 手动比较会遗漏图分析能够发现的问题 | 先安装 trailmark |
| “差异结果为空,所以结构没有变化” | trailmark diff 默认将 --language 设为 python,对于其他目标语言会以空数组退出且返回码为 0,因此,无论代码未变化还是语言设置错误,差异结果看起来都一样 | 显式传入 --language 并重新运行,然后再判断是否没有变化 |
前置条件
必须安装 trailmark。如果 uv run trailmark 运行失败,请执行:
uv tool install trailmark
# Python snippets: uv run --with trailmark python - (a tool env is not importable)
不要以“手动比较”或阅读源文件来代替运行 trailmark。必须安装工具并以编程方式使用。如果安装失败,请报告错误。
快速开始
# Compare two git refs (e.g., tags, branches, commits)
# 1. Build graphs at each snapshot
# 2. Run pre-analysis on both
# 3. Compute structural diff
# 4. Generate report
# Step-by-step: see Workflow below
决策树
├─ Need to understand what each metric means?
│ └─ Read: references/evolution-metrics.md
│
├─ Need the report output format?
│ └─ Read: references/report-format.md
│
├─ Already have two graph JSON exports?
│ └─ Jump to Phase 3 (run native diff + graph_diff.py)
│
└─ Starting from two git refs?
└─ Start at Phase 1
工作流
Graph Evolution Progress:
- [ ] Phase 1: Create snapshots (git worktrees)
- [ ] Phase 2: Build graphs + pre-analysis on both snapshots
- [ ] Phase 3: Compute structural diff
- [ ] Phase 4: Interpret diff and generate report
- [ ] Phase 5: Clean up worktrees
阶段 1:创建快照
使用 git worktree 获取每个引用的干净副本,而不影响工作树。
# Create temp directories for worktrees
BEFORE_DIR=$(mktemp -d)
AFTER_DIR=$(mktemp -d)
# Create worktrees (run from repo root)
git worktree add "$BEFORE_DIR" {before_ref}
git worktree add "$AFTER_DIR" {after_ref}
如果比较的是两个目录而不是 git 引用,请跳过此阶段,并在阶段 2 中直接使用目录路径。
阶段 2:构建代码图并运行预分析
为两个快照构建 Trailmark 代码图,并分别运行预分析。预分析会计算影响范围、污点传播、权限边界,并枚举入口点。
from trailmark.query.api import QueryEngine
def build_and_export(target_dir, output_path, language="auto"):
"""Build graph, run pre-analysis, export JSON."""
engine = QueryEngine.from_directory(target_dir, language=language)
engine.preanalysis()
json_str = engine.to_json()
with open(output_path, "w") as f:
f.write(json_str)
return engine.summary()
import tempfile, os
work_dir = tempfile.mkdtemp(prefix="trailmark_evolution_")
before_json = os.path.join(work_dir, "before_graph.json")
after_json = os.path.join(work_dir, "after_graph.json")
before_summary = build_and_export(
"{before_dir}", before_json
)
after_summary = build_and_export(
"{after_dir}", after_json
)
检查摘要输出,确认两个代码图均成功构建。如果任一构建失败,请使用显式语言名称或逗号分隔的语言列表重新运行,而不要使用 auto。
阶段 3:计算结构差异
运行两项:
- Trailmark 原生结构差异工具,用于节点、边和入口点
- 插件的
graph_diff.py辅助工具,用于子图成员变更
使用阶段 2 中的同一个 work_dir,并传入阶段 2 构建时使用的同一个 --language 值。trailmark diff 默认将该标志设为 python,因此对于任何其他目标语言,默认设置都会以状态码 0 退出,并写入空数组,而不会报告不匹配。
trailmark diff --json --language auto "{before_dir}" "{after_dir}" > "{work_dir}/trailmark_diff.json" || \
uv run trailmark diff --json --language auto "{before_dir}" "{after_dir}" > "{work_dir}/trailmark_diff.json"
uv run {baseDir}/scripts/graph_diff.py \
--before "{before_json}" \
--after "{after_json}" > "{work_dir}/subgraph_diff.json"
如果阶段 2 使用了显式语言,或使用了逗号分隔的语言列表而不是
auto,请在此处使用相同的值。
如果任一差异命令失败,或写入了空 JSON 文件,请停止并报告错误,不要继续阶段 4。
如果 trailmark_diff.json 中的 nodes、edges 和 entrypoints 数组全部为空,说明要么结构没有变化,要么两个快照解析出的图几乎为空。请根据阶段 2 的图摘要判断:如果任一快照的节点数为 0,或相对于目标而言少得不合理,就说明解析未能识别代码——请显式指定语言集合(rust、solidity、python,rust),然后重新运行。两个快照的节点数都正常且差异为空,则说明结构确实稳定。
原生 Trailmark 差异包含:
| 键 | 内容 |
|---|---|
summary_delta | 节点/边/入口点数量的变化 |
nodes.added | 新增的函数、类、方法 |
nodes.removed | 已删除的函数、类、方法 |
nodes.modified | CC、参数或代码行范围发生变化的函数 |
edges.added | 新增的调用/继承/导入关系 |
edges.removed | 已删除的关系 |
entrypoints | 新增、删除和修改的入口点 |
子图差异包含:
| 键 | 内容 |
|---|---|
subgraphs | 各子图的成员变更(tainted、high_blast_radius 等) |
阶段 4:解读差异并生成报告
读取两个差异 JSON 文件,并生成一份以安全为重点的 Markdown 报告。 完整模板请参见 references/report-format.md。
解读优先级(从高到低):
- 新增受污染路径——进入
tainted子图的节点,尤其是同时出现在指向敏感函数的新增边中的节点 - 权限边界变更——结合原生入口点/边差异和子图差异,识别新增或移除的信任转换
- 攻击面扩大——从
trailmark_diff.json中识别新增入口点,尤其是untrusted_external - 影响范围扩大——进入
high_blast_radius的节点 - 复杂度激增——受污染节点或可从入口点到达的节点的 CC 增幅 > 3
- 结构新增——新增节点和边(需要审查)
- 结构移除——确认已移除的安全函数是否已被替换
将结构变更与 git diff {before_ref}..{after_ref}
交叉比对,为发现结果补充源代码层面的背景。
严重性分类:
| 严重性 | 结构信号 |
|---|---|
| 严重 | 新增通往敏感函数的受污染路径,移除认证边界 |
| 高 | 新增入口点且影响范围扩大,受污染节点的 CC 大幅增加 |
| 中 | 新增跨越信任边界的边,CC 适度增加 |
| 低 | 新增节点,但无法从入口点到达 |
| 信息 | 移除死代码,复杂度降低 |
详细的指标定义请参见 references/evolution-metrics.md。
阶段 5:清理
报告写入后,移除 Git 工作树:
git worktree remove "{before_dir}"
git worktree remove "{after_dir}"
Diff 参考
trailmark diff --json --language auto BEFORE AFTER
uv run {baseDir}/scripts/graph_diff.py [OPTIONS]
trailmark diff --language 默认为 python。对于其他语言的目标,该默认设置仍会以状态码 0 退出,并输出格式正确、nodes、edges 和 entrypoints 数组均为空的 JSON,因此请务必传入该标志:auto 会检测并合并目标下找到的所有受支持语言;单个名称(rust、solidity)或逗号分隔的列表(python,rust)则会指定明确的语言集合。如果快照中没有可解析的内容,auto 会明确报错 No supported languages detected under <path>,这正是你想要的结果。先确认语言,之后空 diff 才能作为没有任何变化的证据。
使用 trailmark diff 进行:
- 节点/边变更
- 新增/移除/修改的入口点
- 易读的结构差异报告
使用 graph_diff.py 进行:
- 从
engine.preanalysis()得出的子图成员变更 tainted、high_blast_radius、privilege_boundary及相关集合
| 参数 | 默认值 | 描述 |
|---|---|---|
--before | 必填 | “之前”图 JSON 的路径 |
--after | 必填 | “之后”图 JSON 的路径 |
--indent | 2 | JSON 输出缩进 |
graph_diff.py 输入格式:来自 engine.to_json() 的 Trailmark JSON 导出内容。
graph_diff.py 输出:节点、边和子图的 JSON 结构差异。
质量检查清单
交付报告前:
- 两个图均成功构建(检查摘要)
- 已对两个快照运行预分析
- 已计算原生 Trailmark diff(
trailmark_diff.json);如果结果为空,则两个快照的 Phase 2 节点计数均非零,因此空结果意味着结构稳定 - 已计算子图 diff,且结果非空(
subgraph_diff.json) - 已解读所有子图变更(tainted、爆炸半径等)
- 关键发现均包含证据(节点 ID、边差异)
- 已为所有发现指定严重级别
- 已通过 git diff 交叉引用补充源代码上下文
- 已清理工作树(或移除临时目录)
- 报告已写入
GRAPH_EVOLUTION_*.md
集成
trailmark skill: Phase 2 使用 trailmark API 构建图并进行预分析。 所有 trailmark 查询模式均可用于任一快照的引擎。
differential-review skill: 使用 graph-evolution 进行结构分析,使用 differential-review 进行逐行代码审查。两者相辅相成:graph-evolution 能发现文本 diff 无法捕捉的攻击路径,而 differential-review 则提供 git blame 上下文和微观对抗性分析。
trailmark-review-gate skill: 当分支、拉取请求、修复提交或发布 diff 需要生成 PASS/WARN/FAIL/UNKNOWN 结构审查包时,在 graph-evolution 之后使用 trailmark-review-gate。该门禁会对 graph-evolution 输出应用确定性的审查规则;它不能取代人工审查。
genotoxic skill: 如果 graph-evolution 发现新的高 CC tainted 节点,请将其交给 genotoxic 进行变异测试分诊。
diagramming-code skill:
生成前后对比图,以可视化结构变更。
使用聚焦于变更节点的 call-graph 或 data-flow 图。
支持文档
- references/evolution-metrics.md — 各项结构指标的含义及其对安全的重要性
- references/report-format.md — 报告模板、严重级别分类及示例发现