首页 / 资讯中心 / 文章详情

Agent安全审计实战:security-audit-skill技能包设计与findings.json解析

Agent安全审计实战:security-audit-skill技能包设计与findings.json解析 ★ FEATURED ARTICLE
1. 从零拆解 security-audit-skill一个让 Agent 真正会做安全审计的技能包第一次看到security-audit-skill这个标题我脑子里蹦出来的不是“又一个安全扫描脚本”而是一个更具体的问题怎么让一个通用 Agent 在拿到一份代码仓库之后像一名有经验的安全审计工程师那样按部就班地跑完整个审计流程最后还能交出一份结构化的、可追溯的结论这个标题里的skill不是随便加的它指向的是当下 Agent 生态里一个非常关键的抽象——技能包。而security-audit则限定了这个技能包的领域。两者组合起来本质上是在回答一个问题安全审计这件事能不能被封装成一个 Agent 可以稳定调用、可复现、可评估的能力单元。我接触过不少团队做 Agent 安全审计最常见的失败模式不是模型不够聪明而是流程没有骨架。Agent 东扫一下、西看一眼最后给你一段“看起来很像那么回事”的自然语言总结但你没法验证它到底覆盖了哪些文件、哪些规则、哪些风险点被漏掉了。security-audit-skill这类项目的价值就在于它把“审计”从一次性的对话行为变成了一个有输入、有中间产物、有最终交付物的工程化流程。从热搜词里出现的findings.json和coverage-ledger.json就能看出来这个技能包的核心设计思路是用结构化文件承载审计状态而不是让结论只存在于模型的上下文里。这篇文章适合几类人看。如果你正在做 Agent 开发尤其是想让 Agent 承担代码审查、安全扫描这类高责任任务那这个技能包的设计思路可以直接借鉴。如果你是一名安全工程师想理解 Agent 时代的审计流程和传统 SAST 工具的区别这里会有不少实操层面的对比。如果你只是刚入门 Agent 开发想找一个有完整闭环的项目来学习技能包怎么写那security-audit-skill也是一个很好的样本因为它同时涉及工具调用、状态管理、结果聚合和覆盖率追踪这几个核心问题。我下面会按照“设计思路 → 核心文件解析 → 实操流程 → 问题排查”的顺序来讲中间会穿插我自己在类似项目里踩过的坑以及一些参数和结构上的取舍逻辑。你不需要有很深的 Agent 框架经验但最好对代码审计的基本概念有一点了解这样读起来会更顺。2. 为什么安全审计需要被封装成 Skill 而不是一个 Prompt2.1 Prompt 式审计的三个致命缺陷很多人做 Agent 安全审计的第一反应是写一个超长的 Prompt把 OWASP Top 10、CWE 清单、审计步骤全塞进去然后让模型对着代码仓库跑。我早期也这么干过实测下来有三个问题几乎无法回避。第一个问题是上下文漂移。当仓库文件数量超过几十个之后模型在长上下文里会逐渐丢失早期的审计指令。你可能在 Prompt 开头写了“必须检查所有涉及用户输入的 SQL 拼接”但跑到第二十个文件的时候模型已经开始只做表面扫描了。这不是模型能力问题而是注意力机制在长上下文下的固有衰减。第二个问题是结论不可追溯。Prompt 式审计的输出通常是一段自然语言报告你很难判断某一条结论对应的是哪个文件、哪一行、哪条规则。当开发团队反问“你说这里有注入风险具体在哪”的时候你只能让 Agent 重新跑一遍而重新跑的结果可能又不一样。第三个问题是覆盖率无法量化。一个仓库有 200 个文件Agent 到底看了多少个哪些目录被跳过了哪些规则被应用了Prompt 模式下这些问题基本没有答案。而安全审计恰恰是一个对覆盖率极度敏感的工作漏掉一个文件可能就意味着漏掉一个高危漏洞。security-audit-skill的设计正是针对这三点。它把审计流程拆成可枚举的步骤每一步的中间结果都落到文件里最后用coverage-ledger.json来记录覆盖情况。这就把“模型记不记得”变成了“文件里有没有”可靠性完全不是一个量级。2.2 Skill 与 Agent 的分工边界热搜词里有个高频问题skill 和 agent 的区别是什么。放到这个项目里边界其实很清楚。Agent 负责决策和调度比如“现在该扫描哪个目录”“这个发现要不要标记为高危”“下一步是继续扫描还是生成报告”。Skill 负责提供确定性的能力比如“给定一个文件路径返回其中的危险函数调用列表”“给定一组发现生成符合 schema 的 findings.json”。这个分工的好处是Agent 的行为可以变化但 Skill 的输出格式是稳定的。你可以换一个 Agent 框架甚至换一个模型只要 Skill 的接口不变整个审计流程的骨架就不会散。这也是为什么security-audit-skill这类项目通常会定义一个明确的输入输出契约而不是把逻辑全写在 Prompt 里。我在实际项目里会把 Skill 分成两类一类是采集型 Skill负责从代码里提取事实比如文件清单、依赖列表、危险模式匹配另一类是聚合型 Skill负责把事实整理成结论比如去重、定级、生成报告。采集型 Skill 要求确定性极高最好用脚本实现聚合型 Skill 可以允许一定的模型判断但输出必须符合固定 schema。2.3 findings.json 与 coverage-ledger.json 的角色定位这两个文件是整个技能包的骨架。findings.json承载的是“发现了什么”每一条 finding 通常包含文件路径、行号、规则 ID、严重级别、描述和证据片段。coverage-ledger.json承载的是“检查了什么”记录每个文件或每个目录的审计状态比如已扫描、已跳过、扫描失败。这两个文件分开设计是有讲究的。如果只有 findings你无法判断一个没有 finding 的文件是真的安全还是根本没被检查。如果只有 coverage你又拿不到具体的风险结论。两者结合才能回答“在已覆盖的范围内发现了哪些问题”这个完整问题。我在自己的项目里会额外加一个字段叫scan_reason记录某个文件为什么被跳过比如“二进制文件”“测试夹具”“生成代码”。这个字段在后期复盘时非常有用因为很多漏报其实不是规则问题而是覆盖策略问题。3. 核心文件结构与字段设计详解3.1 findings.json 的 schema 设计要点一个能用的findings.json不是随便塞几个字段就行。我见过太多项目把 finding 写成{file: a.py, issue: SQL injection}这种结构在后续聚合和展示时几乎没法用。下面是我在实际项目中验证过的一套字段设计你可以直接参考。字段名类型是否必填说明finding_idstring是全局唯一 ID建议用规则ID文件哈希行号生成rule_idstring是对应规则库中的规则标识便于统计和去重severityenum是critical / high / medium / low / infocategorystring是如 injection、auth、crypto、configfile_pathstring是相对于仓库根目录的路径line_startint是起始行号line_endint否结束行号单行可省略evidencestring是触发问题的代码片段建议截取上下文messagestring是人类可读的描述confidenceenum是high / medium / low表示判断置信度remediationstring否修复建议finding_id的生成方式很关键。如果每次扫描生成的 ID 都不一样你就没法做跨次对比。我通常用rule_id file_path line_start的哈希作为 ID这样同一个问题在代码没变的情况下 ID 稳定方便追踪“这个问题是这次新引入的还是历史遗留的”。confidence字段是很多人会忽略的。安全审计里有一类问题是“疑似但不确定”比如一个变量经过了多层传递才进入危险函数静态分析很难 100% 确认。这时候标confidence: medium比强行标 high 更诚实也方便后续人工复核时优先看高置信度的发现。3.2 coverage-ledger.json 的记录粒度coverage-ledger.json的粒度选择是一个权衡。记到文件级别信息最全但文件会很大记到目录级别文件小但定位精度不够。我的建议是文件级别记录目录级别汇总。具体结构可以是这样顶层有一个summary对象记录总文件数、已扫描数、跳过数、失败数下面有一个entries数组每个 entry 对应一个文件包含file_path、status、reason、finding_count、scan_duration_ms。status的取值建议固定为scanned、skipped、failed、partial四种。partial这个状态很有用。有些大文件可能因为超时只扫描了一部分标成scanned会误导标成failed又太绝对。partial配合一个scanned_lines字段能准确反映实际情况。注意coverage-ledger 一定要在审计开始前初始化而不是审计结束后补写。我踩过的坑是Agent 中途崩溃后没有 ledger完全不知道跑到哪了只能从头再来。边扫边写 ledger崩溃后可以从断点恢复。3.3 两个文件之间的关联方式findings.json和coverage-ledger.json通过file_path关联。一个文件在 ledger 里是scanned状态但 findings 里没有对应记录说明这个文件被检查过且没发现问题。一个文件在 ledger 里是skipped那 findings 里就不应该有它的记录如果有说明流程有 bug。我在项目里会加一个校验步骤审计结束后遍历 findings 里的每个file_path检查它在 ledger 里是否存在且状态为scanned或partial。如果发现 findings 里的文件在 ledger 里是skipped直接报流程错误。这个校验能抓出不少 Agent 调度层面的问题。4. 实操流程从仓库到审计报告的完整链路4.1 环境准备与技能包加载假设你用的是常见的 Agent 框架技能包通常以一个目录的形式存在里面有skill.yaml或类似的清单文件、scripts/目录放采集脚本、schemas/目录放 JSON schema、rules/目录放规则定义。加载技能包的过程就是把清单文件注册到 Agent 的工具列表里。我建议在加载阶段做一次 schema 校验确保findings.json和coverage-ledger.json的 schema 文件存在且合法。这一步看起来多余但实际能避免很多“跑到一半发现输出格式不对”的问题。schema 校验用 Python 的jsonschema库几行代码就能搞定。import json from jsonschema import validate with open(schemas/findings.schema.json) as f: findings_schema json.load(f) with open(schemas/coverage.schema.json) as f: coverage_schema json.load(f) def validate_findings(data): validate(instancedata, schemafindings_schema)环境准备阶段还要确认一件事Agent 有没有文件系统读取权限。安全审计需要遍历仓库如果 Agent 只能通过受限的工具访问文件那采集型 Skill 的设计就要相应调整比如改成批量读取而不是逐个读取。4.2 仓库预扫描与文件分类正式审计之前先做一次预扫描把仓库里的文件分类。这一步的目的是确定哪些文件需要审计、哪些可以跳过。常见的分类维度包括文件类型源码、配置、文档、二进制、是否生成代码、是否测试夹具、文件大小。我通常会把文件分成四类必审业务源码、配置文件、选审测试代码、脚本、跳过二进制、依赖目录、生成代码、待定超大文件、无法识别的类型。这个分类结果直接写入coverage-ledger.json的初始状态status先标成skipped或pending。分类规则可以用 glob 模式加文件大小阈值来实现。比如**/node_modules/**直接跳过*.min.js直接跳过超过 1MB 的源码文件标成partial候选。这些规则不复杂但能显著减少无效扫描。提示跳过规则一定要记录reason。我见过一个项目把vendor/目录整个跳过了结果那个目录里其实有被修改过的第三方库藏了一个后门。跳过可以但要知道自己跳过了什么。4.3 逐文件审计与发现记录这是整个流程的核心环节。对每个pending或scanned状态的文件Agent 调用采集型 Skill 提取事实然后根据规则库判断是否存在问题。每发现一个问题就往findings.json里追加一条记录同时更新 ledger 里对应文件的finding_count。这里有个实操细节不要等所有文件扫完再写 findings。每扫完一个文件就落盘一次虽然 IO 次数多了但崩溃恢复的成本低得多。我一般用追加模式写 findings每条记录一行 JSON这样即使中途崩溃已写入的记录也不会丢。规则匹配这块我建议把规则定义和规则执行分开。规则定义放在rules/目录下的 YAML 文件里每条规则包含rule_id、pattern、severity、category、message_template。规则执行用一个通用引擎读取 YAML 然后对文件内容做匹配。这样加规则不用改代码改 YAML 就行。- rule_id: SQL_INJECTION_001 pattern: execute\\(.*\\.*\\) severity: high category: injection message_template: 检测到字符串拼接形式的 SQL 执行可能存在注入风险 languages: [python]4.4 覆盖率核算与报告生成所有文件处理完之后进入聚合阶段。先核算覆盖率scanned partial的文件数除以必审 选审的文件数得到一个百分比。这个数字要写进最终报告因为它直接决定了审计结论的可信度。覆盖率 60% 的报告和覆盖率 95% 的报告可信度完全不一样。然后对 findings 做去重和排序。去重主要针对同一位置被多条规则命中的情况保留 severity 最高的那条。排序按 severity 降序同级别按文件路径排序。最后生成一份人类可读的报告通常包含审计概览覆盖率、发现数量、严重级别分布、高危发现详情、中低危发现列表、跳过文件说明。报告生成这一步我建议也做成 Skill而不是让 Agent 自由发挥。因为报告的格式一旦不稳定后续做趋势对比就没法自动化。用模板引擎比如 Jinja2渲染报告输入就是 findings 和 coverage 两个 JSON输出是 Markdown 或 HTML。5. 常见问题与排查技巧实录5.1 审计结果不稳定两次跑出来不一样这是最常见的问题根因通常有三个。一是 Agent 的调度有随机性比如文件处理顺序不固定导致某些依赖上下文的判断结果不同。解决办法是固定文件处理顺序按路径排序后再逐个处理。二是规则匹配用了模糊匹配比如正则写得太宽泛不同上下文下命中结果不同。解决办法是把正则收紧必要时加上语言和文件类型限制。三是模型判断环节的 temperature 设置过高解决办法是把聚合型 Skill 的 temperature 调到 0 或接近 0。我在项目里会做一个“稳定性测试”同一个仓库连续跑三次对比三次的 findings 集合。如果差异超过 5%就说明流程里有不稳定因素需要定位。这个测试在 CI 里跑能提前发现很多问题。5.2 覆盖率上不去大量文件被跳过先看 ledger 里的reason分布。如果大部分跳过原因是“文件过大”那就要调整大小阈值或者对大文件做分片扫描。如果原因是“无法识别的类型”那就要补充文件类型映射表。如果原因是“二进制文件”那要确认这些二进制文件是否真的不需要审计有些配置文件虽然扩展名奇怪但其实是文本。还有一种情况是 Agent 主动跳过了某些文件因为它“觉得”这些文件不重要。这是 Prompt 设计问题需要在技能包的指令里明确跳过任何文件都必须记录 reason且 reason 必须在预定义的枚举值里。不允许 Agent 自由发挥跳过理由。5.3 findings 里出现重复记录重复通常来自两个地方。一是同一段代码被多条规则命中比如一个函数既匹配了“危险函数调用”又匹配了“未校验输入”。这种情况在聚合阶段按file_path line_start去重保留 severity 最高的。二是同一个文件被扫描了两次这通常是 ledger 状态更新不及时导致的。解决办法是在扫描前检查 ledger 状态已经是scanned的文件直接跳过。我还会加一个dedup_key字段用rule_id file_path line_start生成聚合时按这个 key 去重。这样即使规则库有重叠也不会产生重复 finding。5.4 Agent 执行中途报错终止热搜词里有个agent execution terminated due to error这在安全审计场景里很常见因为审计过程长、工具调用多任何一步出错都可能导致整个流程挂掉。我的做法是把审计流程做成可恢复的每个文件处理完就更新 ledgerAgent 重启后先读 ledger找到第一个pending或partial的文件继续处理。另外要给每个 Skill 调用加超时和重试。单个文件扫描超过 30 秒就标partial跳过不要让一个文件卡死整个流程。重试次数建议 2 次超过就记录失败原因继续下一个。问题现象可能原因排查方向解决手段两次结果不一致调度随机、正则过宽、temperature 高固定顺序、收紧正则、降 temperature加稳定性测试覆盖率低跳过规则过严、类型识别缺失看 ledger 的 reason 分布调整阈值、补类型映射findings 重复多规则命中、重复扫描检查 dedup_key聚合去重、状态前置检查中途终止单文件超时、工具报错看 ledger 最后状态断点恢复、超时重试5.5 独家避坑规则库的版本管理这一点很少有人提但非常重要。规则库是会演进的你今天加了 10 条规则明天可能改 5 条。如果不做版本管理你就没法解释“为什么上周没报的问题这周报了”。我的做法是给规则库打版本号每次 findings 里记录ruleset_version。这样对比两次审计结果时能区分是代码变了还是规则变了。规则库的版本号建议用语义化版本主版本号在规则有破坏性变更时递增次版本号在新增规则时递增修订号在修改规则描述时递增。这个习惯一旦养成后期做审计趋势分析会轻松很多。6. 把 security-audit-skill 用起来的几个实战建议如果你打算在自己的项目里落地这套东西我有几个具体建议。第一先从一个小仓库开始不要一上来就扫几万行代码的 monorepo。小仓库能让你快速跑通全流程验证 findings 和 coverage 两个文件的结构是否合理。第二规则库不要贪多先写 10 到 20 条高置信度的规则确保误报率低。误报率高的规则库会让整个审计结果失去信任。第三把 coverage-ledger 的初始化做成独立步骤不要混在审计流程里。这样即使审计流程改了ledger 的结构也不会受影响。关于 Agent 的选择我的经验是采集型 Skill 用脚本实现聚合型 Skill 用模型实现两者通过文件系统解耦。这样你可以随时替换模型而不影响采集逻辑。热搜词里提到的各种 Agent 框架本质上都是调度器真正决定审计质量的是 Skill 的设计和规则库的质量。最后分享一个我在实际使用中的小技巧在 findings 里加一个first_seen和last_seen字段记录这个问题第一次和最近一次被发现的时间。这样你可以快速识别出“长期未修复”的问题在报告里单独列出来。这个字段用审计时间戳填充跨次审计时保留历史值。对于安全团队来说一个存在了三个月还没修的高危漏洞比一个新发现的高危漏洞更值得关注。
阅读完成 · 觉得有帮助?
咨询建站