最近几个月我几乎每天都在用 Codex、OpenCode 这类 AI Agent 写代码。代码量上去了心里却越来越没底——AI 生成的代码表面干净内里难保没有硬编码密钥、依赖漏洞或者危险函数调用。以前我会在对话框里补一句“帮我看下有没有安全问题”但结果基本是空泛的“建议使用参数化查询”这类正确废话根本没有真正跑过扫描器也没有定位到具体行号。后来我干脆把整套安全审计流程做成了一个 security-audit-skill一个可复用的 Agent 技能包。装上之后只要说一句“审计当前仓库”Agent 就会自动调用 Semgrep、Gitleaks、Bandit 这些工具再基于扫描结果输出一份带风险等级、文件行号和修复建议的报告。这篇文章就记录这个项目的完整落地过程从设计思路到 SKILL.md 怎么写再到实际装进 Codex 的踩坑记录希望对同样在用 AI 写代码、又不想把安全隐患带进生产环境的读者有点帮助。1. 为什么要给AI Agent做一套安全审计Skill1.1 AI写代码的速度与安全风险完全不对等先说一个很直观的现象一个熟练的开发手写代码一天撑死写几百行想清楚每个分支并不难AI Agent 不同它十分钟能生成几千行数据库查询、鉴权逻辑、第三方 API 调用混在一起。开发者代码审查的速度完全跟不上生成速度几处隐藏问题很容易就被夹带过去了。更麻烦的是你让 AI 自查它会有一种“礼貌性自信”哪怕没有证据也会告诉你“这里风险较低”。如果不限定流程它根本不会主动去跑任何工具。安全审计这种工作靠的是证据和上下文不是靠感觉。我需要一个东西能强制 AI 先跑扫描器、再读结果、最后写结论而不是直接凭训练数据里的经验来“即兴点评”。这就是我决定做 security-audit-skill 的直接原因。它本质上是一个把“人类安全审计员的固定动作”翻译成 Agent 可执行流程的技能包让 AI 在合代码之前先做一轮客观的静态检查。1.2 Skill不是Prompt它是可执行的技能包社区里最近在热议 skill很多人问 skill 和 agent 的区别。我理解很简单Agent 是执行者Skill 是它学会的技能包。你写一句“帮我看下安全”Agent 只能自由发挥你给它一个 skill它就有了明确的执行流程、工具调用顺序、输出格式要求。skill 通常由指令文件、脚本和知识文档组成比一个 prompt 要重得多。Codex、OpenCode 这类工具都开始支持 skill 机制本质上就是把“专家经验”固化下来。security-audit-skill 做的事情就是把我平时做代码审计时的那套 checklist翻译成 Agent 能执行的流程和脚本。普通的 prompt 是一次性的这次写了下次还得重写skill 是装在 Agent 技能库里的每次需要时自动加载。而且 skill 里可以带脚本这意味着 Agent 不只是“聊安全”而是真的能调用 Semgrep、Gitleaks 这些本地工具去扫描这是单纯靠对话很难实现的。1.3 这个项目到底解决了什么问题做之前我列了几个痛点项目的目标就是逐个解决它们。第一是重复劳动。每次审计都要重新写提示词不同项目的语气还不一样效果全看运气。把 skill 装好之后一句话就能触发完整流程。第二是工具执行缺失。Agent 在对话里很擅长“想象”扫描结果但不会真的去跑 scanner。skill 里的 SKILL.md 明确要求它先执行 audit.py这就把“想象”变成了“执行”。第三是输出不统一。多个工具的输出格式五花八门Agent 看多了容易晕所以我用脚本把结果统一成 JSON再由 Agent 转成 Markdown 报告。第四是安全规则分散。Semgrep、Gitleaks、Bandit、Trivy 各有各的用途人工一个个跑很麻烦。skill 把它们包成一个入口根据项目类型自动启用合适的工具。最终效果是无论哪个项目只要 Agent 加载了这个 skill审计路径是固定的结果格式是统一的。2. Security Audit Skill的整体设计思路2.1 先划定边界静态审计为主动态渗透不做我设计这个 skill 时首先划了边界。它做四类检查密钥与敏感信息泄露Secret Detection、依赖与第三方组件漏洞SCA、危险代码模式SAST、以及云基础设施和 CI 配置问题IaC。不做什么也很重要不做动态渗透测试不尝试登录生产环境不执行需要业务凭据的验证。原因很简单skill 跑在 Agent 的本地环境里动态测试需要大量上下文和时间而且容易产生副作用。静态审计可以离线完成最适合做成可重复执行的技能包。以我实际项目为例绝大多数在合代码前能拦下来的问题其实都是静态问题一个 API key 忘在配置文件里、一处 SQL 拼接没有参数化、一个依赖版本锁得不够紧。这些问题用静态扫描器完全可以发现不需要起一个测试环境去“打”一遍。动态的东西留给安全团队或者线下测试skill 不越权。2.2 输出结构化结果Agent才接得住第二件事是定义输出。Agent 接不住乱七八糟的原始日志它需要结构化数据。我规定 audit.py 只输出一份 JSON 中间结果包含 issues 数组每个 issue 有等级、规则 ID、文件路径、行号、描述和修复建议。然后 prompt 模板再让 Agent 把 JSON 转成人类友好报告。这个分层很关键工具产生证据Agent 负责解释。如果反过来让 Agent 直接读终端输出结果会乱得不可收拾。我见过太多 AI 看日志看到一半开始编结论的例子。原始扫描输出里混着 warning、error、统计信息、堆栈人眼都容易看花更别说 Agent。所以我在脚本里做了严格的解析层每个工具返回的 JSON 只提取真正有问题的 issue统一格式后再写入结果文件。Agent 读取的永远是干净、字段固定的 JSON而不是原始的 stdout。这一步设计直接决定了后面报告的稳定性和可信度。2.3 工具选型按场景组合而不是追求大而全工具选择上我踩过一些坑。最开始想把所有工具都集成进来后来发现没必要。我的标准是必须命令行可调用、输出可解析、支持自定义规则、尽量开源。最终选了 Semgrep 作为 SAST 主力Gitleaks 管密钥检测Bandit 管 Python 专属问题Trivy 管依赖漏洞和 IaC 配置Checkov 补一下云原生配置检查。工具定位输出格式适用场景SemgrepSAST/自定义规则JSON/文本多语言、特定框架危险调用Gitleaks密钥/敏感信息JSON硬编码密钥、Token、私钥BanditPython SASTJSONPython 代码常见漏洞TrivySCA/IaCJSON/表格依赖漏洞、镜像、配置文件CheckovIaC 安全JSONTerraform/K8s/CloudFormation实际使用中我会在脚本里按项目类型组装这些工具。比如一个 Python Django 项目跑 Bandit Semgrep Gitleaks Trivy 就够了一个 Kubernetes 配置仓库则跑 Checkov Gitleaks。这样避免每次都把所有工具跑一遍太慢。选型上我不追求“全家桶”因为 Agent 的上下文和时间都有限工具跑得太多结果文件巨大反而会把真正的问题淹没。3. 核心实现手写一个security-audit-skill3.1 Skill目录结构从入口到知识库目录结构是 skill 的骨架。我最终的项目目录长这样security-audit-skill/ ├── SKILL.md ├── scripts/ │ ├── audit.py │ ├── gitleaks.toml │ └── semgrep_rules/ │ ├── dangerous-eval.yaml │ └── jwt-none-alg.yaml ├── prompts/ │ ├── report_assistant.md │ └── remediation_assistant.md └── knowledge/ ├── cwe_top25.md └── supply_chain_risks.mdSKILL.md 是入口Agent 加载 skill 时第一个读它。scripts/ 是真正干活的audit.py 负责统一调用扫描器并解析结果。gitleaks.toml 是白名单配置用来降低误报。semgrep_rules/ 放自定义规则主要针对团队内部技术栈的特定模式。prompts/ 里是给 Agent 的辅助提示词report_assistant.md 教它怎么写报告remediation_assistant.md 教它怎么给修复建议。knowledge/ 里放一些参考知识防止它遇到常见漏洞模式时无话可说。这个目录结构有个好处职责分明。Agent 不需要把整个 skill 目录都读进上下文它只需要先读 SKILL.md然后按图索骥去调用 scripts、参考 prompts。如果知识文档非常大Agent 也可以只查需要的部分而不是一次性全部加载。3.2 SKILL.md告诉Agent按流程执行而不是自由发挥SKILL.md 是 skill 的灵魂。Codex、OpenCode 这些平台对 skill 的加载机制虽然不同但 SKILL.md 的编写思路是通用的。我这份是这样写的--- name: security-audit description: 对代码仓库执行静态安全审计定位密钥泄露、漏洞依赖、危险代码和错误配置。当用户要求进行安全审计、检查漏洞、扫描密钥时使用。 --- ## 使用条件 - 用户要求进行安全审计、检查代码漏洞、扫描密钥时触发。 ## 工作流程 1. 确定被审计目录默认为当前工作目录。 2. 执行: python3 scripts/audit.py --path target --format json --out audit_result.json 3. 读取 audit_result.json。如果命令失败向用户说明原因。 4. 根据 audit_result.json 中 issues 数据结合 prompts/report_assistant.md 生成中文报告。 5. 报告必须包含: 风险等级汇总表、关键发现列表、每项的文件路径/行号、修复建议。 6. 禁止编造审计结果。没有任何证据的问题不得出现在报告中。这里最关键的是第 2 条和第 6 条。第 2 条强制 Agent 先跑脚本第 6 条严格禁止幻觉。很多人写 skill 容易漏了最后这条结果 Agent 在扫描器没装的情况下直接“填空”编出一堆漏洞。另外我在 SKILL.md 里刻意不写“如何做安全审计”这种理论知识因为 Agent 的知识储备已经够了它缺的是执行路径不是通识。把流程写清楚比写一堆安全概念有用得多。3.3 audit.py把外部扫描器封装成统一接口audit.py 是这个 skill 的引擎。它的核心思想是把多个扫描器封装成一个命令。我简化后的大概逻辑如下#!/usr/bin/env python3 Security Audit Skill - 统一扫描入口 import argparse, json, os, shutil, subprocess, sys TOOLS { semgrep: [semgrep, scan, --json], gitleaks: [gitleaks, detect, --no-banner, --report-formatjson, --report-path], bandit: [bandit, -r, -f, json], trivy: [trivy, fs, --formatjson], checkov: [checkov, -d, --download-external-modulesfalse, -o, json], } def check_tools(): missing [t for t in TOOLS if shutil.which(t) is None] if missing: print(json.dumps({error: missing_tools, tools: missing})) sys.exit(1) def run(tool, args, cwd): try: cmd TOOLS[tool] args proc subprocess.run(cmd, cwdcwd, capture_outputTrue, textTrue, timeout180) return proc.stdout, proc.stderr, proc.returncode except subprocess.TimeoutExpired: return , f{tool} timeout, 1 def parse_issues(tool, stdout, stderr, returncode): # 这里做各工具的JSON解析统一成 issue 对象 issues [] # ... 每个工具解析逻辑不同按实际schema编写 return issues def main(): parser argparse.ArgumentParser(descriptionSecurity Audit Wrapper) parser.add_argument(--path, requiredTrue) parser.add_argument(--format, choices[json, markdown], defaultjson) parser.add_argument(--out, defaultaudit_result.json) parser.add_argument(--profile, defaultgeneral) args parser.parse_args() check_tools() all_issues [] # 根据 profile 选择工具并调用 run parse_issues # 聚合结果 result {tool: security-audit-skill, issues: all_issues} with open(args.out, w) as f: json.dump(result, f, indent2, ensure_asciiFalse) print(fFound {len(all_issues)} issues, results saved to {args.out}) if __name__ __main__: main()为了博文可读性我省去了每个工具的解析函数。实际工程中每个扫描器的输出 schema 都不一样最麻烦的是 Semgrep 的 JSON 嵌套很多Gitleaks 的老版本和新版本字段也不一致。我建议在写 parser 时先各跑一次样例把 JSON 结构记录下来再写对应的取值路径。这步偷懒后面 Agent 读到的数据就是一团乱麻。另外还要处理几个细节超时。一个大型 repo 跑全部工具可能超过十几分钟Agent 调用往往有超时限制。我在脚本里给每个工具设置了 180 秒超时并支持 profile 参数来减少扫描范围。还有退出码。很多扫描器找到漏洞会返回非 0 退出码subprocess 不要因此直接抛异常要正常解析输出。把错误处理写充分Agent 才能在你不在场的情况下顺利执行完整流程。3.4 提示词模板教Agent“读报告”和“写建议”audit.py 产出 JSON 之后Agent 需要把它读进上下文并写报告。问题来了JSON 可能很长直接丢给 Agent 会让上下文爆炸。prompt 模板就是用来约束它怎么读、怎么用的。我在 prompts/report_assistant.md 里写了下述要求你是一名安全审计工程师。请根据用户提供的 audit_result.json 生成报告。 要求 - 先用 Markdown 表格呈现各风险等级的问题数量。 - 每个问题需要给出风险等级、规则/工具、文件路径、行号、问题描述、修复建议。 - Critical/High 级别问题必须逐条列出Medium 最多列出10条Low 只给摘要。 - 如果JSON中没有任何issue明确写“未发现可疑问题”不要补充“潜在风险”。 - 修复建议要具体到代码例如“将 os.system(subprocess) 改为 subprocess.run([ls], shellFalse)”不要写“提升安全性”。很多人会忽略这样一个细节你不写“最多列出 10 条”Agent 在报告高等级问题时还能克制到了中低风险就会开始凑数。加了数量限制后报告质量和上下文占用都好了很多。另外修复建议必须具体到代码。如果只写“提升安全性”等于什么都没写。Agent 其实有能力给出具体 diff关键是 prompt 里要逼它做到这一步。4. 实操把Skill装进Codex和OpenCode4.1 安装与目录约定下面说一下怎么装进 Codex 和 OpenCode。Codex 的 skill 目录默认在 ~/.codex/skills/OpenCode 在 ~/.config/opencode/skills/不同版本可能有差异可以先跑 skill list 看默认路径。安装其实就是把整个目录复制过去# 安装到 Codex cp -r security-audit-skill ~/.codex/skills/ # 安装到 OpenCode cp -r security-audit-skill ~/.config/opencode/skills/ # 验证 codex skills list opencode skill list我踩过的一个坑是很多 skill 示例把 SKILL.md 写得很复杂装了之后 Agent 根本不触发。解决办法是在 description 里写明触发场景比如“当用户要求进行安全审计、检查代码漏洞、扫描密钥时使用”。Agent 判断是否使用 skill主要看 description 和你对话的语义是否匹配这一点比 SKILL.md 正文还重要。我在第一次安装后测试了很久最后才发现是 description 写得太宽泛导致 Agent 总在犹豫要不要用。4.2 从触发到报告一次完整调用过程安装完后回到对话里直接说“用 security-audit skill 审计当前仓库。”Agent 会读取 SKILL.md执行 audit.py然后基于结果生成报告。这里有个核心机制Agent 执行 shell 命令获得输出再在后续回复中使用。它不会自己把所有脚本都调用一遍而是严格按照 SKILL.md 的流程来。整个流程如下用户发出审计请求。Agent 定位当前目录并运行python3 scripts/audit.py --path . --format json --out audit_result.json。脚本将各扫描器结果合并写入 audit_result.json。Agent 读取 JSON使用 report_assistant.md 组织语言。输出 Markdown 报告并在末尾指出审计结束。这五步里第 2 步最容易被卡住。因为 Codex 在安全策略上会限制一些命令如果 Agent 拒绝执行 audit.py就要检查 SKILL.md 是否描述了命令用途并且确认不是危险操作。一般来说在本地项目目录内执行扫描脚本是允许的但如果你的 Agent 使用容器沙箱需要把目录挂载进去。另外有些 Agent 会要求命令必须有明确参数所以我在 SKILL.md 里给了完整的命令行而不是只写python3 scripts/audit.py。4.3 一个微型仓库的真实审计演示我建了一个微型测试仓库故意放了三类问题一个硬编码的 API Key一个 Flask 路由里直接拼接 SQL一个 requirements.txt 里固化了带漏洞依赖版本。运行 audit.py 后audit_result.json 里面会有 Gitleaks、Bandit、Trivy 三个来源的 issue。Agent 最终生成的报告大概是这样的## 安全审计报告 ### 风险统计 | 等级 | 数量 | | High | 2 | | Medium | 1 | | Low | 0 | ### 关键发现 1. [High] 硬编码 API Key - 文件: app/config.py:12 - 工具: gitleaks - 建议: 改为环境变量注入并轮换已泄露密钥。 2. [High] SQL注入风险 - 文件: app/views.py:48 - 工具: bandit / semgrep - 建议: 使用参数化查询例如 cursor.execute(SELECT * FROM user WHERE id%s, (user_id,))。 3. [Medium] 依赖版本过旧 - 文件: requirements.txt:4 - 工具: trivy - 建议: 升级到修复版本并重新运行测试。这种报告比我手动让 AI“看看有什么问题”要踏实得多因为每个结论都有扫描器证据不是 AI 拍脑袋。换言之skill 的价值不在“AI 比人聪明”而在“AI 被约束在正确的流程里”。5. 常见问题与排查实录5.1 扫描器缺失或版本不一致你在新环境跑 skill最常遇到的就是命令找不到。我建议 audit.py 启动时先做环境检查把缺失工具一次性列出来而不是跑到一半才报错。安装时可以使用 pip 和 brew但我在 SKILL.md 里也写了一句“如果环境缺少工具向用户说明需要安装哪些命令不要尝试自行安装。”为什么因为 Agent 如果在没有用户确认的情况下执行系统级安装既慢又有风险。让用户动手装反而更干净。版本不一致的问题也很常见。Semgrep 不同版本对规则的兼容性有差异Gitleaks 旧版输出字段名也不一样。解决办法是在项目里固定一个 requirements.txt 或脚本安装说明确保环境一致。如果你像我一样经常在多个机器之间切换建议录制一个 install.sh一键装齐所有工具。5.2 扫描结果太多Agent上下文爆炸大型仓库扫描后 JSON 可能有几百个 issue全塞进上下文会让 Agent 失焦。我的方案有三个第一脚本支持 --max-issues 参数默认只保留 Medium 以上前 50 条第二报告模板要求只详细写 HighMedium 给列表Low 给摘要第三把完整 JSON 落盘报告里只展示关键字段。这三个组合下来上下文占用大概只有原来的三分之一。上下文爆炸不仅影响 Agent 的理解还会让响应变得很慢。如果你的项目里有 node_modules 之类的超大目录一定要先配置忽略规则否则扫描器会一个文件一个文件地读进去结果和上下文都会失控。宁可少看几个低风险项也要确保高风险项没有被淹没。5.3 密钥检测的误报与白名单Gitleaks 误报是常态尤其是测试代码里的假 token。我维护了一个 gitleaks.toml 白名单文件并提供 --skip-paths 参数。示例[extend] useDefault true [allowlist] description 测试数据与mock密钥 paths [ tests/, fixtures/, *.md, ] regexes [ sk_live_test_only, fake_secret, ]注意白名单不要写太宽否则真密钥也会被跳过。我一般只在确定是单元测试或文档示例时才加白名单。白名单本身也要放进代码评审范围避免有人打着“测试数据”的幌子把真实密钥混过去。这个看起来不起眼的文件其实是长期维护 skill 时投入产出比最高的一个。5.4 Agent幻觉没有证据的漏洞不要写这是最坑的问题。有一次工具没装好audit.py 返回错误Agent 竟然在报告里写了“发现 3 个高风险漏洞”。查了才知道SKILL.md 只说了“运行脚本后读结果”没说不许编。从那以后我在 SKILL.md 和 report prompt 里都加了铁律“只能报告 JSON 中存在的 issues不得补充推测风险。”另外脚本出错时应该输出结构化错误字段这样 Agent 能直接告诉用户出了问题而不是自行补全。幻觉这个坑需要反复强调。AI 的生成惯性很强只要 prompt 里没有明确禁止它就会顺着“安全审计报告”这个语境开始编造。最好的办法是让结果文件的 schema 足够清晰并且把“无结果”也当成一种正常结果写进 prompt。比如 JSON 里 issues 为空数组时Agent 必须写“未发现可疑问题”而不是加一句“但建议人工复核”之类的万金油。5.5 文件权限与忽略规则有时候仓库里有不该扫的目录比如 node_modules、build、vendor。如果不忽略会导致扫描时间暴涨还会给出大量无关结果。我在端点支持一个 .auditignore 文件同时默认忽略常规目录。这个文件用 Gitignore 的语法好记也好写。node_modules/ dist/ build/ vendor/ .venv/ __pycache__/在 audit.py 里解析它并把目录列表传给扫描器能省下大量时间。这点在做真实项目审计时几乎是必须的。除了性能考虑忽略文件目录也能减少误报。比如 build 目录里的压缩代码可能触发大量无意义的规则而这些问题实际根本不会进入生产环境。6. 让这个Skill更好用的几个经验6.1 按项目类型加载不同的扫描Profile根据项目类型动态启用工具。比如爬取项目的 package.json、requirements.txt、go.mod、*.tf判断语言和基础设施类型。Python 用 Bandit SemgrepNode 用 Semgrep npm auditK8s/Terraform 用 Checkov。这样不仅快报告也更聚焦。Profile 的定义可以放在一个简单的配置文件里例如 profiles.json在 audit.py 启动时读取。这样即使你不改 SKILL.md也能随时调整不同场景下要启用的工具组合。我在实际使用中深有体会一个 Web 项目每次启动都跑 Checkov会带来大量的噪音而一个只包含 Kubernetes yaml 的仓库跑 Bandit也毫无意义。按 Profile 分流是保证报告质量最重要的一步。6.2 沉淀自定义规则把团队规范写进Skill通用规则经常漏掉团队内部的危险用法。比如我们内部禁用了某些 ORM 的 raw 方法我写了一条 Semgrep 规则放在 semgrep_rules/ 下skill 自动加载。rules: - id: no-orm-raw languages: [python] message: 禁止直接使用 raw SQL 方法 severity: HIGH patterns: - pattern: $OBJ.raw(...) metadata: cwe: CWE-89这类自定义规则其实是 security-audit-skill 最有价值的部分因为它是团队经验的代码化。通用规则仓库可能不会覆盖你们内部框架的特定反模式而自定义规则可以。时间长了这个 skill 会越来越贴合团队的技术栈审计结果的可信度也会明显上升。6.3 给Agent加一个修复建议步骤我后来给 skill 补了一个 remediate 子命令让 Agent 根据 High 风险列表生成修复建议并且直接给出 diff。但我的经验是不要让 Agent 直接自动修改除非测试通过。更稳妥的做法是生成补丁文件人 review 之后再应用。之所以不建议自动修改是因为修复本身可能引入新的问题。比如把os.system()改成subprocess.run()参数没处理好反而会有兼容性问题。Agent 生成 patch 可以作为初审但合并前的测试和 review 不能省。在实际工作流里我会让 Agent 输出建议 patch然后复制到分支里跑一遍测试再决定是否采用。6.4 从个人技能包走向团队CI规范skill 在本地好用之后自然想推广到 CI。你可以把 audit.py 作为预提交钩子或 GitHub Action在每次 PR 时跑一遍。这和 Agent Skill 不冲突反而形成互补本地 Agent 用 skill 辅助开发CI 保证合入前一定要过审计。在 CI 里跑的时候不需要 SKILL.md 和 prompts 这两层只需要 scripts 目录直接复用 audit.py 的 JSON 输出然后在 workflow 里检查是否有关键级别漏洞。这样做的好处是开发者在本地用 skill 发现问题、提前修复CI 作为最后一道网防止漏网之鱼。整个流程合起来才是安全的闭环。最后说一点我自己的体会。security-audit-skill 不是要取代安全工程师它更像给你配了一个“先跑工具的实习生”。真正的漏洞分析、业务风险评估、修复方案评审仍然需要人来把关。但我实际用了两周之后最大的感受是那些因为“图省事”而漏掉的低悬果实——硬编码密钥、直接拼 SQL、忘了锁依赖版本——几乎都挡在了合并之前。如果你也在用 AI Agent建议花半天时间把类似流程做成自己的 skill这比每天在对话里重复“注意安全”有用得多。
阅读完成 · 觉得有帮助?