我一直觉得把安全审计的流程塞进 AI Agent 里最大的问题不是模型不够聪明而是它不知道该怎么“系统地怀疑”。很多人以为让大模型读一遍代码它就能像安全专家一样把所有漏洞找出来但实际用下来就会发现模型很擅长回答“这段代码有没有问题”却完全不擅长回答“这个仓库里哪些地方值得优先怀疑”。直到我上手做了 security-audit-skill 这个项目才真正想明白 Skill 机制该怎么用——它不是一个提示词也不是一个插件而是一整套能把安全审计方法论固化下来的运行单元。这篇文章就把我设计、开发、实测这个 skill 的完整过程写出来包括为什么会有这个项目、核心结构怎么拆、SKILL.md 怎么写、接入 Codex/Claude/OpenCode 时有哪些坑以及拿真实项目跑一遍后的效果。如果你正在用 AI 编程助手做代码审查、安全巡检或者想给自己的 Agent 配一个能反复复用的安全审计能力这篇文章适合你。我会尽量把思路和代码都摊开讲不藏私。1. 为什么安全审计需要专门的 Skill先说一个反直觉的结论让大模型直接“看代码找漏洞”和“用 Skill 做安全审计”两者的差距不在模型的智力而在执行框架。1.1 大模型做安全审计的天然短板最开始我尝试用普通对话方式让 Claude 或 Codex 审计一个项目做法是直接把几个关键文件贴进去然后问“有没有安全漏洞”。结果分几种情况小文件还好能给出点像样的反馈文件一多模型就开始“丢三落四”它会盯着前面几个函数使劲分析后面的代码基本被无视了。更麻烦的是模型特别容易“顺杆爬”——你给它一个文件它就只在这个文件里找问题不会主动去看这个文件被谁调用、传进来的参数是否可控。安全审计里最重要的数据流分析在普通对话里基本做不了。还有个问题是“表现不一致”。同一个函数你今天让它审计它报了个 SQL 注入明天换个姿势再问它又说没问题。因为你没有给它一套固定的检查框架它每次都在自由发挥。安全审计是需要稳定方法论的先看入口再追数据流然后检查过滤和编码最后验证能不能利用。这套流程如果只靠对话里的 prompt 去约束效果完全随缘。1.2 Skill 与 Prompt 的本质区别后来 Anthropic 推了 Agent SkillsOpenAI 的 Codex 也支持自定义 skill我才意识到问题的解法不在 prompt 技巧而在“把技能封装成模块”。Skill 和 Prompt 的核心区别在于Prompt 是一次性的指令Skill 是可加载的指令知识工具的工作包。一个 Skill 可以有独立的目录里面有主指令文件、参考文档、脚本和规则库。Agent 只有在遇到匹配任务时才会把这个 Skill 加载进来平时不占用上下文。这样你可以在 SKILL.md 里写满安全审计的方法论、检查项、输出格式而不用在每次对话里重复粘贴一堆规则。用生活化的类比Prompt 就像你临时跟一个实习生说“帮我把这个项目安全问题查一下”他能做但做得好不好完全看他当天心情和悟性。Skill 则是你给这个实习生一本《安全审计 SOP 手册》手册里写清楚了第一步干什么、第二步干什么、遇到什么情况用什么工具、报告怎么输出。哪怕实习生经验一般只要他照着手册执行结果至少是稳定、可复用的。1.3 security-audit-skill 到底解决什么问题security-audit-skill 这个项目本质上就是把“安全审计 SOP”打包成一个 Agent 可以直接执行的能力。它的目标很具体对指定代码目录做自动化安全审计覆盖注入、认证、敏感数据泄露、不安全配置等常见类别。输出结构化报告包含漏洞位置、风险等级、问题类型、修复建议而不是一堆零散的聊天结论。让 Agent 在审计时能主动调用静态扫描工具而不是只靠“眼力”读代码。把安全审计专家的经验沉淀成规则让不懂安全的开发者也能借助 Agent 做初步自查。如果你每天都在用 AI 编程助手但还在靠复制粘贴代码片段来查漏洞那这个 skill 就是为你准备的。它适合个人开发者做上线前自查也适合小团队在没有专职安全工程师的情况下做基础的代码安全把关。2. security-audit-skill 的设计骨架从目标拆解到规则落地一个 Skill 好不好用不在代码多不多而在设计骨架是否清晰。我在设计 security-audit-skill 的时候最先做的不是写代码而是把“安全审计”这件事拆成了三层审计什么、怎么审计、怎么输出。2.1 审计目标分类不能眉毛胡子一把抓安全审计对象很杂如果没有分类Agent 就会东一榔头西一棒子。我按照漏洞利用路径和常见风险把审计项分成六类分别制定不同的检查策略。类别典型问题适合的方式注入类SQL 注入、命令注入、模板注入静态规则数据流分析认证与会话弱口令、硬编码 token、JWT 校验缺陷代码模式识别需结合业务上下文敏感信息泄露硬编码密钥、日志打印密码、前端暴露密钥正则规则库路径与访问控制路径穿越、越权访问、缺少鉴权需模型推理业务逻辑不安全反序列化pickle.load、yaml.load 等危险函数代码模式匹配依赖与配置漏洞第三方库已知 CVE、错误配置 CORS/HTTPS工具扫描数据库比对这六类不是平均用力。像硬编码密钥、不安全函数调用这类用正则和静态规则就能快速扫出候选点而越权、认证绕过这种业务逻辑漏洞必须靠模型理解上下文才能判断。所以 skill 的工作流必须分成两层先用工具做粗筛再用模型做精判。2.2 把 CWE/OWASP 检项转成可执行指令光有分类还不够需要把分类映射到具体的检查指令。我在 references 目录里放了一份自己整理的《安全审计检查清单》把 OWASP Top 10 和 CWE Top 25 里的高频问题转成了模型能看懂的指令。比如对 SQL 注入的描述不是简单写“检查 SQL 注入”而是找到所有执行 SQL 的位置包括execute、executemany、raw、query等方法调用。检查这些 SQL 语句是否使用了参数化查询。如果使用了字符串拼接追踪拼接内容是否来自用户输入。如果来自用户输入继续追踪是否经过过滤或编码。只有真实存在可控传递链时才报告为漏洞。这样模型就不用靠“直觉”判断漏洞而是按证据链走。这也是 Skill 和普通 Prompt 的关键差异Prompt 只给目标和角色Skill 会给流程和判定标准。2.3 上下文窗口有限如何分批审计有一个非常实际的限制Agent 的上下文窗口不是无限的。一个大仓库可能有几百个文件如果让 Agent 一次性把整个项目的代码都读一遍它会立刻超出上下文限制或者把前面的代码“忘掉”。我的方案是让 skill 自带“分批审计”的策略。不是按文件类型分而是按审计目标分先对仓库文件做一次名称和依赖关系扫描生成候选文件列表。把候选文件按功能模块分组比如 controllers、services、models。每组文件作为一个批次单独执行审计流程。最后把所有批次的报告汇总成一份总报告。这样做的好处是上下文可控而且每个批次 focus 清晰。我在 SKILL.md 里明确写了这个批次策略并告诉 Agent如果单批文件太多就再按函数粒度拆不要在一个批次里分析超过 500 行核心逻辑。2.4 输出格式定义让结果可直接落地审计报告如果没有固定格式Agent 会给你一个散文式的总结看起来很全面但没法自动化处理。我在 skill 里定义了标准输出格式强制 Agent 按字段填写。每一条漏洞必须有风险等级Critical / High / Medium / Low漏洞位置文件路径行号函数名漏洞类型对应 CWE 编号或 OWASP 分类问题描述清晰说明为什么是漏洞数据流说明从输入源到危险函数的传递路径修复建议具体到代码层面的修改方式这个输出格式不仅方便人读也方便后续接入 CI/CD 自动生成工单。我在后面的实测案例里会展示真实输出长什么样。3. 手把手写出第一个可用的 security-audit-skill现在到了动手环节。我默认你已经用过至少一种支持 Skill 机制的 AI 工具比如 Claude 的 Agent Skills、Codex 的 custom skills或者 OpenCode 的 skill 脚本。如果还没用过也不影响下面我会把目录结构和关键文件都写清楚你照着做就能用。3.1 Skill 文件结构与元信息定义一个标准的安全审计 skill 目录可以长这样security-audit-skill/ ├── SKILL.md ├── scripts/ │ ├── scan_python.py │ ├── scan_secrets.py │ └── semgrep_rules/ │ ├── sqli.yml │ ├── command-injection.yml │ └── insecure-deserialization.yml └── references/ ├── audit-checklist.md ├── cwe-mapping.md └── owasp-api-top10.md其中SKILL.md是核心入口Agent 会优先读取它。文件开头必须有 YAML front matter至少要包含 name 和 description。--- name: security-audit description: 对指定代码目录进行安全审计覆盖注入、认证、敏感信息泄露、不安全反序列化、依赖漏洞等常见风险输出结构化漏洞报告。 ---这里面最关键的是 description 的写法。Agent 靠 description 来判断何时加载这个 skill所以描述里要写清楚“能对什么输入做什么事”而不是堆关键词。我一开始写了“专业的代码安全审计助手”结果 Agent 匹配精度很差经常该用的时候不用改成分号加具体词后效果好很多。3.2 主提示词中的角色与约束SKILL.md 的正文部分不能只写“你是一个安全审计专家”那太虚了。我把主指令拆成了角色、工作流程、判定原则、输出格式、硬性约束五个模块。角色部分我写的是你是一名信息安全审计工程师专门负责代码安全审查。你的目标是帮助开发者识别代码中可能导致数据泄露、系统被入侵和业务被绕过的安全缺陷。你在给出漏洞结论前必须基于代码证据链不允许凭直觉报漏洞。约束部分是最容易踩坑的地方。如果约束不写清楚模型会过度“脑补”。举个例子我要求它当某个变量实际上是硬编码的常量而不是用户输入时即便拼接了 SQL 语句也不应该报 SQL 注入。这个判定需要模型有一个“先追踪来源再判断”的习惯而普通 prompt 很容易忽略。我这里直接放出 SKILL.md 的核心流程部分你可以参考## 工作流程 1. 收集上下文先查看项目目录结构、使用的语言和框架、依赖管理文件。 2. 加载规则根据项目语言加载 references/audit-checklist.md 中的检项。 3. 静态扫描运行 scripts/scan_secrets.py 和 scripts/scan_python.py获取初步可疑点。 4. 深度验证对每个可疑点做数据流追踪确认是否存在输入源到危险函数的完整路径。 5. 给出报告按统一格式输出漏洞列表每个漏洞必须包含证据链和修复建议。给 Agent 明确的工作流程比给它一堆角色定义有用得多。它会按步骤执行而不是直接跳到结论。3.3 工具调用与命令执行的设计Skill 不只是提示词它还可以带脚本。我给这个 skill 配了三个基础脚本scan_secrets.py用正则和熵检测扫描硬编码密钥比如 AWS Access Key、GitHub Token、私钥等。scan_python.py针对 Python 项目封装 semgrep 命令运行本地规则集并解析 JSON 结果。scan_deps.py读取 requirements.txt / package-lock.json通过 OSV API 查询已知漏洞。脚本不必完美但要给 Agent 一个清晰的入口。我在脚本开头用 argparse 接收目标路径参数输出统一为 JSON这样 Agent 可以直接 parse。不要让 Agent 自己去读 scan 的原始输出格式那不是它的强项。另外需要注意Agent 执行脚本是需要权限的我在 SKILL.md 里加了一条如果脚本执行失败或环境不允许执行 shell请跳过工具扫描转为纯静态分析并在报告中注明“本结果未经过工具扫描验证”。这个回退策略很重要。因为不同用户的运行环境不一样有的 Agent 沙箱封闭根本跑不了 semgrep。如果不做回退设计skill 会在环境受限时直接罢工。3.4 将 skill 接入 Codex/Claude/OpenCode 的不同姿势目前几个主流工具对 skill 的接入方式大同小异核心都是“把目录放到 Agent 能发现的位置”。我在 Codex 里的做法是把整个 security-audit-skill 目录复制到用户的 skills 目录下然后在对话里直接触发。Codex 会通过 description 自动匹配 skill也可以显式调用。如果你用的是 Claude 的 Agent Skills同样是把目录放到指定 skills 路径并在项目里用security-audit这样的语法引用。OpenCode 稍微特殊一点它更倾向于把 skill 当成可执行脚本或 markdown 插件来用。我的做法是把 SKILL.md 作为规则文件引入同时把 scripts/ 挂到工具路径里。这样的优点是即便 model 没有很强的 tool calling 能力也可以通过“读取 markdown 规则调脚本”的方式完成审计。如果你不太确定自己的工具怎么加载最简单的方法是查看工具官方文档中 “skills” 或 “custom instructions” 部分通常就是把目录路径配置好就完事。4. 实测案例用它审计一个 Python 项目的全过程光说不练假把式。我为了验证这个 skill特意构造了一个带漏洞的 Flask 项目项目大概有 10 个文件包含数据库查询、用户登录、文件上传、API 路由等常见业务代码。然后把 security-audit-skill 挂上让它跑完整审计流程。4.1 准备一个带漏洞的 Demo 项目Demo 项目结构flask-demo/ ├── app.py ├── models.py ├── routes/ │ ├── auth.py │ ├── files.py │ └── search.py ├── templates/ └── requirements.txt我预埋的漏洞包括routes/search.py中用户输入直接拼接进 SQL 查询没有参数化。routes/auth.py中 JWT 的 secret 是硬编码在代码里的。routes/files.py中下载文件路径没有做规范化处理存在路径穿越。app.py中配置了 CORS 允许所有来源。requirements.txt里有一个存在已知漏洞的旧版本库。这些漏洞都算常见且容易复现适合拿来测试 skill 的效果。4.2 实际运行与结果分析执行完 skill 后我拿到的报告摘要如下脱敏简化文件行号类型风险等级修复建议routes/search.py23SQL 注入 (CWE-89)Critical使用参数化查询routes/auth.py5Hardcoded Credentials (CWE-798)High从环境变量读取 secretroutes/files.py41Path Traversal (CWE-22)High使用 secure_filename 并校验绝对路径app.py15Security Misconfiguration (CWE-16)Medium限制 CORS 来源requirements.txt-Vulnerable Dependency (CWE-1104)Medium升级到安全版本整体来说这个输出比我预期要好。虽然每个漏洞都是我预埋的但 skill 不只是简单地把问题列出来还给每一条都写了数据流说明。比如 SQL 注入那条它写了“参数 keyword 从 request.args.get 获取未经过滤直接拼接到 cursor.execute 的字符串中”这种证据链正是我想要的效果。更让我满意的是它没有大范围误报。我故意在这个 demo 里放了一个使用参数化查询的 SQL 语句作为干扰项它没有报错。这说明 work flow 中“先追踪来源再判断”的约束起到作用了。4.3 误报和漏报的取舍当然实测也不是完美的。我观察到两个明显问题第一对于逻辑漏洞比如越权访问它基本无能为力。因为越权需要理解整个业务规则比如“普通用户能不能访问管理员的接口”模型只看单个函数是判断不出来的。这不算 skill 的缺陷但说明它定位是“静态审计助手”不能替代人工渗透测试。第二误报集中在缺少上下文的情况下。比如某个文件里的用户输入实际上在入口处已经做了白名单校验但 skill 如果没有先去读那个入口文件就会误报一个注入风险。后来我在工作流程里加了一步“如果数据流跨越文件必须查找相关入口和过滤器再下结论”误报率明显下降。还有一个细节是扫描脚本输出的是 JSON里面可能有很多低质量告警。如果直接喂给模型它会被大量结果淹没。我后来做了一个简单的取舍策略脚本只输出 High 以上风险并且每组相似告警只保留一条代表模型再把它们展开分析。这个策略大大减少了上下文浪费。5. 踩坑总结与进阶优化每个项目做完最值钱的不是代码而是坑。security-audit-skill 虽然不复杂但我在这上面踩过的坑绝对值得写出来以免你重复走弯路。5.1 最常见的三个坑上下文污染、规则冲突、执行权限上下文污染是第一个坑。最开始我把 SKILL.md 写得特别全把安全审计的所有知识都塞进去结果 Agent 加载 skill 后真正的项目上下文反而被挤占了它开始跟你聊审计方法论而不是专心看代码。解决方法是把详细规则放到 references 目录里SKILL.md 只保留精简版流程和核心约束。Agent 需要的时候会去 references 里查细节不会一直占着上下文。第二个坑是规则冲突。静态扫描工具和模型判断之间经常出现矛盾。比如 semgrep 报了一个 SQL 注入但模型追踪后发现这个查询执行的是硬编码 SQL不存在注入风险。如果 skill 没有定义“以模型追踪结果为准”的优先级Agent 就会在报告里自相矛盾。我在 SKILL.md 里加了一条硬性规定如果工具扫描结果与模型追踪分析结果冲突以模型追踪结果为准但必须在报告中保留工具告警作为参考。这样既保留了工具敏感性又保证了结论一致性。第三个坑是执行权限。在很多受限环境里Agent 根本没有权限运行 shell 脚本或者 semgrep 并没有安装。如果 skill 强制依赖工具它就会在第一步卡死。所以必须设计好 fallback没有工具就纯靠模型静态分析但报告里要诚实标注“未经工具验证”。不要小看这个标注它决定了整份报告的可信度。5.2 如何跟踪最新的漏洞情报避免 skill 过期安全领域变化很快今天写的规则明天可能就过时了。我做了两件事来对抗过期问题。一是把依赖漏洞扫描做成“动态查询”而不是静态规则。我用 scripts/scan_deps.py 读取项目的依赖锁定文件然后调用 OSV API 查每个依赖的已知漏洞。这样只要 OSV 的数据库更新了skill 的扫描结果就是新的不需要我手动维护 CVE 列表。二是每隔一段时间就更新 references/cwe-mapping.md 和 semgrep rules。我在项目仓库里加了 GitHub Dependabot 和自定义的 workflow每周自动拉取最新的 CWE 案例和 semgrep 规则库。模型不一定能用上所有新规则但至少规则集不会落后太多。如果你也想长期维护一个 audit skill建议预留一个“规则更新”的机制别把规则写死。5.3 与 CI/CD 集成的思路skill 的价值还可以进一步放大——接入 CI/CD让每次 PR 提交都能自动跑一轮安全审计。我的做法是写了一个 GitHub Actions workflow当 PR 触发时对代码仓库运行一个叫audit.sh的脚本。这个脚本会调用 codex CLI并指定加载 security-audit-skill让它审计本次 PR 变更的文件。#!/bin/bash # audit.sh - 在 CI 环境中运行安全审计 skill # 需要设置 OPENAI_API_KEY 或 ANTHROPIC_API_KEY TARGET_DIR${1:-.} OUTPUT_FILE${2:-security-report.json} # 调用 codex 执行带 skill 的审计任务 codex exec --skill security-audit \ --input 请审计 $TARGET_DIR 目录中本次变更的代码按标准格式输出 JSON 报告 \ --output $OUTPUT_FILE然后在 PR 评论区自动张贴报告摘要。实测下来这种方式能拦截掉绝大多数低级的注入和密钥硬编码问题让人工 reviewer 把时间放在真正的业务逻辑上。我在实际使用中还有一个体会security-audit-skill 不是一个“一键清空所有漏洞”的神器它更像一个非常认真的助手能把重复性的、模式固定的安全检查和初筛结果做得又快又稳。真正复杂的安全问题仍然需要人来判断但有了这个 skill你不必再把 SqlMap、semgrep、依赖库检测这些琐事一件件手动完成。最后再分享一个小技巧在 SKILL.md 里明确设置“退出条件”——如果完成了所有计划中的文件批次必须输出最终报告并停止不要自主扩大审计范围。这能防止 Agent 在报告生成后继续“自我怀疑”产出又长又没用的追加分析。
