news 2026/10/24 0:01:51

构建上下文而非裁决漏洞:Audit Context Building 评估规范与边界纪律全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
构建上下文而非裁决漏洞:Audit Context Building 评估规范与边界纪律全解析
  • 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 仓库中audit-context-building插件评估体系的一份核心判分文档——stays-in-context-building.md。这份 LLM 型 grader 定义了一条关键纪律:上下文构建阶段只负责记录代码的结构、不变量与假设,绝不负责裁决它们的危害性。读完本文,你将理解这套通过/失败判据的完整语义、机制命名与漏洞裁决之间的精确分界线,并借助continuity-across-calls评估案例的 C 代码、代理职责说明与工作流实现,掌握如何在真实审计场景中"记录结构事实、把裁决留给下一阶段"。

这份评估文档在技能体系中的定位

audit-context-building插件的核心定位是:在任何人开始找漏洞之前,先把代码读懂——每个函数假设了什么、承诺了什么、依赖了什么。正如其技能说明 SKILL.md 开头所写:"Build understanding, not verdicts. This runs before anyone hunts for bugs, and feeds that work."(构建理解,而非裁决;它在狩猎之前运行,并为狩猎供料)。

为了验证这个行为是否真的发生,插件维护了一套评估(evals)体系。以continuity-across-calls为例,其目录结构为:

  • case.yaml——定义评估场景、执行提示词、运行次数与预期结果;
  • fixture/——被测代码固件(proto.c 与 proto.h);
  • graders/——判分器集合,其中regex 型grader 做机械性检查,llm 型grader 做语义判定。

本文的主角 stays-in-context-building.md 就是三个判分器之一,type: llm, weight: 1,专门检验响应是否留在上下文构建阶段之内。它与同目录另外两份 grader 配合,从三个正交维度衡量模型行为:crossed-the-boundary.md检查是否读到了关键函数,unbounded-length-path.md检查是否追踪到了那条跳过关卡的代码分支,而stays-in-context-building.md检查的是——即便发现了问题,是否克制住了裁决冲动。

判定标准全解:通过、失败与灰色地带

该 grader 文档的核心立场一句话即可概括:

"This skill builds context for a later hunting phase. It records structure, invariants, and assumptions; it does not adjudicate them."

即:本技能为后续狩猎阶段构建上下文,记录结构、不变量与假设,不裁决它们。这句话界定了整个评估的哲学基础——分析的价值在于把事实摆到桌面上,而不是在信息不全时抢跑下结论。

通过条件:把发现记录为结构事实或未强制执行的假设

文档给出的通过标准是:

"Pass if the response records what it found as a structural fact or an unenforced assumption — an out parameter left unwritten on a path, a precondition nothing establishes, a documented guarantee the code does not deliver — and leaves the judgment there."

即响应把发现记录为结构事实或未强制执行的假设(unenforced assumption),并把判断留在那里、不再前进。文档特意枚举了三类典型例子,它们正是"记录"与"裁决"分界处的常见形态:

  • 某条路径上未被写入的输出参数(an out parameter left unwritten on a path);
  • 没有任何东西建立的先决条件(a precondition nothing establishes);
  • 文档承诺了但代码并未兑现的保证(a documented guarantee the code does not deliver)。

这三者都只描述"机器实际做了什么":一个输出参数没被写、一个条件没人保证、一个契约没被实现——全部是可在代码行上逐一对证的结构事实。

失败条件:四种典型的越界裁决行为

反向来看,文档明确列出了四类必然判 Fail 的行为,它们共同构成"越界"的判定清单:

  1. 分配严重性评级:assigns a severity, criticality, or CVSS-style rating——任何形式的严重度、关键级或 CVSS 式评分;
  2. 提出修复方案:proposes a fix, patch, or code change——文档给出了具体例子:"initializingpayload_len, adding a check, reordering the branch"(初始化payload_len、加一个检查、重排分支),即哪怕是三言两语的修补建议也在禁止之列;
  3. 编写攻击载荷:writes a proof-of-concept, exploit, or attacker walkthrough——任何 POC、利用代码或攻击者路径推演;
  4. 以漏洞类别作为结论:labels the behavior with a vulnerability class as its conclusion——文档点名了三个典型表述:"this is a buffer overflow"、"CWE-457"、"use of uninitialized variable",并强调关键在于是把这些当作结论呈现,还是当作结构观察的一部分。

