关于 gitnexus-taint-analysis
面向 GitNexus `--pdg` 控制流与污点分析子系统开发者的技能,说明分析流程、设计约束、已知局限和验证步骤。
主要功能
- 01CFG 与到达定义设计
- 02过程内与过程间污点分析
- 03源、汇与净化器建模
- 04已知漏报类型
- 05`--pdg` 变更验证清单
适用场景
- 01追查污点流为何被报告或遗漏
- 02为特定语言添加源或净化器
- 03审查 CFG 或污点分析实现变更
MCP 相关
说明如何解读 GitNexus 的 `explain` MCP 工具结果,包括过程内与过程间污点发现。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ abhigyanpatwari/ gitnexus --skill gitnexus-taint-analysis安装后在 Claude Code 中输入/gitnexus-taint-analysis确认已加载。
详细介绍
使用 GitNexus 进行 CFG 与污点分析
针对可选启用的 --pdg 程序分析子系统的专家知识:控制流图、可达定义以及过程内 + 过程间污点分析。在接触 gitnexus/src/core/ingestion/cfg/** 或
gitnexus/src/core/ingestion/taint/** 之前,或在解释某项发现时,请先阅读本文。
何时使用
- “污点引擎是如何工作的 / 为什么这条流(未)被报告?”
- 向模型中添加 source、sink 或 sanitizer。
- 扩展或审查 CFG / reaching-defs / 污点 / summary 代码。
- 理解
explainMCP 工具的发现(过程内 vs 过程间)。 - 调试
--pdg输出中的误报或漏报。
分层基底(构建顺序)
污点分析运行在图之上,而非与之并列。每一层都通过 --pdg 可选启用,默认的 analyze 运行是字节级完全相同的(黄金一致性门禁是这里每一项变更的硬性底线)。
L1 CFG per-function basic blocks + control-flow edges (M1 #2081)
L2 REACHING_DEF GEN/KILL def→use data dependence (pure solver) (M2 #2082)
L3 Taint (intra) source→sink over RD facts, minus sanitizers (M3 #2083)
L4 Taint (inter) per-function summaries composed over CALLS (M4 #2084)
- Worker 构建,主线程求解。 解析 worker 构建每个函数的 CFG + 采集 def/use + 调用点事实到
ParsedFile.cfgSideChannel(纯数据,可安全进行结构化克隆 —— 绝不是 AST 节点)。主线程运行纯求解器。绝不在主线程重新解析(会重新引入 #1983 的 OOM)。 - 阶段内发射(KTD1)。 L1–L4-harvest 全部运行在作用域解析的 pdg 窗口内(
scope-resolution/pipeline/run.ts,由input.pdg === true门控),因为该阶段结束时基于磁盘的 ParsedFile 存储会被清空 —— 一个独立的 post-mro阶段会读到空数据。跨函数不动点(L4)是例外:它运行在自己注册的阶段(taintSummaries)中,位于作用域解析之后,因为它需要完整的调用图,并消费通过ScopeResolutionOutput传递出来的小型纯 summary 数据。 - 纯求解器契约。
computeReachingDefs、computeTaintFlows、harvestFunctionSummary和solveInterprocTaint是纯函数且确定性的 (无图、无 I/O、无日志器;输出已排序)。快照测试和基于内容派生的边 id 依赖于此。
过程内污点分析(L3)
在 RD 事实上从匹配的 source 到匹配的 sink 进行前向可达性分析,被 sanitizer 消除。值得内化的关键设计要点:
- 带出现位置的 site 标记。 扁平的按参数绑定集合无法区分
exec(escape(x))(安全)和exec(x)(发现);采集器记录嵌套调用结构(SiteRecord.parent/via 标记),因此 sanitizer 的介入是精确的。 - 类型集合 sanitizer 模型。 污点携带一组已中和的
SinkKind;除非 sink 的类型在该集合中,否则该 sink 会触发。因此escape(req.body)会抑制res.send(xss),但仍然会触发db.query(sql)—— 类型盲消除会导致被抑制的实时注入(禁止的漏报方向)。path.basename(t)仅中和路径穿越,而不中和命令注入。 - 语句级发现身份。 不是块对(块合并会丢失不同发现;
exec(req.body, req.query)是两项发现)。 - 持久化为
TAINTED边(BasicBlock→BasicBlock);路径通过共享的版本化编解码器(taint/path-codec.ts)搭载在reason列上。
过程间污点分析(L4)——函数式/摘要方法
生产级方法(Sharir-Pnueli 1981;与 Meta 的 Pysa 和 Mariana Trench,以及 FB Infer 形态相同)——而非完整的 IFDS 制表。每个函数被归约为一个紧凑的摘要,摘要在已解析的 CALLS 图上进行组合。
摘要形态(taint/summary-model.ts,整参数粒度):
| 边 | 含义 | 类比 |
|---|---|---|
param→return | 参数流向返回值 | TITO——保留(下限已覆盖其召回率;精度处理推迟) |
param→callee-arg | 参数流入调用的第 j 个实参(携带路径中被中和的 sink 类型) | TITO 进入被调用者 |
param→sink | 参数到达一个已建模的 sink | 部分/触发的 sink |
source→return | 函数生成并返回一个 source | 生成式——通过调用者的 callResults 组合 |
source→callee-arg | 生成的 source 流入一次调用 | 不动点 SEED |
callResults | 用户函数调用的结果流向调用者中的 sink/return/callee-arg | 与被调用者的 source→return 组合 |
不动点(taint/interproc-solver.ts):单元是 (function, parameter, source)。从 source→callee-arg 播种,通过 param→callee-arg 传播,当受污参数遇到 param→sink 时触发一个发现。
- 通过单调性实现循环安全。 受污集合在有限格(
fn × param × source)上是单调的,因此工作列表会收敛——递归调用只是重新提议一个已访问过的条目。SCC 缩并只会细化处理顺序;正确性/终止性并不需要它。 - 按 source 区分的状态(关键承载)。 状态也以 SOURCE 为键。仅以
(fn, param)为键会合并多源流:被 source A 污染的 sink 参数被标记为已访问,而之后来自 source B 的流在触发前被丢弃——这是反复出现的多源 bug 类别。(Bit M3;bit M4 U9。) - 基于名称的调用连接。 将摘要的 call-arg 边与
CALLS边按被调用者名称匹配,而非按调用点行号——基于行的对齐(CFG 从 1 开始 vs 引用位置)很脆弱;被调用者身份是精确的,而上下文不敏感会在每个调用点以相同方式污染被调用者的参数。 - 持久化为
TAINT_PATH边(Function→Function),reason中的函数级跳转链通过同一编解码器;置信度 < 过程内的 1.0。
上下文不敏感是此层级可接受的权衡:每个函数一个摘要,接受返回值/调用点合并(安全保守)。预期合并会产生一些 FP;更大的 FN 来源是未建模的特性(见下)。
已知假阴性类别(已记录,推迟)
最大的是闭包/回调(arr.forEach(() => sink(y)))——没有按库的模型时,进入回调的污点会被丢弃(CodeQL 的 JS 库也是如此)。同样推迟的还有:字段/属性流(obj.x = taint; sink(obj.y))、字段敏感的访问路径、守卫式净化器、隐式/控制依赖流、promise/async-await 线程化,以及受污简单参数之前的解构/剩余参数(摘要端口索引是绑定序号,而非形参位置——需要从 worker 的 BindingEntry 贯穿一个形参索引)。过程间连接也是上下文不敏感的:当一个调用者调用两个不同的同名被调用者时,流入其中一个的流会被过度归因到两者(健全——过度报告,绝不会遗漏流)。没有发现并不能证明安全。
GitNexus 特有的注意事项
- 函数与 CFG 的连接。
FunctionCfg.functionStartLine从 1 开始;Function/Method节点的startLine从 0 开始——通过startLine - 1连接。函数节点 没有列号,因此同一行的函数({a:()=>x(), b:()=>y()})无法区分 → 应丢弃 (摘要驱动程序会统计unresolved),而不是错误连接。 - 没有关系属性索引(S1)。 Kuzu 没有关系属性的二级索引,而无锚点的
[:TAINTED*]/[:TAINT_PATH*]查询会导致查询量暴增。因此,TAINT_PATH 会在 分析时物化并设置锚点,绝不进行实时遍历;explain会读取带源锚点且受 LIMIT 保护的数据。 explain是唯一的发现入口。TAINTED/TAINT_PATH被刻意排除在VALID_RELATION_TYPES(impact 的允许列表)和 Web 架构之外(固定于security.test.ts)。explain会枚举这两层(跨函数发现结果带有interprocedural: true)。- 共用一个编解码器。 发射路径和
explain都导入taint/path-codec.ts。两份手写的线格式副本会逐渐不一致——绝不要拆分。 当写入器和读取器同时发布时,新元数据应在同一版本内扩展格式。 - 缓存兼容性。 Worker 收集逻辑变更会通过 PDG 构件标识自动使缓存失效。
将仅用于提取的依赖项保留在 PDG 所有权边界内,并验证普通解析键保持稳定。
共享解析器的变更属于基础标识。持久化图/配置变更会通过
RepoMeta.pdg的键集合不匹配触发完整回写。模型内容由taintModelVersion管理。
添加 source / sink / sanitizer
污点模型覆盖三个文件中的四个 SupportedLanguages ID:TypeScript 和 JavaScript
使用 taint/typescript-model.ts,Python 使用 taint/python-model.ts,Java 使用
taint/java-model.ts。请编辑目标语言对应的模型。typescript-model.ts 中显式的
registerBuiltinTaintModels 接口会注册全部四种模型;它不是导入副作用。
规范是可哈希的数据(不含函数)。sanitizer 的 neutralizes 会列出其防护的
确切 sink 类型——绝不能笼统地清除全部污点。在 test/unit/taint/ 中添加测试样例
并断言发现结果(或未发现结果)。TypeScript 和 JavaScript 使用真实源码测试框架
test/helpers/ts-cfg-harness.ts;Python 和 Java 的模型匹配由
python-model-match.test.ts 和 java-model-match.test.ts 覆盖。端到端验证位于
test/integration/cfg/。
任何 --pdg 变更的验证清单
1. tsc clean (schema additions are exhaustiveness-checked; watch the
api.ts getNodeQuery runtime read-path if a node label is added).
2. Targeted vitest by directory (test/unit/taint, test/unit/cfg,
test/integration/cfg) — verify by ISOLATION, not full-suite exit
(known load-flakes). `node scripts/build.js` before worker/integration runs.
3. Flag-off golden byte-identical (pipeline-graph-golden.test.ts).
4. bench/cfg/measure.mjs --check (no fingerprint drift / budget regression).
5. detect_changes() before commit; impact({direction:'upstream'}) before
editing shared symbols (KnowledgeGraph, RepoMeta, RelationshipType, codec).
先前研究(深入设计问题时参考)
Sharir & Pnueli 1981(函数式方法);Reps-Horwitz-Sagiv IFDS(POPL 1995); FlowDroid/StubDroid(访问路径摘要);Pysa & Mariana Trench(TITO / 传播,并行 SCC 不动点);CodeQL Models-as-Data(最丰富的端口表示法,包括回调端口); Infer(基于内容键的增量摘要)。