1. 为什么安全审计要“做成一个 skill”
先说结论:这个security-audit-skill,本质上不是传统意义上的安全扫描脚本,也不是一个单纯挂在聊天窗口里的“帮我审一下这段代码”的提示词,而是给AI编码代理(类似Codex、Claude Code、OpenCode这一类工具)配的一套可复用的、带流程和模板的审计工作流。
你可能已经注意到,最近“skill”这个词在AI工程圈里突然火起来了。各种skill库、skill插件、数学建模skill、会议纪要skill、论文skill满天飞。很多人第一反应是“这不就是Prompt吗,换个马甲”。我最初也这么想,直到自己把一个几十页的审计流程压成skill包,并在真实仓库里跑了几轮之后,才意识到它的价值在哪。
skill跟普通Prompt最大的区别在于:普通Prompt是“关于怎么做的一段描述”,而skill是一整套“程序性工作包”。它里面包含工作流定义、判断标准、输出模板、可参考的规则文件,很多时候还内嵌脚本或资源配置。你把skill交给代理时,它知道“我要按什么顺序执行哪些检查,检查到什么算通过,什么算不通过,最后用什么格式交付结果”。
对安全审计这个垂直场景来说,这套属性太合适了。安全审计天然要求流程规范、输出结构化、结论可复现。如果只是在对话框里写“帮我找找漏洞”,AI可能给你一个泛泛而谈的清单,换个仓库又完全不是那么回事。但把审计流程沉淀成一个skill,等于把你脑子里那套“先看依赖,再看鉴权,再看数据流,最后看日志”的套路,完整转移到代理身上,而且换个项目还能保持一致。
适合参考这份经验的,是那些正在给团队配AI编码工具、打算让AI在代码评审阶段就开始介入安全风险识别的人,也包括对Agent技能体系感兴趣、想搞清楚skill到底怎么设计怎么写的同学。在动手之前,先花点时间搞清楚“skill和agent到底什么关系”“多大粒度的任务适合做成skill”,后面写起来会顺手很多。
1.1 先弄清楚“skill”和“agent”的关系
搜索“skill”相关热词时,看到最多的问题除了“skill是什么”,就是“skill和agent的区别”。这两个概念确实容易混,尤其是在同一个产品里同时出现时。
我的理解是:agent是大脑加手脚,它负责感知任务、拆解步骤、调用工具,在多个能力之间切换;而skill更像是agent身上的“预制技能包”,是提前编好的、可以在特定场景下被触发和执行的流程。你训练一个人当保安,是“agent”层面的事情;但你给他一张“巡逻路线+打卡点+异常上报流程”的卡片,那是“skill”层面的事情。
所以设计skill的时候,心态不是“写一个能思考的prompt”,而是“写一套新人来了照着做也能做对的操作手册”。代理仍然负责临场判断、读代码、调用工具,但判断框架、优先级、输出格式都应该由skill定死。前面提到的“根据审稿意见形成修改方案的skill”“软件测试skill”“python skill读取mysql”,本质上都是同一思路:把重复性高、流程明确的任务从“临时发挥”变成“按流程执行”。
1.2 安全审计这个场景,为什么特别适合做成skill
安全审计和很多其他编码任务不一样,它有三个特点:
第一,它必须分阶段。你一次性把所有安全问题丢给AI,它容易抓不住重点,最后给出的是“可能有SQL注入风险”“建议增加输入校验”这类放到任何项目上都成立的车轱辘话。真正有效的审计,一定是分层的:先摸清项目结构和依赖,再锁定外部输入边界,然后追数据流进入的敏感操作,最后验证鉴权逻辑,每一步有独立的检查清单和出口标准。
第二,它需要知识库支撑。比如OWASP的常见漏洞类型、特定框架的配置检查点、敏感信息泄露的正则规则。这些知识要么写进skill的规则文档里,要么借助外部扫描工具的结果。知识库不前置的话,代理就只能靠模型内部记忆硬扛,不同模型之间效果天差地别。
第三,它的输出必须可落责任。审计报告不是一个“高风险提醒”就完了,得落实到具体文件、具体行号、具体修复建议和复测方法。普通Prompt可以给你建议,但不会主动按统一格式整理,也不会在报告里区分“确定性问题”和“疑似问题”。
把这三条放进skill的工作流设计里,正好能把安全审计从一个“问AI一嘴”的动作,升级成“交给代理执行一次质量受控的检查任务”。
2. security-audit-skill的整体设计思路
开始动手组装之前,我花了一晚上把过去做审计的经验画成了流程图,再翻译成skill的目录结构和检查步骤。这个“先设计后编码”的过程省了很多返工。很多人写skill失败,不是因为不会写Markdown,而是根本没想清楚这个技能要在什么输入条件下被触发、执行完输出什么、遇到模糊情况往哪边走。
我建议你按这个顺序想清楚再做:触发场景 -> 执行流程 -> 判定标准 -> 输出格式 -> 边界兜底。下面是我最终落地的设计方案。
2.1 技能包的目录结构
一个可以被主流编码代理识别的skill包,通常是一个独立目录,目录名就是skill名。我的做法是写成这样:
security-audit-skill/ ├── SKILL.md ├── resources/ │ ├── rules/ │ │ ├── owasp-checklist.md │ │ └── secret-patterns.md │ ├── templates/ │ │ └── audit-report-template.md │ └── scripts/ │ └── quick-risk-scan.py └── assets/ └── severity-matrix.md这个结构不算标准答案,但它每一层都有明确用途。SKILL.md负责定义触发条件和主流程,resources里的rules是知识库,templates是输出框架,scripts放一些可以自动化执行的辅助脚本。assets目录用来放对照表这类“上下文资料”,比如风险等级矩阵。
目录结构的意义在于:当你同时装了十几个skill时,代理需要快速定位“这个任务该加载哪套规则”。如果所有内容都塞进一个巨大的SKILL.md,一是加载慢,二是检索命中率低,三是更新起来很容易改出bug。把它拆成独立文件,规则文件可以单独维护,模板被多个项目复用,脚本也可以单独测试。
2.2 SKILL.md:一个可执行的工作流定义
SKILL.md是所有编码代理都会优先读取的入口文件。它的核心是让代理看懂三个问题:这个技能管什么事、什么时候不归我管、我按什么步骤办事。因此文件结构我固定为四段式:元信息、触发条件、执行流程、输出约束。
执行流程不用写太长,但顺序必须严格。我在实际项目中把它定义成六步:仓库信息收集、依赖风险识别、边界和数据流分析、认证授权检查、敏感信息扫描、报告生成。每一步对应一个检查清单,代理执行完一步,把中间结果写进工作区,再进下一步。这跟人做审计的节奏是完全一致的。
还有一个关键点:SKILL.md里要明确写出“该否决的场景”。比如只改了一行CSS的提交,就不需要触发全量依赖审计;没有涉及用户输入变更的PR,也不需要跑完整边界分析。没有这一步,skill很容易被过度触发,久而久之用户就懒得用了。
2.3 知识库和模板,决定了技能的下限
很多skill写出来效果不好,问题出在知识库太薄。安全审计技能的rules目录里,我至少放三类内容:通用漏洞检查清单、框架特定配置检查项、敏感信息正则库。通用清单回答“哪些风险常见”,框架检查项回答“这个框架这个版本容易踩什么坑”,正则库用来扫硬编码密钥、token、AK/SK这一类问题。
模板的作用则是把审计结果拉回“可用”的标线。没有模板的审计结果往往是散文,有了模板,它才能输出原因、证据链、修复建议、复测步骤和负责人建议。更重要的是,模板让结果可以被后续的自动化流程解析,比如直接贴进工单系统或者合并进日报。
3. 从零搭建security-audit-skill的实操过程
这一节是完整的“抄作业”参考。我会带你从空目录开始,搭一个能在Codex、Claude Code、OpenCode三种环境里跑起来的最小可用版本。我自己实测过这套流程,过程中踩的坑会一并标出来。
3.1 第一步:创建目录和元信息
先建目录,然后写SKILL.md的头部。这部分决定了代理在什么时候、用什么关键词去匹配你这个skill。
mkdir -p security-audit-skill/resources/rules mkdir -p security-audit-skill/resources/templates mkdir -p security-audit-skill/resources/scripts cd security-audit-skill接着在SKILL.md最前面写YAML风格的元信息。这一步非常关键,描述写得不好,skill就永远触发不了。我最初的版本描述写的是“Audit code security”,结果很多时候代理认不出来,因为触发它的是“帮我审一下这个仓库有没有泄露密钥”这样具体的表达。
推荐的做法是描述里同时包含模糊关键词和精确关键词:
--- name: security-audit description: 用于对代码仓库执行安全审计。当需要检查代码漏洞、依赖风险、敏感信息泄露、身份认证绕过、输入校验缺失时使用。常见触发词:安全审计、security audit、漏洞排查、找问题、检查密钥泄露、代码安全评估。 allowed-tools: bash, read, grep, glob, python ---这里还顺手声明了允许调用tools集合,好处是限制代理在执行审计过程中不要跑去访问网络或者写文件,减少越权行为。实测下来,声明tools后稳定性明显提升,尤其是Codex,它的工具选择不再飘。
3.2 第二步:写核心审计流程
这是整个skill的心脏,我建议用步骤编号加检查项的方式写,每一步都有一段“执行说明+退出标准”。下面这段是我简化后的核心流程,可以直接改一改用在你们的仓库里。
## 执行流程 ### Step 1: 仓库信息收集 - 读取项目根目录的 README、package.json、requirements.txt、go.mod 等依赖清单文件 - 识别语言栈、框架、版本、项目用途 - 输出: project-profile.md ### Step 2: 依赖风险识别 - 逐一核对依赖清单中的关键依赖版本 - 检查是否有已知高风险的旧版本、不再维护的依赖、多余的高危依赖 - 输出: dependency-risk.md ### Step 3: 边界和数据流分析 - 找到所有对外入口(API路由、消息队列监听、文件上传入口、命令执行入口) - 从入口出发追踪数据流向 - 标记未经过滤/未经过鉴权就触达敏感操作的位置 - 输出: boundary-trace.md ### Step 4: 认证和授权检查 - 检查登录状态校验、会话管理、角色权限控制 - 重点看越权风险、硬编码凭证、认证绕过逻辑 - 输出: authz-findings.md ### Step 5: 敏感信息扫描 - 运行 secret-patterns.md 中的正则规则 - 检查仓库历史、配置文件、日志里的AK/SK、Token、密码 - 输出: secret-findings.md ### Step 6: 报告生成 - 汇总所有中间结果 - 按照 audit-report-template.md 输出最终报告 - 区分“确定风险”“疑似风险”“提示项”三级每步之间不要贪快。安全审计里最忌讳的就是跳过边界分析直接去猜漏洞,一旦跳过,后面所有结论都不可信。
3.3 第三步:内置一个快速扫描脚本
虽然代理本身能读文件,但用一个临时脚本跑一遍敏感信息扫描,效率比让AI逐行grep高得多。我在resources/scripts里放了一个Python脚本,逻辑很简单就是遍历当前目录、跳过常见的依赖文件夹,然后用一组正则去匹配疑似密钥和Token。
#!/usr/bin/env python3 import os, re # 跳过目录 SKIP_DIRS = {'.git', 'node_modules', 'dist', 'build', '.venv', 'venv', 'vendor'} # 简化版敏感信息正则 PATTERNS = [ (r'(?i)(api[_-]?key|secret|token|password|passwd)\s*[:=]\s*["\'][^"\']{8,}["\']', '疑似硬编码密钥'), (r'AKIA[0-9A-Z]{16}', '疑似云厂商访问密钥'), (r'(?i)-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----', '疑似私钥泄露'), (r'[0-9a-fA-F]{32,64}', '疑似哈希或Token'), ] def scan(path: str): hits = [] for root, dirs, files in os.walk(path): dirs[:] = [d for d in dirs if d not in SKIP_DIRS] for fname in files: fpath = os.path.join(root, fname) try: with open(fpath, 'r', encoding='utf-8', errors='ignore') as f: for lineno, line in enumerate(f, 1): for pat, desc in PATTERNS: if re.search(pat, line): hits.append((fpath, lineno, desc, line.strip()[:120])) except Exception: pass return hits if __name__ == '__main__': hits = scan('.') for fpath, lineno, desc, snippet in hits[:50]: print(f'{fpath}:{lineno} [{desc}] {snippet}') print(f'--- total: {len(hits)} ---')这个脚本的价值不是替代AI判断,而是提供“机器扫描”和“人工阅读”之间的中间层。AI拿到脚本输出后,可以直接决定哪些命中需要人工复核,哪些是误报、哪些是真实风险。
3.4 第四步:写输出模板
先把模板定好,再让它生成报告,不然输出格式每次都不一样。我的模板是按照“报告摘要-风险清单-修复计划-复测记录”四个区块设计的。
# 安全审计报告 ## 摘要 审计对象:{repo_name} 审计时间:{date} 总体结论:{高风险数量}/{中风险数量}/{低风险数量}/{提示项数量} ## 风险清单 | 编号 | 风险等级 | 风险类型 | 文件位置 | 问题描述 | 修复建议 | |------|---------|---------|---------|---------|---------| | 001 | 高 | 硬编码密钥 | src/config.py:42 | AK暴露 | 改用环境变量 | ## 修复计划 - 依据风险等级排序 - 每个修复动作需绑定负责人建议和预计工时 ## 复测记录 - 复测方式 - 复测结果 - 是否通过模板写好后,代理在生成报告时就有了硬性框架,不会给你输出一堆“多注意安全”式的废话。
4. 安装到常见编码代理与调用技巧
skill做出来不用就是废的。我在实际使用中试过好几类代理,安装路径和加载规则略有出入,但核心逻辑一致:把skill目录放到代理约定的技能目录下,让代理在启动时扫到它,用描述文本建立触发映射。
4.1 不同代理的安装位置对比
下面这个表是我实测过的放置方式,版本不同可能路径有变化,以官方文档为准:
| 代理 | 推荐安装路径 | 说明 |
|---|---|---|
| OpenAI Codex | ~/.codex/skills/security-audit-skill | Codex CLI启动时会扫描该目录 |
| Claude Code | .claude/skills/security-audit-skill或~/.claude/skills/ | 项目级或用户级均可 |
| OpenCode | 在配置文件的skill目录配置项里指定 | 支持自定义技能根目录 |
| Spring AI | 通过配置类或JSON声明SkillDefinition | 需要按Spring AI的Skill接口封装 |
对于Claude Code,我更推荐放项目级目录。审计一个仓库,skill跟着项目走,团队成员clone下来后拉一次配置就能用,不需要每个人都在全局目录手动装一遍。
4.2 触发方式的设计
skill装好后,能不能顺利触发,取决于Agent对用户请求的意图识别。根据我的经验,触发语最好覆盖三类:直接命令型、场景描述型、疑问型。
直接命令型是“用security-audit skill审计这个仓库”或“跑一下安全审计流程”,这种命中率最高。场景描述型是“帮我看看登录接口有没有越权风险”“检查一下有没有把密钥提交到git”,这种完全靠description的模糊匹配。疑问型是“这段代码安全吗”,这个最容易失效,因为太宽泛了。所以我通常会在description里多写几个同义词和常见问法,代码代理的语义匹配并没有大家想的那么聪明,别嫌多写几个词。
还有一个技巧:如果你同时装了多个skill,而代理总是加载错,可以在请求里直接点出技能名,比如“用security-audit跑一遍”。这算是个笨办法,但却是最可靠的兜底。
4.3 和其他技能的配合
skill真正发挥威力是在组合使用的时候。我经常把security-audit-skill和code review类技能串在一起:代码评审技能先过一遍逻辑缺陷,security-audit再补一遍安全视角。两套检查互补,比单独用任何一个都稳。
另外,如果你的团队在用Spring AI这类框架,那skill的接入方式和在终端代理里的方式完全不一样。Spring AI里的skill更像是一个可编程调用的函数,需要在代码里显式定义输入输出。我倾向于把SKILL.md里的检查流程保持为纯文档,再单独写一个Java封装类,这样文档可以被任何代理复用,封装类只负责对接程序调用。
5. 实际使用中的坑与排查实录
5.1 skill加载了但没触发
这个坑我碰到过好多次,明明目录放对了,代理也承认能看到skill文件,但就是不按流程走。排查一圈,多数原因是description覆盖不了用户的实际说法。比如用户说“看下代码里有没有不安全的写法”,但description里只写了“安全审计”和“security audit”,模型匹配失败,自然绕回普通对话。
解决方案是给description加“覆盖范围说明”,把用户可能使用场景提前写进去:
description: 适合代码审查、上线前检查、依赖安全评估、密钥泄露排查、越权风险分析等场景。如果用户提到任何与安全、漏洞、风险、泄露、权限相关的问题,优先考虑使用本技能。5.2 审计结果太泛,落到不了具体位置
第一次跑完整个skill,我看到的输出是“存在SQL注入风险,建议使用参数化查询”,没有任何文件路径和调用链。这个问题在于Step 3“边界和数据流分析”执行得不够彻底,代理跳过了真实调用链追踪,直接基于经验给结论。
解决方式是在SKILL.md里强制要求:每个高危发现必须附带文件名、函数名、调用链路径和三行以上代码引用。如果做不到,就把它降级为“疑似风险”,不能写入“确定风险”列表。加了这个要求后,报告质量明显上升。
5.3 内置脚本输出格式不稳定
快速扫描脚本在不同操作系统上跑,输出有时候带编码问题,有时候路径分隔符不一致,导致代理解析失败。后来我做了三处修改:统一用pathlib处理路径、输出格式固定为TSV、在脚本头部加UTF-8声明。修改后跨平台稳定多了。
5.4 误报太多,报告可信度下降
一开始正则库写得很宽,凡是hash类型的字符串都标成疑似Token,结果一个前端项目跑出200多条“疑似密钥”。人工看完80%是混淆后的ID和样本数据。后来我加了两个过滤策略:排除明显非密钥上下文(比如测试数据、样例代码)、要求命中行同时包含密钥变量名和赋值符号。误报率降了大约70%。
给出一个比较实用的收尾经验:skill的价值不在文件多少,而在于“边界定义”是否清晰。我在反复迭代security-audit-skill的过程中,大部分时间其实不是在增加检查项,而是在压缩判断标准——什么算通过、什么算高优、什么情况下中止交付。你把这个想清楚,skill用起来就顺手了。
另外一个小建议:如果你准备把技能分享给团队,记得把规则文档里的示例代码都换成脱敏后的虚构样例,尤其是密钥正则库里不要放真实格式的密钥样例,免得被当成反向利用的字典。安全审计技能本身是为了让AI更可靠地发现问题,这个前提不能丢。