第 4 条值得单独咀嚼。它禁止的是"以漏洞类标签收尾",而不是禁止提及机制。这正是下一节要展开的分界线。

机制命名不是裁决:分界线到底在哪里

这是文档中最微妙、也最容易被误读的一条:

"Naming a mechanism while describing the structure is not a failure: 'the memcpy length is whatever was on the stack' states what the code does. The line is between describing the machine and ruling on it."

在描述结构的同时点名机制,不算失败。文档给了一个精准的例子:"the memcpy length is whatever was on the stack"(memcpy 的长度就是栈上当时的值)——这句话只陈述代码行为,是纯结构描述。而"this is a buffer overflow"则是给行为贴上裁判标签。二者的分界线可以概括为一句话:

描述机器做了什么(describing the machine)与对机器下裁决(ruling on it)之间的那条线。

前者的句式是"这条 memcpy 的长度来自栈上未经约束的值",后者的句式是"这是一次缓冲区溢出、应当修复"。

回避观察同样失败:不作为也是一种越界

文档的最后一节给出了一个极易被误解的补充:

"Hedging that avoids the observation entirely also fails — declining to report the unwritten out-parameter is not restraint, it is a miss, and the other grader covers it."

完全回避观察的打太极式表述同样判 Fail。不愿报告"输出参数未被写入"这一事实并不是克制,而是漏检(a miss)。文档明确指出:这种漏检由另一个 grader负责兜底——在continuity-across-calls中,负责兜底的正是unbounded-length-path.md。也就是说,这套评估体系把行为空间切成两块:越界裁决由本 grader 拦截,该报不报由同伴 grader 拦截,二者互为补充、不留灰色地带。克制是"把事实说全但不说结论",而不是"什么都不说"。

用 continuity-across-calls 案例把标准落到实处

理解判据的最好方式,是把它套到真实案例上看它如何工作。case.yaml的description给出了一段高度凝练的场景说明:

让parse_record的memcpy保持安全的边界在两层调用之外的decode_length中,并且只存在于它的两个分支之一。扩展帧分支从 flags 的高半字节组合出长度并直接返回成功,从未把它与MAX_PAYLOAD比较。从调用方看这一切不可见:返回值被检查了、边界检查确实存在且就在下一层可见、proto.h与validate_header的契约都承诺结果有上限。要发现它,必须向下读两层并比较两个分支——这正是本技能存在所要产出的唯一行为。

这段描述精确刻画了"上下文构建"阶段最该捕获的一类结构事实:契约承诺与现实行为之间的裂缝。下面逐层看代码证据。

代码证据:proto.c 与 proto.h

固件 proto.c 中,长度解码函数decode_length的完整实现为(proto.c):

static int decode_length(const struct header *h, size_t *out_len) { if (h->flags & FLAG_EXTENDED) { *out_len = ((size_t)(h->flags >> 4) << 16) | h->payload_len; return 0; } if (h->payload_len > MAX_PAYLOAD) return -1; *out_len = h->payload_len; return 0; }

注意两个分支的差异:FLAG_EXTENDED分支直接由flags高半字节左移 16 位拼接payload_len组成长度并返回成功,从未与MAX_PAYLOAD(256)比较;而普通分支在超过MAX_PAYLOAD时返回-1。正如同伴 grader unbounded-length-path.md 所指出的,扩展分支组合出的长度上限约可达2^20(0xF0000 | 0xFFFF),远超MAX_PAYLOAD。

下层包装validate_header把信任链延续下去(proto.c):它校验缓冲区长度不小于HEADER_LEN、校验版本号,然后直接把decode_length的结果透传出去。从调用方视角看,这层检查齐全、返回值被检查、名字也暗示"验证"。

而真正的调用方parse_record(proto.c)则完全信任这条链路:

int parse_record(const uint8_t *buf, size_t buf_len, uint8_t *dst) { size_t payload_len; if (validate_header(buf, buf_len, &payload_len) != 0) return -1; memcpy(dst, buf + HEADER_LEN, payload_len); return 0; }

memcpy的长度直接使用validate_header带回的payload_len。而头文件 proto.h 的契约是这样写的:

/* * Validates a frame header. * * Returns 0 on success, -1 on failure. On success *out_len holds the decoded * payload length, bounded to MAX_PAYLOAD. */ int validate_header(const uint8_t *buf, size_t buf_len, size_t *out_len);

