ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

AI安全审计实战:从零构建可用的security-audit-skill

AI安全审计实战:从零构建可用的security-audit-skill “你让大模型审一段代码它说得头头是道这里注入那里越权等你拿真实工具一跑十有八九是误报。”这是我最近大半年折腾 AI 辅助安全审计最大的感受。后来我把整个审计流程沉淀成了一个 security-audit-skill装到 Claude 和 Codex 这类支持 skill 机制的 Agent 里才算是把“AI 审代码”从“聊天式猜漏洞”变成了“工具链驱动出报告”。这篇文章就把这个 skill 从设计到落地的完整过程拆开讲。适合两类人看一类是天天跟代码安全打交道、想用 AI 提效的工程师另一类是正在研究 skill 到底是什么、和 Agent 有什么区别、怎么写一个真正能用的 skill 的人。我会把输入怎么设计、工具链怎么选、SKILL.md 怎么组织、实测数据是什么样、踩过哪些坑一次性说清楚。1. 先搞清楚security-audit-skill 是什么和 Agent 到底啥关系1.1 Skill 不是插件也不是一段提示词热词里全是“skill 插件”“skill 脚本”“好用的 skill”这类搜索说明很多人还没搞清楚 skill 在技术栈里的位置。我理解 skill 的本质是把某一类任务的“专业知识 操作流程 工具调用方式 输出格式约定”打包成一个可复用的资产交给 Agent 去调用。它不是一个没有状态的 API 插件也不是写死的一段提示词更不是装完就自动跑的守护进程。打个比方Agent 是一个刚入职的实习生脑子聪明但没经验skill 就是一份带着详细操作规程的岗位手册。手册里写着“接到任务先检查哪些条件”“第一步跑什么工具”“第二步解析什么格式”“报告按什么模板填”。实习生不需要重新发明流程照着手册执行就能交付质量稳定的结果。而插件更像螺丝刀skill 则是“怎么用螺丝刀把这面墙拆了”的完整施工方案。GitHub 上那些 skill 技能库比如 various 组织下面的 awesome-claude-skills以及各家的 skill 仓库下载下来你会发现大多数都是一个目录一个 SKILL.md 加上若干脚本和配置文件。SKILL.md 是给大模型读的“岗位手册”脚本是真正干活的。你给 Agent 装了 skill它读到 SKILL.md就知道“哦这个场景我应该按这个流程来”。1.2 安全审计为什么必须“技能化”不能直接“问大模型”直接让大模型审代码最大的问题是三个不可执行、不可复现、不可量化。它说“这里有 SQL 注入”你让它拿出证据它只能给你念一段代码不会真的去跑工具验证你让它说“这个项目整体风险有多高”它给不出一个基于扫描结果的量化结论你改一行代码再问一次它可能给出完全不同的判断。我把 security-audit 这件事拆开看它天然适合 skill 化。安全审计的流程是高度标准化的先确定审计目标和范围再跑静态代码扫描SAST、依赖漏洞扫描、密钥泄露检测、容器镜像检查最后把结果汇总成报告。流程里的每一步都有成熟的开源工具可用工具跑出来的结构化 JSON 结果再交给大模型做“翻译”和“解读”正好避开大模型“凭空编漏洞”的短板。所以这个 skill 的核心设计逻辑是让大模型做“项目经理”安排工具去干活然后把工具返回的真实数据和上下文结合生成报告。大模型不直接判断“有没有漏洞”它判断“工具报的这个结果结合代码上下文是否真的构成风险”。这样误报率能降一个量级报告也有据可依。1.3 Skill 和 Agent 的配合方式决定了落地效果很多人在搜“skill 和 agent 的区别”我的理解是Agent 是执行体Skill 是能力包。一个 Agent 可以挂十几个 skill遇到不同任务动态选择合适的 skill 来执行。比如你有一个“代码审查 Agent”它既挂着 security-audit-skill也可以挂着 code-review-skill、dependency-update-skill。Agent 负责理解用户意图、拆解任务、调度 skillskill 负责把某一类专业任务的执行细节定死。举个例子“找一下这个 Java 项目的安全问题”和“帮我修复这个安全漏洞”前者直接调 security-audit-skill 出报告就行后者则需要 Agent 先调审计 skill 定位问题再结合修复规范生成补丁。所以 skill 设计得越纯粹、职责越单一Agent 组合起来就越灵活。我在设计这个 skill 时刻意没让它包含“自动修复”的逻辑只做到“发现 定位 生成报告”修复是另一个 skill 的事。2. 设计 Skill 前先把安全审计的“活儿”拆明白2.1 输入设计审计对象、语言、级别、范围一个不能少一个 skill 好不好用第一个试金石就是它的输入定义。我看到很多新手写 skillSKILL.md 里只写“对项目进行安全审计”大模型拿到这种描述完全不知道从哪里下手。我在设计 security-audit-skill 时把所有输入场景枚举了一遍最终收敛成四个必填字段和两个可选字段。必填字段包括审计目标的本地路径保证有真实代码可以扫、目标语言或技术栈决定启用哪些扫描工具、审计级别快速扫描、标准扫描、深度扫描、输出格式markdown 报告还是 JSON 结果。可选字段包括忽略路径列表比如跳过 vendor、node_modules、合规标准比如是否按 OWASP Top 10 归类。这样设计的原因是大模型最怕的是指令模糊你给它四个必填字段它就知道第一步去解析参数第二步按参数去匹配工具。审计级别的设计我参考了实际使用场景。快速扫描一般只跑 Semgrep 的高危规则和依赖检查控制在 1 分钟内标准扫描会加上密钥检测和更全的 Semgrep 规则集深度扫描则启用 CodeQL 这类需要编译分析的引擎耗时可能达到 10 分钟以上。这个级别字段直接决定工具链的调度逻辑让不同场景都能找到合适的“档位”。2.2 工具链选型静态扫描、依赖检查、密钥检测、容器扫描各司其职安全审计不是一把锤子砸所有钉子我最终选了五类工具每类都有明确的分工。选型标准就三条开源优先、有结构化输出、社区活跃且规则持续更新。这五类工具配合起来才能覆盖一个应用从代码到依赖到配置的完整风险面。工具所属分类主要作用输出格式SemgrepSAST 静态代码扫描检测注入、XSS、不安全反序列化等代码级漏洞JSONBanditPython 专项检测 Python 代码中的安全风险JSONnpm audit / pip-audit依赖漏洞扫描检测第三方依赖的已知 CVEJSONGitleaks密钥泄露检测扫描硬编码的 API Key、密码、TokenJSONTrivy容器/文件系统扫描检测镜像和文件系统中的漏洞与配置问题JSONSemgrep 是这个 skill 的绝对主力。它速度快支持几十种语言规则写起来简单而且可以本地跑完全不依赖云端。我实测过一个中型 Java 项目1 万行代码标准规则集跑下来 20 秒左右出结果。Bandit 则是对 Python 场景的补充因为 Semgrep 在 Python 的某些框架特性上覆盖不全。npm audit 和 pip-audit 很轻量主要用来做依赖层检查跑一次几秒到几十秒不等。Gitleaks 是做安全审计时最容易给人惊喜的工具。很多项目从外部迁移过来的git 历史里往往藏着各种硬编码密钥Gitleaks 可以直接遍历 git 历史做深度扫描。Trivy 我放在深度审计阶段对包含 Dockerfile 的项目扫描镜像依赖。工具链的设计原则是“按需启用”不是每个项目都跑全套而是由 SKILL.md 里的规则决定检测到 Python 就调 Bandit检测到 package-lock.json 才跑 npm audit。2.3 输出设计结构化报告才能让大模型“有话说”输出设计是整个 skill 的灵魂。工具跑完会产生大量 JSON但原始 JSON 大模型读起来效率很低而且它读 JSON 时很容易被干扰项带偏。我的方案是先把工具结果做一层“压缩归并”提炼成统一的中间格式再把中间格式喂给大模型生成最终报告。中间格式包含五个字段规则编号对应 CWE/OWASP 分类、文件路径和行号、问题描述、危险等级、匹配的代码片段。这五个字段本质上是给大模型搭了一个“提词框架”它在生成报告时只需要基于这些字段做上下文分析而不是从几千行的 JSON 里大海捞针。我写了一个 transform.py 脚本专门负责把各工具的输出统一成这个格式这一步是整个 skill 工程化含量最高的部分。最终报告我设计了两种输出人读的 Markdown 报告和机器读的 JSON 摘要。Markdown 报告包含风险统计、按严重级别排序的问题列表、每个问题的定位和修复建议JSON 摘要则是给上一个环节用的方便把安全审计 skill 接到更复杂的自动化流水线里。之所以这么设计是因为我发现如果只输出一份报告Agent 后续想基于审计结果做修复还得回头自己去解析报告太蠢了。3. 实操从零搭一个可运行的 security-audit-skill3.1 目录结构长什么样SKILL.md 怎么写我先给你看这个 skill 的目录结构这是实践下来最顺手的组织方式security-audit-skill/ ├── SKILL.md ├── scripts/ │ ├── semgrep_scan.sh │ ├── bandit_scan.sh │ ├── dependency_scan.sh │ ├── gitleaks_scan.sh │ ├── transform.py │ └── report_template.md └── rules/ └── semgrep_additional.ymlSKILL.md 是整个 skill 的“大脑”它的质量直接决定大模型能不能正确使用这个技能。我总结了一个实用的写作骨架开头写清楚 skill 的定位和适用场景中间写清楚工具调用规则和参数规则结尾写清楚输出格式约定。--- name: security-audit description: 对指定代码目录执行多引擎安全审计输出结构化风险报告。适用于代码安全基线评估、上线前安全检查、第三方代码接入审查。 ---description 字段一定要写“在什么场景下用”而不是只写“能干什么”。原因是 Agent 在选择使用哪个 skill 时靠的就是这个描述做语义匹配。我最初写的是“代码安全审计工具”结果 Agent 经常不知道该什么时候调用它改成“适用于上线前安全检查、第三方代码接入审查”之后触发准确率明显提升。SKILL.md 正文部分我分了四个小节输入参数定义、执行流程、工具调用规则、输出格式。关键是执行流程要写得像清单让大模型一步一步执行。你会发现写 SKILL.md 本质上是在“给大模型做流程编排”你的流程越清晰它的表现越稳定。3.2 把工具调用封装成脚本屏蔽环境差异工具调用这块踩过不少坑。直接让大模型在终端里运行 semgrep scan --configauto 这类命令它会因为参数记不熟而出各种错。我的做法是把所有审计命令封装成带标准参数的脚本大模型只需要执行脚本传几个参数不用关心工具内部细节。以 Semgrep 封装脚本为例核心是输出 JSON 文件并屏蔽掉终端里的进度条干扰#!/bin/bash # scripts/semgrep_scan.sh TARGET_DIR$1 OUTPUT_FILE$2 RULE_CONFIG${3:-auto} if [ ! -d $TARGET_DIR ]; then echo {\error\: \target directory not found: $TARGET_DIR\} exit 1 fi semgrep scan \ --config$RULE_CONFIG \ --json \ --quiet \ --output$OUTPUT_FILE \ $TARGET_DIR 2/dev/null if [ $? -eq 0 ]; then echo {\status\: \ok\, \output\: \$OUTPUT_FILE\} else echo {\status\: \error\, \message\: \semgrep scan failed\} fi脚本里有个细节值得留意--quiet参数。Semgrep 扫描大项目时会在终端打大量进度日志如果不加--quiet大模型执行脚本后拿到的输出里混着日志和 JSON它很容易被干扰。让脚本输出干净的结构化结果是对大模型最基本的尊重。依赖扫描脚本我做了更细的处理。它自动检测项目里存在哪种锁文件然后决定调用 npm audit 还是 pip-audit而不是把选择权交给大模型。这样规则更可控大模型不需要知道“package-lock.json 要用 npm audit”这种细节。封装脚本的目的就是让大模型把精力放在“审计的流程编排”上而不是放在“某个工具怎么用”上。3.3 实测跑通一个审计流程看每一步在发生什么我们用一个有已知漏洞的测试项目跑一遍完整流程看看每一步发生了什么。假设项目在 /tmp/testapp技术栈是 Node.js包含一个硬编码的 AWS Key 和一个命令行注入漏洞。第一步Agent 读取 SKILL.md识别出输入参数目标路径 /tmp/testapp、语言 JavaScript、审计级别标准。它没有直接去读代码而是先调用依赖扫描脚本bash scripts/dependency_scan.sh /tmp/testapp /tmp/testapp_dep.json脚本检测到 package-lock.json于是执行npm audit --json把结果写到 testapp_dep.json。假设返回 3 个中危漏洞、1 个高危漏洞。第二步Agent 执行 Semgrep 扫描bash scripts/semgrep_scan.sh /tmp/testapp /tmp/testapp_semgrep.json这一步会命中代码里的 child_process.exec 拼接用户输入的问题Semgrep 返回一条 command injection 规则命中记录。第三步跑 Gitleaks 扫描bash scripts/gitleaks_scan.sh /tmp/testapp /tmp/testapp_gitleaks.json第四步transform.py 把三份 JSON 统一成中间格式。我设计的统一 JSON 格式长这样{ findings: [ { tool: semgrep, rule_id: javascript.lang.security.audit.child-process-exec-command-injection, severity: ERROR, file: /tmp/testapp/server.js, line: 12, cwe: CWE-78, message: Detected shell command injection via user input, snippet: exec(ls userInput) }, { tool: gitleaks, rule_id: aws-access-token, severity: HIGH, file: /tmp/testapp/config.js, line: 3, cwe: CWE-798, message: AWS Access Key detected in source, snippet: aws_secret_access_key AKIA... } ] }看到这个结构你就知道大模型的活儿有多轻松了。它不需要自己去理解 Semgrep 的输出格式只需要逐条阅读 findings结合 snippet 判断风险就能生成一份准确度很高的报告。我在实测中发现给大模型喂统一 JSON 之后它生成报告的速度和准确率都比喂原始 JSON 高一大截。3.4 让 Claude / Codex / Cline 装上这个 Skill装 skill 的方式取决于你用的客户端。Claude Code 和 Cline 都支持通过目录放置 skill 文件。以 Claude Code 为例把整个 security-audit-skill 目录放到~/.claude/skills/下启动 Claude Code 后它就会自动扫描并识别这个 skill。Codex 的配置方式类似放在~/.codex/skills/下。但要注意版本差异有些客户端需要手动在配置文件里声明 skill 路径建议装完先跑一下skills list确认识别成功。我自己常踩的坑是把 skill 目录放在项目仓库里而不是全局目录。这样换一个项目就找不到了还得重新配置。比较稳妥的做法是全局目录放一份项目仓库里通过软链接引用。另外skill 的脚本文件记得加执行权限chmod x scripts/*.sh最后验证一下ls ~/.claude/skills/security-audit-skill/ cat ~/.claude/skills/security-audit-skill/SKILL.md | head -20输出正常后你可以在对话里直接说“审计一下当前项目的安全问题”看它能不能自动触发这个 skill。我实测下来描述里带有“审计”“安全”“漏洞”等关键词时触发准确率比较高但如果只说“检查一下代码质量”它会优先调别的 skill。所以平时注意区分“安全检查”和“代码审查”两个场景对应的 skill 别混着写。4. 实测三批项目误报率、召回率和耗时的真实数据4.1 三批项目的测试配置与结果为了验证 skill 的实用性我拿三种不同类型的项目做了实测一个 2 万行左右的 Java Spring 后端、一个 1 万行的 Python 数据处理服务、一个 5 千行的 Node.js 前端工程。三批项目都采用“标准审计”级别工具链是 Semgrep Gitleaks 对应语言的依赖扫描。结果如下表所示项目代码行数审计耗时原始发现数人工确认有效问题误报率Java Spring 后端约 2 万3 分 12 秒47980.9%Python 数据处理服务约 1 万1 分 45 秒28678.6%Node.js 前端工程约 5 千51 秒19384.2%站在纯工具角度看这个误报率是很正常的。Semgrep 这类 SAST 工具本身就是“宁可错杀一千不放过一个”的调性它报的很多问题在特定上下文里并不是真实漏洞。但站在使用角度看如果让大模型不经过分析直接把 47 个发现粘贴成报告那这份报告基本没人看。skill 的价值就体现在这大模型拿到 47 个原始发现后结合代码片段逐个判断“这个输入真的可控吗”“这个路径真的有外部请求入口吗”最终把 47 个发现压缩成 12 个“需要人工重点关注”的问题。虽然大部分判断还是要靠人来确认但对审计工程师来说等于有人先把 80% 的噪音过滤掉了。4.2 参数调优把误报率从 80% 压到 40%第一轮实测误报率接近 80%这个数字有点吓人但别急着放弃。后面我做了三件事把人工确认有效问题的占比提升到了接近 60%。第一件事是调 Semgrep 的规则集把那些“出现频率太高但实际风险低”的规则从自动扫描里剔除。第二件事是在 transform.py 里加了一层“上下文预判”把没有数据流传递的发现标记为“低置信度”。第三件事是让大模型在生成报告时只保留 ERROR 和 HIGH 级别的发现WARNING 级别只在附录里出现。规则裁剪的效果最明显。比如 ESLint 风格的 no-console 高危告警在 Node.js 项目里意义不大日志里可能大量出现但根本不是安全问题。默认规则集里这类噪声不少我在 rules 目录里专门维护了一个 semgrep_additional.yml给默认规则做减法rules: - id: disable-no-console languages: [javascript, typescript] severity: INFO path: rules/disable-no-console.yml这个文件声明了“对 no-console 这类规则降级为 INFO不进入正式报告”。类似的做法还能应用到很多“测试代码里的硬编码”“mock 数据里的假密钥”等场景。规则裁剪是一个持续积累的过程每次审计发现某类噪声反复出现就把它加进降级清单这个 skill 在你的业务场景里会越用越准。第三个调整是 prompt 级别的约束。我在 SKILL.md 里明确要求大模型“遇到多个相同模式的发现时只保留代表性的一到两处其余合并为统计信息”。这个约束能显著降低报告篇幅也能避免大模型机械地把 20 条 SQL 注入候选全部列出来。合并之后报告里每个问题都有独立分析而不是一堆无脑罗列。4.3 把 Skill 接进 CI/CD做回归基线skill 除了在交互式环境里用还可以接进 CI。我的做法是在 GitHub Actions 里加一个 security-audit job每次 push 到 main 分支时自动跑这个 skill 的脚本部分保存审计结果对比上一次的基线。基线对比的意义在于安全审计最怕的不是“当前有漏洞”而是“这次改动引入了新漏洞”。具体实现上CI 里不会跑整个 skill 的完整流程毕竟需要大模型参与而是直接调用封装好的脚本- name: Run security audit script run: | bash scripts/semgrep_scan.sh . /tmp/semgrep.json bash scripts/gitleaks_scan.sh . /tmp/gitleaks.json bash scripts/dependency_scan.sh . /tmp/dep.json脚本跑完后再用 transform.py 生成对比报告然后判断新增的高危发现数是否超过阈值。这一步的价值是把这个 skill 从“个人效率工具”升级成了“团队安全门禁”。不过提醒一句CI 里跑 Gitleaks 对大型仓库的 git 历史扫描会非常慢建议只对增量代码跑或者设定只看最近 N 次提交。5. 常见问题与排查技巧实录5.1 工具装不上、权限报错先排查这几个点这个 skill 依赖五个工具每个都有各自的安装方式装不上的概率其实不低。Semgrep 推荐用 pip 安装Python 环境要注意版本冲突最好用虚拟环境隔离。Gitleaks 推荐直接下编译好的二进制放到 /usr/local/bin 下测试时跑 gitleaks version 确认能执行。Trivy 的安装方式根据系统不同差异较大建议参考官方文档而不是靠包管理器硬装。权限类报错也常见。比如执行 semgrep_scan.sh 提示 Permission denied先检查是不是没有 chmod x。有些环境下还会遇到 Docker 权限问题Trivy 扫描容器镜像时需要 Docker 套接字权限CI 里要给 runner 加 docker 组权限。遇到这类问题最有效的排查方式是让大模型先去看脚本本身的报错输出而不是让它凭经验瞎猜因为脚本输出通常已经指明了方向。5.2 Skill 跑得慢先找瓶颈再优化审计速度慢是最影响体验的问题。我遇到比较典型的情况是Semgrep 跑一个大型 Monorepo 项目要 8 分钟Gitleaks 跑完整 git 历史甚至要 20 分钟。首先做减法把 node_modules、vendor、dist 目录加进 Semgrep 的排除路径。这一步通常能省掉一半时间。其次控制 Gitleaks 的扫描深度用 --log-opts 限制只看最近提交gitleaks detect --log-opts-n 50 --report-formatjson第三善用并行。Semgrep 和 Gitleaks 之间没有依赖关系SKILL.md 里可以指示它们并行执行而不是串行能在总时长上节省一大截。我测试过让 Semgrep 和依赖扫描同时跑总耗时能从原来的 3 分钟压到 1 分半。Mini 的经验就是审计速度的瓶颈通常在 IO 和扫描范围而不是工具本身的计算能力。5.3 防止大模型“编”漏洞结构化约束和二次核对我见过最离谱的情况是大模型在报告里写“检测到潜在的反序列化漏洞建议修复”但底层的扫描结果里根本没有这条发现。这就是大模型不在约束下工作时的典型幻觉。要解决这个问题我在 SKILL.md 里加了硬性规则所有报告里的问题必须能在 findings 数据源中找到对应记录找不到对应数据的发现一律不写。实现这个约束的关键是让 transform.py 输出一个“发现计数”字段。大模型生成报告后把报告里的问题数和 findings 里的数量做一次比对数字对不上就说明它“加戏”了。我还在 SKILL.md 里加了一条“如果你无法从数据源中找到某个问题的对应记录请在报告中标注‘需要人工确认’而不是直接删除或编造上下文。”最后我还建议增加一个人工复核环节。即使 skill 已经做了大量过滤和压缩安全审计的最终结论仍需要懂业务的人来确认。这个 skill 的真正定位是“把审计工程师从 80% 的重复筛查中解放出来让人专注在剩下 20% 的高价值判断上”而不是替代人做决策。在我实际用了三个月之后最大的体会是skill 本身并不神秘它就是一个把专业流程固化下来的载体。security-audit-skill 能做到的是让 AI 从一个“看起来懂安全”的聊天机器人变成一个“按规范操作、有据可依、输出稳定”的审计助理。写 skill 最花力气的不是写 SKILL.md而是把流程拆清楚、把数据格式定好、把工具调教顺。这一套方法论不限于安全审计你把它换成代码审查、论文检索、会议纪要逻辑完全一样。如果你也想写自己的技能包我建议从一个小场景开始先把一条最核心的流程跑通再慢慢加分支场景而不是一上来就追求大而全。
返回列表