☰
属性测试技能触发边界评测:property-based-testing 插件 14-neg-benchmark 负样本用例深度解析
2026/10/10 13:44:04 网站建设 项目流程
  • AI 技能
  • AI 插件
  • 应用安全
  • 网络安全
  • AI 评测

【免费下载链接】skills

Trail of Bits Claude Code skills for security research, vulnerability detection, and audit workflows

项目地址:https://gitcode.com/gh_mirrors/skills8/skills
点击查看免费下载

导读

本文以 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覆盖以对比旧版描述
--modelopus(MODEL可覆盖)触发率是"描述 × 模型"的共同属性,必须钉死模型才能比较
--output-format stream-json --verbose—输出结构化 JSON,供检测器解析
--max-turns200(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 记录的消融评测结果印证了这类防线的工作方式:

casefireswithwithoutΔ
02-neg-cargo-fuzz-coverageno1.001.000.00
03-dependency-is-users-callyes0.500.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

解读结果表时把握三个要点:

  1. 看判词列:14-neg-benchmark一行的期望是false,正确结果应显示0/3命中且ok——触发率低于 1/2 且与期望一致;
  2. 看 NOTE 列:任何timeout/crash:*都意味着该行数据不是测量,整轮INVALID(退出码 3);
  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

项目地址:https://gitcode.com/gh_mirrors/skills8/skills
点击查看免费下载
上一篇:推荐:JPVideoPlayer - 高性能的iOS视频播放库
下一篇:【亲测免费】 推荐开源项目:Animation Nodes - 动画节点系统

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询