如果你最近在折腾AI编程助手,一定绕不开一个词:skill。GitHub上各类skill仓库一夜之间多了起来,有人做会议纪要,有人做PPT生成,有人把论文检索流程也塞进去。但真正面向安全审计场景的skill,翻来覆去就那么几个,而且大多只是把一份安全清单丢给模型,让它“照着检查”,结果模型吐出来的报告又空又泛,落到实际项目里基本没法用。我花了一段时间,把代码安全审计、依赖风险核查、敏感信息扫描和配置基线检查这几件事拆开,重新整理成了一个可复用的security-audit-skill,今天把它的设计思路、实现细节和踩坑记录完整写下来。
先给一个基本判断:安全审计是一个天生适合Skill化的工作。它的流程高度固定,结果要求可验证,而且同一个项目的不同模块、不同项目的同类框架之间,审计规则可以大量复用。只要把规则拆清楚,让模型按固定路径执行,产出就能稳定很多。这篇文章适合正在做Agent Skill开发的人,也适合想把AI用进代码审计、又不想只会写“请检查安全漏洞”这种无效提示词的工程师。
1. 为什么需要一个专门的安全审计 Skill
1.1 从“会写代码”到“会审代码”的距离
很多团队对AI编程助手的期待是“写完功能顺便把安全也查了”,但现实是,模型写代码和审代码是两个完全不同的能力。写代码时模型倾向于“生成看起来合理的实现”,而安全审计要求的是“带着怀疑去读每一行代码”,这两种思维模式在一个对话里很容易互相干扰。你让模型“检查这个项目有没有安全问题”,它常常会顺着功能逻辑走,最后给你几条不痛不痒的建议,比如“建议使用参数化查询”,却说不清楚具体哪个文件、哪一行存在注入风险。
所以我需要的不是一个“会聊安全的模型”,而是一个“按安全审计流程执行任务的技能包”。security-audit-skill的出现,就是想把“人工审计经验”转成模型能稳定执行的步骤:先看什么文件、按什么规则匹配、证据怎么定位、报告怎么分级。这个思路和把某个领域的专家经验做成SOP是同一个道理,只是在AI场景里,这份SOP需要写得更精确,因为模型不会像人一样自动理解“模糊的经验”。
1.2 Skill 到底是什么,和 Agent 有什么区别
最近“agent skill”这个词被反复提到,搜“skill和agent的区别”的人也不少。我在做这个项目之前,也反复确认过边界。我的理解是:Agent 是调度器,它负责理解用户目标、拆解任务、调用工具、循环执行;Skill 是操作手册,它告诉模型“在某个特定领域里,应该用什么方法、按什么步骤、输出什么格式”。
两者不是替代关系,而是配合关系。一个Agent可以在同一个任务里调用多个Skill:先用“代码检索Skill”定位文件,再用“security-audit-skill”做审计,最后用“报告生成Skill”整理结果。Skill本身不负责决策,只负责把特定领域的方法论固化下来。如果你把Agent比作一个项目经理,Skill就是项目经理手里那一沓标准作业流程卡。一个人可以带一沓卡,也可以随时换卡,但卡本身替代不了项目经理的判断。
| 对比项 | Skill | Agent |
|---|---|---|
| 核心职责 | 固化和复用某个领域的操作方法 | 理解目标、规划步骤、调度资源和工具 |
| 是否自主运行 | 不自主,等Agent或用户调用 | 能自主执行任务循环 |
| 输出形式 | 通常是一组指令、规则、模板、脚本 | 是最终任务结果或交互过程 |
| 典型例子 | 安全审计技能、PPT制作技能、论文检索技能 | 一个能接管整个“代码评审”任务的智能体 |
很多新手容易把“写一个skill”当成“做一个agent”,其实skill做得再好,也需要一个合适的Agent/模型框架去加载它。反过来,Agent如果没有对应领域的skill,处理专业任务时只能依赖通用推理,效果会明显差一截。security-audit-skill的目标就是在“安全审计”这个专业领域里,把通用模型补成“懂流程、有规则、会取证”的审计员。
1.3 我把安全审计拆成了哪些能力单元
做skill最容易犯的错是一上来就写一堆规则,然后等模型自己消化。我在这个项目里做的第一件事,是拆解安全审计的最小能力单元。目前拆成六块,每一块对应一个独立的规则文件:
- 输入校验:重点关注SQL注入、命令注入、路径穿越、反序列化入口。
- 认证与授权:重点看登录逻辑、会话管理、越权访问、硬编码凭据。
- 密码学实践:重点看加密算法选择、密钥存储、随机数使用是否安全。
- 依赖风险:基于构建文件检查已知漏洞版本、过期依赖和许可证风险。
- 配置基线:检查默认密码、调试开关、敏感端口、云存储权限、数据库配置。
- 敏感信息泄露:在全项目中扫描密钥、token、证书、内网地址。
这六块的顺序也有讲究。每次审计先做敏感信息扫描,再做输入校验,再做认证授权,最后看配置和依赖。原因是前两类问题在代码静态特征上最容易定位,模型给出高置信度结论的概率更高,能够快速建立“这个skill确实有效”的正反馈;而认证授权和配置基线更依赖业务上下文,适合放到后面结合具体代码结构分析。拆完之后,每个规则文件只负责一个领域,模型在审计时按顺序读取规则,而不是一次性吞下一大段混杂的文本,这样上下文更干净,误报率也低很多。
2. 安全审计 Skill 的整体设计思路
2.1 输入设计:只给模型看该看的东西
安全审计最大的敌人是上下文杂乱。很多人在使用AI审计时喜欢把整个项目目录都抛给模型,觉得“看得越多越安全”,但实际上模型在长上下文里的注意力会明显衰减,很容易把核心漏洞淹没在大量无关代码里。我在设计security-audit-skill时,第一步就是限定输入范围。
输入阶段要求先做两件事:识别项目类型和构建文件,再圈定审计范围。比如Java项目优先看pom.xml、src/main/java下的Controller、Service、Utils和配置文件;Python项目优先看requirements.txt、pyproject.toml、入口文件和所有涉及外部输入的函数。生成代码、依赖缓存目录、构建产物目录默认跳过,不是不审,而是不值得在首轮审计中占用上下文。如果一轮审不完,就按模块分批继续。
同时,我会要求模型先输出“本次审计范围内的文件清单”,再开始逐项分析。这一步看起来多余,但它有两个实际作用:一是让模型明确任务边界,避免跑到无关文件里浪费时间;二是给使用者一个确认机制,如果发现该审的文件没被纳入,可以及时补充,而不是等报告出来才发现漏了核心模块。
2.2 规则分层:把人工经验转成可执行步骤
安全审计规则不能是“一句建议”,而应该是“一组可执行的动作”。我在项目里把规则分成三层,模型和脚本分别处理不同粒度的问题。
第一层是通用基础规则,适用于任何语言和项目,比如“硬编码密钥”“使用过时的加密算法”“未使用参数化查询”这类跨语言通病。第二层是框架特定规则,比如Spring Boot项目的权限注解是否齐全,Express项目的helmet中间件是否引入,Django项目的SECRET_KEY是否暴露在版本库中。第三层是环境基线规则,比如是否开启了DEBUG模式、数据库连接串是否有弱口令特征、对象存储桶是否配置了公共读写。
这三层规则不是一起灌给模型的,而是先通过脚本做初筛,再交给模型做语义判断。比如“扫描硬编码密钥”这类特征明确的检查,用正则脚本扫一遍比模型逐个文件读Token准确得多;而“越权风险是否真实存在”这种只能靠业务语义判断的问题,脚本做不了,就交给模型处理。这样设计的好处是,把模型从低价值的重复劳动里解放出来,让它把注意力集中在真正需要推理的地方。
2.3 输出设计:不只是报告,还要给修复建议
审计报告如果只列“存在哪些问题”,对团队来说价值有限。真正可执行的报告必须包含四个部分:问题定位、风险等级、证据片段、修复建议。缺任何一个环节,都会增加使用者的确认成本。
我在SKILL.md里规定报告必须使用统一模板,每个问题都必须带上“文件路径+行号+代码片段+问题类型+风险等级+修复建议”。风险等级分为Critical、High、Medium、Low四档,Critical表示可直接利用且暴露面大,例如数据库连接串硬编码在公网仓库中;High表示大概率可被利用,例如用户输入直接拼接到SQL语句;Medium表示需要特定条件才能利用;Low表示代码风格或潜在隐患,建议人工确认。等级判断标准也写进规则文件里,避免同一个问题在不同模型版本里输出不同结论。
输出模板还被要求包含一个“已检查项清单”。这一步很重要,模型在被要求逐条列出已检查规则时,会自然更严格地执行规则,而不是跳步直接给结论。这算是我在调试过程中发现的一个很实用的技巧:你越要求模型展示过程,它越不容易敷衍。
3. 核心实现:SKILL.md 和配套脚本
3.1 仓库目录结构
整个security-audit-skill的仓库结构非常简单,核心是SKILL.md加rules、scripts、templates三个目录。目录结构如下:
security-audit-skill/ ├── SKILL.md ├── rules/ │ ├── 01-input-validation.md │ ├── 02-auth.md │ ├── 03-crypto.md │ ├── 04-dependency.md │ ├── 05-config.md │ └── 06-secret.md ├── scripts/ │ ├── secret_scan.py │ ├── dep_check.py │ └── file_index.py ├── templates/ │ └── audit_report.md ├── examples/ │ ├── spring-boot-audit.md │ └── node-express-audit.md └── README.mdrules目录里的每个文件都只解决一个问题域,文件名前面的数字决定了模型读取规则的顺序。scripts目录里放着轻量辅助脚本,用来完成文件索引、敏感信息初筛、依赖版本对比这类机械性工作。templates目录是报告模板,所有审计结果都按这个模板输出。examples目录是示例报告,模型可以参考示例来对齐自己的输出风格。
这个结构并不复杂,但它刻意保持了一种“开放约定”:SKILL.md被Agent框架加载后,框架会按约定去读rules目录里的文件,而scripts目录里的脚本既可以被模型通过命令行调用,也可以被其他自动化流水线独立使用。这样一来,skill本身不依赖特定框架,谁都能拿过去用。
3.2 SKILL.md 该怎么写
SKILL.md是整个skill的入口文件,几乎所有主流的skill加载方式都要求这个文件存在。它通常包含两部分:YAML元信息和执行指令。我目前使用的模板大致是这样的:
--- name: security-audit description: 当用户要求对代码项目进行安全审计、漏洞扫描、依赖风险检查、敏感信息泄露排查或配置安全检查时,使用此技能。 --- # security-audit ## 目标 对指定项目进行系统化安全审计,输出包含证据、风险等级和修复建议的可执行报告。 ## 输入要求 1. 先识别项目类型与构建文件,确认审计范围。 2. 使用 scripts/file_index.py 生成待审计文件清单。 3. 默认跳过生成目录、依赖缓存目录、构建产物目录。 ## 执行步骤 1. 读取 rules/ 目录下的规则文件,按照编号顺序加载。 2. 对每个规则,先使用脚本或关键词初筛,再结合代码上下文判断是否命中。 3. 每个问题必须记录文件路径、行号、关键代码片段。 4. 按 templates/audit_report.md 输出审计报告。 ## 输出格式 必须包含:风险等级、问题描述、证据定位、修复建议、已检查项清单。 ## 注意事项 - 不要只给结论,必须给证据。 - 无法确认的风险统一标记为“待人工确认”。 - 不要建议关闭安全机制来修复问题。这里最值得注意的细节是description字段。它不是给人看的,而是给Agent的匹配器看的,它决定了模型在什么情况下会触发这个skill。写得太笼统(比如“安全相关”)会导致很多无关任务误触发;写得太具体(比如“只处理Spring Boot项目”)又会漏掉其他语言项目。好的写法是列出用户可能的多种表达方式,包括“安全审计”“漏洞扫描”“依赖风险”“敏感信息泄露”等,让匹配器的召回率更高。
3.3 规则文件与脚本配合
每个rules目录下的文件都有固定结构,我以02-auth.md为例拆解一下。它开头是一段简短的目的说明,中间是具体检查项,最后是记录格式。检查项不是一句废话,而是尽量写成“可判断的命题”,比如“是否使用框架自带的登录认证机制,而不是自研登录逻辑”“是否将密钥存储在环境变量或密钥管理系统中,而不是写在代码中”“是否存在未加权限校验的管理接口”。每个检查项后面还会标注“匹配方法”,比如“关键词搜索”“调用链分析”“人工确认”,这样模型知道自己该用什么手段取证。
脚本和规则配合的关键点在于:脚本只做“初筛”,不直接判定漏洞。比如secret_scan.py会用正则扫出疑似硬编码的密钥,但它输出的只是“可疑位置”,最终是否确认为泄露,需要模型结合上下文判断。我在脚本里专门加了置信度字段,正则匹配到明文密码字符时标记为“高”,匹配到变量名暗示密钥但值不明确时标记为“低”,这个设计大大降低了模型的误报率。
依赖检查脚本的核心逻辑也比较简单,先用parsers解析构建文件里的依赖列表,再去本地或在线漏洞库比对版本。考虑到不同项目环境差异很大,这个脚本支持离线模式,可以预先导入一份漏洞版本表,避免要求用户每次审计都联网更新。这样即使内网环境也能正常跑。
3.4 通用适配:别绑死某个Agent
做skill时最容易踩的坑,是把自己绑定到某一个Agent框架上。现在流行的Codex、Claude、各类支持skill的开源框架,加载skill的方式都不完全一样。有的要求把SKILL.md放在特定目录,有的要求通过插件市场安装,有的直接用文件夹名作为技能名。我写这个skill时尽量只依赖SKILL.md和通用脚本,不做任何框架专属配置,这样无论是手动放入项目目录,还是通过第三方skill市场导入,都能正常运行。
如果你也想让自己的skill有更强的适配性,有几点可以参考:不要使用某个框架独有的模板语法,不要在YAML头里写只对某个平台生效的字段,脚本用shell或Python这类通用语言,控制在单文件内,尽量不引入需要额外安装的依赖。这样别人拿到你的skill后,只需复制目录、导入规则,就能在自己的Agent环境里跑起来,而不是被迫跟着你用的框架走。
4. 实操:从代码到审计报告
4.1 准备与加载
拿到一个项目后,我会先把它放到本地目录,再把security-audit-skill复制到Agent能够加载的位置。以命令行工具为例,可以在项目根目录执行:
cd /path/to/your-project # 把skill目录放到当前Agent约定的skills目录下 # 例如:~/.config/agent/skills/security-audit/ cp -r security-audit-skill ~/.config/agent/skills/security-audit加载完成后,直接给Agent一句话任务:“请使用security-audit技能审计当前项目,重点看认证和依赖风险。”这里的关键是明确表达“使用这个技能”,而不是简单说“帮我看看安全”。显式触发会让Agent优先读取SKILL.md,而不是凭感觉自由发挥。
系统会先运行file_index.py,扫描出项目中需要审计的文件清单。正常情况下,一个中等规模的Java项目会在几秒内生成一份列表,格式类似下面这样:
$ python3 scripts/file_index.py --project . --skip target,node_modules,.git [INFO] Found 126 Java files, 3 pom.xml, 41 config files. [INFO] Audit scope generated: src/main/java, pom.xml看到这个结果,我会再和阿特确认一下,比如“先审Controller和Service层,工具类和DTO先跳过”。这一步能有效控制上下文长度,让模型把精力放在风险最集中的逻辑代码上。
4.2 执行一次完整审计
我拿一个模拟的Spring Boot项目跑了完整流程,项目不大,有60多个Java文件、2个pom.xml。初步审计后,模型按模板输出了一段报告,其中一个关键问题被定位在用户登录接口:
// UserController.java:42 String sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";对应报告片段写得很清楚:
- 风险等级:Critical
- 问题类型:SQL注入
- 证据定位:src/main/java/com/example/controller/UserController.java:42-44
- 问题描述:用户输入name直接拼接进SQL查询语句,攻击者可通过构造单引号闭合语法,获取未授权数据。
- 修复建议:改用JPA参数绑定或MyBatis的#{}占位符,不要用字符串拼接;同时为数据库账号设置最小权限,降低注入后的影响范围。
这个例子里,模型不是光说“建议使用参数化查询”,而是先给出了精确到行的证据,再给修复建议。这个效果就是SKILL.md里规定“证据定位”和“风险等级”带来的。如果没有这两条硬性要求,模型很容易泛泛而谈。
除了代码漏洞,脚本还扫描出pom.xml里有三个依赖版本低于当前安全版本线,其中spring-webmvc的版本存在已知的反序列化风险,被标为High。依赖检查脚本直接给出“推荐升级到5.3.39+”这样的建议,省去了我去查漏洞库的时间。
4.3 结果解读与修复闭环
拿到报告后,我最关心的不是“有多少个问题”,而是“Critical和High有几个,能不能复现,修复后有没有回归”。这个skill输出的报告自带优先级排序,我一般按等级逐个处理:先把Critical级别的问题修完,再处理High,Medium和Low人工快速过一遍。
修复后,我会重新跑一次审计,重点看两个地方:一是有没有新增问题,二是原问题的“证据定位”是否消失。很多团队修完漏洞后不做回归,结果同一个位置换了一种写法又引入了类似问题。这个问题用AI审计倒是很好解决,因为模型是重新扫描整个项目,只要规则没变,它不会因为“上次已经报告过”就降低标准。我在实际使用中还会把修复后的commit记录保存下来,便于以后回溯。
整个流程走下来,从准备到出报告,大概十来分钟。如果完全靠人工审,同样的范围至少需要半天。效率提升是很明显的,但前提是你要接受“AI审计的结果需要人工复核”这件事。
5. 常见问题与排查技巧实录
5.1 模型不按规则走怎么办
这是我在调试过程中遇到最多的一个问题。第一次写完SKILL.md时,模型经常跳过读取规则文件的步骤,直接凭常识给结论。后来我在执行步骤里加了一条强制要求:“必须先输出正在读取的规则编号和规则名称,再输出该规则的检查结果。”一旦模型需要“展示过程”,它就很难跳过规则文件,因为不读取规则就写不出过程说明。
另一个有效手段是在输出模板里加“已检查项清单”。每次审计报告最后必须列出“本次覆盖了哪些规则、哪些规则因为范围限制未覆盖”。这一条会让模型主动确认自己还有哪些没做,出现漏检的概率下降很多。如果模型还是没有执行规则,优先检查是不是description写得太含糊,导致Agent根本没有触发这个skill,或者加载时把skill目录结构放错了位置。
5.2 误报率太高怎么压
误报率高通常不是模型的错,而是规则本身写得不够精准。早期我在规则里写“检查是否使用弱加密算法”,模型就会把所有用到对称加密的地方标成Medium,哪怕是AES-256-GCM这种目前还合理的算法也被误伤。后来我把规则改成“优先关注硬编码密钥、固定IV、ECB模式、MD5/SHA1用于密码存储”这类具体特征,误报率立刻降下来一大截。
同时,脚本初筛的置信度标注也很有用。secret_scan.py对结果分为“高置信度”和“低置信度”,高置信度结果才会进入审计报告,低置信度结果放在“待确认”附件里供人工参考。这样既不会漏报,也不会让报告被疑似项刷屏。如果你发现误报还高,建议把规则里的“是否”式问句,全部改成更具体的“匹配什么特征”式命题,越具体越不容易误判。
5.3 上下文太长导致审计变形
大型项目的上下文控制是逃不掉的问题。有一次我试过对一个大仓库做全量审计,结果模型到后半段开始“失忆”,前文定义的规则名都记不住,明明扫描到的漏洞也漏报。后来我把审计策略改成了分批模式:先按目录拆分成多个模块,每个模块单独审计,再汇总所有模块的报告。模块大小控制在“100个文件以内、核心逻辑文件不超过60个”比较稳。
另一个更轻量的方案是先用脚本把“文件摘要”生成好,再把摘要和关键代码片段一起喂给模型。比如用file_index.py输出每个文件的功能摘要和风险关键词,模型不需要读全部文件内容,就能先判断哪些文件值得深入审。这种方式对超大型项目特别有效,可以把上下文占用压缩到原来的三分之一以下。
5.4 多语言项目怎么处理
一个项目里同时存在Java、Python、JavaScript很常见。我的做法是始终先跑“通用规则”,再按语言加载对应规则。通用规则覆盖硬编码密钥、硬编码密码、过时加密算法、反序列化入口等跨语言问题,语言特定规则再处理各自框架的常见坑。比如Java看Spring Security配置有没有生效,Python看Django的DEBUG和ALLOWED_HOSTS,前端看是否把API密钥打包进静态资源。
scripts/file_index.py会识别项目里的构建文件和源码后缀,自动生成“按语言分类的可审计文件清单”。模型收到这个清单后,会按语言分组执行审计,最后报告里分类呈现。这个方案避免了在同一个上下文里反复切换语法规则导致的混乱,也方便团队里的不同语言owner按模块认领问题。
6. 一点个人经验总结
最后分享几个实际做下来真正有效的体会。第一,skill的规则一定不要贪多,少而精才是关键。一个能稳定执行五条规则、每条都出证据的skill,远比一个列了一百条规则、但模型只能走马观花的skill有用。我早期把规则加到接近四十条,结果报告质量反而下降,最后精简到六类核心规则,效果稳定很多。
第二,AI安全审计最适合的定位是“第一道安检”,不是“最终法官”。它能帮你快速定位可疑代码、缩小人工审查范围,但高危漏洞是否真实可利用、修复方案是否影响业务,仍然需要人来做最终判断。把这个定位想清楚,你就不会对模型产生不切实际的期待。
第三,skill的价值会在长期使用中持续累积。每次审计完,把误报和漏报的案例整理成新的规则项或示例,不断迭代规则文件,这个skill会越来越贴合你团队的代码风格和业务场景。我现在的建议是,单独建一个runtime_notes.md,专门记录哪些规则在本项目里误报高、哪些高风险点值得追加检查。经过几轮迭代后,审计准确率会有非常明显的提升。
如果你也想动手写自己的skill,不妨就从安全审计这个方向切入,它足够专业、规则清晰、结果可验证,比做一个泛泛的“通用助手skill”更能锻炼你对Agent和模型行为边界的理解。后面我还会把SBOM生成、容器镜像扫描这些能力逐步并入这个skill,让审计覆盖面更完整。