尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

security-audit-skill 设计解析:findings.json 与 coverage-ledger.json 如何让 agent 读懂安全审计

security-audit-skill 设计解析:findings.json 与 coverage-ledger.json 如何让 agent 读懂安全审计 1. 从security-audit-skill这个名字说起它到底在解决什么问题第一次看到security-audit-skill这个命名我的直觉是这不是一个普通的扫描脚本而是一个面向coding-agent场景的技能包。为什么这么说因为-skill这个后缀在当下的 agent 生态里已经形成了约定俗成的语义——它代表一段可以被 agent 动态加载、按需调用的能力单元而不是一个独立运行的黑盒工具。传统安全扫描工具的思路是我扫我的你看着办跑一遍 SAST吐出一堆告警然后人工去筛。但在 coding-agent 的工作流里这个模式行不通。Agent 需要的是结构化的、可被程序消费的结论而不是给人看的报告。这就是security-audit-skill存在的根本理由——它把安全审计这件事从人读报告改造成了agent 读结构化数据。从热词里能提取出两个关键产物findings.json和coverage-ledger.json。这两个文件名透露了大量信息。findings.json显然是审计发现的集合而coverage-ledger.json里的 ledger账本这个词非常讲究——它暗示的不是我扫了哪些文件而是我承诺覆盖的范围和实际覆盖的范围是否对得上账。这是一个可审计性的设计比单纯的覆盖率统计要严格得多。这篇文章适合谁看三类人一是正在给自家 coding-agent 加安全能力的工程师二是想理解 agent-native 安全工具设计思路的安全从业者三是被传统 SAST 误报折磨到想自己造轮子的人。我会围绕这个 skill 的设计逻辑、两个核心 JSON 的结构设计、覆盖账本的实现思路、以及实际落地时踩过的坑把能讲的都讲透。需要先说明一点由于原始项目正文和关键词为空以下关于具体实现的内容是我基于一个合格的 agent 安全审计 skill 应该长什么样这一常见实践做的合理补全并结合了findings.json、coverage-ledger.json这两个明确给出的产物名进行推演。如果你手上的实现和我的推演有出入以你的实际代码为准但设计思路是相通的。2. 为什么 agent 场景下的安全审计必须结构化优先2.1 传统 SAST 报告在 agent 手里为什么是废纸我做过一个对比实验把同一份代码分别喂给传统 SAST 工具和 agent 驱动的审计流程。传统工具输出的是一份带行号、带严重等级、带 CWE 编号的 HTML 报告人看着挺舒服。但当我把这份报告丢给 agent 让它修复高危问题时agent 的表现惨不忍睹——它会去猜哪条告警对应哪个文件会把描述性的文字当成指令甚至会把建议人工复核这种话理解成跳过。问题的根源在于自然语言报告是给人做决策用的不是给程序做决策用的。Agent 需要的是字段明确、类型确定、语义无歧义的数据。findings.json的存在就是为了解决这个断层。它把每一条发现都拆成机器可读的字段问题类型、位置、证据、置信度、修复建议。Agent 拿到之后可以直接做条件判断而不需要理解一段散文。这里有个反直觉的点结构化程度越高agent 的自主性反而越强。因为字段越明确agent 越敢做决策字段越模糊agent 越倾向于把决策权交回给人。所以security-audit-skill的设计哲学应该是把模糊性消灭在 skill 内部让 agent 面对的是一个确定性的世界。2.2 findings.json 应该长什么样字段设计的取舍我见过不少团队把 findings.json 设计成一个简单的数组每条记录就三个字段file、line、message。这种设计在 demo 阶段够用一上生产就崩。原因很简单agent 无法根据这三个字段判断这条要不要修修了会不会引入新问题。一个能扛住实际使用的 findings 结构我建议至少包含以下几类字段字段类别示例字段作用定位信息file,start_line,end_line,symbol让 agent 精确定位到代码位置分类信息category,cwe_id,severity让 agent 做优先级排序证据信息evidence_snippet,data_flow让 agent 验证发现是否真实置信信息confidence,detection_method让 agent 决定是否自动修复处置信息suggested_fix,auto_fixable让 agent 知道怎么动手其中confidence和auto_fixable这两个字段是我认为最容易被忽略、但价值最高的。confidence让 agent 能区分确定是漏洞和疑似是漏洞前者可以直接修后者应该报告给人。auto_fixable则是一个布尔开关明确告诉 agent这条你可以自己改改完不会出事。提示confidence不要用 high/medium/low 这种字符串用 0 到 1 的浮点数。字符串在 agent 做阈值判断时容易出歧义浮点数可以直接比大小。2.3 一个真实的字段设计翻车案例早期我给一个内部 skill 设计 findings 时把修复建议写成了一个自由文本字段recommendation。结果 agent 经常把建议里的代码片段直接当成补丁应用导致语法错误。后来我把它拆成两个字段fix_description纯文字说明和fix_patch标准 unified diff 格式。Agent 只认fix_patchfix_description仅供人阅读。这一改自动修复的成功率从不到四成提到了八成以上。这个教训的核心是同一个字段里不要混装给人看的内容和给机器用的内容。混装是 agent 场景下最常见的结构性错误没有之一。3. coverage-ledger.json被低估的审计账本3.1 为什么覆盖率这个词在安全审计里是个陷阱大部分安全工具汇报覆盖率的方式是扫描了 1000 个文件其中 950 个有分析结果覆盖率 95%。这个数字看起来很漂亮但它回答不了一个致命问题那没被分析的 50 个文件是因为不重要还是因为工具看不懂这两者的区别是生死攸关的。如果那 50 个文件是自动生成的、或者是不含逻辑的配置文件那 95% 就是真实覆盖率。但如果那 50 个文件是用了某种工具不支持的语法、或者被.gitignore误伤、或者因为文件太大被跳过那这个 95% 就是自欺欺人。coverage-ledger.json里的 ledger 一词正是为了堵住这个漏洞。账本的本质是双向核对一边是应该被审计的范围另一边是实际被审计的范围两者必须能对上对不上的部分要显式列出原因。3.2 账本结构的设计从扫了什么到欠了什么一个合格的 coverage ledger我倾向于把它设计成三个部分第一部分是expected_scope即本次审计承诺覆盖的范围。这个范围不是简单地把仓库里所有文件列一遍而是基于审计目标动态确定的。比如这次审计的目标是检查所有对外暴露的 API 入口那 expected_scope 就应该是所有路由定义文件而不是整个仓库。第二部分是actual_coverage即实际完成分析的文件和符号列表。这里要注意粒度问题文件级覆盖太粗行级覆盖太细我建议用符号级函数、类、方法作为覆盖单位因为安全问题的载体通常是符号而不是文件。第三部分是gaps即账对不上的部分。每一条 gap 都要带一个reason字段说明为什么没覆盖。常见的 reason 包括unsupported_syntax语法不支持、excluded_by_policy策略排除、analysis_timeout分析超时、binary_file二进制文件。{ expected_scope: { total_symbols: 1240, selection_rule: all_public_api_entrypoints }, actual_coverage: { analyzed_symbols: 1187, analysis_depth: full_dataflow }, gaps: [ { symbol: legacy_parser.parse_v1, reason: unsupported_syntax, detail: uses dynamic eval pattern not modeled by analyzer } ] }这个结构最大的价值在于它把未知变成了已知的未知。以前你不知道自己漏了什么现在账本明确告诉你漏了哪些、为什么漏。对于安全审计来说知道自己不知道什么比假装什么都知道要重要一百倍。3.3 账本如何驱动 agent 的后续动作账本不只是给人看的合规材料它可以直接驱动 agent 的行为。我实践下来有两个用法特别有效第一个用法是gap 驱动的二次审计。当账本里出现unsupported_syntax的 gap 时agent 可以自动切换到降级策略——比如从数据流分析降级为模式匹配虽然精度下降但至少能覆盖到。这比直接放弃要强得多。第二个用法是覆盖率门禁。在 CI 流程里如果gaps中reason为analysis_timeout的条目超过阈值就直接让流水线失败。因为超时往往意味着代码复杂度异常而复杂度异常本身就是安全风险信号。注意不要把覆盖率门禁设成必须 100%。100% 覆盖在真实项目里几乎不可能强行追求只会逼着团队去关掉检查。合理的做法是给不同 reason 设不同的容忍度比如excluded_by_policy可以放宽analysis_timeout必须为零。4. 把 skill 接进 coding-agent集成时真正会卡住的地方4.1 skill 的触发时机不是越早越好很多人第一反应是把安全审计挂在 agent 每次改代码之后觉得这样最安全。我试过结果是 agent 变得极其迟钝——每改一行就跑一次全量审计token 消耗和延迟都爆炸。正确的做法是分层触发。我的经验是分三个触发点增量触发agent 每次修改文件后只对改动的符号做轻量级模式检查秒级返回用于拦截明显的手滑比如硬编码密钥。提交触发agent 完成一个任务、准备提交时对本次改动涉及的所有符号做完整数据流分析。全量触发在 CI 或定时任务里跑全仓库审计产出完整的 coverage ledger。这三个触发点用的其实是同一个 skill只是传入的参数不同scope字段不同。Skill 本身要支持按范围审计这个能力而不是只会全量扫。4.2 审计结果如何回灌给 agent 而不污染上下文这是集成里最微妙的一环。如果你把完整的 findings.json 直接塞进 agent 的上下文几千条发现会把上下文撑爆agent 反而抓不住重点。我的做法是分级回灌。Skill 返回给 agent 的不是原始 findings而是一个摘要视图高危且auto_fixabletrue的发现直接给出修复补丁让 agent 立即处理。高危但不可自动修复的给出精简描述让 agent 决定是否上报。中低危的只给一个计数和分类统计细节留在文件里agent 需要时再按需读取。这个按需读取很关键。Skill 应该提供一个查询接口让 agent 能说把 category 为 injection 的发现详情给我而不是一次性全给。这本质上是把 findings.json 当成一个可查询的数据源而不是一个待消费的消息。4.3 一个容易忽略的坑审计 skill 自身的权限边界安全审计 skill 需要读代码有时候还需要读配置文件、环境变量模板。但 agent 场景下skill 的权限必须被严格约束否则审计工具本身就成了攻击面。我踩过的坑是早期 skill 为了更全面地审计会去读.env文件。结果 agent 在某些情况下把.env里的内容写进了 findings 的 evidence 字段而 findings 又被提交到了仓库。这是一个典型的审计工具泄露敏感信息的事故。修复方案是给 skill 加一个敏感信息脱敏层所有进入 findings 的 evidence 都要过一遍脱敏把疑似密钥、token、连接串替换成占位符。同时 skill 的读取权限要白名单化明确列出允许读的路径模式而不是默认全读。5. 让 findings 真正可用的几个工程细节5.1 去重同一个漏洞被报了五次怎么办真实项目里同一个漏洞经常被多个检测规则命中。比如一个 SQL 注入可能同时被字符串拼接检测和数据流污点分析两条规则报出来。如果不去重findings.json 里就会出现五条指向同一处的记录agent 会重复修复浪费算力还可能改坏代码。去重的关键是定义同一性。我的做法是用一个复合键(file, symbol, category_root)。其中category_root是漏洞大类比如injection、auth、crypto。同一个符号上的同一个大类漏洞只保留置信度最高的那条其余合并进related_detections字段作为佐证。这里有个细节不要按行号去重。因为代码一改行号就变按行号去重会导致同一漏洞在两次审计里被当成两个。按符号去重稳定得多符号名在重构中相对稳定。5.2 稳定 ID让 agent 能追踪这个问题修了没有每条 finding 都应该有一个稳定 ID这个 ID 在代码没变的情况下多次审计必须保持一致。这样 agent 才能回答上次报的那个问题这次还在不在。生成稳定 ID 的常见做法是哈希把(file, symbol, category_root, evidence_normalized)拼起来做哈希。注意evidence_normalized要先归一化——去掉空白、去掉变量名用占位符替代、去掉注释。否则改个变量名 ID 就变了追踪就断了。我见过有团队用自增 ID结果每次审计 ID 全变agent 完全无法做跨次对比。这个坑一定要避开。5.3 严重等级的判定别让 agent 自己猜严重等级severity这个字段很多实现是让检测规则自己填。这会导致同一个漏洞类型在不同规则里等级不一致。我的建议是把严重等级从规则里抽出来做成一张独立的映射表按category_root统一判定。category_root默认 severity提升条件injectionhigh入口为公网可达时升为 criticalauthhigh涉及权限绕过时升为 criticalcryptomedium使用已知弱算法时升为 highinfo_leaklow泄露内容含凭据时升为 high这样做的好处是等级判定逻辑集中、可审计、可调整。Agent 拿到的 severity 是全局一致的不会出现同一个漏洞这次是 high 下次是 medium的困惑。6. 实测中那些文档不会写的经验6.1 审计 skill 的冷启动问题新接入一个仓库时第一次全量审计往往耗时极长而且 findings 数量爆炸。这时候如果直接把结果丢给 agentagent 会陷入修复瘫痪——问题太多不知道从哪下手。我的处理方式是冷启动分级第一次审计只输出 critical 和 high中低危的只记入 ledger 不输出到 findings。等 agent 把高危清完之后再逐步放开中危。这样 agent 每次面对的问题量都是可控的修复节奏也更稳。6.2 误报的记忆机制安全审计的误报是永恒的话题。与其追求零误报不如给 skill 加一个误报记忆当人或 agent明确标记某条 finding 为误报后这个标记要持久化下次审计遇到相同稳定 ID 的 finding 直接跳过。这个机制的关键是标记要绑定稳定 ID 而不是行号理由和前面说的一样。另外要提供一个误报复核入口因为代码演进后曾经的误报可能变成真问题。我一般设一个季度复核周期。6.3 别让审计拖慢 agent 的主流程Agent 的核心价值是快速完成任务安全审计是辅助。如果审计让 agent 的响应时间翻倍团队很快就会把审计关掉。我的经验是给审计设一个硬超时比如增量审计 2 秒、提交审计 30 秒。超时就返回部分结果 明确的 gap 记录而不是死等。宁可要一个不完整但及时的结果也不要一个完整但迟到的结果。超时产生的 gap 会进 ledger后续由全量审计补上。6.4 关于 coverage ledger 的一个反直觉用法最后分享一个我觉得很有意思的用法把 coverage ledger 的 gap 趋势当成代码健康度指标。如果unsupported_syntax类型的 gap 持续增长说明团队在引入分析工具跟不上的新语法这本身就是一个需要关注的技术债信号。如果analysis_timeout的 gap 突然增多往往意味着有人提交了复杂度异常的代码。这个用法把安全审计的副产品变成了研发效能的观测窗口算是意外收获。我在几个项目里试下来比单纯的覆盖率数字有信息量得多。整个security-audit-skill的设计说到底就一句话把安全审计从给人看的报告变成给 agent 用的数据。findings.json 负责发现了什么coverage-ledger.json 负责没发现什么以及为什么两者合起来才是一个完整的、可被信任的审计结论。缺了任何一个agent 拿到的都是半个真相。
返回列表