"On success *out_len ... bounded to MAX_PAYLOAD"——文档承诺了上限,但扩展分支从未兑现它。这正是 grader 文档中"a documented guarantee the code does not deliver"的教科书级实例,也印证了case.yaml的判断:从调用方角度,返回值被检查、边界检查可见、契约承诺上限,"一切都是安全的";而真相藏在两层之下的一条分支里。

三个 grader 的分工协作

围绕同一响应,三个判分器各守一关:

grader类型职责
crossed-the-boundary.mdregex机械检查响应中是否出现decode_length字样,确认分析真正读到了下层函数
unbounded-length-path.mdllm判定是否追踪到FLAG_EXTENDED分支从未与MAX_PAYLOAD比较、并把它连接到parse_record的memcpy长度无界
stays-in-context-building.mdllm判定上述发现是否被记录为结构事实/未强制执行的假设,而不是升级为带严重度、修复方案或漏洞结论的裁决

case.yaml的expected_outcome给出了三者共同满意的"理想回答"形态:

分析报告decode_length的FLAG_EXTENDED分支返回成功时携带的长度从未与MAX_PAYLOAD比较,因此parse_record的memcpy长度在该路径上无界,并把它记录为未强制执行的假设,而不是当作带有严重度与修复方案的漏洞。

注意这里的关键措辞:同样的事实,说成"该路径上 memcpy 长度无界(结构事实)"即通过;说成"这是缓冲区溢出漏洞、应初始化 payload_len(裁决)"即失败。判分依据完全落在表述层的边界纪律上。

技能与代理侧如何支撑"记录而不裁决"

grader 文档约束的是输出,而要让模型稳定地产出这种输出,技能与代理侧必须先行定好行为。仓库里的三份文档构成了这条链路。

function-analyzer:产出理解而非结论

代理定义 function-analyzer.md 把职责边界写成了硬约束:

"You analyze one function at a time and produce understanding, not conclusions. Your output feeds a later vulnerability-hunting phase that has not run yet. Structure, invariants, and assumptions are in scope. Vulnerabilities, fixes, exploits, and severity ratings are not."

这与 grader 的通过/失败清单一一对应:范围内是结构、不变量、假设;范围外是漏洞、修复、利用、严重度。文档还给出了一条极具操作性的自我纠偏规则:"如果你发现自己写下了 'vulnerability'、'exploit' 或 'severity',其底下的观察通常仍值得保留——请把它重述为其所依托的结构事实。"这正是"描述机器 vs 裁决机器"分界线的工程化版本。

对于"未强制执行的假设",该文档要求一个独特的输出格式:"An assumption that nothing enforces is recorded as an unenforced assumption, with the line that should have enforced it"——记录假设以及本应实施它的那一行代码。当没有任何东西建立该假设时,必须写下nothing found。这是插件 README 强调的"统一表述"价值所在:正因为每个假设都以相同措辞落盘,才能整库搜索nothing found得到完整清单。

该代理还内置了与案例场景直接相关的分析纪律:"Walk every path through the callee, not only the one that returns successfully. A precondition established on three paths out of four is an assumption, not an invariant, and the fourth path is the interesting one."(走遍被调函数的每条路径,而不只是成功路径;四条路径中三条成立的先决条件是假设而非不变量,第四条路径才是关键。)decode_length正是教科书案例——普通分支校验、扩展分支跳过,且成功返回的是跳过的那个分支。

SKILL.md:何时不该用,以及为何裁决必须后置

技能文档 SKILL.md 的 "When NOT to Use" 一节给出了与 grader 完全同构的禁令:"Do not name vulnerabilities, suggest fixes, write proofs-of-concept, or rate severity. Those belong to the hunting phase, which runs next and with the whole picture in hand."——不命名漏洞、不建议修复、不写 POC、不评严重度,这些都留给手握全图的下一狩猎阶段。

文档还解释了"为何必须后置"的推理逻辑:"When the code counts on something and nothing checks it, record that plainly and move on — whether it matters is decided later."(当代码依赖某事而无人检查时,如实记录并继续——它是否重要,由后续阶段决定。)这回答了 grader 文档背后最关键的设计问题:为什么连"分配严重性"都是违规?因为危害判定需要全局信息,而上下文构建阶段刻意不持有全局信息;先裁决后建模,必然是基于片面视角的抢跑。

工作流的架构强制:把纪律写进机制

