比较两个源代码快照(Git提交、标签或目录)的Trailmark代码图,揭示与安全相关的结构变化。 能够检测文本差异无法发现的新攻击路径、复杂度变化、影响范围增长、污点传播变化和权限边界修改。 适用于比较提交或标签之间的代码、分析结构演变、检测攻击面增长、审查审计快照之间的变化, 或发现文本差异遗漏的安全相关变化。
图演化
在两个源代码快照上构建Trailmark代码图,并计算结构差异。揭示文本级差异无法发现的安全相关变化:新攻击路径、复杂度变化、影响范围增长、污点传播变化和权限边界修改。
使用场景
- 比较两个Git引用以理解结构上的变化
- 审计一系列提交的安全相关演化
- 检测代码变更产生的新攻击路径
- 发现影响范围或复杂度悄然增长的函数
- 识别重构中的污点传播变化
- 发布前的结构比较(标签到标签或分支到分支)
不适用场景
- 行级代码审查(使用
differential-review进行文本差异分析) - 单快照分析(直接使用
trailmark技能) - 从单个快照生成图表(使用
diagramming-code技能) - 变异测试分类(使用
genotoxic技能)
应拒绝的理由
| 理由 | 错误原因 | 正确操作 |
|---|---|---|
| "我们只需要结构差异,跳过预分析" | 没有预分析,会遗漏污点变化、影响范围增长和权限边界偏移 | 在两个快照上运行engine.preanalysis() |
| "文本差异已经覆盖了变化" | 文本差异会遗漏新攻击路径、传递性复杂度变化和子图成员变化 | 使用结构差异补充文本差异 |
| "只有新增节点重要" | 删除的安全函数和偏移的权限边界同样危险 | 审查删除和修改,而不仅仅是新增 |
| "低严重性的结构变化可以忽略" | INFO级别的变化(如死代码删除)可能掩盖删除的安全检查 | 分类每个变化,审查删除是否被替换 |
| "单个快照的图足以比较" | 单快照分析无法检测演化——需要前后两个快照 | 始终构建并导出两个图 |
| "工具未安装,我手动比较" | 手动比较会遗漏图分析能捕捉的内容 | 先安装trailmark |
前提条件
必须安装trailmark。如果uv run trailmark失败,运行:
uv pip install trailmark
不要退而求其次使用“手动比较”或读取源文件来替代运行trailmark。必须安装并程序化使用该工具。如果安装失败,报告错误。
快速开始
# 比较两个Git引用(例如标签、分支、提交)
# 1. 在每个快照上构建图
# 2. 对两者运行预分析
# 3. 计算结构差异
# 4. 生成报告
# 逐步操作:见下方工作流
决策树
├─ 需要理解每个指标的含义?
│ └─ 阅读:references/evolution-metrics.md
│
├─ 需要报告输出格式?
│ └─ 阅读:references/report-format.md
│
├─ 已有两个图JSON导出?
│ └─ 跳转到阶段3(运行原生diff + graph_diff.py)
│
└─ 从两个Git引用开始?
└─ 从阶段1开始
工作流
图演化进度:
- [ ] 阶段1:创建快照(git worktrees)
- [ ] 阶段2:在两个快照上构建图 + 预分析
- [ ] 阶段3:计算结构差异
- [ ] 阶段4:解释差异并生成报告
- [ ] 阶段5:清理worktrees
阶段1:创建快照
使用git worktrees获取每个引用的干净副本,而不干扰工作树。
# 为worktrees创建临时目录
BEFORE_DIR=$(mktemp -d)
AFTER_DIR=$(mktemp -d)
# 创建worktrees(在仓库根目录运行)
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"):
"""构建图,运行预分析,导出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:
trailmark diff --json "{before_dir}" "{after_dir}" > "{work_dir}/trailmark_diff.json" || \
uv run trailmark diff --json "{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"
如果任一diff命令失败或写入空的JSON文件,停止并报告错误,而不是继续到阶段4。
原生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}交叉引用,为发现添加源代码级上下文。
严重性分类:
| 严重性 | 结构信号 |
|---|---|
| CRITICAL | 新的到敏感函数的污点路径,删除的身份验证边界 |
| HIGH | 新入口点 + 高影响范围,污点节点上CC大幅增加 |
| MEDIUM | 新的跨信任边界边,CC适度增加 |
| LOW | 新增节点但不可从入口点到达 |
| INFO | 死代码删除,复杂度降低 |
有关详细指标定义,请参见references/evolution-metrics.md。
阶段5:清理
报告写入后删除git worktrees:
git worktree remove "{before_dir}"
git worktree remove "{after_dir}"
差异参考
trailmark diff --json BEFORE AFTER
uv run {baseDir}/scripts/graph_diff.py [OPTIONS]
使用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差异已计算且非空(
trailmark_diff.json) - [ ] 子图差异已计算且非空(
subgraph_diff.json) - [ ] 所有子图变化已解释(污点、影响范围等)
- [ ] 关键发现包含证据(节点ID、边差异)
- [ ] 所有发现已分配严重性级别
- [ ] 通过git diff交叉引用添加了源代码级上下文
- [ ] Worktrees已清理(或临时目录已删除)
- [ ] 报告已写入
GRAPH_EVOLUTION_*.md
集成
trailmark技能:
阶段2使用trailmark API进行图构建和预分析。所有trailmark查询模式均可用于任一快照的引擎。
differential-review技能:
使用graph-evolution进行结构分析,使用differential-review进行行级代码审查。两者互补——graph-evolution发现文本差异遗漏的攻击路径,而differential-review提供git blame上下文和微观对抗分析。
trailmark-review-gate技能:
在graph-evolution之后使用trailmark-review-gate,当分支、拉取请求、修复提交或发布差异需要PASS/WARN/FAIL/UNKNOWN结构审查包时。该门控对graph-evolution输出应用确定性审查规则;它不取代人工审查。
genotoxic技能:
如果graph-evolution揭示新的高CC污点节点,将其输入genotoxic进行变异测试分类。
diagramming-code技能:
生成前后图表以可视化结构变化。使用聚焦于变化节点的call-graph或data-flow图表。
支持文档
- references/evolution-metrics.md —
每个结构指标的含义及其对安全的重要性 - references/report-format.md —
报告模板、严重性分类和示例发现






