最近半年我一直在用 AI 编码助手写生产代码,Codex、Claude 换着用,效率确实高,但有一个问题始终让我心里不踏实:AI 生成代码的速度太快了,快到没人来得及做安全审查。功能跑通了、测试过了,但那段动态 SQL 到底会不会被注入?那个文件上传接口有没有校验类型?新引入的依赖有没有已知 CVE?这些事不是 AI 没提醒,而是每次都要我手动追问,追几次就烦了。
后来我把这个问题想明白了——缺的不是安全知识,而是一个能把安全审计这件事标准化、自动化的流程。于是我自己写了一个叫security-audit-skill的技能包,专门给 Agent 用的那种 skill。这篇文章就聊聊这个技能包我是怎么设计的、规则文件怎么写、踩了哪些坑,以及它和 Fortify、Kali 这类传统安全工具之间的分工。
1. 为什么我会想到做一个安全审计专用 Skill
先交代背景。市面上不是没有安全审计工具,Fortify SCA、SonarQube、Semgrep,我全都用过,它们能扫出不少问题。但用一段时间就会发现,这类工具和 AI 编码助手的配合其实很割裂:工具跑完出一份报告,我拿到报告还得自己对着代码找位置、想修复方案,然后再把修复指令喂给 Agent。一来一回,审查效率并没有真正提升。
真正让我下定决心自己做 skill 的,是一次线上事故的复盘。当时我们上线了一个 AI 帮忙写的文件预览功能,代码 review 的时候大家都觉得逻辑没问题,结果上线第二天被安全测试发现存在任意文件读取漏洞,问题的根源就是路径拼接没有做规范化处理。代码生成器其实有能力识别这种问题,但它默认你没有让它检查,它就专注于把功能写完。那次之后我就意识到,必须把"安全审计"变成一个 Agent 默认会执行的固定步骤,而不是靠人临时想起来。
security-audit-skill的定位很简单:它是一个装在 Agent 里的专业技能包,让 Agent 在拿到一份代码仓库时,能按照预先定义好的检查项,系统性地完成安全审计,并输出结构化的报告。它不替代人做决策,也不替代传统扫描工具做全量分析,它解决的是"AI 自己能感知、能修、但没人触发它做安全检查"这个断层问题。
1.1 AI 编码助手把安全审查变成了"盲区"
我在团队里观察到一个现象:用 AI 写代码越多的同学,越容易把安全测试当成"上线前的事"。以前手写代码,写的时候就会自然想到"这个输入能不能被恶意利用",因为每一步都是自己敲的。现在 AI 把整段代码吐出来,人的注意力集中在"功能对不对",很少有人会再逐行追一遍安全边界。这不是态度问题,是注意力分配的问题——人脑同时盯功能正确性和安全性的能力本来就很有限,AI 时代这个矛盾被放大了。
所以做 skill 的第一个原则:不要指望开发者记得触发安全审计,也不要指望 Agent 自发地每次都做。要把审计动作写进 skill 的加载逻辑里,让 Agent 在完成任务时默认带上安全视角。我见过一些人把检查项写在系统提示词里,但提示词会被功能需求挤掉,而独立的 skill 文件天然占有一块上下文,不会被轻易覆盖。
1.2 Skill 和 Agent 之间的分工边界
很多刚开始接触 skill 的人会问:skill 和 agent 到底有什么区别?我自己的理解是这样:agent 是那个能思考、能调用工具、能决定下一步干什么的执行体,而 skill 是给这个执行体提供"领域知识+操作流程"的外挂硬盘。拿安全审计来说,Agent 本身知道什么是 SQL 注入,但它未必知道你们项目里哪些目录需要优先审、哪些规则是硬性的、报告要按什么格式输出。这些内容写在 skill 里,Agent 加载 skill 之后就等于雇了一个安全专家在旁边给它递检查清单。
实际用下来,一个 skill 不需要写太多"教育"性质的内容,重点是流程和标准。安全专家不会给工程师讲一遍什么是注入,而是直接说"这几处必须改"。Skill 也一样,它应该是一本操作手册,不是教科书。
1.3 这个 Skill 到底解决什么问题
总结一句话:它把"安全审计"从一个需要人主动发起的动作,变成了 Agent 任务流里的固定环节。对个人开发者来说,写完代码让 Agent 跑一遍 skill,能挡住大部分低级的漏洞;对团队来说,可以把团队自己的安全规范沉淀成 skill 文件,新成员或者新项目都能用同一套标准来检查,而不是依赖某个安全负责人的个人经验。
2. Skill 的目录骨架:从小工具演进到完整技能包
我第一次写 skill 的时候特别随意,就一个 Markdown 文件,把所有检查项全部塞进去。结果文件写到两千多行之后,Agent 的响应质量和命中率都开始下降。后来我参考了社区里一些成熟 skill 的做法,把security-audit-skill拆成了一个标准目录结构,每个文件管一类事情,改动起来也清爽得多。
一个可以落地的目录结构长这样:
security-audit-skill/ ├── SKILL.md ├── rules/ │ ├── web-injection.md │ ├── access-control.md │ ├── sensitive-data.md │ ├── dependency-risk.md │ └── config-baseline.md ├── templates/ │ ├── audit-report.md │ └── fix-suggestion.md └── reference/ ├── spring-security-checklist.md └── owasp-top10-notes.md这个结构看起来简单,但每个文件都有明确的定位,后面我一个个拆开讲。
2.1 SKILL.md 是整个技能包的入口
SKILL.md 是整个技能的入口,Agent 加载 skill 时首先读的就是它。这个文件的打磨程度直接决定了 Agent 对技能的理解质量。我的 SKILL.md 分成三个部分:技能声明区、执行流程区、输出要求区。
技能声明区写给 Agent "看"的描述,要写清楚这个技能适合用在什么场景、不适合用在什么场景,避免 Agent 在无关任务上也强行套用。执行流程区是整个文件里最重要的部分,我会在下面单独细讲。输出要求区定义了审计报告应该包含哪些字段,让结果可以直接复用而不是每次都要重新解析。
一个精简的 SKILL.md 大概是这种感觉:
--- name: security-audit description: 对代码仓库执行安全审计,覆盖 Web 注入、访问控制、敏感信息、依赖风险和配置基线五类问题,输出带风险等级与修复建议的审计报告。 --- # Security Audit Skill ## 执行流程 1. 梳理仓库结构,识别语言、框架和部署方式,确定审计范围。 2. 按 rules/ 目录下的规则文件逐项检查,不跳项,不遗漏。 3. 对于命中规则的位置,必须先确认是否真实可控,再写入报告。 4. 报告必须按 templates/audit-report.md 的格式输出,不得遗漏风险等级。 ## 核心原则 - 只报告有把握的问题,不确定的标记为"待确认"。 - 修复建议必须具体到文件和函数,不允许只写"建议使用参数化查询"这种空话。 - 优先检查敏感文件、认证相关逻辑、文件上传和命令执行位置。这段描述我迭代了很多版,最大的体会是:描述必须具体到"怎么做",不能用"请全面检查"这种模糊指令。Agent 对模糊指令的理解经常偏保守,要么扫得太浅,要么为了"全面"输出一堆噪音。
2.2 rules/ 目录是审计规则的核心资产
rules/ 目录存放的是按主题拆分的审计规则,这是整个 skill 真正的资产。我把规则按问题类型分成了五个文件,每个文件内部再按单条检查点组织。这样设计的考虑是:规则文件之间保持独立,命中率和内容都比较可控。
每个规则文件内部统一用"检查点+触发特征+验证方法+风险等级"四段式:
# SEC-WEB-001 SQL 注入 - 检查点:搜索所有执行 SQL 的位置,识别字符串拼接形式 - 触发特征:用户可控参数 + 字符串拼接 + executeQuery/executeUpdate - 验证方法:确认是否使用了 PreparedStatement 或参数化查询 - 风险等级:高危 # SEC-WEB-002 文件上传类型校验 - 检查点:定位文件上传接口,检查扩展名和 MIME 类型校验 - 触发特征:MultipartFile / upload 相关接口 - 验证方法:校验白名单是否存在、是否可绕过 - 风险等级:中危我一开始写的规则特别笼统,比如"检查所有输入验证",这种规则 Agent 执行起来基本靠猜。改成上面这种结构之后,Agent 就知道它是一个 checklist,需要逐条核对,输出也更一致。
2.3 templates/ 和 reference/ 的辅助作用
templates/audit-report.md 是审计报告的格式模板,包含风险等级统计、问题列表、逐条定位和建议修复方案。有了模板,Agent 每次输出的报告结构一致,我可以写一个简单的脚本去解析结果并自动记账,不用每次手动整理。
reference/ 目录放的是支撑材料,比如 Spring Security 配置检查要点、OWASP Top 10 的补充笔记,Agent 在执行审计时如果遇到不确定的情况,可以回去翻阅。这个目录不需要做得特别大,但最好放一些"只有你们项目/团队才有的规范",让它成为团队安全知识的沉淀地。
3. 把安全专家经验拆成可执行规则
前面说到规则文件是 skill 的核心,但把一套"安全专家的经验"翻译成 Agent 能稳定执行的规则,中间是有方法论的。这一节我讲讲自己用得比较顺的套路。
3.1 规则设计的三层结构
我写规则时遵循"入口-路径-出口"三层结构。入口指的是用户可控的数据源,比如 HTTP 参数、请求头、上传文件、环境变量;路径指的是数据流动经过的处理逻辑,比如字符串拼接、路径拼接、反序列化、模板渲染;出口指的是数据到达的危险操作,比如 SQL 执行、命令执行、文件写入、响应输出。
规则的作用就是让 Agent 沿着"入口到出口"这条链路去追踪数据流,看看中间有没有做有效的清理和校验。这个思路和传统 SAST 工具的污点分析是相通的,只不过 Agent 能借助语义理解抓住一些工具抓不到的上下文。比如工具可能会报告所有的eval()调用,但 Agent 能判断某一个eval()的输入其实来自服务端硬编码,风险很低,不必报。
3.2 三个可落地的规则示例
拿最常见的三类问题举例。
第一类是命令注入检查。检查点很明确:搜索执行外部命令的位置,比如 Java 的Runtime.exec()、Python 的os.system()、Node 的child_process.exec()。触发特征是两个条件的叠加:命令字符串由外部参数拼接而成,且没有经过白名单过滤。验证方法是确认参数是否可以被用户控制,同时确认是否存在 shell 解释。
第二类是硬编码敏感信息检查。检查点覆盖配置文件和源码中的密钥、密码、Token。触发特征是一些典型的模式,比如password = ""、api_key = ""、secret = ""、私钥块。验证方法需要区分测试代码和生产代码,测试代码里的假密钥不应该报高危,这一点我会在规则里单独注明,避免 Agent 一刀切。
第三类是越权检查。越权是最难靠静态规则完全覆盖的问题,因为需要理解业务语义。我的做法是让 Agent 重点检查"从请求参数获取资源 ID 后是否直接执行查询"这类模式,观察是否存在资源归属校验。这类检查 Agent 不能只靠代码本身,还需要看接口的调用上下文,所以规则里我会写"当无法确认时标记为待确认,由人工复核"。
3.3 控制规则粒度的经验
规则粒度是最难拿捏的部分。规则太细,Agent 的检查路径会变得异常长,稍微复杂一点的仓库就会跑很久,而且容易在无关紧要的角落浪费时间;规则太粗,Agent 只会给出"项目整体安全性较好"这种毫无价值的结论。
我的经验是:每个规则文件控制在 8 到 12 个检查点之间,分三档优先级,高风险检查点必须逐条过,中风险检查点跑到了相关代码才过,低风险检查点放到最后统一处理。这样做的好处是,即使 Agent 因为上下文限制没法把全部规则跑完,高风险项的覆盖率也能得到保证。
4. 审计覆盖面:从 Web 应用到系统安全配置
Skill 的审计范围不是只盯着源代码,配置文件的检查同样重要。实际工作中很多安全事件都不是代码逻辑漏洞,而是配置问题——服务端口全开、数据库弱口令、接口无需认证就能访问。我把审计范围分成了三个层面,这也是我在 reference/ 目录里沉淀内容的主要框架。
4.1 应用层:Spring Security 等框架配置的栅栏式检查
现在 Java 后端基本离不开 Spring Security,我在 reference/spring-security-checklist.md 里整理了配置审计的要点。比如 CSRF 防护是不是被整体关闭了、会话管理是不是用了默认配置、权限规则是不是用了permitAll()大面积放行。还有一个在版本升级时经常踩的坑:Spring Boot 3 之后 Security 的配置方式从WebSecurityConfigurerAdapter迁移到了SecurityFilterChainBean 的方式,这个迁移过程很容易把原本细粒度的权限规则简写成requestMatchers("/**").permitAll()。
Skill 里专门有一条规则是"检查框架版本升级相关的 Breaking Change 对安全配置的影响"。因为很多开发者升级依赖时只是让项目能跑起来,安全配置被静默丢弃的情况时常发生。Agent 做版本差异检查时可以同时对照官方迁移指南,这个活以前需要人工去查,现在完全可以交给 skill 去完成。
4.2 系统层:Linux 安全模块与 Windows 安全基线
很多做应用开发的工程师会觉得系统安全和自己无关,但 skill 适合同时覆盖部署环境的检查,因为 Agent 本身就能读 Dockerfile、Kubernetes 配置、CI 脚本。我增加了两个规则文件,一个检查容器和 Linux 层面的安全配置,另一个检查 Windows 相关安全基线。
Linux 侧我会让 Agent 检查 Dockerfile 里是不是用 root 用户运行应用、是不是把特权模式开给了容器、capabilities是不是被删除了,以及对 Linux Security Modules 的几个关键目录是否有基本的感知——比如确认应用是否运行在只读文件系统上。Windows 侧可以检查常见的基线设置,比如 LSA Protection(Local Security Authority process 保护)是否开启、账户锁定策略是否配置到了合理的阈值。这些以前需要用reg命令或者注册表路径去核查,现在可以让 Agent 直接读配置仓库里的脚本或者组策略导出文件来审计。
4.3 依赖与供应链:把工具能干的活留给工具
依赖层面的审计,我的观点和建议是:不要让 skill 干扫描器擅长的事。检查依赖有没有已知 CVE、license 是否合规、版本是不是落后太多,这些功能 Trivy、OSV-Scanner、Snyk 已经做得很好,用 skill 再做一遍纯属浪费时间,而且 Agent 的漏洞库更新速度肯定不如专业工具。
所以我在 skill 里的做法是:把依赖审计拆成两步,第一步提示开发者先跑一遍专业扫描器,第二步让 Agent 对扫描结果做"解读和修复建议"。扫描器输出的是长长的 CVE 列表,很多条目和实际运行路径无关,Agent 可以结合仓库代码判断哪些漏洞真正可达、修复优先级应该怎么排。这个分工既发挥了 Agent 的理解能力,也避开了它在数据时效性上的短板。
5. 在 Codex、Claude、OpenCode 里把 Skill 跑起来
写好了 skill 文件,怎么让它真正跑起来也是一个问题。不同 Agent 对 skill 的加载方式不完全一样,但思路是共通的,这一节讲讲实际安装和测试中的经验。
5.1 安装与加载的通用思路
主流的 Agent 编程工具,比如 Codex、Claude Code、OpenCode,基本都支持从项目目录或用户全局目录加载 skill。通用的做法是在项目根目录下建一个专门放 skill 的隐藏文件夹,把security-audit-skill整个目录放进去,然后在 Agent 的启动配置里指向这个目录。有些工具支持在项目里用特殊注释或者命令来触发指定 skill,比如让 Agent "读取 security-audit-skill 并执行一次完整审计",它会自动加载 SKILL.md 和相关规则。
我自己的习惯是在项目根目录放一份说明文件,写明哪些目录属于生成的代码、哪些目录属于手写逻辑,这样 Agent 审计时可以按优先级分配注意力。如果是纯 AI 生成的老项目,这个区分就更有必要了,因为 AI 生成代码的安全问题往往集中在几个特定位置。
5.2 实测中的表现与误报处理
实测一轮下来,效果让我比较满意,但也没有一开始想的那么完美。对一个 Spring Boot 项目做全量审计,Agent 大概能稳定识别出硬编码密钥、数据库连接配置泄露、文件上传未校验类型这类问题,准确率比较高。对于 SQL 注入这类需要追踪调用链的问题,准确率取决于代码结构的复杂度,结构越清晰,Agent 的把握越大。
误报方面要有一个心理预期:没有任何基于语义理解的方式能做到零误报。skill 能做的是把误报控制在一定范围内,同时对不确定项明确标注"待确认",而不是强行给结论。我在规则里专门加了一条:"当无法确认某个数据源是否可控时,必须在报告中标注不确定原因,禁止臆断"。有这条规则兜底,报告的可信度会提升很多。
5.3 和 Fortify、Kali 这类传统工具的定位差异
有不少人问我,都有了 Fortify 和 Kali,为什么还要折腾 skill。我的看法是,工具解决的是"扫描和利用",skill 解决的是"理解和修复"。Fortify SCA 的 Audit Workbench 能给出很详细的漏洞分析,但它不会顺手把修复代码写完,也不会告诉你这个漏洞在你们当前的业务上下文里到底有多严重。Kali Linux 的价值在于渗透验证,可以确认某个漏洞真实可利用,但并不会帮你改代码。
security-audit-skill和这些工具完全可以串成一条流水线:先用 Fortify 或 Semgrep 做全量静态扫描,把工具报告喂给 Agent,Agent 结合 skill 里的规则做上下文判断和修复建议,再针对可疑点用 Kali 里的渗透工具做实际验证。这套组合拳下来,既有机器扫描的广度,也有专家判断的深度。
6. 调优心得与迭代方法
Skill 不是写完就完了,它跟代码一样需要持续迭代。我自己的维护节奏是每两周更新一次规则,结合这期间的真实漏洞报告和新的安全公告来调整检查项。最后一个部分,分享几个最值得记录的调优心得。
6.1 上下文被塞爆的教训
第一次把 SKILL.md 和所有规则文件直接塞进对话时,Agent 的可用上下文一下子少了很多,审计复杂度稍微高一点,它就有点"力不从心",甚至会漏掉一些本该检查的目录。后来我学到一个策略:把规则文件改成"按需加载",SKILL.md 里只写主流程和关键规则,详细的检查点留着遇到相应代码时再让 Agent 去读取对应文件。
比如 SKILL.md 里写"当发现文件上传相关代码时,必须读取 rules/web-injection.md 中的 SEC-WEB-002 并逐项核查",而不是一股脑把所有规则都加载进来。这样虽然多了一次文件读取的交互,但上下文占用大幅下降,审计的质量反而提高了。
6.2 用真实漏洞报告反向迭代规则
我维护规则的主要依据来自两个渠道:一个是公开的漏洞数据库和安全公告,另一个是我自己项目里实际发生过的问题。真实漏洞的价值在于,它说明"我们当前的做法存在盲区",反向补充规则往往比正向设计更高效。
举个例子,有一次我们的 Node.js 项目因为child_process.exec使用不当被安全测试发现命令注入风险,我立刻把这个场景写进规则,标记为"高危+必查"。后来其他项目再用这个 skill 时,Agent 都会主动留意类似的 exec 调用,团队里的同类问题确实大幅减少。
6.3 后续可以扩展的方向
现阶段的 skill 还是一个以代码仓库输入为主的审计工具,后面我打算往三个方向扩展。第一是审计报告的自动化流转,把报告输出成与缺陷管理工具兼容的格式,省掉人工录入的步骤;第二是加入更多业务语义相关的检查规则,比如支付相关的金额篡改、优惠券风控逻辑,这类问题通用工具很难检测,但 skill 可以定制;第三是把 skill 的覆盖面从源码扩展到云上基础设施配置,检查 IaC 代码里是不是存在高危暴露,比如存储桶权限配置错误、安全组规则过宽这类问题。
最后再分享一个我在实际使用中最受用的细节:无论 skill 写得多好,都要保留"人审"的环节。我会要求 Agent 在报告末尾列出本次审计的未覆盖名单,比如"因为缺少测试环境,这部分逻辑未能验证"。这个不起眼的清单,让每次审计的边界变得清晰,也让人工复核有了明确的方向。安全这件事,永远都值得多留一个心眼。