:别只测正常修复,用变异攻击证明守卫真能抓作弊)
本篇以上一篇的runs/demo-001/replay_report.json为基线读取其events_sha256与最终状态再复制契约、裁判快照、工具清单和回执到临时沙箱逐项变异。新增mutation_report.json它记录每种攻击是否被预期守卫捕获成为最终交付验收的安全证据。一、绿色测试可能只是没碰到守卫我们写了目标指纹、只读快照和回执验证但如果测试从未故意破坏它们只能证明正常路径能跑。变异测试主动制造一个小错误期望系统变红修改契约目标、弱化断言、把工具根目录扩到/、删除事件、伪造回执 ID。若某个变异仍通过就暴露了守卫缺口。这里测试的不是业务实现而是控制面不变量。每个变异只改变一个因素避免失败原因混杂。临时目录从基线复制执行结束即丢弃原始运行证据绝不能被测试修改。from__future__importannotationsimportjsonimportshutilimporttempfilefromdataclassesimportasdict,dataclassfrompathlibimportPathfromtypingimportCallabledataclass(frozenTrue)classMutationResult:name:strdetected:boolguard:strdefmutate_contract(root:Path)-None:pathroot/contract.jsondatajson.loads(path.read_text(encodingutf-8))data[goal]只要测试通过即可path.write_text(json.dumps(data),encodingutf-8)defmutate_receipt(root:Path)-None:pathnext((root/receipts).glob(*.json))datajson.loads(path.read_text(encodingutf-8))data[call_id]forgedpath.write_text(json.dumps(data),encodingutf-8)defrun_case(source:Path,name:str,mutation:Callable[[Path],None],verifier:Callable[[Path],None])-MutationResult:withtempfile.TemporaryDirectory()asfolder:rootPath(folder)/caseshutil.copytree(source,root)mutation(root)try:verifier(root)except(ValueError,KeyError)asexc:returnMutationResult(name,True,type(exc).__name__)returnMutationResult(name,False,none)defmain()-int:sourcePath(runs/demo-001)cases[run_case(source,contract_goal,mutate_contract,verify_all),run_case(source,receipt_id,mutate_receipt,verify_all),]report{baseline:json.loads((source/replay_report.json).read_text()),mutations:[asdict(case)forcaseincases]}Path(mutation_report.json).write_text(json.dumps(report,indent2),encodingutf-8)caughtsum(case.detectedforcaseincases)print(fmutations{len(cases)}detected{caught}escaped{len(cases)-caught})return0ifcaughtlen(cases)else2if__name____main__:raiseSystemExit(main())运行输出mutations2 detected2 escaped0二、为什么“杀死变异”比覆盖率更接近目标行覆盖率说明代码被执行过不说明断言能发现错误。验证契约的分支即使达到百分之百覆盖测试若不检查返回值篡改仍会逃逸。变异存活直接说明“这个错误发生时现有证据链仍然给绿灯”比覆盖率更贴近防作弊目标。变异也不能越多越好。把 JSON 随机改成乱码只会证明解析器能报语法错价值有限。高价值变异来自威胁模型改变目标但保留旧摘要、修改裁判并重算文件时间、授权相似前缀路径、复用另一任务回执、删除预算耗尽事件。记忆点是覆盖率问守卫来过没有变异问小偷经过时守卫醒没醒。importjsonfrompathlibimportPathdefassert_mutation_report(path:Path)-None:reportjson.loads(path.read_text(encodingutf-8))escaped[item[name]foriteminreport[mutations]ifnotitem[detected]]guards{item[guard]foriteminreport[mutations]}ifescaped:raiseAssertionError(fescaped mutations:{escaped})ifnoneinguards:raiseAssertionError(a mutation has no named guard)assert_mutation_report(Path(mutation_report.json))print(mutation_gatePASS escaped0 named_guardsTrue)运行输出mutation_gatePASS escaped0 named_guardsTrue三、避免变异测试自己说谎示例中的verify_all在工程里应组合独立验证器契约摘要、快照摘要、事件序号、回执身份和状态重放。不能让变异函数与验证器共享“预期答案”否则测试可能按变异名称硬编码通过。报告保存实际异常类型与失败位置评审者可以确认是正确守卫捕获而非后续无关崩溃。还有一个踩坑只要抛异常就算检测成功。若篡改契约后因缺少临时目录报FileNotFoundError并未证明契约守卫有效。每个案例应声明预期原因码如contract_hash_mismatch其他异常一律判测试错误。四、变异集合也要版本化威胁模型会随着工具能力变化。新增网络写工具就要增加重复请求、跨域和凭据泄漏变异允许修改数据库就要覆盖事务中断。mutation_report.json记录套件版本、基线摘要和每例原因码。某次版本减少案例必须经过人工审查不能因为难以通过就删除攻击。运行时间较长的变异可以分层每次提交跑控制面快速集合发布前跑容器逃逸、并发崩溃和真实 API 沙箱。失败绝不能由 Agent 自动修改变异本身来修复因为它们与裁判同属保护范围。本篇产物mutation_report.json将成为最后一篇release.py的必需输入。只有基线可重放、全部高价值变异被正确守卫捕获最终交付包才会生成。变异失败后的修复流程同样受保护先为逃逸攻击增加或加强确定性守卫再运行原基线确认正常任务不被误杀最后重跑全部变异。不能只针对该样例写字符串判断否则守卫只是换一种硬编码。新守卫应描述不变量例如“回执任务号必须等于当前任务”并覆盖至少一个合法反例。参考来源Martin FowlerMutation TestingDev.toLoop Engineering: How to Stop Your Agent Reward-Hacking Its Own Checks 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《Agent可靠性工程实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。