- AI 技能
- AI 插件
- 应用安全
- 网络安全
- AI 评测
【免费下载链接】skills
Trail of Bits Claude Code skills for security research, vulnerability detection, and audit workflows
导读
本文以 Trail of Bits Skills 仓库中property-based-testing插件的负样本评测用例14-neg-benchmark.md为切入点,解析该插件如何用"带标签的真实查询"验证技能描述的触发边界——为什么"测量新排序是否比旧排序更快"这类 benchmark 请求不应触发属性测试技能,以及这套评测机制如何通过run.sh触发率评测、effectiveness.sh效果评测与--self-test自检来保证"技能该触发时触发、不该触发时不触发"。读完本文,你将掌握该插件的评测协议、负面用例设计思路,以及如何在本仓库中亲手运行与解读这类触发率评测。
一、负样本用例本体:14-neg-benchmark 的构成
关联文档plugins/property-based-testing/evals-extra/14-neg-benchmark.md全文仅有一段 YAML frontmatter,却承载着整个评测套件的一个关键边界:
--- query: "i want to measure whether the new sort is actually faster than the old one across input sizes" should_trigger: false ---这个文件在评测框架中扮演"负样本(negative case)"角色,由两个字段组成:
query:一段真实用户请求原文——"我想测量新排序在多种输入规模下是否真的比旧排序更快"。注意它没有被加工成工整的技术措辞,而是保留了真实开发者的口语化表达,这正是评测的设计意图:测试的是技能描述在真实对话中的判别力,而不是在理想化问题上的表现。should_trigger:期望标签。false表示该查询不应触发property-based-testing技能,评测判定的正确结果是"不触发"。
与同类负样本的家族关系
14-neg-benchmark.md并非孤立存在,evals-extra/目录下聚集了一批结构完全相同的文件,其中同属负样本(should_trigger: false)的还包括:
10-neg-mutation-campaign.md:查询"我们的 mutation 跑一次要 4 小时,如何只针对改动文件缩小范围"——mutation 测试战役同样不在技能范围内;09-neg-libfuzzer-harness.md、13-neg-slither-scan.md、11-neg-flaky-ci.md、12-neg-crud-unit-test.md、15-neg-pytest-setup.md、16-neg-e2e-playwright.md:分别针对 libFuzzer、Slither、flaky CI、普通 CRUD 单测、pytest 搭建、E2E Playwright 等场景。
而正样本(should_trigger: true)如01-roundtrip-codec.md("wire format 的 pack()/unpack() 总出怪 bug,帮我写能抓住这类问题的测试")、02-normalizer-idempotence.md、04-echidna-invariant.md等,覆盖了编解码、规范化、Echidna 不变量等属性测试的核心应用场景。
正负样本成对存在,才能测量技能描述既不高频误报、又不漏报的双向判别力。
二、为什么"测量排序性能"不应触发属性测试:技能描述中的排除列表
负样本的设计依据直接写在技能描述里。查看 SKILL.md 的description字段,其中明确写着:
Not for coverage-guided binary fuzzing (libFuzzer, AFL), mutation-testing campaigns, static analysis, benchmarking, or end-to-end UI tests.
即技能排除清单包含:覆盖率引导的二进制模糊测试(libFuzzer、AFL)、mutation 测试战役、静态分析、benchmarking(性能基准评测)、端到端 UI 测试。
14-neg-benchmark的查询恰恰落在benchmarking这一项上:"测量新排序是否比旧排序快"是典型的性能对比基准评测任务——它需要的是可复现的计时数据、多组输入规模的测量设计与统计显著性判断,而不是"对全输入域断言不变量"。属性测试技能在这里毫无用武之地:
- 排序的正确性(
is_sorted(sort(x))、sort(sort(x)) == sort(x)幂等)是属性测试的正当对象; - 排序的速度却不是任何可断言的代数性质,它与属性测试的方法论无关。
从 SKILL.md 开篇对技能适用性的定位也能印证这一点:"代码具备代数形状(逆运算、不变量、oracle)时才值得做属性测试,否则就写示例测试,直接说明这一点也是有效结论。"性能测量没有这种形状,因此正确行为是不触发。
这也就是评测框架所守护的"over-trigger guard(过度触发防线)":一个描述写得过宽、对什么都响应的技能,会白白消耗上下文与 API 预算,甚至误导用户。负样本把描述中明确排除的每一项都转换成一个真实查询,持续验证防线没有被后续改动悄悄破坏。
三、评测机制:run.sh 如何消费这些负样本
负样本文件本身不执行任何逻辑,真正驱动它的是评测脚本 run.sh。该脚本实现了一个完整的"技能触发率评测"循环:对每条带标签的查询,启动一次真实 Claude Code 会话,记录技能是否被调用,再与should_trigger标签比对。
3.1 frontmatter 解析
脚本启动时会扫描evals-extra/*.md目录下的所有评测文件(run.sh):
- 用一段内嵌 Python 通过正则
^---\n(.*?)\n---抽取 frontmatter,再用^query:\s*"(.*)"\s*$提取查询文本; - 用
grep -oE '^should_trigger:[[:space:]]*(true|false)'提取期望标签; - 缺少
should_trigger的文件直接让脚本以退出码 2 终止——"发现不到查询/格式损坏"与"评测出回归"被严格区分,绝不允许坏文件悄悄混入。
3.2 真实会话运行与触发判定
对每条查询,脚本启动一次claude -p <query>会话(run.sh),关键参数包括:
| 参数 | 默认值 | 含义 |
|---|---|---|
--plugin-dir | 插件根目录 | 加载技能,可被PLUGIN_DIR覆盖以对比旧版描述 |
--model | opus(MODEL可覆盖) | 触发率是"描述 × 模型"的共同属性,必须钉死模型才能比较 |
--output-format stream-json --verbose | — | 输出结构化 JSON,供检测器解析 |
--max-turns | 200(TURNS) | 上限故意设为不可达,用于兜住 timeout 看不见的"快速死循环" |
--permission-mode plan/--disallowed-tools Agent | — | 限定会话行为 |
会话结束后,skill_invoked检测器(run.sh)逐行解析 stream-json 输出,寻找message.content中type == "tool_use" && name == "Skill"的调用记录,并确认其input中包含property-based-testing技能 ID。
3.3 判词词汇表:把"模型拒绝了"和"我们没拿到答案"分开
check_triggered(run.sh)输出的是一套固定判词而非退出码,这是整个设计最关键的决策:
yes:检测到技能调用;no:会话健康结束且未调用技能——唯一的"有效测量";timeout:命中TIMEOUT_S(默认 600 秒)上限;crash:rc<N>/crash:nooutput/crash:detector:进程异常退出、零输出、或检测器自身失败。
判词必须独立于退出码的理由,脚本注释里记录了一次真实教训:Python 解释器本身的失败退出码是 1,恰好与检测器返回"未找到"的码一致,于是一个损坏的解释器把所有正向会话都记成了干净的no,整轮 sweep 看起来像一次召回率回归。所以判词被设计为打印 token,no只表示"模型做出了决定",其余一切失败都必须显式暴露,绝不与负面结果混淆。
3.4 调用优先于失败:正确性判定的顺序
check_triggered内部存在一处load-bearing 的判定顺序(run.sh):先跑检测器,后查退出码。原因在于技能调用是"正向且最终"的事件——会话后面再怎么崩溃,都不能抹掉已经发生的调用。而只有当会话运行到做出决定时,"未调用"才成立。最初的实现顺序恰好相反(先查退出码),结果把"调用了技能后撞上--max-turns上限"的会话全部丢弃;而探索量最大的查询恰恰最可能触顶、其正向证据也最有价值,于是错误的顺序悄悄压低了最关键查询的召回率。
四、触发率评测协议细节
4.1 随机性与多次运行
技能触发是随机过程:脚本注释记录了一个实证案例——同一条 Echidna 查询在某次 sweep 中得 0/1,紧接着一次完全相同的运行却触发了技能(run.sh)。因此单次运行把噪声当成信号,默认RUNS=3、阈值取"触发率须超过 1/2"(threshold_num=1 / threshold_den=2,run.sh),这是规范推荐的最小测量组合。RUNS=1只是冒烟测试,不是测量。
4.2 会话调度与人工可等待性
sweep 总量为"查询数 × 运行次数",默认 15 条查询 × 3 次 = 45 个会话,按JOBS=4一波波并行派发(run.sh),每波结束向 stderr 输出进度。选用"分批 wait"而非 bash 4.3 的wait -n,是为了兼容 macOS 默认的 bash 3.2。插件 README.md 记载:默认 45 个会话在JOBS=4下实测耗时约 51.9 分钟、花费约 $36.50——这正是它不进入 CI、只作为手动评测运行的原因。
4.3 失败会话使整轮失效,而非被阈值吸收
聚合阶段(run.sh)中,任何非yes/no的结果都计入broken。一旦broken > 0,脚本立即以**退出码 3(invalid)**终止并打印全部失败会话的原始捕获路径。这个设计堵住了一个危险的漏洞:10 条查询 × 3 次运行、及格线 27 的 floor 能容忍 3 次未命中,若崩溃被算作未触发,某查询连续崩溃 3 次仍能凑够 27 分报"通过"——那是把评测框架自身的失败洗白成绿色结果。
4.4 退出码语义
run.sh的退出码构成完整的可编程接口(run.sh):
- 0:每条查询都达到期望,且每个会话都返回了判词;
- 1:回归——通过数低于
EXPECT_PASSfloor(默认 13); - 2:harness 失败——没发现查询、评测文件格式损坏、缺
uv或缺 CLI; - 3:invalid——至少一个会话崩溃、超时或零输出。
"回归"与"框架坏了"必须可区分,这正是脚本对--self-test的断言也会覆盖的内容。
五、自检:--self-test 用桩二进制证明分类器仍会判别
一个永远输出no的分类器看起来像稳定、可辩护的结果,因此 run.sh 内置--self-test,用**桩二进制(stub claude)**驱动 17 个断言,零成本、可进 CI。桩二进制通过STUB_MODE环境变量模拟各种会话形态,最关键的断言包括:
| 场景 | 期望判词 | 设计意图 |
|---|---|---|
| 调用了技能 | yes | 正向检测成立 |
| 未调用技能、正常作答 | no | 负向判定成立 |
| 调用了其他技能 | no | 防止张冠李戴 |
| 非零退出 | crash:rc42 | 崩溃≠未触发 |
| 干净退出但零输出 | crash:nooutput | 输出缺失必须暴露 |
| 崩溃时 stderr 有最后一行 | crash:rc1: auth failed: token expired | 判词必须携带原因 |
| 调用技能后又超 turn 数 | yes(而非 crash) | 正向证据优先于失败状态 |
| 真实 CLI 形态:错误在 stdout 的 result 记录里 | crash:rc1: error_during_execution api error: overloaded | 防止只读 stderr 漏掉七次真实崩溃 |
| 检测器本身跑不了 | crash:detector(绝非no) | 坏 harness 不得伪装成负样本 |
| 有崩溃会话的整轮 sweep | 退出码 3 | 失效优先于分数 |
其中"调用技能后又撞上限 →yes"来自真实线上观察:某会话先调用了技能、随后才因error_max_turns退出。此外还有一组matched pair断言:两条查询得分相同(8/15),唯一区别是一条查询在 staking 问题上崩溃——对照组钉死 8 分确实过得了 floor(退出码 0),实验组因同一分数 + 一次崩溃必须判 invalid(退出码 3),从而证明"失效短路"真的拦在了 floor 判定之前。
自检同样覆盖效果评测脚本 effectiveness.sh:一条真实的幂等属性测试能检出 fixture 缺陷(yes)、只断言返回类型的属性不得分(no)、无法 import 的套件是ERR而非干净 miss、补丁无法套用的 fixture 是ERR而非降级为part,外加 effort pin 守卫的拒跑断言。
六、触发 ≠ 有用:effectiveness.sh 补齐的另一个维度
触发率评测回答"描述是否正确地触发",但不回答"技能加载后是否真的有用"。插件 README.md 明确写道:"the skill fires" 和 "the skill helps" 是两个不同的论断,只有后者对用户有意义。因此 effectiveness.sh 负责测量效果维度。
它的方法不依赖模型的自述,而是差分评分:让模型针对 fixture/src/codec.py 编写属性测试,先对含缺陷的canonicalize_url跑一遍套件,再用脚本把该函数替换为恒等函数跑第二遍(effectiveness.sh);凡"修前失败、修后通过"的测试,就是真的检出了这个缺陷,无论模型给它起了什么名字。
fixture 里的缺陷是经典的双重编码 bug(effectiveness.sh):
canonicalize_url("a b") == "a%20b" canonicalize_url("a%20b") == "a%2520b" # 不一致,非幂等canonicalize_url的 safe 集合未包含%(见 fixture/src/codec.py),导致对自身输出再编码时把转义符又转义了一遍。断言f(f(x)) == f(x)幂等的属性套件在st.text()策略的几乎任何输入上都能将其推翻(实测 30/30 运行),而基于 happy path 手写的示例测试永远发现不了——这正是该评测要测量的差距。
七、负样本如何反过来守护描述质量
回到本文主角14-neg-benchmark.md:它与02-neg-cargo-fuzz-coverage等一起构成了技能的"过度触发防线"。插件 README.md 记录的消融评测结果印证了这类防线的工作方式:
| case | fires | with | without | Δ |
|---|---|---|---|---|
02-neg-cargo-fuzz-coverage | no | 1.00 | 1.00 | 0.00 |
03-dependency-is-users-call | yes | 0.50 | 0.00 | +0.50 |
其中02的 Δ0 是正确的负样本结果——覆盖率引导模糊测试在描述排除清单上,3 次带插件运行中技能一次都没触发。同理,14-neg-benchmark的 benchmark 查询若被评测出触发,就说明描述中的排除项被后续编辑冲掉了,评测会以回归(退出码 1)或更严重的方式报警。
负样本的持续存在还服务于一个工程约束:floor 门槛(EXPECT_PASS=13)只允许"描述改进带来的上升",run.sh 明确要求"提升描述时才能抬高 floor,永远不要为了变绿而降低它"。负样本让每一次描述改动都要付出真实的 API 预算来证明自己没有侵蚀边界。
八、如何亲手运行与解读这套评测
在当前仓库中按需执行(注意:触发率评测消耗真实 API 预算,不是 CI 任务):
# 冒烟测试:每条查询只跑 1 次,最快 RUNS=1 ./plugins/property-based-testing/evals-extra/run.sh # 正式测量:每条查询 3 次,默认阈值"触发率 > 1/2" ./plugins/property-based-testing/evals-extra/run.sh # 只跑基准相关的一条查询(文件名含 "14") ONLY=14 ./plugins/property-based-testing/evals-extra/run.sh # 串行调试单条会话 JOBS=1 ./plugins/property-based-testing/evals-extra/run.sh # 零成本自检:桩二进制驱动分类器,可进 CI ./plugins/property-based-testing/evals-extra/run.sh --self-test # 效果评测:模型生成的套件能否检出 fixture 中的真实缺陷 EFFORTS=low ./plugins/property-based-testing/evals-extra/effectiveness.sh解读结果表时把握三个要点:
- 看判词列:
14-neg-benchmark一行的期望是false,正确结果应显示0/3命中且ok——触发率低于 1/2 且与期望一致; - 看 NOTE 列:任何
timeout/crash:*都意味着该行数据不是测量,整轮INVALID(退出码 3); - 看退出码:0 为全部达标、1 为低于 floor 的回归、2 为框架故障、3 为存在失效会话。原始 stdout/stderr 捕获会保留在启动时打印的 artifact 目录,便于事后诊断。
结语
14-neg-benchmark.md虽只有三行 frontmatter,却是 property-based-testing 插件评测体系中不可替代的一环:它把"benchmarking 不属于属性测试"这一描述边界固化成一条可重复验证的真实查询,与 run.sh 的判词词汇表、失败失效机制、--self-test自检共同构成一套"该触发时触发、不该触发时不触发、框架坏了绝不能伪装成好结果"的可信评测闭环。理解这个负样本的设计,也就理解了技能描述如何被工程化地持续守护。
- AI 技能
- AI 插件
- 应用安全
- 网络安全
- AI 评测
【免费下载链接】skills
Trail of Bits Claude Code skills for security research, vulnerability detection, and audit workflows
相关推荐
SerenityOS LibTest/Randomized:深入解析 SerenityOS 的属性测试(Property-Based Testing)框架
SerenityOS LibTest/Randomized:深入解析 SerenityOS 的属性测试(Property Based Testing)框架 Li
操作系统内核驱动PostHog SQLV2 节点结果交付机制解析:从短轮询到 pub/sub 推送与结果分页存储设计
PostHog SQLV2 节点结果交付机制解析:从短轮询到 pub/sub 推送与结果分页存储设计 SQLV2 是 PostHog 新版 Notebook(n
AI 技能AI 插件应用安全网络安全AI 评测智能合约的基于属性的测试(Property-based Testing):用 Echidna 与 Manticore 发现未知缺陷
智能合约的基于属性的测试(Property based Testing):用 Echidna 与 Manticore 发现未知缺陷 属性测试(Property
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考