去年有段时间,我在帮团队维护一套内部项目的安全自查流程。每次发版前都要人工核对一堆代码坏味道、敏感信息泄露和越权风险,效率低就算了,最关键的是每个人审查的标准还不一样。后来我开始尝试把安全审计流程交给大模型来做,但直接用“帮我检查一下这个项目有没有安全问题”这种对话式Prompt去驱动AI,效果很不稳定——它有时候能发现几个明显问题,有时候又答得特别空泛,像是在“乱猜”。试了几次之后我意识到,问题不在于大模型本身,而在于我根本没有给它一套可重复的、结构化的审计方法论。
于是就有了“security-audit-skill”这个项目。它本质上不是一段Prompt,而是一个可以装进Agent框架里直接调用的“技能包”。如果你接触过Claude Code、Codex、OpenCode或者最近很火的agent-skill体系,你会发现这类skill的本质是把某个专业领域的完整流程、判断规则、输出格式和边界约束打包成一个可复用的工具。而“安全审计”这个场景,恰好非常适合用这种形态来做。这篇文章我就把这个项目的设计思路、规则库怎么写、踩过的坑、以及实际运行效果一次说清楚。适合正在做AI编码助手二次开发、或者想把自己团队的审计流程“固化”成可复用技能的同学参考。
1. 内容整体设计与思路拆解
1.1 为什么是“skill”而不是普通Prompt
很多人第一次看到“security-audit-skill”这个名字时会想:这不就是一个提示词吗?把一堆审计规则塞给AI让它去执行,不就可以了么?
理论上确实可以,但实际效果会打折扣。普通Prompt是一次性的、松散的输入,它没有“边界意识”,也没有“输出契约”。你给它一段代码让它审计,它可能今天给你返回一份表格,明天给你一段对话式的总结,后天直接写一篇散文。这种不确定性在真实工程场景里是非常致命的问题——下游没法接,结果没法比对,审计质量也无法追踪。
而Skill的形态解决的是“稳定复用”的问题。它拥有固定的元数据(名称、触发条件、版本号)、固定的工作流程(Step 1做什么、Step 2做什么)、固定的输出结构(审计报告格式)、以及明确的“禁区声明”(哪些内容不做、什么时候必须拒绝回答)。当我把“安全审计能力”封装成skill时,AI就不再是“用一个Prompt临时客串审计师”,而是“进入一种专业的审计工作状态”。这个区别很微妙,但使用体验差异巨大。
1.2 审计场景的三个关键特性
我之所以选择安全审计作为第一个动手做的技能包,是因为这个场景有三个天然适合被“技能化”的特征。
第一是高一致性要求。安全审计最怕的就是两个人审出两套结论,同一段代码,A说高风险,B说没问题。用skill固化标准之后,只要规则库稳定,AI每次输出的标准偏差就会很小。
第二是高透明性要求。安全审计不光要给出结论,还必须给出“为什么”。审计报告里每一处风险点都要能追溯到具体的代码行、具体的规则依据,以及危害等级的判定逻辑。这类推理过程可以通过skill里的输出模板强行约束。
第三是高规范性要求。安全领域有大量公认的标准和红线,比如OWASP Top 10、SANS 25这类清单。把这些标准内置到skill里,可以借力权威知识沉淀,而不是让AI凭空判断什么重要什么不重要。
基于这三个特性,我在设计skill时把重心放在了“流程结构化”和“规则明确化”上,而不是一味追求让AI“更聪明”。
1.3 整体目录结构设计
实际落地时,我把整个skill拆成了几个相对独立的模块,目录结构是这样的:
security-audit-skill/ ├── SKILL.md # 主文件:定义skill的元信息、工作流程、输出契约 ├── audit_rules/ │ ├── injection.md # 注入类风险检测规则 │ ├── sensitive_data.md # 敏感信息与密钥泄露检测规则 │ ├── authz.md # 权限与越权检测规则 │ ├── unsafe_api.md # 危险函数与不安全API调用检测规则 │ └── output_safety.md # LLM输出内容安全自检规则 ├── references/ │ ├── owasp_top10.md # 内置参考知识 │ └── severity_guide.md # 危害等级判定标准 ├── templates/ │ ├── audit_report.md # 最终审计报告模板 │ └── checklist.md # 审计前快速检查清单 └── examples/ ├── demo_flask_app/ # 故意包含漏洞的示例项目 └── sample_report.md # 样例审计报告这样分层的设计是刻意的。主文件只负责流程调度,不负责具体知识;规则库按领域拆分,方便单独维护和扩展;references提供知识底座;templates约束输出。如果你在某些Agent框架里使用,会发现这种结构可以直接被识别为“可调用的技能”,不需要额外写解析器。
2. 核心细节解析与实操要点
2.1 SKILL.md主文件怎么写
SKILL.md是整个skill的灵魂,它负责告诉大模型“你是谁、你要做什么、你按什么流程做、你绝对不能做什么”。很多人在写skill文件的时候会犯一个错误:把大量审计知识塞进主文件里,结果导致模型上下文被撑爆,反而影响核心任务的执行准确率。
我的做法是在SKILL.md里只保留以下四块关键内容:
元数据区:定义skill的名字、描述、触发条件。描述要写得尽量“可被模型识别”,比如“当用户要求对代码进行安全审计、安全复核、风险扫描时,使用本技能”。描述写得越贴近真实使用场景,Agent框架判断何时调用这个skill就越准确。
流程区:明确审计步骤的先后顺序。比如我的流程是:收集上下文 → 建立基线 → 分域扫描 → 交叉验证 → 生成报告。每一步都要说明输入和输出,让模型知道“现在进行到哪一步了”。
输出合约区:固定报告格式,告诉模型必须按模板输出。这一点极其重要——审计报告是要给人和机器读的,格式不规范,后面所有下游工作都做不了。
禁区声明区:明确写清楚这个skill“不会执行什么”,比如“本技能未获得授权时,不执行任何代码修改操作;只报告不修复”。这些边界能让模型在安全领域更谨慎,避免过度操作。
2.2 规则文件中的“论证思维”
在写audit_rules目录下的规则文件时,我特别强调了一个原则:所有规则必须要求模型提供证据链。
举个例子。在注入风险检测规则里,我简单列了几个危险函数,比如Python的eval()、exec()、os.system(),Java的Runtime.getRuntime().exec(),以及JavaScript的child_process.exec()。但真正的审计不光是匹配函数名,还要分析数据流。所以我在规则里专门加了一段“深入分析提示”:
如果检测到可疑函数,请按以下路径做分析:这个函数的参数来源是否涉及用户输入?用户输入在到达该函数前经过了哪些处理?是否存在绕过过滤的方式?基于以上分析,判定风险等级,并说明判定依据。
为什么要这么写?因为直接让AI“列出所有调用了eval()的位置”是入门水平,而安全审计应该做的是“判断这条调用链是否真的构成可利用风险”。把这种论证思维写进规则,模型就不会盲目报一堆函数名,而是会像真正的审计员一样去跟踪数据流和攻击路径。
2.3 危害等级判定标准
我在references/severity_guide.md中定义了四级风险等级:严重、高、中、低。判定标准刻意做得表格化,方便模型查阅。
| 等级 | 定义 | 典型场景 |
|---|---|---|
| 严重 | 可被未授权用户直接利用且造成全面影响 | 未授权SQL注入可拖库、硬编码云厂商AK/SK可接管账号 |
| 高 | 需要特定条件但利用难度较低,影响范围可控 | 越权访问他人数据、命令注入点位于低权限容器内 |
| 中 | 风险需要叠加其他条件才能利用,或只影响边缘功能 | 反射型XSS需要配合社工,敏感信息记录在日志中 |
| 低 | 不符合安全规范但不构成直接入侵路径 | 注释中的内部路径泄露、代码风格安全优化建议 |
这个等级表不是拍脑袋定的。它借鉴了很多安全团队的评估方法,核心思路是同时看“可利用性”和“影响面”。在给模型的指令里,我要求它对每个问题都先套用这个表,再给出判断。
2.4 输出模板:审计报告必须能“落库”
最后再讲一下templates/audit_report.md的设计。审计报告的模板我做得非常死板,甚至可以说有点“机械化”,因为它的目的是让结果可以结构化存储。
报告包含六大部分:概况摘要、风险统计、问题清单、证据链、修复建议、免责声明。其中“修复建议”是我特别强调的——审计不能只发现问题就不管,缺少建议的审计报告价值会打对折。同时,我要求所有代码路径用绝对行号标注,所有风险描述里必须包含“为什么这是一个风险”的解释,坚决禁止只输出“这里存在安全问题”这种没有信息量的废话。
3. 实操过程与核心环节实现
这一节我会把从零到一搭建security-audit-skill的核心过程拆开,每一步都给可直接参考的写法和配置方式。我默认你使用的Agent框架支持常见的skill格式定义。如果你用的是Claude Code或Codex这类支持技能仓库的工具,这套写法基本可以直接套用。
3.1 第一步:定义核心工作流程
最开始我写SKILL.md时,工作流程写得特别散,四个步骤之间没有明确的前置条件,结果模型经常在“分析风险”和“输出报告”两个阶段之间来回横跳,输出质量很不稳定。后来我把流程改成严格的六步,每一步都设定了产出物。
- 上下文确认:确认审计对象、审计范围、代码语言栈、框架版本。此步输出“审计基准说明”。
- 基线检查:根据references里的清单做快速初筛。此步输出“初步风险扫描记录”。
- 分域审计:按安全域拆分任务,包括应用代码、配置项、依赖清单、部署脚本。此步输出“分域风险清单”。
- 数据流分析:针对初筛中命中高风险的问题,进行调用链和数据来源追溯。此步输出“深度证据链记录”。
- 交叉验证:把不同安全域中排查到的信息进行关联,排查组合风险。此步输出“关联风险分析记录”。
- 报告生成:按模板汇总所有结论,输出最终审计报告。
第5步交叉验证是很多人忽略的环节。真实攻击往往不是靠单点漏洞,而是多个看起来不起眼的小问题串联成一条完整的攻击链。举个例子,Nginx配置里泄露了一个内部服务地址(中风险),同时FastCGI调用未做缓存键隔离(中风险),单独看都不至于被定为高风险。但攻击者如果注意到这个地址,再结合FastCGI的参数污染,就可能打出一套远程利用的组合拳。只有做了交叉验证,报告的价值才会从“逐个列问题”升级为“还原攻击视角”。
我建议你在写SDILL.md的时候,把每一步的“输入-操作-产出”写在一个小节里,不要写得太意识流。模型是靠这些操作指令来知道当前应该干什么的,你把步骤写得越清晰,它的行为就越可控。
3.2 第二步:编写注入类审计规则
规则文件是整个skill的知识核心。以injection.md为例,我把审计目标拆成了系统命令注入、SQL注入、代码注入和LLM提示注入四大类,针对每类设了独立的分析指令。
以系统命令注入为例,我的规则文本大体长这样:
## 系统命令注入审计 **检测目标** - socket / 子进程 / 环境变量 / 文件路径等外部可控数据源 - 涉及调用:exec、system、popen、subprocess、child_process.exec等 **审计步骤** 1. 定位所有命令执行函数调用点; 2. 分析参数来源:如果参数来自用户输入、请求头、文件名等,标记为“可疑外部输入”; 3. 检查是否存在清理逻辑:白名单校验、转义、路径归一化; 4. 若无可信校验,记录为“命令注入疑似点”,并给出利用假设。 **证据要求** - 必须标注数据流的完整路径:输入从哪里来,经过哪些函数,最后进入哪个执行点。这一段之所以可以作为“可直接抄作业”的范例,是因为它根本不是让模型去“发现漏洞”,而是让模型去“描述调用链”。当模型能把一条数据流完整画出来的时候,它自然就具备了判断漏洞的能力,而不是靠猜。
3.3 第三步:编写权限与敏感信息审计规则
权限审计是安全审计里比较难做的一块,因为它不完全是代码层面的问题。我在这份skill里把“越权”拆成了三个层次来写。
第一层是未授权访问:接口或页面缺少身份校验中间件,直接允许匿名请求落地到业务逻辑。第二层是越权访问(水平):用户A能操作用户B的数据,典型特征是ID直接从请求参数中取用且无所有权校验。第三层是越权操作(垂直):普通用户可以调用管理员的接口,典型特征是缺少角色校验注解。
针对这三个层次,我在权限规则文件里分别给了一组“审计问题”,让模型按问题列表去代码中找答案。比如针对水平越权,问题列表是这样的:
- 数据查询是否使用了当前登录用户ID,而不是客户端传入的参数?
- 对象级权限是否有统一校验入口?
- 是否存在直接通过拼接ID访问文件、订单、消息等资源的逻辑?
写完权限规则之后,我强烈建议你再补一份敏感信息检测规则。这块查阅频率很高,而且有非常典型的命中模式:AWS AK/SK、支付宝/微信密钥,以及自定义算法的加解密密钥。我在规则里写的是正则匹配加模式分析双模式,正则负责初筛,模式分析负责减少误报——比如发现一段疑似密钥的字符串后,会检查它是否被真实用在访问外部服务的代码路径上,如果只是测试代码里的随机样例,就会降级提示。
3.4 第四步:接入工具并实测
规则编写好之后,不能直接在某个Agent对话框里测试一遍就算完。我把skill挂到常用的Agent框架里,专门准备了一个故意内置了漏洞的Flask项目作为测试样本,分别跑了五轮,检查输出稳定性和命中率。
接入过程其实就是将整个skill目录放到Agent框架指定加载的skills路径下,然后在会话里输入“对当前项目执行安全审计”,框架会自动识别到security-audit-skill并触发。如果你使用OpenCode或类似支持指令式驱动的工具,也可以直接通过/skill执行审计技能。
第一次实测跑下来,效果惊喜但是问题也不少。惊喜的是AI能自己组合多个文件里的信息还原出完整攻击链,比如在SQL注入检测中,它能追踪到参数从HTML表单进来、经过一个未使用参数化查询的DAO层、最终拼进原生SQL的完整链路。问题则主要集中在误报率偏高,下一节详细说。
4. 常见问题与排查技巧实录
4.1 常见问题速查表
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 报告格式不稳定,时而是表格时而是段落 | 没有把输出模板写进规则库 | 在SKILL.md里加入“强制按模板输出”指令,并把模板全文嵌入references |
| 审计结果偏向“表面文章”,只罗列函数名 | 规则文件中缺少数据流分析引导 | 补充审计步骤,要求跟踪输入来源和调用链,禁止只报函数名不解释流向 |
| 误报率过高,把正常代码判定为漏洞 | 规则没有区分“危险函数”和“可利用漏洞” | 在规则中增加“前置条件”判断,只有数据源不可信时才标记为疑似漏洞 |
| 上下文超限,导致审计结果截断或丢失细节 | 规则库过大,一次性加载内容过多 | 将规则按审计域拆分,主文件只保留索引,让Agent按需加载对应rules文件 |
| 模型在“审计”和“修复”之间摇摆,甚至自动改代码 | 边界声明不明确 | 在SKILL.md里明确写“由于存在风险,本技能默认不执行任何代码修改操作,只输出审计报告与修复建议” |
| 遇到非代码目标时答非所问 | 触发条件太宽泛 | 在元数据的description里写明适用范围,例如“仅用于代码仓库、配置文件、部署脚本的审计,不用于代码生成” |
| 同一份代码跑两次,结论差异很大 | 流程中缺少交叉验证步骤 | 检查SKILL.md中的流程是否完整,确定第5步交叉验证没有被省略 |
4.2 误报率调优的经验之谈
第一次测试时,这个skill的报告出来我自己看着都尴尬——把正常的参数化查询误判成SQL注入,把测试代码里的假密钥报成高风险,把完全没有外部输入的命令执行也标成“疑似注入点”。问题出在哪里?出在规则文件里没有“污染源判定”这一环。
所谓污染源判定,就是让模型在报洞之前先确认“这条输入到底是不是用户可控的”。我后来在规则里加了一个强制动作:每个疑似风险点都先回答三个问题——这个输入变量能被外部用户修改吗?修改之后能到达危险函数吗?中间有校验或净化吗?如果三个问题的答案都是否,那就不是漏洞,最多算一个风格建议。
加完这个强制动作之后,误报率下降了非常明显。所以如果你是第一次做类似的skill,我的建议是宁可让规则文件多写一点“判定前提”,也不要让AI靠关键词命中直接报风险。审计不是静态扫描器,解的应该是“真实的攻击可能”,而不是“代码里有这个词”的可能。
4.3 让AI“论证”而非“断言”
另一个值得单独拿出来说的经验,是关于语言表达层面的引导。AI在不确定安全问题时,很爱用“可能存在安全风险”、“建议进一步确认”这类含糊表达。初看没什么,但审计报告里到处都“可能”,负责人根本无从下手。
我在SKILL.md的规则里专门加了一段“论证要求”:所有风险描述必须是“结论+证据+影响”的结构,禁止输出无依据的推测。举一个正确的示例结构:某个接口存在水平越权,原因是订单查询接口ID直接取自URL参数,且后端未校验订单归属,攻击者可遍历订单ID查看他人订单数据。这种输出形式比十个“建议确认”都有用。
4.4 上下文管理:不要一次性塞太多
当前主流Agent的上下文窗口虽然很大,但实际审计项目动辄几百个文件,全部塞进去是不可能的。这个skill的应对策略是分轮扫描:先扫入口文件(路由、控制器),再扫核心逻辑(服务层、工具类),接着扫配置与部署文件,最后汇总关联。每一轮只分析一部分文件,把中间结论写入临时记录,最后统一生成报告。
这也意味着你在给审计场景定义skill时,最好在流程里明确“分批扫描”策略,而不是硬让模型一口气处理整个仓库。我在SKILL.md的流程区里加了一条提示:“如果目标项目文件数量超过100个,请分批扫描,建议顺序为入口层→业务逻辑层→数据访问层→配置与部署层。”
5. 扩展方向:让安全审计Skill具备真正的“职业素养”
5.1 对接依赖漏洞库与代码扫描工具
skill本身跑的是模型对代码的理解,但它不排斥外部工具。我在后续迭代时给它加了一个“外部工具钩子”:当需要识别第三方依赖漏洞时,skill会建议运行pip-audit或npm audit,并把命令返回的结果纳入审计报告,作为“依赖安全”章节的数据支撑。
为什么要这么设计?因为大模型的训练数据有截止时间,对于新曝出的漏洞,它可能并不知道。而扫描工具用的是实时漏洞库,两边正好互补。把工具结果引入报告,既不牺牲模型的分析能力,又能补上时效性的短板。配置方法是通过SKILL.md中的“工具调用说明”来定义,让模型在审计依赖文件时按需推荐用户执行对应扫描命令,再把输出喂给报告。
5.2 建立团队专属审计规则库
通用安全规则库永远无法完全匹配团队自己的技术栈和业务特点。比如你可能用了一套内部RPC框架,它的鉴权方式和Spring Security完全不同;你可能在代码里约定了一个特殊的前缀表示“内部接口”,这些规则没法从通用库直接获得。
我的解决方案是,skill支持在references目录下增加“团队补充规则”文件。这个文件里可以写团队内部的编码规范、框架特定风险点、以及历史安全事故复盘总结。模型在审计时会把通用规则和补充规则同时作为参考。这样做的好处是审计标准从“行业通用”变成“团队通用”,更贴合实际工作流。
5.3 Skill与Agent的边界
很多人搞不清楚skill和agent的区别。我觉得可以用一个不太严谨但好理解的说法:Agent是一个“有自主行动能力的员工”,而Skill是一本“岗位操作手册”。员工能做什么、能调动哪些资源,由Agent层面的决策逻辑来定;而具体每一步该怎么做、有什么纪律、按什么格式交付,则由Skill来约束。
所以当有人问我“security-audit-skill能代替安全工程师吗”,我的回答是不能。它是一个很称职的“审计助理”,能按照你定好的标准流程把活干得又快又规范,但它不具备安全专家那种根据业务上下文灵活调整判断维度的能力。最好的使用姿态是:让skill出初稿,让人做终审。模型负责“全面、快速、不遗漏”,人负责“定性、决策、拍板”。
6. 实操心得与复盘
跑通security-audit-skill之后,我陆陆续续又写了几个别的场景的skill,慢慢摸出了一些相对通用的方法论。这里写几条个人体会,供想入坑的人参考。
第一,写skill最耗时间的不是写规则,而是“定边界”。让AI输出一份权威感十足的偽报告很容易,难的是让它清楚“什么不归自己管”。我在SKILL.md里写“不执行修复、不保证绝对安全、不替代人工专家复核”这些否定式描述,花的精力比写审计流程本身还多。但效果也确实立竿见影——AI开始懂得收敛自己了。
第二,skill一定是一个需要持续迭代的动态文件。别指望一次写完就一劳永逸。第一次实测跑了五轮,每次都手动记录失败案例,再根据失败案例向规则库里补充新的分析指令,这样迭代了三个版本之后,整体的误报率才降到可以接受的水平。每次在上线新框架、新语言时,持续往references里补一些规范化的底料,skill才会越用越顺手。
第三,如果你准备在生产环境中用这个skill,我建议你设置一个人工复核环节,把重点放在高危漏洞的人工确认工序上。任何时候,文档里都应当写清楚:由大模型产出的审计报告只能作为辅助参考,最终结论必须以安全工程师的复核为准。这既是对业务负责,也是对skill能力的合理定位。
最后分享一个我个人的小习惯:每改一次规则,我都会用同一份故意埋雷的测试项目重新跑一遍,然后对比前后两次结果的差异。这个方法帮我发现了很多“改一处、崩一片”的连带问题。如果你也在做类似的东西,强烈建议保留一份属于你自己的“雷区样板项目”,它就是你调试skill时最趁手的工具。