ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

把安全审计方法论固化成Skill:让Agent稳定执行审计任务

把安全审计方法论固化成Skill:让Agent稳定执行审计任务 最近两个月我一直在折腾一件事把安全审计的整套方法论固化成一个可复用的 security-audit-skill让 Claude、Codex 这类 agent 能稳定地替我完成一部分重复性审计工作。试过的人应该都有同感——直接丢一句帮我看下这个项目有没有安全问题给模型出来的东西基本不能直接用要么是泛泛而谈的安全建议要么是拿训练数据里的旧 CVE 编号一本正经地编故事折腾几轮下来效率和人工审计差不多。后来我把思路换了不再依赖对话框里的临时提示词而是把审计流程、工具调用、输出规范全部写进一个 skill 包里让 agent 每次运行时都按同一套方法论执行。效果是肉眼可见的所以把整个设计思路和踩坑过程整理出来给打算做类似事情的人一个参考。这篇文章适合正在用 agent 辅助做安全审计的技术人也适合想搞懂 skill 到底是什么、怎么把自己领域经验固化成 skill 的开发者。1. 为什么通用提示词撑不起安全审计这件事1.1 安全审计不是找漏洞三个字能概括的很多人对安全审计有误解觉得就是拿着扫描器到处扫扫到漏洞就完事。真实的安全审计是一个完整链路资产与依赖盘点、威胁建模、入口枚举、漏洞挖掘、利用验证、风险评估、报告输出。每个环节的输出会成为下一个环节的输入链条上断掉任何一环最后的结果都不靠谱。我拿体检来类比一个合格的体检流程不会上来就直接拉你做 CT而是先问诊、量血压、做常规血检根据初步结果再决定要不要做深度检查。安全审计同理agent 如果没有分诊这个概念上来就盯着某个文件猛审很容易漏掉真正的风险面。所以在设计 security-audit-skill 的时候我首先做的不是写代码而是把审计流程本身拆成了明确的阶段并在 skill 里规定每个阶段要做什么、产出什么。这个看起来很简单但绝大多数通用提示词恰恰缺了这一层。1.2 通用提示词的三个典型失效场景我拿真实的失败案例说话。第一次尝试时我让一个通用 agent 去审计一个 Node.js 项目结果它把依赖列表里所有包都标成了建议升级没有威胁等级、没有证据链、没有复现条件整篇报告就是一份可读性很差的噪音清单。这类问题反复出现总结下来主要是三个原因缺少上下文锚点agent 不知道应该先看 package.json 还是先看入口文件没有项目骨架的概念审计路径完全是乱的。知识截止时间带来的幻觉模型对漏洞库的记忆停留在训练数据阶段它可能把已经修复的低危问题描述成新的严重漏洞也可能对 CVE-2021 和 CVE-2022 的编号张冠李戴。输出格式不稳定同一段代码让同一个模型审两次给出的漏洞描述、严重级别、修复建议都可能不一样。人工还得花时间二次整理严重削弱了效率优势。这三个问题靠加几句提示词是解决不了的。因为它们本质上是局部上下文的限制——你在对话框里给的 prompt 再长也只是一次性的agent 没有一套稳定的、可回溯的流程资产。1.3 prompt 和 skill 的关键差异从一次性对话到可执行资产prompt 是对话上下文用完就丢skill 是文件系统里的资产可以被反复加载执行。security-audit-skill 的价值不在于里面写了多少条安全提示而在于它把审计方法论从人的脑子里搬到了磁盘上agent 每次加载它的时候会按照预定义的结构化流程去工作而不是自由发挥。这个差异有点像口头交代实习生干活和给实习生一本详细操作手册的区别。口头交代实习生这次记住了下次换个人可能就变了操作手册放在那里每次照着做偏差就小得多。skill 的思路就是把安全审计的操作手册实体化。2. security-audit-skill 的骨架流程、工具与输出约束2.1 skill 的物理结构一份说明文档加一堆可调用的脚本我参考了目前主流的 agent skill 目录约定把整个 skill 组织成这样的结构security-audit-skill/ ├── SKILL.md ├── scripts/ │ ├── scan_deps.sh │ └── parse_lockfile.py └── references/ ├── dangerous_functions.md └── audit_checklist.mdSKILL.md 是这个 skill 的说明书用自然语言加结构化描述来定义安全审计员的角色、流程和边界。agent 加载 skill 的时候实际上就是在读这份说明书然后按照它的指示去执行任务。scripts 目录放的是实际的扫描脚本references 目录放的是供 agent 在审计过程中参考的领域知识清单。这个结构看起来平淡无奇但它是整个 skill 的地基。没有这个目录架构后面所有流程定义都无处安放。2.2 侦察阶段让 agent 先画系统全景再动手写第一版 skill 的时候我犯了一个错误直接要求 agent扫描漏洞跳过了项目侦察。结果就是 agent 连项目是什么技术栈都没搞清楚就凭感觉开始审了。后来我在 SKILL.md 里明确加了侦察阶段要求 agent 在处理任何审计请求时先完成一系列确定性动作读 README 和项目配置文件判断项目类型和技术栈找到依赖管理文件package.json、requirements.txt、go.mod、pom.xml 等识别入口点包括 HTTP 路由、消息队列消费者、定时任务入口列出项目的暴露面比如对外开放的端口、外部可访问的接口。这一步的价值在于给后续审计提供上下文。agent 只有在知道这是一个 Express MySQL 的 Web 项目之后才知道自己应该重点审视 SQL 拼接、鉴权逻辑、文件上传这类高风险点而不是去查一堆毫无关联的检查项。2.3 扫描阶段模型只做调度和解读不做漏洞数据源这是整个 skill 里最关键的一条设计原则不要让模型直接生成漏洞信息让它去调用工具拿真实数据然后负责解读和归类。道理很简单模型的训练数据是滞后的它可能知道某个依赖在某个大版本存在过漏洞但具体到小版本号它就很可能记混。手动让 agent 通过 curl 去查外部漏洞库也不现实因为很多场景下的环境根本不允许任意外连。所以我在 skill 里把扫描动作规定成脚本调用。比如依赖审计时agent 先运行扫描脚本脚本会把项目锁文件解析出来和本地的漏洞特征库做比对输出一份结构化的 json 结果。agent 拿到这份 json 之后再根据 SKILL.md 里定义的规则把结果整理成有威胁等级、有证据链、有修复建议的审计报告。这个让工具干活、让模型思考的模式极大程度规避了幻觉问题。agent 可以不懂漏洞库的具体内容它只需要理解工具输出的含义并按照既定模板组织语言。2.4 报告阶段输出规范决定了下游能不能直接使用审计报告的格式在我早期的尝试里是最被忽略的环节但它恰恰是能不能落地的分水岭。如果输出是一堆散文式描述审计人员还得自己整理效率并没有提升。我在 SKILL.md 里给每个漏洞条目规定了固定字段漏洞位置文件路径和行号、漏洞类型、影响面、复现步骤、修复建议、威胁等级。同时规定报告必须按威胁等级从高到低排序高风险项必须在最前面。这样 agent 输出的结果可以直接粘进工单系统或者转给对应的开发负责人。3. 从零写一个依赖审计 skill含可直接抄的骨架3.1 为什么先拿依赖审计开刀依赖审计是所有安全审计场景里最适合先做 skill 化的因为它的目标是明确的找出依赖清单里存在已知漏洞、或已经严重过时的第三方包。这个过程逻辑简单、有工具支撑、结果可验证。我见过不少安全团队还在人工核对依赖列表——打开 package-lock.json一个包一个包地查 CVE 库这个动作重复度高、容易出错、效率低下。把它固化成 skill 是最自然的入手点。3.2 SKILL.md 的元信息和任务匹配逻辑主流的 agent skill 约定里都要求 SKILL.md 在开头写清楚自己是什么技能、在什么场景下被触发。这一段信息会直接影响 agent 是否会在正确的时候加载这个 skill。我写的元信息大概长这样--- name: security-audit-deps description: 当用户要求检查项目依赖安全、查找存在已知漏洞的第三方包、审计依赖风险时使用。适用于包含 package-lock.json、yarn.lock、requirements.txt、go.mod 等依赖清单文件的项目。 ---description 要写得像标签一样精准把可能触发这个技能的表述都覆盖到。我见过一些写得特别抽象的 description比如执行安全操作结果 agent 在完全不相关的场景里也加载了 skill干扰了主任务。完善的描述能让任务匹配快、准、稳。3.3 核心流程定义用确定性动作代替开放指令SKILL.md 里最核心的流程部分我现在的写法是给出一串顺序明确的动作指令而不是开放式的分析漏洞当用户请求审计依赖时按以下步骤执行定位项目的依赖清单文件优先使用锁文件package-lock.json、yarn.lock、pnpm-lock.yaml没有锁文件时降级使用 package.json 并显著标注置信度下降。运行 scripts/scan_deps.sh 传入锁文件路径读取脚本输出的 JSON 结果。对 JSON 结果按威胁等级排序提取每个漏洞的包名、影响版本、修复版本和建议。检查是否存在与漏洞相关的实际调用点。这一步要求 agent 在项目代码里搜索该依赖的 import/require 语句判断它是否真的被使用、是否在受影响的代码路径上被调用。生成报告并按固定模板输出。这里有一个刻意而为的设计第 4 步。很多依赖审计工具会把存在漏洞依赖直接报成项目存在漏洞但实际情况是不少有漏洞的依赖根本没有在代码里被调用到实际风险要低很多。我把判断是否有真实调用点这一步放进流程里就是让 agent 不只看工具结果还要结合代码上下文做一层人工复核式的判断。3.4 脚本让工具去查 CVE别让模型自己猜我在整个技能包开发过程中踩过最深的一个坑就是早期没有脚本兜底直接让模型报告项目依赖中的已知漏洞。结果非常惨烈模型给我报了一串漏洞我拿去人工验证发现至少三分之一要么 CVE 编号错误要么影响版本范围对不上要么包的名称根本不存在。后来我写了对应的解析脚本用真实漏洞库数据做比对agent 只负责从结果里筛选、排序、解释。这样设计基于一个朴素的认知大模型擅长的是语义理解和自然语言表达不擅长记忆精确的、快速变化的编号数据。让每个工具做自己擅长的事整体效果才会好。3.5 实测中遇到的意外锁文件格式兼容和依赖来源第一版脚本只支持 npm 的 package-lock.json 格式。结果测试第二个项目的时候就翻车了——那个项目用的是 pnpm锁文件结构和 npm 完全不同脚本直接报错。agent 按照我的指令卡死在流程里整个审计无法继续。这个问题的教训是skill 里不能假设上游环境是理想化的。我在脚本和流程定义里都加了自适应逻辑优先识别锁文件类型JSON 结构里有 lockfileVersion 的是 npm有 packages 字段且没有 lockfileVersion 字段的可能是 pnpm按不同解析器处理解析不了就标注该锁文件格式暂不支持置信度降级。另外我还发现有些项目的依赖里带 git 引用或本地路径引用这类依赖无法用 CVE 库覆盖需要单独归类说明不能混在常规漏洞结果里误导审计人员。4. 把代码审计经验收进去污点追踪与入口思维4.1 代码审计的难度不在查字典而在找路径依赖审计解决的是已知漏洞清单的问题但代码审计面对的往往是业务代码里独有的逻辑漏洞没有现成的漏洞字典可以查。同一个功能模块换一种写法可能就引入了 SQL 注入换一种写法可能就安全了。所以代码审计的 skill 化重点不是给 agent 塞一个漏洞清单而是教会它一套推理路径。代码审计的核心思维模型之一是污点追踪taint tracking。先把用户能控制的输入点标记为污点源再追踪这些数据流向了哪里最终是否到达了能够执行代码、修改数据库、写入文件的危险操作。整个过程可以拆成三个环节找入口、找终点、找从入口到终点的通路。4.2 在 skill 里描述找入口和找终点的具体方法我给 references/dangerous_functions.md 里写了一组常见入口和终点的清单供 agent 快速定位可疑点。入口包括HTTP 请求参数GET/POST 参数、请求头、文件上传内容、环境变量、数据库读取结果、消息队列消息体。终点包括SQL 语句拼接、系统命令执行如 Python 的 os.system、subprocess 调用JS 的 child_process.exec、文件路径拼接导致的任意文件读写、反序列化操作、模板引擎的渲染函数。但仅仅给清单是不够的关键在让 agent 理解怎么从入口追踪到终点。我在 SKILL.md 里举了一个具体例子假设在 Flask 项目里看到某个路由函数接收了 request.args.get(name)随后这个值被直接拼进了一条 SQL 语句。这时 agent 应该给出的不是一句轻飘飘的存在 SQL 注入风险而是要完整描述证据链入口在哪个路由、参数名是什么、数据流经过了哪些变量和函数、最终在哪里拼接进了查询语句、具体是哪一行触发了执行。我把这套要求命名为证据链规则并把它设为代码审计报告的最低门槛。4.3 危险函数清单的实际应用它只是一个初筛器我见过有人把危险函数清单设计成命中即漏洞的规则这是很危险的简化。eval 函数在面试题里被判死刑但在很多动态配置系统里确实有合理的应用场景。真正要判断的是这个 eval 的输入是否可控、是否有上下文保障。所以我在 SKILL.md 里明确要求references/dangerous_functions.md 里列出的函数只是初筛信号命中之后不能直接定级为漏洞必须继续做输入源追溯。把这条规则写进 skill 是一个我认为非常重要的审慎设计它避免了 agent 输出大量伪漏洞也减少了因为误报而让审计人员对 skill 失去信任的风险。4.4 代码审计阶段的人工兜底skill 的边界声明代码审计比依赖审计复杂得多。业务逻辑漏洞、越权访问、并发竞态条件这些问题目前的 agent skill 很难稳定识别。我并没有试图让 security-audit-skill 把所有难题都包圆而是在它的边界声明里明确写了几条本 skill 能辅助完成代码层面的安全扫描、风险点定位和初步建议本 skill 不自动修复代码不负责漏洞定级最终判断涉及业务逻辑层的高风险判断必须在人工复核之后才可进入报告所有审计动作仅针对已授权的项目环境禁止对未授权目标执行任何安全检查。边界声明不是免责声明它很重要的一点是给 agent 一个世界观它应该知道自己的能力范围避免为了讨好用户而给出超出可靠性的结论。我在实测中发现写明边界的 skill 反而输出质量更高因为 agent 不再强行编造它不确定的内容。5. skill 上线之后评估、维护和人工兜底5.1 用已知漏洞的测试项目做回归无论 skill 写得多完整最后都要用结果说话。我给 security-audit-skill 准备了一个专门的测试项目库一个故意包含多种漏洞类型的 Web 应用包括 SQL 注入、路径穿越、命令执行、反射型 XSS、不安全的反序列化以及若干存在已知漏洞的依赖包。每次改完 SKILL.md 或者脚本我都会在这个测试集上跑一遍记录各项漏洞的检出率和误报率测试项期望行为实际结果SQL 注入链路检出并给出完整证据链通过依赖漏洞检出并指向真实 CVE 编号通过误报控制合理上下文中的 eval 不报高危通过这个回归过程帮我发现过不少问题。比如有一版我在 SKILL.md 里加了一段历史漏洞分析的指令结果 agent 在测试集里把一处已经验证过存在过滤条件的位置硬说成命令注入增加了误报。通过回归测试我才发现了这个倾向并及时回退了版本。所以建议所有做 skill 的人都建立自己的回归集别靠感觉判断 skill 的好坏。5.2 维护节奏与迭代策略skill 不是写完就完事的静态资产。至少有两个方面需要定期维护一是漏洞特征库的数据源要定期更新否则扫描结果会漏掉新披露的漏洞二是 SKILL.md 本身的流程和参考文件要根据实际使用反馈持续迭代。我迭代 skill 的方式比较朴素每次用 agent 做审计的时候凡是遇到模型表现不符合预期的场景我都会记录下来然后判断是流程定义不清楚、参考文件范围不够还是 prompt 引导方式有问题。根据原因去修改对应的部分。这个小习惯让我在短短一个月里迭代了十几版每一版都能感知到输出质量的提升。另外需要留意的是不同 agent 运行时对同一份 SKILL.md 的理解程度有差异。我实测下来同样的 skill 在 Claude 和 Codex 上表现不完全一致主要差异体现在工具脚本的主动调用时机和长文本指令的遵循程度上。跨平台使用时建议按平台微调 SKILL.md 中的表述风格。5.3 自动化审计不能替代人的复核说到最后还是要泼一盆冷水security-audit-skill 能帮我处理掉大量重复性、机械性的扫描和初筛工作但它的定位始终是审计员的放大器。依赖审计、已知漏洞比对、代码危险点定位、报告草稿整理这些是它能稳定做好的事。但最终的漏洞定级、业务逻辑风险的判断、修复方案的合理性和优先级排序我还是要求人工把关。我在实际使用中的分工方式是agent 跑完所有流程产出带证据链的报告初稿我花十几分钟快速复核关键结论确认没有明显误报或漏报后才把报告转给研发团队。这样的配合模式才是 skill 在安全审计场景下最高效的打开方式。单靠 skill 全自动出报告就表决早晚会出事。如果后续有人打算做类似的安全 audit skill我给的建议是从依赖审计这种目标明确的小场景起步把流程跑顺了再逐步扩展到代码审计和更开放的安全分析场景。别想着一口气覆盖所有安全问题——把一个小场景做到可用远比做一个大而全但不稳定的东西有价值。
返回列表