最近在折腾AI编程工具Claude Code、Codex、OpenCode 这类的同学应该都注意到一个高频词——skill。有人把它理解成“插件”有人叫它“技能包”还有人干脆把它等同于“Agent”。这些东西在社区里吵得挺热闹但真正上手写过的人其实不多。今天我就以security-audit-skill为例聊一聊我如何从零搭建一套可复用的安全审计技能包包括目录结构怎么设计、SKILL.md 怎么写、规则文件怎么拆、实际跑一遍审计流程会遇到哪些坑全程附带经验和教训。如果你正准备给自己的 AI 编程助手配置技能包或者打算把团队的安全检查经验沉淀成可复用资产这篇内容会非常对胃口。我会直接从实操出发把那些文档里不会写、只有踩过坑才知道的细节一并抖出来希望能给你一个可以直接抄作业的范本。1. 为什么安全审计需要单独做成一个 skill1.1 先从“skill 到底是什么”说起我发现很多人对 skill 的理解存在偏差。在 Claude Code、Codex、OpenCode 这类工具里skill 本质上是一套带指令、带上下文、带校验规则的提示词工程产物。它不是一个普通的 prompt而是把某一类任务的做法、边界、输出格式、禁止事项全部封装在一个目录里让模型在需要时自动加载、按规则执行。打个比方普通 prompt 就像你临时抓一个同事说“帮我看看这个项目有没有安全问题”对方可能会乱看一通给你一堆泛泛而谈的废话而 skill 相当于你给这位同事一本《安全检查SOP手册》里面写着先看什么、后看什么、什么算高危、什么可以忽略、报告怎么写他照着手册执行结果自然稳定得多。安全审计这个场景恰恰是 skill 发挥价值的好地方。因为安全检查不是“问一句答一句”的聊天它有明确的流程、明确的检查点、明确的报告格式而且不同项目的检查重点还不一样。如果你每次都临时写 prompt模型给出的结果会非常不稳定而把它封装成 skill就等于把“资深安全工程师的判断逻辑”固化下来了每次执行都能保持同一水准。1.2 安全审计场景下的三个痛点我在实际使用中发现普通对话模式做安全审计有三个非常明显的缺陷第一是漏检。模型默认的注意力是均匀分布的你让它“检查安全问题”它可能盯着某一个文件猛看其他文件草草略过。而 skill 里可以写清楚“必须覆盖所有新增文件、必须每个依赖项都过一遍”这种强制约束能大幅降低漏检率。第二是误报。没有规则的模型很容易把正常代码当成安全隐患比如把console.log当日志泄露把普通变量命名当成硬编码密钥。skill 里可以内置判断标准告诉模型“什么情况下算风险、什么情况下是误报”精确率能明显提升。第三是报告不可用。模型默认输出的安全报告多半是散文式的哪里有问题、严重程度多高、怎么修全靠读者自己提炼。skill 里可以规定输出格式比如固定输出一个表格、按严重程度排序、每条问题附文件行号和修复建议。这样的报告才能真正拿给团队用而不是躺在聊天记录里吃灰。可以说安全审计 skill 解决的核心问题就是把一次性的、质量不可控的 AI 安全审查变成流程化、标准化、可复用的工程能力。这也是我为什么专门花时间把它做成 skill而不是每次手动敲 prompt 的原因。1.3 适用人群和前置条件在往下看之前先给这篇文章定位一下适合谁正在使用 Claude Code、Codex、OpenCode 等支持 skill 机制的开发者负责团队代码质量或安全合规想用 AI 辅助做代码审查的人对 skill 机制好奇想通过一个完整案例学会自己写 skill 的人。前提条件是你对 AI 编程助手的基本操作不陌生能跑起来一个项目另外对常见的安全风险比如明文密码、注入、越权、依赖漏洞有基本概念。如果你是完全的小白建议先了解一下 skill 的基本语法再回来看这篇会更顺畅一些。2. 整体设计一套安全审计 skill 的架构拆解2.1 skill 的核心目录结构我在设计 security-audit-skill 的时候参考了社区里主流的 skill 组织方式最终确定了一个非常清晰的目录结构。一个标准的审计 skill 应该是这样的security-audit-skill/ ├── SKILL.md # 技能入口文件模型优先读取 ├── rules/ │ ├── 01-input-validation.md │ ├── 02-authentication.md │ ├── 03-dependency-scan.md │ └── 04-output-format.md ├── scripts/ │ ├── scan_deps.py │ └── collect_files.sh └── templates/ └── audit_report.md这套结构并不复杂但每个文件都有自己的使命。先说 SKILL.md它是整个 skill 的门面模型在加载技能时优先读这个文件。它要回答三个问题这个技能是干嘛的、在什么场景下激活、有哪些核心规则必须遵守。我用的是比较简洁的 YAML frontmatter 加 Markdown 正文因为目前主流工具都兼容这种格式识别率高不容易踩语法坑。rules 目录放的是审计规则每一条规则拆成一个独立文件。这样做的好处是方便单独维护和更新比如某天你想加强依赖扫描的力度只需要改03-dependency-scan.md这一个文件不需要动整个 skill。scripts 目录放辅助脚本比如我写了一个小脚本收集项目里所有需要审计的文件清单避免模型自己乱猜templates 目录放报告模板确保每次审计的输出结构一致。2.2 SKILL.md 怎么写才算合格很多人写 SKILL.md 喜欢一上来堆一大段描述其实这是低效的做法。模型读取 SKILL.md 的时候是带着“这个技能要解决什么问题”的预期来的最好的写法是先定义激活条件再给执行流程最后列硬性规则。我项目里的 SKILL.md 开头是这样写的--- name: security-audit description: 对代码仓库进行安全审计覆盖输入校验、认证授权、 依赖风险、敏感信息泄露等检查项输出结构化报告。 when_to_use: 当用户要求审查代码安全、检查漏洞、评估风险时激活。 --- # Security Audit Skill 执行审计任务时必须遵循以下流程 1. 收集项目文件清单确定审计范围 2. 按 rules 目录中的规则逐项检查 3. 对每一条发现的风险点判断严重等级 4. 输出 Markdown 格式的审计报告。这里的关键是when_to_use字段。我见过很多 skill 写得很好但模型就是不自动激活问题往往出在 activation condition 写得太模糊比如只写“检查安全”模型可能根本不知道什么时候该调用。写得具体一点比如“审查代码安全”“检查漏洞”“评估风险”都列进去命中率会高很多。还有一点要特别注意SKILL.md 不是越详细越好。模型一次能处理的上下文有限如果你把规则全部堆在入口文件里反而会稀释重点。入口文件只要写流程和边界细节规则放到 rules 目录里让模型按需加载即可。这个设计我是在实际测试了几轮之后才调整过来的最初我试图把所有规则塞进一个文件效果非常差输出经常答非所问。2.3 规则文件的设计原则基于风险优先级rules 目录里的文件不能想到什么写什么要按照风险优先级来组织。我自己的排序逻辑是这样的第一优先级高危漏洞比如命令注入、SQL 注入、硬编码密钥第二优先级中危问题比如缺失输入校验、弱认证策略、依赖过期第三优先级低危提示比如日志中包含敏感信息、注释里残留调试内容。对应的我的 rules 目录也按这个优先级排序01-input-validation.md处理输入校验02-authentication.md处理认证授权03-dependency-scan.md处理依赖风险。这样模型在检查时会有明确的优先级意识不会在低危问题上纠缠太久而漏掉真正的高危项。每个规则文件的内部结构也要标准化。我是按“风险说明、检查方法、判定标准、修复建议”四段来组织的。举个例子02-authentication.md里的“硬编码密钥”检查项我写的是## 硬编码密钥 风险说明开发人员在代码中直接写入 API Key、数据库密码、 Token 等敏感信息可能导致信息泄露。 检查方法搜索 api_key、password、secret、token 等关键字的赋值语句检查配置文件是否包含真实凭据。 判定标准 - 高危真实密钥硬编码在生产代码中 - 中危密钥硬编码在测试代码或配置文件中 - 低危使用占位符但未说明替换方式。 修复建议使用环境变量或密钥管理系统剥离敏感信息 对已泄露的密钥立即作废并轮换。这种写法最核心的好处是把判断标准显式化了。模型不再需要“猜”某个问题到底算高危还是中危而是照规则套用即可。实际执行下来误报率确实下降了很多。3. 核心细节解析与实操要点3.1 依赖扫描脚本从手动到自动安全审计里面最机械、最不适合用模型硬判断的部分是依赖扫描。让模型去猜某个第三方库有没有已知漏洞既不准确也不实时因为模型的知识截止日期摆在那里。我的做法是用脚本去跑把扫描结果直接喂给模型做汇总分析。我写了一个简单的 Python 脚本scan_deps.py负责读取项目的依赖清单文件比如package.json、requirements.txt、go.mod然后请求漏洞数据库的 API 做比对最后输出一个风险清单。这里要说明一下我用的漏洞数据源是完全公开合法的公共安全公告数据库大家自建的时候一定要选正规的数据源。import json import sys import urllib.request # 读取依赖清单文件解析出依赖名版本号列表 def parse_deps(filepath): if filepath.endswith(package.json): with open(filepath) as f: data json.load(f) deps data.get(dependencies, {}) deps.update(data.get(devDependencies, {})) return [{name: k, version: v} for k, v in deps.items()] elif filepath.endswith(requirements.txt): deps [] with open(filepath) as f: for line in f: line line.strip() if line and not line.startswith(#) and in line: name, version line.split() deps.append({name: name, version: version}) return deps else: return [] # 调用公开漏洞数据接口查询风险 def query_vulns(deps): results [] for dep in deps: try: url https://api.osv.dev/v1/query payload json.dumps({ package: {name: dep[name], ecosystem: PyPI}, version: dep[version] }).encode() req urllib.request.Request(url, datapayload, headers{Content-Type: application/json}) with urllib.request.urlopen(req, timeout10) as resp: data json.loads(resp.read().decode()) if data.get(vulns): results.append({ name: dep[name], version: dep[version], vuln_count: len(data[vulns]) }) except Exception as e: results.append({name: dep[name], version: dep[version], error: str(e)}) return results if __name__ __main__: deps parse_deps(sys.argv[1]) results query_vulns(deps) print(json.dumps(results, indent2, ensure_asciiFalse))实际使用中我不会让模型直接执行这个脚本而是先由用户在终端里跑一次把输出结果贴给模型或者通过工具调用机制把运行结果接入上下文。这样既保证了数据的新鲜度又避免了模型在“执行代码”和“分析结果”之间来回切换带来的不稳定。如果你项目里依赖特别多建议加一个缓存机制把查询结果缓存在本地文件里避免重复请求。我最初没有加缓存结果在一个大项目上跑了三分钟才跑完后续优化后基本几秒钟就能出结果。3.2 文件收集的另一层考量上面提到的collect_files.sh作用是把审计目标范围内的文件都找出来。这里有个小细节很多人会忽略默认情况下AI 编程助手可能只会关注当前打开的文件或者最近修改的文件但如果我们要做一次全量安全审计范围必须是整个仓库不能只盯着某几个文件。我的脚本会排除掉node_modules、vendor、dist、.git这类无关目录把源码文件、配置文件、Dockerfile、CI 脚本全部都收集进来。一下是这个脚本的核心部分#!/bin/bash # 收集仓库中需要审计的文件排除无关目录 find . -type f \ -not -path ./node_modules/* \ -not -path ./vendor/* \ -not -path ./dist/* \ -not -path ./.git/* \ \( -name *.js -o -name *.ts -o -name *.py \ -o -name *.go -o -name *.java -o -name *.rb \ -o -name *.yaml -o -name *.yml -o -name *.json \ -o -name Dockerfile -o -name *.sh \) \ | sort这个脚本本质上是在给模型划定“审计边界”。模型一旦知道边界在哪里就不会去做无谓的扩散也不会漏掉应该检查的文件。我在跑真实项目时发现加上这层边界之后审计的完整度有了质的提升之前那种“模型只看了一两个文件就交差”的情况基本绝迹了。3.3 输出模板让报告变成可用资产第三个关键模块是报告模板templates/audit_report.md。我见过很多 AI 生成的安全报告最大的问题是“散了”要么是一大段散文要么是零散的结论根本没法直接拿去跟团队沟通。我在设计模板时强制要求输出以下结构# 安全审计报告 - 审计时间{date} - 审计范围{scope} - 审计引擎security-audit-skill v1.0 ## 风险概览 | 严重等级 | 数量 | 说明 | |---------|------|------| | 高危 | {n} | 建议立即修复 | | 中危 | {n} | 建议近期修复 | | 低危 | {n} | 建议排期处理 | ## 高危问题详情 ### 问题 1{标题} - 文件{file}:{line} - 风险描述{description} - 修复建议{suggestion} ## 中危问题详情 ... ## 低危问题详情 ... ## 审计建议 {summary}模板看着简单实际上起到了两个作用。一是约束输出格式模型按模板填空报告结构就不会乱二是引导思考顺序模板里的“风险概览、高危、中危、低危”天然把模型的注意力按严重等级分配不会出现低危问题写一大堆、高危问题一笔带过的情况。我在调试这个模板时反复调整过几次。最早我用的模板只有“问题列表”和“建议”两个部分结果模型经常把低危问题混进高危列表报告没法直接用后来加上了“严重等级”维度并且明确要求每个问题都必须给出文件行号和修复建议质量才稳定下来。4. 实操过程与核心环节实现4.1 安装与加载让 skill 真正跑起来文件都写好了接下来的问题是怎么让 AI 编程助手加载这个 skill。不同工具的加载方式略有差别但大体思路一致把 skill 目录放到指定位置然后在对话中触发。以我常用的工具为例我习惯把 skill 放在项目根目录的.claude/skills/security-audit-skill/下或者放在用户级目录~/.claude/skills/下。前者是项目级生效适合这个项目经常要做安全检查后者是全局生效任何项目都能用。两种方式各有优劣我个人建议团队项目用项目级目录个人工具链用全局目录这样既灵活又不至于互相污染。放好之后我会先做一个加载测试。最简单的验证方法是在对话里问一句“你现在有哪些可用技能”或者直接说“请加载安全审计技能”看看模型能不能正确识别。如果加载失败最常见的原因是 SKILL.md 的格式有问题比如 YAML frontmatter 写错了或者目录位置不对。这种基础问题我先自查再去看模型日志基本都能快速定位。4.2 完整执行一次审计实跑记录为了让大家有更直观的感知我拿一个模拟的 Node.js 项目做了完整审计演示。这个项目不大包含了一个登录接口、一个文件上传接口和几个配置文件我故意在代码里埋了几个典型问题。第一步我执行文件收集脚本把审计边界列出来$ bash scripts/collect_files.sh src/app.js src/routes/auth.js src/routes/upload.js src/config/db.js package.json Dockerfile第二步跑依赖扫描把依赖风险查出来$ python scripts/scan_deps.py package.json [ { name: express, version: 4.17.1, vuln_count: 2 }, { name: lodash, version: 4.17.20, vuln_count: 3 } ]第三步把这两部分结果和项目代码一起提供给 AI 助手让它按照 skill 的规则执行审计。整个执行过程大概持续了几分钟期间模型会逐条检查规则文件里的检查项。最终输出的报告结构清晰既有风险概览也有具体问题定位。这整个过程实际上就是一个“人机协作”的流程脚本负责机械的数据收集和比对模型负责语义层面的逻辑判断而规则文件负责让模型的判断保持稳定和可控。三者缺一不可。4.3 参数与规则调优一次真实测试上面我们说过审计结果的稳定性来自规则文件的约束力。但规则文件不是一次写好的需要根据实际测试反馈不断调优。我这里分享一个具体的规则调优案例希望能帮你找到调优的感觉。最初我在01-input-validation.md里对 SQL 注入的检查规则写得太宽泛导致模型把很多正常的字符串拼接都当成了风险。比如代码里const query SELECT * FROM users WHERE name name 这确实是风险但const filePath baseDir / fileName也被模型拉出来说成路径注入就有点过度了。我调整的思路是在“判定标准”一节增加排除条件明确说明“字符串拼接用于文件路径、日志等非 SQL 上下文时不作为 SQL 注入上报但应提示路径拼接风险”。同时我加了一条“输入验证完整性检查”的规则要求模型检查每个外部输入是否经过校验如果没校验就使用才上报中危。实际测试下来调整后的误报率至少降了一半审计报告的可用性明显提升。这种调优周期我通常会在一个新 skill 上线后的前两周集中做后面基本稳定了就不太动了。4.4 在复杂项目中的一次真实审计体验除了模拟项目我还拿一个真实的业务代码仓库跑过一次。这个仓库大概有 300 多个文件涉及前后端代码、若干配置文件、自动化部署脚本还有几个历史遗留的 PHP 文件。在完全靠人肉眼审计的情况下至少得花上大半天而配合 AI skill 之后我先把文件清单和依赖扫描结果收齐再把仓库目录结构喂给模型让它基于 skill 规则做逐项检查整个流程压缩到了 20 多分钟。那一次审计最大的收获不是数量而是发现的盲区。有个配置文件的权限写的是777这种问题靠人工审查很容易漏掉因为人看一眼就过去了但模型严格按照“敏感配置检查”规则扫描时会把它单拎出来报警。还有一个废弃接口没有做鉴权模型在“越权访问检查”这一步发现了它。这些发现可能单看都不算特别严重但累积起来就是真实的安全短板。当然我也必须诚实地说AI 审计的结果不能直接“无脑信任”。模型对业务逻辑的理解还是会有局限有些看起来像是漏洞的问题实际上可能因为后续的过滤函数而变得无害。所以我的习惯是把 AI 报告当作“第一轮筛选”高危问题必须由人复核后在代码里确认一遍绝不省略人工复核环节。这一点后面讲安全边界的时候还会再展开。5. 常见问题与排查技巧实录实际搭建和使用 security-audit-skill 的过程中我踩过不少坑也积累了一些排查技巧。这里挑几个高频问题分享出来基本覆盖了新手最容易卡壳的地方。5.1 SKILL.md 加载失败或识别不到这个问题是我被问得最多的也是我自己最开始就遇到过的。症状是把 skill 目录放好之后对话里怎么触发都没反应模型好像完全不知道有这个技能存在。排查步骤通常是这样的第一步检查目录位置。不同工具要求的 skill 目录不一样放错位置等于没放。最好先去官方文档确认一下路径规范。第二步检查 SKILL.md 的 frontmatter 格式。name和description字段必须准确description 要写清楚什么时候用不然模型可能判定“当前不相关”不触发。第三步检查文件编码。我遇到过 SKILL.md 是 GBK 编码的情况模型读取直接乱码。统一转成 UTF-8 就好。第四步如果工具支持开启调试日志模式看看模型是否成功加载了 skill 文件。如果日志里有加载失败的报错按报错信息处理即可。5.2 审计结果不稳定同一份代码两次结果不同这是 LLM 应用的典型问题非确定性输出。同一个项目、同一个 skill连续跑两次报告可能有一两处差异。针对这个问题我的做法有三点。第一在 SKILL.md 里明确规则文件的读取顺序让模型按固定顺序逐项检查减少跳转带来的随机性第二在报告模板里要求“每条发现都必须附证据”比如代码片段、文件行号这能迫使模型基于证据而不是印象做判断第三把温度参数调低但这个问题可能与模型本身的推理策略有关如果工具支持参数配置就调到更“保守”的模式。不过说实话完全消除随机性是不可能的。只要大方向一致、高危问题的覆盖率稳定其实就不影响实际使用。5.3 弱项业务逻辑漏洞识别不准最让我头疼的是业务逻辑层面的安全问题。比如“用户 A 能否越权修改用户 B 的数据”这种问题模型如果不理解整个业务流程很难准确判断。代码文件、接口文档、数据库模型这些信息模型都有但它未必能把这些拼成完整的业务图景。我的应对思路是在 SKILL.md 里增加一个“数据流分析”的提示要求模型先梳理“用户输入 → 处理逻辑 → 数据访问 → 响应输出”的完整链路再逐环节检查是否存在越权、缺失校验等问题。这种方式对部分场景有效但无法做到完美。说到底纯静态的代码审计本身就很难覆盖复杂的业务权限问题这需要动态测试和人工分析配合。另外我在规则文件里也强调了一个原则提请人工复核。对高危问题模型应该在报告中标注“建议人工确认”而不是给出一个斩钉截铁的结论。把 AI 定位成“辅助筛子”而不是“最终裁决者”这是我一直坚持的边界。5.4 依赖漏洞数据库的接口限制与乱用风险前面提到依赖扫描脚本调用了公开的漏洞数据接口但这个接口有速率限制单位时间内请求次数过多会返回 429。在大项目上跑全量依赖扫描的时候很容易触发这个限制。我的解决办法是加本地缓存和分批查询。这里再次提醒如果你要自己搭建一个全量的依赖漏洞扫描能力务必要选择正规的公共服务平台或合规的商业数据源同时注意处理数据更新的延迟问题。绝不要用来源不明、来路可疑的索引服务。在团队内部使用这套 skill 时还有一条规范化要求每次扫描结果要留痕方便事后追踪和历史对比。我在脚本里加了输出到文件的功能扫描结果自动存入audit-reports/目录文件名带上时间戳。这样不仅方便复盘也能在审计时有据可查。6. 从安全审计 skill 延伸skill 与 agent 的边界最后聊一个概念问题也是社区里讨论热度最高的skill 和 agent 有什么区别。围绕 security-audit-skill 的实践我把它说透。skill 本质上是“一组规则 方法 模板”的集合它增强的是模型在某个具体任务上的表现。它没有自主行动能力更像是一本操作手册而 agent 是一个自主决策和执行的主体它能感知环境、规划步骤、调用工具、根据结果调整策略。放到安全审计这个场景里skill 提供的是“怎么审计”的方法论而 agent 则是“自己去发现目标、自己决定查哪里、自己调用扫描工具、自己评估风险”的完整执行者。做一个粗浅但形象的类比skill 是一本菜谱告诉你做宫保鸡丁需要哪些食材、按什么顺序下锅、火候怎么控制而 agent 是一个会自己决定做什么菜、自己去买菜、做完了还会自己尝一口调整味道的厨师。菜谱再好也要有个执行者来动手但没有菜谱执行者做出来的菜可能完全看心情。从实践角度讲这两个概念并不冲突。我的 security-audit-skill 既可以作为独立的技能被模型加载配合用户交互执行也可以嵌入到一个更复杂的 agent 工作流里作为“安全审计”这一环节的执行工具。如果你在搭 agent无论你想让 agent 干什么——总结文档、处理日常问答、做代码分析——你都可以考虑把这项工作沉淀成一个高质量的 skill再交给 agent 调度。这就是我常说的“先写 skill再搭 agent”的工作思路。一句话总结skill 是能力的封装agent 是行为的封装。两者互相配合但不该混为一谈。理解这个区别对设计自己的工作流非常有帮助。另一个经常被问到的点是“好用的 skill 去哪里找、怎么下载运用”。我的经验是与其到处找现成的不如自己动手写一个。因为 skill 的核心价值在于把你自己或团队的判断逻辑沉淀下来这个是无法从外部下载的。通用规则可以参考别人的但针对你自己项目特性的检查项只能自己加。就拿安全审计来说不同团队的技术栈、合规要求、风险偏好都不一样你需要的 skill 一定是高度个性化的。在实践这几周之后我个人的体会是不要把 skill 想得太神秘也不要把它想得太复杂。它本质上就是一个结构良好的提示词工程产物。真正让它发挥价值的不是文件格式多花哨而是你对自己的业务流程有没有足够深的理解能不能把那些“老手的本能判断”显式地变成规则。安全审计 skill 之所以值得做恰恰因为它把“知道哪里容易出问题”这个经验从人的脑子里搬到了模型的执行逻辑里。最后再分享一个小技巧写完 skill 之后一定要准备一个“测试夹具”也就是一个故意埋了各种问题的模拟项目。每次改动规则之后都拿它跑一遍对比前后输出。这样你才能快速判断改动是变好了还是变差了而不是靠感觉。我在维护 security-audit-skill 时就专门建了一个test-fixtures/目录里面放着各种类型的问题样例这套东西比任何文档都管用。
