1. 从"security-audit-skill"这个名字说起:它到底在解决什么问题
第一次看到security-audit-skill这个命名,我的直觉是:这不是一个普通的脚本,而是一个被封装成"技能"的安全审计能力单元。关键词里同时出现了skill、agent、findings.json、report-schema.json,这几个词放在一起,基本可以勾勒出它的轮廓——一个面向 AI Agent 生态的安全审计技能,输入是待审计的目标,输出是结构化的findings.json,并且遵循一份固定的report-schema.json报告规范。
为什么我这么判断?因为skill这个词在当下的 Agent 开发语境里,已经不再是"技能"的字面意思,而是指一种可被 Agent 动态加载、按需调用的能力模块。它和传统的"函数"或"工具"最大的区别在于:skill 通常自带上下文说明、执行边界、输入输出契约,甚至包含失败重试和结果校验逻辑。换句话说,skill 是"给 Agent 看的说明书 + 可执行代码"的合体。
那security-audit-skill具体能做什么?从命名和关键词推断,它至少覆盖这几件事:
- 对代码仓库、配置文件、依赖清单进行安全层面的扫描与审计;
- 把发现的问题按统一格式写入
findings.json,方便后续 Agent 或流水线消费; - 用
report-schema.json约束报告结构,保证不同审计任务产出的结果可对比、可聚合; - 作为 Agent 的一个可插拔能力,被编排进更大的自动化流程里。
适合谁来参考?三类人最需要:一是正在做 Agent 开发、需要给 Agent 加"安全审计"能力的工程师;二是负责 CI/CD 流水线、想把安全卡点自动化的 DevOps;三是刚接触 skill 概念、想通过一个真实案例理解"skill 和 agent 到底怎么配合"的初学者。
我见过太多人把安全审计做成"跑个扫描器、贴个报告"就完事,结果报告格式五花八门,Agent 拿到之后根本没法自动处理。security-audit-skill的价值恰恰在于它把"审计能力"和"报告契约"绑在一起,这才是它区别于普通扫描脚本的核心。
2. skill 与 agent 的分工:为什么安全审计要拆成独立技能
2.1 skill 和 agent 的区别,用一句话讲透
热词里反复出现"skill和agent的区别""harness和agent区别",说明这是很多人的困惑点。我的理解是:agent 是决策者,skill 是执行者。Agent 负责"要不要做、先做哪个、做完之后怎么办",skill 负责"具体怎么做、做完产出什么"。
打个比方,agent 像是一个项目经理,skill 像是项目经理手下的专业外包团队。项目经理不需要自己会写安全审计报告,他只需要知道"现在该调用安全审计 skill 了",然后把结果拿回来做下一步决策。这种分工的好处是:agent 的逻辑保持轻量,skill 的能力可以独立迭代、独立测试、独立替换。
security-audit-skill就是这样一个"专业外包团队"。它不关心是谁调用了它、调用它的目的是什么,它只关心:给我输入,我按report-schema.json的规范产出findings.json。
2.2 为什么安全审计特别适合做成 skill
安全审计有几个特点,决定了它天然适合被封装成 skill:
第一,输入输出高度标准化。审计的输入无非是代码、配置、依赖,输出无非是问题列表。这种"契约清晰"的任务,最适合做成 skill,因为 Agent 不需要理解审计的内部逻辑,只需要按契约传参和取结果。
第二,执行过程需要隔离。安全审计往往要读取敏感文件、执行扫描命令,如果直接写在 agent 主流程里,一旦出错会污染整个 agent 的状态。做成独立 skill 后,执行环境可以隔离,失败也不会拖垮主流程。
第三,结果需要被多方消费。findings.json不只给当前 agent 看,还可能给 CI 流水线、给报表系统、给人工复核看。用report-schema.json固定结构,等于给所有消费方一个稳定接口。
我踩过的一个坑是:早期我把审计逻辑直接写进 agent 的 tool 里,结果每次改报告格式,都要动 agent 的核心代码,牵一发动全身。后来拆成独立 skill,报告格式的迭代就完全不影响 agent 了。这个教训让我彻底理解了"skill 独立"的意义。
2.3 skill 的边界应该划在哪里
划边界是设计 skill 最难的部分。我的经验是:skill 的边界应该划在"能力"层面,而不是"步骤"层面。
什么意思?security-audit-skill应该是一个完整的"审计能力",而不是"读取文件""执行扫描""写报告"三个碎步骤。因为如果拆得太碎,agent 就要负责编排这些步骤,那 agent 就变重了,失去了分层的意义。
但也不能太粗。如果security-audit-skill把"审计"和"修复建议生成"都包进去,那它就承担了它不该承担的职责。审计就是审计,修复是另一个 skill 的事。边界清晰,才能让每个 skill 都可测试、可复用。
3. findings.json 与 report-schema.json:报告契约才是这个 skill 的灵魂
3.1 为什么必须有 report-schema.json
很多人做 skill 时忽略了一件事:skill 的输出格式,比 skill 的功能更重要。因为功能是给机器执行的,格式是给下游消费的。如果输出格式不稳定,下游就没法自动化。
report-schema.json的作用就是给输出"上锁"。它定义了:
| 字段 | 类型 | 说明 | 是否必填 |
|---|---|---|---|
findings | array | 问题列表 | 是 |
findings[].id | string | 问题唯一标识 | 是 |
findings[].severity | enum | 严重级别(critical/high/medium/low) | 是 |
findings[].category | string | 问题分类 | 是 |
findings[].location | object | 问题位置(文件、行号) | 是 |
findings[].description | string | 问题描述 | 是 |
findings[].evidence | string | 证据片段 | 否 |
findings[].recommendation | string | 修复建议 | 否 |
summary | object | 汇总统计 | 是 |
metadata | object | 审计元信息(时间、版本、目标) | 是 |
有了这份 schema,findings.json就不是"随便写写的 JSON",而是"有契约的产物"。Agent 拿到之后可以直接按字段读取,不需要做任何猜测。
3.2 findings.json 的字段设计有哪些讲究
我在设计findings.json时,最看重三个字段:severity、location、evidence。
severity用枚举而不是自由文本,是为了让下游能做排序和过滤。如果允许写"严重""很严重""非常严重",那下游就没法自动处理了。枚举值我建议固定为critical/high/medium/low四档,和业界通用标准对齐。
location用对象而不是字符串,是为了让下游能精确定位。{"file": "src/auth.js", "line": 42}比"src/auth.js:42"更好解析,也更不容易出错。
evidence字段容易被忽略,但它其实是审计报告可信度的关键。没有证据的审计结论,人工复核时根本没法验证。我通常会把触发问题的原始代码片段截取出来,控制在 3-5 行,既够用又不冗长。
3.3 schema 校验应该放在哪一步
一个常见的错误是:等findings.json写完了才去校验 schema。这时候如果发现不合规,要么丢弃结果,要么打补丁,都很别扭。
我的做法是在写入前校验。skill 内部先生成一个内存中的对象,用 schema 校验通过后再落盘。这样保证落盘的findings.json永远是合规的。校验失败时,skill 应该返回明确的错误信息,而不是写一个半成品文件出去。
提示:schema 校验建议用成熟的库,比如 Python 的
jsonschema、Node 的ajv。不要自己手写校验逻辑,容易漏掉边界情况。
4. 把 security-audit-skill 跑起来:从环境准备到首次产出
4.1 环境准备中最容易忽略的三个细节
第一个细节是运行时版本。skill 如果依赖特定版本的运行时(比如 Python 3.10+ 才支持的语法),一定要在 skill 的元信息里写清楚。我见过太多"在我机器上能跑"的案例,根因就是版本不一致。
第二个细节是依赖隔离。skill 的依赖应该独立管理,不要和 agent 主工程的依赖混在一起。否则 agent 升级一个库,可能就把 skill 搞挂了。我通常给每个 skill 配独立的虚拟环境或容器。
第三个细节是权限边界。安全审计 skill 需要读取代码文件,但不应该需要写权限(除了写报告)。在容器里运行时,应该以只读方式挂载代码目录,只给报告目录写权限。这是最小权限原则的落地。
4.2 skill 的输入契约怎么定义
security-audit-skill的输入,我建议定义成这样一个结构:
{ "target": { "type": "repository", "path": "/workspace/project", "include": ["**/*.js", "**/*.py"], "exclude": ["node_modules/**", "**/*.test.js"] }, "options": { "severityThreshold": "medium", "maxFindings": 100 }, "output": { "findingsPath": "/workspace/reports/findings.json", "schemaPath": "/workspace/schemas/report-schema.json" } }这个契约的好处是:target描述"审什么",options描述"怎么审",output描述"审完写哪"。三段式结构清晰,Agent 构造输入时不容易漏字段。
include和exclude用 glob 模式,是为了让调用方灵活控制审计范围。默认排除node_modules和测试文件,是因为这些通常不是审计重点,扫进去只会产生噪音。
4.3 首次跑通的完整步骤
- 确认运行时版本符合 skill 要求,建议用
python --version或node --version核对; - 在独立环境中安装 skill 依赖,不要污染全局环境;
- 准备一份最小测试目标,比如一个只有几个文件的小仓库;
- 按输入契约构造调用参数,先跑一次"空审计"验证链路;
- 检查
findings.json是否生成、是否符合report-schema.json; - 故意在测试目标里埋一个明显问题,验证 skill 能否检出;
- 检查
summary统计是否和findings列表一致。
第 4 步的"空审计"很关键。很多人一上来就跑真实项目,结果链路有问题时,分不清是 skill 的问题还是目标的问题。先用空目标验证链路,能快速定位问题。
4.4 跑通之后最容易踩的坑
跑通不代表可用。我踩过的坑包括:
- 误报太多:skill 把正常的代码模式也报成问题,导致
findings.json里全是噪音。解决办法是加白名单机制,允许调用方传入已知的安全模式。 - 漏报关键问题:skill 只扫了表面,没做深度分析。解决办法是明确 skill 的能力边界,在文档里写清楚"能检出什么、不能检出什么",不要让调用方产生错误预期。
- 性能问题:大仓库扫描慢到不可接受。解决办法是支持增量扫描,或者限制单次扫描的文件数。
- 结果不稳定:同样的输入,两次扫描结果不一致。解决办法是固定扫描顺序,避免依赖文件系统的遍历顺序。
注意:误报和漏报是安全审计的永恒矛盾。不要试图消灭误报,而要提供"误报抑制"机制,让调用方能按需过滤。
5. 让 findings.json 真正被消费:下游集成的几种姿势
5.1 被 Agent 消费:作为决策输入
Agent 拿到findings.json后,通常会做三件事:按severity排序、按category分组、根据结果决定下一步动作。比如发现critical级别问题,Agent 可能直接阻断流程;发现medium级别,可能只记录不阻断。
这里的关键是:Agent 不应该解析findings.json的原始文本,而应该按 schema 读取字段。因为 schema 是契约,字段是稳定的;文本格式是表象,随时可能变。
5.2 被 CI 流水线消费:作为质量门禁
在 CI 里,findings.json通常被用来做质量门禁。比如配置一条规则:"如果存在critical或high级别问题,流水线失败"。这条规则的实现,就是读取findings.json,过滤severity,判断数量。
我建议把门禁规则也做成配置,而不是硬编码。因为不同项目对安全的容忍度不同,硬编码会导致规则无法复用。
5.3 被报表系统消费:作为趋势数据
findings.json里的metadata字段(审计时间、目标版本)让它天然适合做趋势分析。把多次审计的结果聚合起来,就能看到"问题数量是在增加还是减少""哪类问题反复出现"。
这也是为什么我坚持metadata必须包含时间戳和目标标识。没有这两个字段,历史结果就没法关联,趋势分析就无从谈起。
5.4 被人工复核消费:作为审查清单
再好的自动化审计,也需要人工复核。findings.json的evidence和recommendation字段,就是为人工复核准备的。复核人员看证据、看建议,判断是否采纳。
我通常会把findings.json渲染成一份可读的 HTML 报告,按severity分色,按category分节。这样复核人员不用看原始 JSON,体验好很多。
6. 迭代 security-audit-skill 的几个方向
6.1 从"能扫"到"扫得准"
第一版 skill 通常只能做到"能扫",但准确率堪忧。迭代的重点应该放在准确率上:收集误报案例,分析误报模式,加抑制规则;收集漏报案例,分析漏报原因,加检测规则。
这个过程没有捷径,就是不断积累。我建议给 skill 配一个"测试用例集",每个用例包含一段代码和期望的审计结果。每次改规则,都跑一遍用例集,确保没有回归。
6.2 从"单文件"到"跨文件"
很多安全问题不是单文件能看出来的,比如"这个函数在 A 文件定义,在 B 文件被不安全地调用"。要检出这类问题,skill 需要做跨文件分析。
跨文件分析的难点是性能。我的做法是:先做单文件扫描,建立符号表;再做跨文件关联,只分析符号表里有关联的部分。这样能大幅减少分析量。
6.3 从"固定规则"到"可配置规则"
固定规则的 skill 只能适应一种场景。可配置规则的 skill 才能适应多种场景。我建议把检测规则抽出来,做成配置文件,允许调用方按需启用或禁用。
规则配置的格式,我倾向于用 YAML,因为可读性好,非技术人员也能改。每条规则包含:规则 ID、描述、匹配模式、严重级别、修复建议。
6.4 从"独立 skill"到"skill 组合"
单个 skill 的能力总是有限的。真正的价值在于 skill 组合:security-audit-skill检出问题,fix-suggestion-skill生成修复方案,patch-apply-skill应用修复。三个 skill 串起来,就是一个完整的"发现-修复"闭环。
这也是为什么我强调 skill 的边界要清晰。边界清晰,才能组合;边界模糊,组合起来就是一团乱麻。
7. 我在实际使用中总结的几条经验
第一条经验:skill 的文档比代码更重要。因为 skill 是给 Agent 和别的开发者用的,他们首先看的是文档,不是代码。文档要写清楚:能做什么、不能做什么、输入输出是什么、失败时怎么办。
第二条经验:skill 的失败要"优雅"。审计过程中遇到无法读取的文件、无法解析的语法,不应该直接崩溃,而应该记录到findings.json的metadata里,标记为"部分完成"。这样调用方能知道哪些部分没审到。
第三条经验:不要追求一次做完美。security-audit-skill的第一版,能扫出几个明显问题、能产出合规的findings.json,就已经有价值了。剩下的准确率、性能、规则配置,都可以在迭代中完善。
第四条经验:schema 要版本化。report-schema.json一旦发布,就不要随意改字段。如果必须改,就升版本号,让下游能按版本适配。我见过太多因为 schema 悄悄变更导致下游解析失败的案例。
第五条经验:给 skill 加"自检"能力。skill 启动时,先检查自己的依赖是否齐全、schema 是否可读、输出目录是否可写。自检不通过就直接返回错误,不要等到执行到一半才失败。
最后分享一个小技巧:如果你不确定security-audit-skill的边界该怎么划,就先问自己"这个能力能不能独立测试"。能独立测试的,就适合做成独立 skill;不能独立测试的,说明它和别的能力耦合太深,需要重新划分。这个判断标准,我在设计每一个 skill 时都会用,屡试不爽。