security-audit-skill 深度解析:用 findings.json 和 coverage-ledger.json 构建可追溯的代码安全审计

发布时间:2026/9/23 10:45:39
security-audit-skill 深度解析:用 findings.json 和 coverage-ledger.json 构建可追溯的代码安全审计 1. 从零认识 security-audit-skill它到底解决什么问题第一次看到security-audit-skill这个名字很多人会以为它又是一个跑一遍npm audit或者调一下 SAST 扫描器的封装脚本。我一开始也这么想直到真正把它接进自己的 coding-agent 工作流里才发现它想做的事情完全不一样——它要解决的是代码安全审计这件事本身的可复现性和可追溯性。先说结论security-audit-skill是一套面向 coding-agent 的安全审计技能规范核心产物是两个结构化文件——findings.json和coverage-ledger.json。前者记录发现了什么后者记录查过什么、没查什么。这两个文件加起来构成了整个审计过程的完整证据链。为什么这件事值得单独做成一个 skill因为我在实际项目里踩过太多坑了。用传统扫描器跑一遍出来几百条告警团队里没人敢拍板说这些告警处理完了代码是安全的。为什么不敢因为没人知道扫描器到底覆盖了哪些文件、哪些数据流、哪些攻击面。扫描器说没发现问题到底是真没问题还是它根本没扫到那块代码这个疑问不解决安全审计就永远停留在跑个工具交差的层面。security-audit-skill的设计思路就是把审计从一次性动作变成可审计的过程。它适合三类人一是把 coding-agent 接入研发流程的工程师需要让 agent 的安全检查结果可信二是做代码审计的安全同学需要一套标准化的产出格式三是技术负责人需要向团队或客户证明这次审计到底做了什么。关键词里的coding-agent是理解这个 skill 的钥匙。它不是给人用的命令行工具而是给 AI coding agent 用的技能定义。也就是说它规定了 agent 在做安全审计时应该遵循的流程、应该产出什么、应该怎么记录覆盖范围。这一点非常关键后面会反复提到。2. 核心设计思路拆解为什么是 findings.json 加 coverage-ledger.json2.1 传统扫描器输出的三个致命缺陷在讲security-audit-skill的设计之前我得先吐槽一下传统扫描器的输出。我经手过的项目里扫描报告通常长这样一个 HTML 文件里面列了几百条 issue每条有 severity、file、line、description。看起来挺全但实际用起来有三个致命问题。第一个问题是没有覆盖证据。报告只告诉你发现了什么从不告诉你检查了什么。一个 5000 行的项目扫描器可能只分析了 800 行剩下 4200 行它压根没碰但报告里不会写。你拿着这份报告说项目安全其实是在裸奔。第二个问题是发现项缺乏上下文。一条SQL 注入告警它不告诉你这个注入点是否真的可达、参数是否经过校验、调用链上有没有其他防护。安全同学得自己一条条去追效率极低。第三个问题是不可复现。同一个项目今天扫和明天扫结果可能不一样因为扫描器的规则库更新了、配置变了、甚至随机性导致结果波动。你没法说这次审计和上次审计是同一套标准。2.2 双文件设计背后的逻辑security-audit-skill用findings.json和coverage-ledger.json两个文件正好对应解决上面三个问题。findings.json解决的是发现项上下文问题。它不只是一个告警列表而是一个结构化的发现记录。每条 finding 通常包含漏洞类型、位置文件加行号、严重级别、证据触发路径或代码片段、影响分析、修复建议。注意加粗的这三项是传统扫描器最缺的。证据让发现可验证影响分析让优先级可判断修复建议让结果可落地。coverage-ledger.json解决的是覆盖证据和可复现问题。Ledger 这个词用得很准它是账本的意思。这个文件记录的是审计的账哪些文件被检查了、哪些函数被分析了、哪些数据流被追踪了、哪些攻击面被评估了、哪些区域明确标记为未覆盖及原因。有了这本账你才能说这次审计覆盖了 X% 的代码未覆盖的部分是因为 Y 原因。我特别喜欢这个设计的一点是它把没查也当成一种需要记录的结果。传统扫描器对没查是沉默的而security-audit-skill要求 agent 显式声明这块我没查原因是……。这种诚实恰恰是安全审计最需要的品质。2.3 为什么做成 skill 而不是工具有人会问为什么不直接写个脚本生成这两个文件非要做成 skill我的理解是安全审计的核心难点不在生成文件而在判断。判断哪段代码有风险、判断一个发现是否误报、判断覆盖范围是否足够这些都需要语义理解能力而这正是 coding-agent 的强项。做成 skill意味着它定义的是 agent 的行为规范而不是固定的执行逻辑。agent 可以根据项目特点灵活调整审计策略但产出的格式和记录的要求是固定的。这种流程标准化、执行灵活化的组合比死板的脚本更适合真实项目。提示如果你打算自己实现类似的 skill记住一个原则——规范要约束产出什么和必须记录什么而不是约束怎么查。前者保证结果可比后者保留灵活性。3. findings.json 深度解析一条合格的发现长什么样3.1 字段设计与背后的考量findings.json的结构设计直接决定了审计结果能不能被下游消费。我根据实际使用经验梳理出一套比较合理的字段设计你可以直接参考。字段类型是否必填说明idstring是唯一标识建议用FINDING-001这种可读格式categorystring是漏洞类别如 injection、auth、crypto、configseveritystring是critical / high / medium / low / infotitlestring是一句话描述控制在 80 字内locationobject是包含 file、line_start、line_end、functionevidencestring是触发路径或关键代码片段impactstring是被利用后的实际后果recommendationstring是具体修复方案不要写加强校验这种空话confidencestring是high / medium / low表示误报可能性referencesarray否相关规范或文档链接这里我要重点说三个字段。severity 和 confidence 必须分开。很多团队把这两个混为一谈导致一个高危但不确定的发现和一个高危且确定的发现被同等对待浪费大量排查精力。分开之后你可以优先处理 high severity 加 high confidence 的把 high severity 加 low confidence 的放进待验证队列。evidence 字段是灵魂。它必须能让另一个人在不看原始代码的情况下理解这个发现是怎么来的。比如一条命令注入的 evidence应该包含从用户输入到执行点的完整数据流而不是只贴一行exec(cmd)。recommendation 要可执行。我见过太多建议对输入进行校验这种废话。好的建议应该是在parseQuery函数入口处对tableName参数使用白名单校验只允许[a-zA-Z_]字符集。3.2 一个真实的 finding 示例光说字段太抽象直接看一个我实际审计中产出的 finding。场景是一个 Node.js 服务里的文件读取接口。{ id: FINDING-007, category: path-traversal, severity: high, confidence: high, title: 文件下载接口存在路径穿越可读取任意文件, location: { file: src/routes/download.js, line_start: 23, line_end: 31, function: handleDownload }, evidence: req.query.filename 未经规范化直接拼接到 path.join(UPLOAD_DIR, filename)。当 filename 为 ../../etc/passwd 时path.join 会解析到 UPLOAD_DIR 之外。调用链GET /download?filename... - handleDownload - fs.createReadStream。, impact: 攻击者可读取服务器上任意可读文件包括配置文件、密钥文件、源码。若服务以高权限运行可进一步读取系统敏感文件。, recommendation: 使用 path.resolve 得到绝对路径后校验其是否以 UPLOAD_DIR 的绝对路径为前缀或使用 path.basename 剥离目录部分。示例const safe path.resolve(UPLOAD_DIR, path.basename(filename)); if (!safe.startsWith(path.resolve(UPLOAD_DIR))) throw new Error(invalid path);, references: [CWE-22] }这条 finding 的价值在于任何人拿到它都能独立验证、独立修复不需要再去翻代码找上下文。这就是security-audit-skill追求的产出质量。3.3 严重级别与置信度的判定标准判定 severity 和 confidence 是审计中最容易主观化的环节。我总结了一套相对客观的标准供你参考。Severity 判定看三个维度可达性外部能否触发、影响面影响单用户还是全系统、利用难度是否需要特殊条件。三者都高就是 critical两个高就是 high一个高就是 medium都不高就是 low。Confidence 判定看证据强度有完整数据流证据的是 high有代码片段但数据流不完整的是 medium只有模式匹配没有上下文的是 low。这里有个经验宁可把 confidence 标低也不要标高。标低了顶多多花点时间验证标高了可能导致误报被当成真问题修复浪费开发资源。注意不要为了报告好看而虚高 severity。我见过团队把所有发现都标成 high结果开发团队直接不看了。分级的意义在于排序不在于吓人。4. coverage-ledger.json 深度解析审计的账本怎么记4.1 覆盖账本的核心结构如果说findings.json是审计的产出那coverage-ledger.json就是审计的过程凭证。它的核心作用是回答一个问题这次审计到底查了什么没查什么为什么。一个完整的 coverage ledger 通常包含几个层次文件级覆盖、函数级覆盖、攻击面覆盖、以及未覆盖项及原因。我设计的结构大致如下。{ audit_scope: { target: src/, total_files: 142, analyzed_files: 138, excluded_files: 4 }, coverage_by_category: { injection: { checked: 56, total: 60, note: 4 处动态拼接未追踪 }, auth: { checked: 22, total: 22 }, crypto: { checked: 8, total: 10, note: 2 处使用第三方库未深入 } }, uncovered: [ { path: src/legacy/old_parser.js, reason: 已标记废弃不在本次审计范围, risk: medium } ], methodology: 基于数据流追踪的静态分析结合 agent 语义理解 }这个结构里我最看重的是uncovered数组。它把没查的部分显式列出来并标注原因和风险。这样审计报告的读者就能判断未覆盖的部分是否可接受。4.2 为什么未覆盖比已覆盖更重要这一点我要展开讲因为它反直觉。大多数人做审计关注的是我查了多少但真正决定审计可信度的是我没查多少以及为什么没查。举个例子。一个项目有 100 个文件你查了 95 个报告说覆盖 95%。听起来不错。但如果那 5 个没查的文件里有一个是处理用户认证的核心模块那这个 95% 就是虚假的安全感。反过来如果你查了 80 个但明确标注未查的 20 个都是自动生成的 protobuf 代码不含业务逻辑那这个 80% 反而更可信。security-audit-skill强制记录未覆盖项本质上是在对抗一种常见的自欺欺人——用覆盖率数字掩盖审计盲区。我在实际使用中会要求 agent 对每个未覆盖项给出 reason 和 risk 两个字段。reason 说明为什么没查risk 说明不查可能带来的风险等级。这样技术负责人一眼就能看出哪些盲区需要补查。4.3 覆盖账本的动态更新coverage ledger 不是一次性写完就完事的它应该随着审计过程动态更新。我通常会让 agent 分阶段记录初始扫描后记录文件级覆盖深入分析后更新函数级覆盖最后汇总攻击面覆盖。这种动态更新有个好处如果审计中途被打断你也能从 ledger 里看到进度知道哪些部分已经查过、哪些还没查。这在大型项目审计里特别实用因为一次完整审计可能要跑好几个小时中途中断是常事。实操心得让 agent 每完成一个模块的审计就落盘一次 ledger而不是最后统一写。这样即使 agent 崩溃已完成的覆盖记录也不会丢。5. 把 security-audit-skill 接进 coding-agent 的完整实操5.1 环境准备与 skill 定义要让 coding-agent 用上这个 skill第一步是定义 skill 本身。不同 agent 框架的 skill 定义方式不同但核心内容是一致的告诉 agent 什么时候触发这个 skill、执行时遵循什么流程、产出什么格式。我以常见的 skill 定义结构为例给你一个可直接参考的模板。--- name: security-audit-skill description: 对代码库执行结构化安全审计产出 findings.json 和 coverage-ledger.json trigger: 当用户要求进行安全审计、漏洞扫描、代码安全检查时 --- 执行安全审计时遵循以下流程 1. 确定审计范围列出所有待审计文件 2. 按攻击面分类injection/auth/crypto/config/logic逐类审计 3. 每发现一个问题按 findings.json 格式记录 4. 每完成一个模块更新 coverage-ledger.json 5. 审计结束后汇总两个文件并输出摘要这个定义的关键在于流程约束和格式约束。它不规定 agent 用什么方法查但规定了必须按攻击面分类、必须记录覆盖、必须产出两个文件。这种约束保证了不同项目、不同时间的审计结果具有可比性。5.2 审计流程的分阶段执行实际跑起来我会把审计分成四个阶段每个阶段有明确的输入输出。第一阶段范围确定。让 agent 先遍历代码库识别语言、框架、入口点生成初始的 coverage ledger 骨架。这一步不查漏洞只摸清家底。我通常会要求 agent 输出文件清单和初步的攻击面分类。第二阶段分类审计。按攻击面逐类深入。比如先查 injection 类把所有涉及数据库查询、命令执行、模板渲染的代码找出来逐个分析。每查完一类更新 ledger 里对应的 coverage_by_category。第三阶段交叉验证。对高 severity 的发现让 agent 反向验证——假设这个发现是误报找证据推翻它。这一步能显著降低误报率。我实测下来交叉验证能过滤掉大约 30% 的初始发现。第四阶段汇总产出。合并所有 findings生成最终的两个 JSON 文件并输出一份人类可读的摘要。5.3 参数配置与调优security-audit-skill在实际使用中有几个参数值得调优。参数作用建议值max_depth数据流追踪的最大深度5-8太深会拖慢速度confidence_threshold记录发现的最低置信度mediumlow 的可以丢弃或单独记录exclude_patterns排除的文件模式测试文件、生成代码、依赖目录category_focus重点审计的攻击面根据项目类型调整max_depth这个参数我踩过坑。设得太小比如 3跨函数的漏洞追踪不到设得太大比如 15agent 会在复杂的调用链里绕晕产出大量低质量发现。我的经验值是 6 左右对大多数项目够用。exclude_patterns也很关键。如果不排除node_modules、vendor、dist这些目录agent 会把大量时间花在第三方代码上而这些代码通常不是审计重点。我一般会排除依赖目录、构建产物、测试夹具但不排除测试代码——因为测试代码里经常藏着硬编码密钥。注意排除规则要写进 coverage ledger 的 excluded_files 里并说明排除原因。否则排除本身就成了新的盲区。6. 常见问题与排查技巧实录6.1 发现项太多怎么办这是最常见的问题。一个中型项目跑下来agent 可能产出几百条 finding。全看一遍不现实全修更不现实。我的处理策略是三级过滤。第一级按 severity 加 confidence 排序critical 加 high confidence 的优先处理。第二级对同类型发现做聚合比如 20 个文件都有同样的硬编码密钥问题合并成一条 findinglocation 里列多个位置。第三级对低风险发现做批量处理比如所有 info 级别的直接归档不进入修复队列。聚合这一步特别重要。我见过 agent 把同一个漏洞模式在 50 个文件里各报一条报告直接爆炸。让 agent 在产出前做一次聚合能大幅提升报告可用性。6.2 误报率高的排查思路如果发现误报率超过 20%通常是三个原因之一。一是数据流追踪不完整。agent 只看到危险函数调用没追踪参数来源把安全的调用也报成漏洞。解决办法是强制要求 evidence 字段包含完整数据流没有数据流的发现降级为 low confidence。二是框架特性理解不足。比如某些 ORM 框架自带参数化agent 不认识把安全的查询报成注入。解决办法是在 skill 定义里加入框架白名单让 agent 识别常见安全 API。三是上下文缺失。agent 看不到调用方的校验逻辑误判。解决办法是让 agent 在判定前先搜索调用链上游确认是否有防护。6.3 覆盖账本与发现项对不上有时候 ledger 说某个模块已覆盖但 findings 里没有任何该模块的记录。这不一定是 bug可能是该模块确实没问题。但为了可信我要求 agent 在 ledger 里对已覆盖但无发现的模块也做标记比如checked: 22, findings: 0。这样读者能区分查了没问题和没查。6.4 常见问题速查表问题现象可能原因排查方向发现项数量异常多未聚合、误报检查聚合逻辑、confidence 阈值高危发现无法复现evidence 不完整要求补充数据流证据覆盖率高但漏报明显覆盖判定过宽检查 ledger 的 checked 标准agent 中途卡住调用链过深调低 max_depth两个文件不一致未同步更新检查落盘时机6.5 独家避坑技巧分享几个我从实际使用中总结的技巧。第一让 agent 先做一次空跑只生成 coverage ledger 不查漏洞确认范围划分合理后再正式审计。第二对关键模块做人工复核agent 的发现不能全信尤其是 auth 和 crypto 相关的。第三把 findings.json 纳入版本控制这样能对比不同版本的审计结果看出安全状况的变化趋势。还有一个技巧让 agent 在每条 finding 里加一个verification_steps字段写明如何验证这个发现。这个字段对开发同学特别友好他们照着步骤就能复现不用来问安全同学。7. 影响范围与延伸思考security-audit-skill这类技能规范的出现其实反映了一个更大的趋势AI coding-agent 正在从写代码向审代码延伸而审计这件事对可信度的要求远高于写代码。写代码写错了测试能发现审计审错了可能留下长期隐患。所以审计类 skill 的设计必须把可追溯放在第一位。从影响范围看这套东西最先受益的是中小团队。大厂有专门的安全团队和自研扫描平台中小团队往往只能靠开源工具凑合。security-audit-skill加上 coding-agent相当于给中小团队配了一个不知疲倦的初级安全工程师虽然不能完全替代人工但能把基础覆盖做扎实。再往远看findings.json和coverage-ledger.json这种结构化产出天然适合接入 CI/CD。你可以想象这样一个流程每次 PR 提交agent 自动跑一次增量审计产出两个文件CI 检查 findings 里有没有新增的 high 级别问题有就阻断合并。这种安全左移的落地比喊口号实在得多。我自己在实际操作中的体会是不要指望 agent 一次审计就能发现所有问题。把它当成一个持续运行的审计助手每次代码变更都跑一遍用 coverage ledger 追踪覆盖范围的演变用 findings 追踪问题的增减。时间长了你会得到一份项目安全状况的完整历史记录这比任何一次性的扫描报告都有价值。最后分享一个小技巧把coverage-ledger.json里的 uncovered 项单独拉一个清单定期 review。很多时候那些因为时间关系没查的模块恰恰是风险最高的地方。审计的账本记的不只是查过的更是提醒你还有哪些没查。