security-audit-skill 台账校验器源码导读:状态不变量如何守护安全审计的单一事实源
【免费下载链接】security-audit-skillA coding-agent skill for multi-phase security audits with independently verified, machine-readable findings项目地址: https://gitcode.com/GitHub_Trending/se/security-audit-skill
security-audit-skill 是一个面向编程智能体的安全审计技能(coding-agent skill),它将 AI 智能体变成结构化的安全审计员。这篇文章导读其中的核心脚本validate-coverage-ledger.cjs——台账校验器,讲清楚它如何为coverage-ledger.json这本"审计账本"设定状态不变量(state invariants),让多智能体审计的每一步都可验证、可追溯。不需要 Node.js 基础也能读懂。
先搞懂"台账":审计过程中的唯一事实源 📒
整个安全审计分六个阶段(侦察 → 覆盖驱动的猎捕 → 候选验证 → 结构化输出 → 独立复核 → 中立报告)。其中第一阶段的侦察阶段会生成 coverage-ledger.json 台账,后续每个猎手(hunter)智能体的分配、进度、证据都记录在这里。
README 中有一句话点明了校验器的角色:
父智能体在创建台账之后以及每次后续更新台账之后,都会运行
validate-coverage-ledger.cjs。
也就是说:台账是"谁、审了哪些代码、审到什么程度、留下了什么证据"的单一事实源(single source of truth)。而校验器就是看门人——任何一次更新只要违反规则,立刻被拒。这就是"状态不变量"的由来:无论台账经历多少轮更新,某些逻辑关系必须永远成立。
一次校验的完整旅程:五道关卡 🔍
打开 validate-coverage-ledger.cjs(全文 872 行,零依赖),整个流程像一条流水线,输入文件要连闯五道关卡:
| 关卡 | 函数 | 检查什么 |
|---|---|---|
| 1️⃣ 安全读取 | readFileWithinLimit | 拒绝符号链接、FIFO、超大文件、非法 UTF-8 |
| 2️⃣ 文本预检 | preflightJsonText | 在JSON.parse之前先逐字符扫描结构,防栈溢出 |
| 3️⃣ 文档预检 | preflightDocument | 对解析后的文档做深度/规模二次复核 |
| 4️⃣ 单元级校验 | collectUnitErrors | 必填字段、规范 ID、安全路径、状态不变量 |
| 5️⃣ 跨单元一致性 | validateDocument | 重复 ID、语义冲突、字典序排序 |
值得注意的两个"防御性"设计:
- 限制常量集中管理:LIMITS 把"最多 5MB 输入、10000 个顶层单元、每个数组 1000 项、嵌套深度 64、总计 50 万个值"等上限写在一处。测试套件专门构造了 200 万层嵌套的恶意输入来验证:校验器用迭代而非递归实现,绝不会栈溢出,只会报出
exceeds nesting depth limit 64。 - 诊断信息自身也要安全:错误消息里出现的控制字符(如终端转义序列、RTL 覆盖符)会被 safeQuote 转义成
\uXXXX,错误总数封顶 100 条。一个会回显用户输入的校验器,本身不能成为注入面。
重头戏:状态机与"证据不变量" ⚖️
台账里每个单元(unit)有一个status字段,共 8 种取值(见 STATUSES)。核心校验函数 validateStateInvariants 强制的是一条简单而有力的规则:
状态决定证据——什么状态,就只能有什么证据。
下面这张表就是整个校验器的"灵魂"(✅要求非空,⛔要求为空,⬜无约束):
| 状态 | 负责人 agent_id | 审阅路径 reviewed_paths | 本地检查 local_checks | 结果指纹 result_fingerprints | 未决事实 unresolved |
|---|---|---|---|---|---|
planned已计划 | 必须为 null | ⛔ | ⛔ | ⛔ | ⛔ |
in_progress进行中 | 必须非空 | ⛔ | ⛔ | ⛔ | ⛔ |
blocked受阻 | 必须非空 | ✅ | ✅ | ⛔ | ✅ |
covered已覆盖 | 必须非空 | ✅ | ✅ | ⛔ | ⛔ |
candidate候选漏洞 | 必须非空 | ✅ | ✅ | ✅ | ⬜ |
not_applicable/out_of_scope/deferred | 必须为 null | ⛔ | ⛔ | ⛔ | ✅ |
这张表能防住三类典型"说谎":
- 提前报功:
planned单元必须没有任何证据和负责人。想跳过侦察直接标记covered?对不起,covered要求非空的审阅路径和检查记录。 - 证据与结论矛盾:
covered(已确认安全)却还挂着"未决事实",或blocked(受阻)却给出了结论指纹——都会被拦截。 - 指纹越权:
result_fingerprints是"候选漏洞"的编号,只有candidate状态允许持有,其他状态一律必须为空。
还有一条跨字段不变量由 validateReviewedPathOwnership 保证:单元级的reviewed_paths必须恰好等于它名下所有检查各自reviewed_paths的并集——每行代码的审阅都必须有明确的负责人(agent),而负责人的证据也不得凭空出现在聚合列表之外。
换人机制:attempts 归档与"新鲜负责人" 🔄
多智能体审计有一条铁律(README 称之为Adversarial validation):验证发现的智能体,绝不能是发现它的智能体。当"批评者"(critic)智能体发现某个分配有问题需要重新指派时,校验器强制三件事,规则集中在 validateAttempts:
- 旧状态必须整体归档进
attempts,并递增wave(波次);归档的 wave 必须严格递增,且小于当前 wave; - 新负责人必须是"新鲜"的:任何出现在历史尝试里的
agent_id都不能再当新负责人,连历史证据(local_checks 里的 owner)也不能挪到当前状态里; - 产物不可复用:某次尝试产生的 artifact 文件,之后的尝试不能再次引用。
效果是:重分配永远是"清场重来",旧证据留在档案里,新负责人带着干净的上下文进场。这在 HUNTING.md 的编排规则里与 critic 机制配合,构成了整个工作流的对抗性验证基础。
身份系统:规范 ID、安全路径与 Agent 命名 🪪
除了状态机,还有三套"身份不变量":
- 规范覆盖 ID(canonical ID):由 canonicalCoverageId 从
canonical_refs的四个字段(surface / boundary / subsystem / attack_class)按固定顺序 URL 编码后用::拼接而成。单元里手写的coverage_id必须与之逐字节相等——手写一个"好记的别名"会被直接判错。跨单元层面,校验器还会拒绝重复 ID、拒绝"不同 ID 指向同一语义元组"的别名,并要求全部单元按 ID 字典序排列,方便 diff 与多次运行合并。 - 安全相对路径:isSafeRelativePath 拒绝绝对路径、
..穿越、Windows 保留名(con、prn、com1…)、尾随空格/点号、不可见控制字符等近 20 种写法。 - Agent 命名:isSafeAgentId 只接受小写字母数字开头、总长 64 以内的 ID,并避开 Windows 设备名。因为 agent_id 会被用来构造
agents/<id>/artifacts/产物目录——命名不安全,等于把路径穿越的口子留给了智能体自己。
30 秒上手运行 🚀
校验器就是普通 Node.js 脚本,用法一行(见文件头部注释):
node validate-coverage-ledger.cjs <path-to-coverage-ledger.json>输出只有两种:全部通过时打印PASS: N coverage units valid(退出码 0);否则逐条打印ERROR:明细并以FAIL: N validation error(s)结尾(退出码 1)。配套测试 validate-coverage-ledger.test.cjs 多达 740 行,其中不乏"敌意"用例:终端控制字符注入、FIFO 管道、符号链接、受限堆内存下的深度嵌套……校验器的每一个防御点都有对应的回归测试。
关键文件速查 📁
| 文件 | 作用 |
|---|---|
| validate-coverage-ledger.cjs | 台账校验器本体(872 行,零依赖) |
| validate-coverage-ledger.test.cjs | 校验器测试套件,含大量恶意输入用例 |
| HUNTING.md | 阶段 2 编排规则:critic 重分配、attempts 归档 |
| RECONNAISSANCE.md | 阶段 1 侦察:台账的诞生 |
| SKILL.md | 六阶段工作流总览与预算机制 |
| README.md | 项目说明、文件清单与设计原则 |
总结:不变量思维给审计工程上了三道锁 🔒
- 状态即合同:8 种状态各自绑定明确的证据形态,让"进度"无法与"证据"脱节,机器可判定任何一次台账更新是否诚实;
- 换人即清场:attempts 归档 + 新鲜负责人规则,把"自己审自己"从道德约定变成了语法错误;
- 校验器自身也要被审计:5MB 上限、迭代式预检、诊断转义、路径白名单——它防的不仅是坏数据,还有坏数据背后的恶意输入。
这正是 README.md 中设计原则的落地:让 AI 智能体做安全审计,前提是审计过程本身必须比被审计对象更可信。
【免费下载链接】security-audit-skillA coding-agent skill for multi-phase security audits with independently verified, machine-readable findings项目地址: https://gitcode.com/GitHub_Trending/se/security-audit-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考