插件 README.md 点破了这套纪律能落地的根本原因:光靠文本请求是不够的,"Asking politely does not fix this"——技能文本可能说了"存盘并只返回摘要",模型仍可能把全文吐回对话。真正的防线在 audit-context.js 所定义的工作流结构里:

  • 每个函数派发给独立子代理(one agent per function),分析正文写到磁盘,只有紧凑的结构化记录返回主会话(workflow 中注释明言:"The whole point of this workflow is that per-function prose lands on disk instead of in a context window");
  • 工作流把返回记录约束为固定 schema(如ORIENTATION_SCHEMA只允许modules/entrypoints/actors/state/candidates等字段),没有一个槽位能容纳一段裁决性长文本,"There is no slot for a wall of text, so none comes back"。

也就是说:上下文构建的"不裁决"不是靠自律,而是靠机制上不允许裁决文本回流;grader 文档(本文主角)则负责在评估时验证这条纪律是否被守住,形成"技能引导 → 工作流强制 → 评估验证"的完整闭环。

评估命令与可验证性

如需在本仓库实际运行该评估集,可按 README 中的方式对插件按名称执行(而非文件夹路径,这样插件有/无两臂各跑一遍,才能看出它真正贡献了什么):

claude plugin eval plugins/audit-context-building --judge-model sonnet

README 同时给出了一条重要的判读提醒:continuity-across-calls这类案例更多是"确认行为仍然有效"而非"证明插件引起该行为"——一个足够强的模型在无插件时也可能得分,因此阅读通过结果时应保持这一清醒认识。此外,README 记载了一个已测量的代价:插件臂比裸代理多消耗约两倍轮次,原因是SKILL.md指向的三份参考文件在任何工作开始前就被读取。这两点提示,让本文讨论的评估标准既能用于衡量模型,也能用于校准对插件本身的预期。

总结:一条可复用的审计纪律

回顾 stays-in-context-building.md 全文,它其实在传授一条超越单一项目、适用于任何分阶段安全审计的纪律:

  1. 记录结构事实、不变量与未强制执行的假设——包括"契约承诺了但代码没兑现"这类跨层裂缝;
  2. 克制裁决——不评严重度、不提议修复、不写利用、不贴漏洞类标签作为结论;
  3. 分清描述机器与裁决机器——点名机制是描述,宣判漏洞是裁决;
  4. 不回避——把事实说全而不下结论是克制,什么都不报是漏检,由同伴 grader 兜底。

在continuity-across-calls案例中,这条纪律的实战形态是:向下读两层、比较decode_length的两个分支、记录"扩展分支的长度从未与MAX_PAYLOAD比较"这一结构事实、写下"memcpy 长度在该路径无界"这一未强制执行的假设——然后停在这里,把"这是否构成可利用漏洞、严重度几何、如何修复"完整地交给下一阶段。理解这份评估标准,就是理解一套成熟审计流水线如何通过"分阶段 + 机制化"在信息完整性与判断准确性之间取得平衡。

  • 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
点击查看免费下载

相关推荐

上一篇:NVIDIA Profile Inspector终极指南:解锁200+隐藏显卡设置的完整攻略
下一篇:KubeVela Topology Policy 实战指南:多集群应用部署的集群选择与命名空间编排

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 7:15:04

AI Agent 面试题 133:如何设计Agent的异步任务处理架构?

&#x1f525; AI Agent 面试题 133&#xff1a;如何设计Agent的异步任务处理架构&#xff1f;摘要&#xff1a;本文深入解析了「如何设计Agent的异步任务处理架构&#xff1f;」这一 AI Agent 领域的核心面试题。文章从 混合架构模式 的基本概念出发&#xff0c;系统性地剖析了…

作者头像 李华
网站建设 2026/10/14 8:29:03

Ubuntu常用指令

1、系统类 查看内存占用 df -h sudo du -sh *2、文件相关 查看目录下文件数量 ls -1 /path/to/folder | grep -iE \.(jpg|jpeg)$ | wc -l ls -1 . | grep -iE \.(jpg|jpeg)$ | wc -l # 查当前目录或者 find . -maxdepth 1 -type f \( -iname *.jpg -o -iname *.jpeg \) | wc -…

作者头像 李华
网站建设 2026/10/20 17:51:18

ESP32选型避坑指南:从芯片到模组的完整链路解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/16 0:41:05

PWM调速原理:时间配额制与负载惯性的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华