关于 analyze-external-methods
analyze-external-methods 是用于分析 OpenTaint 未能建模方法的技能,可判断方法是否传递数据,并可选判断其是否为安全 sink。它将分类结果写入跟踪文件,并检查批次中的方法是否全部覆盖。
主要功能
- 01分类方法的数据传递行为
- 02可选识别安全 sink
- 03将方法分类记录到 YAML
- 04检查批次分类覆盖情况
- 05复核跳过项以排查遗漏传递
适用场景
- 01为近似建模工作分类未跟踪方法
- 02深度分析时识别 sink 方法
- 03确认计划中的方法均已分类
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ seqra/ opentaint --skill analyze-external-methods安装后在 Claude Code 中输入/analyze-external-methods确认已加载。
附带文件references/java.mdscripts/check-coverage.py
详细介绍
技能:分析外部方法
OpenTaint 是一种数据流污点分析器:它从来源引入的数据开始,逐次跟踪每次调用,直到数据流停止。数据流停止有两种原因——数据到达了一个不会保留任何数据的可调用对象(对受污染的集合调用 size(),其全部内容会折叠成一个数字,因此污点消失);或者到达了一个分析器无法查看其函数体的可调用对象,通常位于外部依赖中。这个不透明的可调用对象可能会消除污点,也可能实际上会继续传递数据——这种情况下,就需要一个近似规则,准确告知引擎数据如何通过该调用传递,否则每条经过它的跟踪路径都会被悄然截断。
你将获得这些被丢弃方法的列表。请判断哪些方法确实会传递数据,哪些不会;对于每个会传递数据的方法,确定它需要哪种近似规则,以便构建阶段恢复数据流。
在深度运行中,这些相同的方法还要回答另一个独立问题——该调用本身是否属于危险操作(即汇点);此检查是步骤 2。
输入
由调用方提供,省略时使用默认值。仅当缺少必需输入且没有合理默认值时才询问
project-root(可选)——目标项目的根目录。Opentaint 会将所有分析产物保存在固定的<project-root>/.opentaint/目录下,因此下文所有.opentaint/...路径都会在此目录下解析。默认值:当前目录language(必需)——此项目的目标语言及特定语言的说明plan(必需)——此 Agent 的批次计划文件路径.opentaint/tracking/approximations/plans/<batch>.yaml:要分类的被丢弃方法sinks(可选)——是否按照步骤 2 对汇点进行分类的标志
工作流
1. 分类传播方式
从计划的 scopes 中获取成员,逐一判断,并将判定结果写入批次文件 .opentaint/tracking/approximations/<batch>.yaml——<batch> 是计划文件名(不含扩展名),也就是批次 ID;下文的覆盖率检查会再次使用它。
始终根据方法的实际代码进行分类,绝不能根据方法名判断。先阅读方法的源代码——如何获取源代码,请参阅 <skill-dir>/references/<language>.md。然后针对每个方法回答:它的输入数据去了哪里?接收方或参数中收到的数据,会通过返回值、方法写入数据的参数、接收方,还是存储数据的对象或字段传递出去?答案决定了它所属的类别:
passthrough——方法通过简单复制,将数据从一个位置传到另一个位置:getter、简单的参数到结果的复制、构建器、将参数存入接收方或其他对象的写入器、先放入再取出的集合操作等dataflow——方法通过函数、lambda 或回调参数,或异步链传递数据。当受污染的数据确实经过这类函数式参数时,优先选择此项而非passthrough。如果数据确实在传播,但不是通过可以明确指出、连接各位置的简单复制来传播——也就是说,传播方式不透明,或过于复杂,难以描述为 passThrough 边——也应优先选择此项,因为 dataflow 模型基于真实代码,可以复现任何行为skipped——方法没有传递数据,并填写简短的reason。这正是数据流结束的地方。以下是几个例子:只测试、比较或测量输入的谓词或检查器(返回布尔值或数字);将数据折叠为不再包含原数据的标量的转换(例如大小、解析为数字、单向哈希——前言中的size());不保留任何数据的副作用。这些只是说明理念的示例,并非封闭列表——许多方法和情况不在其中,因此要根据每个方法的实际代码进行判断
常见陷阱是忽略隐式载体——也就是把数据传到普通返回值以外位置的方法。会将参数写入接收方或其他对象的 void 方法仍然会传递数据:数据会保留在该对象中,数据流也会继续。清理器或编码器也是载体——它会返回经过转换的输入副本,因此数据会经过该方法继续流动;应对其建模,绝不能跳过(转换是否真的能消除污点,将由后续规则处理,不在此处判断)。拿不准时就建模:高估一个不传递数据的方法,代价很低;漏掉真正的载体则会造成运行中无法补救的假阴性。
不透明的外部键值存储——缓存、Redis/DB 模板、会话映射,以及任何将数据存放在进程之外的存储——也是载体,而不是死路:通过 put/set 写入的值会在整个往返过程中保留,因此后续的 get 可以返回攻击者控制的数据。两端都应建模为 passthrough。只有不传递值的键值或布尔操作——delete、exists、hasKey、size——才应标记为 skipped。
每个条目都要记录计划中的方法 signature,以区分重载——传播方式不同的重载应各自作为独立条目。此处只需将每个方法归入相应类别;构建阶段会读取方法自身的代码,精确建模数据的传递方式。
被跳过的方法也可能是应用内部方法,而不只是库代码——如果分析器无法读取其方法体(例如方法是 native、abstract 或 generated),就会跳过它。仍应根据代码的行为按同样方式进行分类。
2. 分类汇(仅深度运行)
设置 sinks 输入时,需要对同一批成员进行第二轮检查,以判断另一项属性:该方法是否为汇?汇是指安全敏感操作:一旦攻击者可控的数据到达该操作,就会形成漏洞。调用会执行或解释其输入,或针对敏感资源处理输入,且存在可被利用的方式,例如将其作为查询或 OS 命令运行、用作文件路径或 URL、对其反序列化、将其渲染到输出中,或通过反射或命名/目录查找解析它,以及其他许多情况。
根据方法自身的代码和行为判断它是否为汇,不要考虑项目如何使用它——不要追踪污点是否实际能到达该调用,这是分析器的工作。同时,要将汇判断与传播判断分开:传播结论不能决定它是否为汇,发现汇也不会改变传播结论。汇可能出现在你刚建模的传递者中,skipped 方法也可能是汇——不向后传递任何内容,并不能说明调用本身没有危险。
根据 Tracking 的要求,将每个汇记录在其所属包的汇单元 .opentaint/tracking/rules/sinks/<package-kebab>.yaml 中,并按描述其不安全用法的漏洞标签分组。先阅读 .opentaint/tracking/rules/tags.yaml,只要现有标签的语义合适,就重复使用。如果没有合适的标签,选择一个精确、规范的 kebab-case *-sink 标签,先将其添加到注册表,再创建该分组。标签应命名可复用的汇类别,而不是单个方法。
tags.yaml 是此次扇出中唯一共享的文件。只有确实需要新标签时才编辑它:在进行追加编辑前立即重新读取,并保留其他叶节点可能已添加的所有标签。使用已注册标签进行常规分类时,绝不写入注册表。
3. 验证覆盖率
完成分类后,从项目根目录对计划运行附带的检查:
uv run <skill-dir>/scripts/check-coverage.py --batch <batch>
传入你的 <batch>。它会列出尚未归入任何分类桶的所有批次方法。对它列出的每个方法进行分类,并重新运行检查,直到报告 0 UNCOVERED。仍有未覆盖项时,不要返回。
4. 再次验证跳过项
返回前,检查每个跳过的方法,并确认它确实不传递任何数据——仅凭名称不足以作为证据。对于代码证明属于传递者的方法,返回步骤 1 重新分类,并将其从 skipped 中移除。只保留已通过代码证明非传递者的方法
输出
简短扼要地报告完成的工作
产物:
.opentaint/tracking/approximations/<batch>.yaml— 批次分类(根据 Tracking).opentaint/tracking/rules/sinks/<package-kebab>.yaml— 每个包的汇单元(设置了sinks时)
摘要:
- 按类型统计的方法数量(passthrough / dataflow / skipped),以及设置了
sinks时的汇数量 check-coverage.py --batch <batch>报告0 UNCOVERED
Tracking
批次分类
.opentaint/tracking/approximations/<batch>.yaml — 一个批次的可调用项分类,<batch> 是计划的文件名主体。每个可调用项恰好属于一个判定类别,以计划中的语言特定 method 和 signature 精确标识,确保不同变体彼此区分:
passthrough、dataflow— 已建模的载体;每个条目为{ method, signature }skipped— 终止型非载体;每个条目为{ method, signature, reason }engine_issues— 单独的类别,用于引擎可证实无法传播的载体(已构建但仍被丢弃);每个条目为{ method, signature, reason }。这是终止类别,处理方式与skipped相同——唯一的区别是原因。merge-skipped会将其带入skipped.yaml,作为与常规跳过的methods并列的独立engine_issues组。
dependencies 列出数据流测试项目所需的依赖标识符。build 块跟踪构建情况——test_project 记录每个数据流方法的测试项目状态(如果样例已写入该批次的测试项目,则为 done;如果无法写入样例,因此该方法未纳入测试项目,则为 failed),done 则存放已完成的 { method, signature }。请通过注释明确说明
passthrough:
- { method: "<qualified-member-a>", signature: "<language-signature-a>" }
dataflow:
- { method: "<qualified-member-b>", signature: "<language-signature-b>" }
skipped:
- { method: "<qualified-member-c>", signature: "<language-signature-c>", reason: "retains none of its input data" }
engine_issues: []
dependencies: []
build:
test_project:
- { method: "<qualified-member-b>", signature: "<language-signature-b>", status: done }
done: []
此技能会写入 passthrough、dataflow、skipped 和 dependencies;请不要改动 build 块和 engine_issues——它们将在后续的近似处理阶段填写。
Sink 单元(仅当设置了 sinks 时)
.opentaint/tracking/rules/sinks/<package-kebab>.yaml — 每个包一个 sink 单元(一个依赖项可以包含多个包,每个包各有一个单元),文件以包名命名,并将 . 替换为 -。dependencies 指明该包所属的依赖项。groups 按可复用的 sink 标签划分包中的危险操作;每个组为 { tag, sinks },每个 sink 为 { method, signature, note, rule_id }。标签属于组,而非单个方法:一条规则可以覆盖多个方法,多个规则也可以扩展同一个漏洞类别。method 和 signature 使用计划中的精确语言特定标识符。stages 跟踪该单元的规则编写进度。请通过注释明确说明
dependencies:
- <dependency-id>
groups:
- tag: path-traversal-sink
sinks:
- { method: "<qualified-member>", signature: "<language-signature>", note: writes data to an untrusted path, rule_id: null }
stages:
test_project: pending
tests_passing: pending
这种划分会将整个包保留在同一个批次中,因此只填充计划中由你负责的单元。请将 rule_id: null 和 stages 留待规则编写阶段处理。如果某个包在之前的轮次中已有单元,请将新方法合并到匹配的标签组中,而不要重写该单元。
约束
OpenTaint 是一种整程序、过程间、字段敏感的别名分析引擎。它已经能够传播经过可见应用代码、调用、别名和单个字段的污点;自定义规则和近似处理仅用于对指定的 source、sink 或不透明方法边界进行建模。编译时常量和字面量不携带污点,因此输出仅为常量的 source 或载体不会引入任何内容。
- 仅判断内在传播,绝不要按项目中的流向判断。不要因为某个载体的数据似乎无法到达此处的 sink 就跳过它,也不要根据数据的去向限制 sink——流是否到达 sink 是分析器的职责,不是你的
- 分类计划分配的所有方法,且只分类这些方法——每个方法都是真正发生数据丢失的位置,不要在计划之外编造方法。返回前,
check-coverage.py --batch必须报告0 UNCOVERED