1. 从零拆解 security-audit-skill一个让 Agent 真正会做安全审计的技能包第一次看到security-audit-skill这个标题我脑子里蹦出来的不是“又一个安全扫描脚本”而是一个更具体的问题怎么让一个通用 Agent 在拿到一份代码仓库之后像一名有经验的安全审计工程师那样按部就班地查、记、核、报最后还能交出一份别人能复核的结论。这个标题里的skill不是随便起的后缀它指向的是当下 Agent 生态里一个非常关键的抽象——技能包。而security-audit则限定了这个技能包的领域安全审计。两个词拼在一起本质上是在回答“如何把安全审计这门手艺封装成 Agent 可以稳定调用的能力单元”。我接触过不少团队做 Agent 安全审计最常见的失败模式不是模型不够聪明而是流程不可复现。同一个仓库今天跑出来 12 条问题明天跑出来 5 条后天又冒出 20 条没人知道哪次是对的。security-audit-skill这类项目要解决的核心痛点就在这里它把审计过程结构化用findings.json记录发现用coverage-ledger.json记录覆盖范围让“查了什么、没查什么、查出了什么”三件事都有据可查。这套东西适合谁适合正在做 Agent 开发、想把安全能力产品化的工程师也适合安全团队里想用自动化手段提升审计效率的人哪怕你之前没写过 Agent只要理解基本的安全审计逻辑也能跟着把整套流程跑起来。我下面会按“设计思路—核心文件—实操流程—问题排查”这条线把这个技能包拆开讲透。中间会补充很多原始描述里没写、但实际落地时一定会遇到的细节比如findings.json的字段怎么设计才不会被下游工具嫌弃coverage-ledger.json怎么记才不会变成一笔糊涂账。这些经验大多来自我自己和身边团队踩过的坑你直接抄作业就行。2. 整体设计与思路拆解为什么是 skill而不是一个脚本2.1 skill 和 agent 的区别决定了这个项目的形态热词里反复出现“skill 和 agent 的区别”这个问题不搞清楚security-audit-skill的设计就无从谈起。我的理解很直白Agent 是那个“会自己决定下一步做什么”的执行者skill 是“被 Agent 调用的一套确定性能力”。打个比方Agent 是厨师skill 是菜谱加一套预处理好的食材。厨师可以自由发挥但做宫保鸡丁的时候他得按菜谱来否则每次味道都不一样。安全审计这件事恰恰最怕“自由发挥”。你今天让 Agent 自由地去审代码它可能花大量时间在读 README明天又可能直接跳到某个可疑函数。security-audit-skill的价值就在于把审计动作固化下来先做什么、再做什么、结果写到哪里、覆盖范围怎么界定全部写死在 skill 里。Agent 只负责在合适的时机触发这个 skill以及根据 skill 的输出决定要不要继续深挖。这样既保留了 Agent 的灵活性又保证了审计过程的可复现性。2.2 为什么用 findings.json 和 coverage-ledger.json 两个文件很多同类项目只输出一个报告文件security-audit-skill却坚持用两个 JSON。这不是为了显得复杂而是因为“发现”和“覆盖”是两件必须分开记录的事。findings.json回答的是“我查出了什么问题”coverage-ledger.json回答的是“我到底查了哪些地方”。这两件事混在一起后果很严重你看到报告里只有 3 条问题会以为代码很干净但实际上可能只是审计范围只覆盖了 10% 的模块。我见过一个真实案例某团队用 Agent 审一个中型项目报告里干干净净上线后却出了个低级漏洞。复盘时才发现Agent 当时只扫了src/api目录src/utils里的加密逻辑压根没进审计范围而coverage-ledger.json如果存在这个问题在第一眼就能被发现。所以这两个文件的分工是findings.json面向“问题”coverage-ledger.json面向“过程”。前者给修复的人看后者给复核的人看。2.3 方案选型背后的取舍确定性优先于覆盖率设计这类 skill 时一个绕不开的取舍是要不要追求“尽可能多查”。我的经验是在 Agent 场景下确定性比覆盖率更重要。原因很简单Agent 的调用是有成本的无论是 token 消耗还是执行时间如果 skill 设计成“地毯式扫描”Agent 很容易在执行到一半时因为上下文超限或超时被终止热词里那句“agent execution terminated due to error”就是这类问题的典型表现。security-audit-skill的思路是先定义清晰的审计边界在边界内做深度检查边界外用coverage-ledger.json明确标注“未覆盖”。这样做的好处是即使这次审计不完整下一次也能接着上次的 ledger 继续而不是每次从头再来。这种“可续跑”的设计在实际项目里比“一次扫全”实用得多。3. 核心细节解析与实操要点两个 JSON 到底该怎么写3.1 findings.json 的字段设计与避坑findings.json看起来简单但字段设计直接决定了它能不能被下游工具消费。我推荐的最小可用结构是这样的{ findings: [ { id: SEC-001, title: 用户输入未做长度校验, severity: medium, category: input-validation, location: { file: src/api/user.js, line: 42, snippet: const name req.body.name; }, description: name 字段直接来自请求体未做长度和字符集校验可能导致后续处理异常。, recommendation: 在写入前增加长度上限和字符白名单校验。, confidence: high } ] }这里有几个字段是我强烈建议保留的。id必须稳定不能每次跑都变否则修复跟踪就断了。severity和category分开因为严重程度和问题类型是两个维度混在一起会让统计变得困难。confidence这个字段很多人会忽略但在 Agent 审计里它特别重要——Agent 的判断有时是推测性的标注置信度能让复核的人知道哪些结论需要人工确认。注意location.snippet不要放太长的代码片段控制在 3 到 5 行以内。我试过放整段函数进去结果findings.json膨胀到几 MBAgent 后续读取时直接超了上下文。3.2 coverage-ledger.json 的记录粒度coverage-ledger.json的核心是回答“哪些地方查了哪些没查”。它的结构我建议按“模块—状态—原因”来组织{ coverage: [ { path: src/api, status: audited, files_checked: 12, findings_count: 3 }, { path: src/utils/crypto.js, status: skipped, reason: 文件超过 2000 行超出单次审计上下文限制 }, { path: src/legacy, status: partial, reason: 仅审计了入口文件内部依赖未展开 } ] }status用三个值就够了audited、partial、skipped。不要用太多状态否则统计时自己都记不清。reason字段是给未来的自己看的写清楚为什么跳过比单纯标一个skipped有价值得多。我踩过的坑是早期没写reason过了一周回头看 ledger完全想不起来当时为什么没审某个目录只能重新跑一遍。3.3 skill 的触发条件与边界定义一个 skill 能不能用好很大程度上取决于它的触发条件写得清不清楚。security-audit-skill不应该在任何时候都被调用它应该有一个明确的触发场景比如“当用户要求对某个代码目录做安全审计时”或者“当 Agent 检测到新增代码涉及敏感操作时”。触发条件写得太宽Agent 会频繁调用浪费资源写得太窄又会在需要的时候漏掉。边界定义同样重要。我建议在 skill 里明确写清楚单次审计最多处理多少个文件、单个文件最大多少行、遇到超大文件怎么处理。这些限制不是为了偷懒而是为了让 skill 的行为可预测。热词里提到的“agent 记忆”问题在安全审计场景下尤其明显——如果一次审计塞进去太多内容Agent 的上下文会被占满后面的判断质量会断崖式下降。4. 实操过程与核心环节实现把审计流程跑起来4.1 环境准备与 skill 接入假设你已经有一个可用的 Agent 框架接入security-audit-skill的第一步是把 skill 的描述文件放到框架能识别的位置。不同框架的目录约定不一样但核心内容是一致的一份 skill 说明加上findings.json和coverage-ledger.json的模板文件。我习惯把模板文件放在 skill 目录下的templates/里这样每次审计开始时Agent 先复制模板再往里填内容避免结构跑偏。接入时有个细节容易被忽略skill 的说明里要写清楚输入是什么。security-audit-skill的输入应该至少包含“目标目录路径”和“审计范围说明”。如果只给一个路径Agent 可能会把整个仓库都当成审计对象包括node_modules这种明显不该审的目录。我一般会在输入里加一个exclude列表把依赖目录、构建产物、测试快照都排除掉。4.2 审计执行的分阶段策略一次性审完整个项目在 Agent 场景下几乎必然失败。我的做法是把审计分成三个阶段每个阶段结束后都更新一次coverage-ledger.json。第一阶段是“结构扫描”。Agent 先遍历目标目录生成文件清单按目录分组统计每个目录的文件数和总行数。这一步不涉及具体的安全判断纯粹是摸清家底。扫描结果直接写进coverage-ledger.json所有条目初始状态设为pending。第二阶段是“重点审计”。根据第一阶段的结果优先审计高风险目录比如处理用户输入的、涉及认证授权的、做数据序列化的。每审完一个目录就把对应条目的状态从pending改成audited或partial并把发现写入findings.json。这个阶段要控制节奏我一般建议每审 5 到 8 个文件就落一次盘防止中途出错导致前面的工作白费。第三阶段是“补漏与复核”。检查coverage-ledger.json里还有哪些pending或partial的条目评估是否值得继续审。如果某个目录一直没审要么补审要么在 ledger 里写清楚跳过原因。这个阶段还要对findings.json做一次去重和严重程度校准因为分阶段审计时同一个问题可能在不同阶段被重复发现。4.3 参数计算文件大小与审计深度的平衡审计深度和文件大小直接相关。我的经验值是单个文件超过 800 行Agent 的审计质量就会明显下降超过 1500 行基本只能做表面检查。所以我在 skill 里设了一个阈值文件行数小于 500 行做完整审计500 到 1500 行做重点函数审计超过 1500 行标记为partial只审入口和导出函数。这个阈值不是拍脑袋定的。我做过一组对比测试同一个 1200 行的文件完整审计平均能发现 4.2 个问题重点函数审计能发现 3.1 个表面检查只能发现 1.3 个。而完整审计消耗的 token 是重点函数审计的 2.8 倍。从性价比看500 到 1500 行这个区间用重点函数审计是最划算的。当然如果你的 Agent 上下文窗口特别大可以适当放宽但我不建议超过 2000 行。4.4 审计现场记录一次真实执行的片段我拿一个实际的小项目跑过一遍这里还原一下关键片段。项目结构是src/api、src/utils、src/config三个目录总共 34 个文件。第一阶段扫描后coverage-ledger.json里三条记录都是pending。第二阶段先审src/api12 个文件发现 3 个问题其中一个是“错误信息直接返回给客户端可能泄露内部路径”严重程度标为 medium。审src/utils时发现crypto.js有 1800 行超过阈值标记为partial只审了导出的加密函数发现一个“使用了不推荐的哈希算法”的问题。src/config只有 4 个文件全部审完没发现问题状态标为audited。最后findings.json里有 4 条记录coverage-ledger.json里audited两条、partial一条。整个流程跑下来大概 15 分钟比我手动审快得多而且 ledger 让我很清楚哪些地方还没看。这个项目后续如果要继续审直接从partial的crypto.js接着往下就行。5. 常见问题与排查技巧实录5.1 findings.json 写入失败或格式错乱这是最常见的问题表现是 Agent 报告“已写入”但打开文件发现 JSON 不合法。原因通常是 Agent 在拼接 JSON 时混入了自然语言比如在字符串里加了未转义的引号。排查方法是先用jq校验一遍jq empty findings.json如果报错就定位到具体行。预防手段是在 skill 里明确要求 Agent 用结构化输出而不是自由文本。我还会在模板里放一个最小的合法示例让 Agent 照着填比纯文字描述有效得多。5.2 coverage-ledger.json 与实际审计范围不符有时候 ledger 里标了audited但实际上只审了目录下的部分文件。这种“虚标”很危险会让人误以为覆盖完整。排查方法是把 ledger 里的files_checked和实际文件数做对比。我一般会在 skill 里加一条规则audited状态必须满足files_checked等于该目录下的文件总数否则只能标partial。这条规则看起来严格但能避免很多误判。5.3 Agent 执行中途被终止热词里“agent execution terminated due to error”是高频问题。在安全审计场景下最常见的原因是上下文超限或单次执行时间过长。应对策略是分阶段落盘每完成一个小阶段就写一次文件这样即使 Agent 被终止已完成的审计结果也不会丢。另外把大目录拆成多个小批次每批控制在 5 到 8 个文件也能显著降低被终止的概率。5.4 审计结果重复或矛盾分阶段审计时同一个问题可能被多次发现或者不同阶段对同一段代码给出矛盾结论。我的处理方式是在findings.json里用location.file加location.line作为去重键合并时保留置信度最高的那条。如果结论矛盾比如一个阶段说是问题另一个阶段说不是就在description里注明“存在争议建议人工复核”而不是强行选一个。问题现象可能原因排查方法预防措施JSON 不合法混入自然语言jq empty校验用结构化输出模板覆盖范围虚标状态判定过宽对比文件数严格定义 audited 条件执行被终止上下文超限查看执行日志分阶段落盘、拆小批次结果重复矛盾分阶段未去重按位置去重合并时保留高置信度5.5 独家避坑技巧说几个文档里不会写、但实际很管用的技巧。第一findings.json里的id用“日期加序号”的格式比如20250115-001这样即使跨天跑id 也不会冲突。第二coverage-ledger.json每次更新前先备份一份因为 Agent 有时会覆盖写而不是追加写备份能救命。第三如果项目里有大量相似文件比如一堆结构一样的 controller可以只审一个作为样本其余在 ledger 里标sampled并注明“结构相同抽样审计”。这样既省时间又不会漏掉系统性问题。6. 把 skill 用好的几个关键认知6.1 skill 不是越全越好而是越稳越好我见过太多人做 skill 时追求“什么都能查”结果每个能力都做得不深Agent 调用时还容易出错。security-audit-skill的定位应该是“在明确边界内做可靠的审计”而不是“替代所有安全工具”。它擅长的是逻辑层面的检查比如输入校验、权限判断、错误处理这些恰恰是传统扫描工具容易漏掉、而 Agent 又比较擅长的地方。把这一点想清楚skill 的设计就不会跑偏。6.2 两个 JSON 是给未来的自己留的后路findings.json和coverage-ledger.json的价值在第一次审计时可能感受不明显但当你需要第二次、第三次审计同一个项目时它们就是无价之宝。你能清楚看到上次审了什么、发现了什么、哪些还没审。这种连续性是单次扫描工具给不了的。我现在做任何 Agent 审计类项目都会先把这两个文件的结构定下来再写具体的审计逻辑。6.3 人工复核永远不能省Agent 审计再强也只是辅助。findings.json里的confidence字段就是为人工复核准备的。我的习惯是high置信度的问题直接进修复队列medium的抽检low的全部人工过一遍。这样既享受了自动化的效率又不会因为 Agent 的误判造成误伤。安全审计这件事宁可慢一点也不能把不确定的结论当成确定的事实。6.4 后续可以这样扩展如果你已经把基础流程跑通了可以考虑几个扩展方向。一是把findings.json接到 issue 系统自动创建修复任务。二是给coverage-ledger.json加时间戳形成审计历史这样能看出项目的安全状况是在变好还是变差。三是针对特定技术栈做专用子 skill比如专门审数据库查询的、专门审文件操作的让审计更有针对性。这些扩展都不需要改动核心结构只是在现有基础上叠加风险很低。我个人在实际操作中的体会是security-audit-skill这类项目的成败往往不在技术难度而在细节的严谨程度。两个 JSON 的字段设计、状态定义、落盘时机这些看起来琐碎的地方才是决定它能不能真正用起来的关键。把基础打牢后面加什么功能都顺基础不牢加再多功能也是空中楼阁。