ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent安全审计技能包设计:从误报到可复用的Skill实战

AI Agent安全审计技能包设计:从误报到可复用的Skill实战 上个月我把一个任务交给公司的Agent跑——审查一下这个仓库的依赖安全性结果它给我输出了一份漂亮的报告列出了七八个高危漏洞其中六个我拿CVE编号去一查版本都对不上。那一刻我意识到安全审计这件事不能靠模型临场发挥必须把它固化成一套可复用的能力。后来我花了两周时间把团队内部的安全审计方法、规则库、工具调用流程和报告标准封装成了一个标准化的Skill——security-audit-skill。这篇文章就用这个项目当引子把这个技能包的设计思路、具体实现和踩坑记录完整摊开讲一遍。聊清楚三件事Skill到底是什么、安全审计技能包到底做成什么样、以及它跟Agent的分工边界在哪里。1. 为什么安全审计必须技能化而不是靠模型自由发挥1.1 Skill的本质给模型一份可执行的操作规范先明确一个概念。在AI Agent的体系里Skill技能是指一个带结构化的能力模块通常包含三部分一段描述何时用、怎么用的指令文本、一组可调用的工具或脚本、一套约束输出格式和判断标准的规则。模型本身不存储这些知识它在运行到相关任务时通过检索机制把Skill加载进上下文然后按照Skill给出的步骤、规则和工具去执行。打个比方模型本身像一个有专业知识但容易飘的新人你直接问他这个项目安全吗他能说出一堆正确的废话但你递给他一本《安全审计作业指导书》上面写了先看什么、用什么工具查、达到什么条件才算漏洞、报告怎么写他的输出质量立刻上一个台阶。Skill就是这本作业指导书而且是可装载、可版本管理、可被多个Agent共享的指导书。1.2 安全审计对一致性的要求比一般任务高得多为什么写文案、写摘要这类任务可以靠模型自由发挥安全审计却不行因为审计结果是要被当作决策依据的。我说一个真实场景开发团队收到Agent的漏洞报告说某个依赖存在反序列化漏洞开发按报告去升级版本结果发现升级后业务崩了再一查发现报告里说的漏洞在目标版本中根本不存在。一次误报消耗的是整个团队对自动化审计工具的信任。安全审计技能化的核心价值就是把审计过程中的判断标准从模型的隐性知识里剥离出来变成显性规则。什么情况判高危、什么情况需要人工复核、证据链必须包含哪些字段全部写死在Skill里。模型只负责执行规则和汇总证据不负责拍脑袋。这样才能保证这次审计和下次审计的标准是一致的换一个模型来执行结果也是相近的。1.3 技能包能解决模型不知道用什么工具的问题还有一个平时不容易注意到的点模型根本不了解你这套CI/CD环境里装了哪些扫描器、漏洞库更新到哪个版本、哪些目标目录可以扫哪些不能扫。如果让模型自己决定调用什么工具它大概率会编一个不存在的命令。Skill的另一种形态是主动声明这个环境里可用以下工具列表每个工具的参数约定如下。模型只要按照Skill里的工具说明去调用就行了遇到Skill没覆盖的情况就停下来问人绝不自己发明。2. security-audit-skill的设计骨架能力清单与协议先行2.1 先列能力边界再写实现动手写Skill之前我先列了一份能力清单。这很重要因为Skill最怕做成什么都能干一点点但什么都干不透的万金油。我最终圈定了下面五个核心审计域审计域典型检查项产出物依赖与供应链已知CVE匹配、lockfile版本核对、间接依赖风险依赖风险清单密钥与敏感信息硬编码密钥、Token、数据库连接串、公网泄露痕迹泄露点清单配置弱点越权配置、Debug开关开启、错误CORS、权限过大配置风险清单权限与访问控制IAM策略过度授权、默认账号、角色继承链权限风险清单行为与数据安全日志中打印敏感数据、明文传输、危险的系统调用数据风险清单这五个域不是拍脑袋定的而是从团队过去一年真实处理过的安全事件里反推出来的高频项。你在做自己的Skill时也建议先翻翻历史工单什么样的审计需求重复出现就把什么做进技能包而不是照抄别人的清单。2.2 SKILL.md的描述文件结构Skill的主体是一个Markdown文件一般命名为SKILL.md放在项目根目录。这个文件不是给人看的文档而是给模型看的说明书所以格式和措辞很讲究。我常用的框架是--- name: security-audit-skill description: 对代码仓库、依赖清单、配置文件实施结构化安全审计。 当用户要求检查安全性 漏洞 风险 审计时使用本技能。 ---description要写得像搜索引擎索引词一样明确什么场景触发、什么场景不触发。我做了一个小技巧在description里写清楚不要做什么——比如不要在未授权的情况下扫描外网目标不要对生产环境执行破坏性命令。这能在很大程度上减少模型乱用Skill的概率。正文部分我按顺序组织成下面几个板块前置条件检查确认审计目标、获取仓库路径、确认权限范围执行流程按依赖→配置→密钥→权限→行为的顺序逐项扫描工具调用规范列出可用命令和参数示例判断标准明确各风险等级的判定条件报告模板规定输出字段、排序规则和建议格式2.3 输入输出协议决定Skill能否被Agent有效编排既然Skill要被Agent调用输入输出就不能随意。我设计了两个结构化的JSON格式一个作为审计任务输入一个作为审计结果输出。输入协议的核心字段{ target: repo_path_or_url, scope: [dependencies, secrets, config, permissions, behavior], depth: quick|full, exclude: [vendor, node_modules, dist], report_style: summary|detailed }输出协议的核心字段{ summary: { total_findings: 5, critical: 1, high: 2, medium: 1, low: 1 }, findings: [ { id: SEC-2024-001, domain: dependencies, severity: critical, title: lodash4.17.21存在原型污染漏洞, evidence: {file: package.json, line: 32, excpt: \lodash\: \^4.17.19\}, reason: 当前版本低于安全修复版本4.17.21, suggestion: 升级到4.17.21 } ], unresolved: [需要人工确认的条目列表] }注意最后那个unresolved字段。审计过程中必然存在模型判断不了的情况比如某个自定义权限配置到底是不是业务故意为之。Skill必须允许模型把这类问题标记为待人工确认而不是硬着头皮定一个级别。这一条是审计技能包的底线原则。3. 把审计规则落成可执行的检查项3.1 依赖审计不能只信模型的CVE知识依赖审计是最容易做也最容易翻车的模块。模型的训练数据里确实有大量CVE信息但它记不住每个CVE影响的具体版本区间也没法实时更新。所以我在Skill里明确规定依赖审计必须调用本地工具不允许模型凭记忆判断。我的执行逻辑是这样的# 1. 提取所有依赖清单文件 find . -name package.json -o -name requirements.txt -o -name go.mod -o -name pom.xml # 2. 生成锁定文件/解析实际安装版本 # package-lock.json、poetry.lock、go.sum 是最佳数据源 # 3. 通过扫描器比对漏洞库 # 可选工具: osv-scanner(免费, 数据来自OSV.dev)、trivy、pip-audit osv-scanner -r --lockfile /path/to/package-lock.json为什么强调lockfile而不是直接看package.json因为package.json里写的是版本范围比如^4.17.19实际安装的可能是完全不同的版本只有lockfile记录了确切的解析结果。让模型直接看package.json判断版本是依赖审计误报率最高的原因之一。扫描输出按标准格式整理后模型再根据Skill里的判断标准分级。这里我又加了一条规则封闭网络环境里如果扫描器无法连接漏洞库导致扫描失败Skill会明确输出扫描未完成而不是让模型假装扫描过。3.2 密钥检测正则会漏纯AI会编要两者结合硬编码密钥检测是个典型的看起来简单做起来难的模块。简单的正则匹配能查出一部分但漏报率很高—比如一段Base64编码的密钥、用变量拼接的Token、藏在注释里的API Key正则根本发现不了。反过来如果完全交给LLM去识别它又会因为觉得某段代码像密钥而误报。我最后的方案是两层叠加第一层用基于熵的检测扫描gitleaks、trufflehog这类工具它们能在一堆代码里挑出高熵字符串对Base64、JWT、私钥块等形态有内置规则命中后记录文件位置和上下文。第二层把第一层的命中结果交给模型做上下文验证。模型去看这段高熵字符串旁边有什么注释、赋值语句、调用方式判断它是测试数据、样例代码还是真实的密钥。这一步能把误报率压掉一半以上。不过说实话密钥审计里最关键的发现往往不在扫描器里而在命名习惯上。比如一个变量叫password、secret、token取值是一串大写字母和数字的组合哪怕熵不是特别高也应该列为可疑项。这类命名暗示规则我会写进Skill的规则库里让模型专门留意。安全审计玩到后面就是跟开发者的命名习惯博弈。3.3 配置与权限审计重点看默认值和过度授权配置审计的核心不是找错误而是找跟安全基线不一致的地方。我在Skill里内置了一份基线配置库涉及Nginx、Docker、Kubernetes、云IAM等常见场景。比如查Dockerfile时明确要求检查镜像是否带latest标签、容器是否以root运行、是否存在--privileged参数、.dockerignore是否排除敏感文件。这一块最花时间的是权限模型检查。我曾花了一个周末梳理Kubernetes的RBAC策略只为了总结出一组危险配置模式绑定到cluster-admin的ServiceAccount哪怕它只用在一个namespace的小应用*通配符出现在verbs或resources字段中允许create pod/exec但没有配套的PodSecurityPolicy限制长期存在的未轮换ServiceAccount Token这个列表越具体模型执行起来就越不容易跑偏。Skill里的规则就要写成这样一条一条可判定的比如如果subjects包含system:anonymous或者system:unauthenticated则标记为高危。模糊的表达例如检查权限是否过宽这种模型根本没法可靠执行。4. Skill和Agent的边界为什么做成Skill而不是Agent4.1 可复用性决定了这个问题的答案学术界和工程里经常争论Skill和Agent到底什么关系。我一直用一句话回答Agent是执行单元Skill是Agent的能力组件一个Agent可以挂载多个Skill一个Skill可以被多个Agent复用。拿安全审计场景来说如果你的安全审计能力是独立Agent那它就只能被当成一个单独的对话入口来用。但实际工作流往往是研发助手Agent在写完代码后需要自动触发一次安全自检代码评审Agent在合并请求中需要扫描变更文件有没有引入新漏洞运维Agent在处理配置变更时需要判断新配置有没有违反基线。这些场景都需要安全审计能力但它们各自的编排逻辑完全不同。如果审计能力是Agent你得为每一个场景单独调一次Agent接口还得处理Agent之间的对话和上下文交接如果审计能力是SkillAgent只需要在合适的节点把任务交给Skill执行拿回结果继续自己的流程整个编排轻量得多。4.2 编排与执行分离带来的实际好处把安全审计做成Skill在工程上还有一个很明显的优势只有一套审计规则维护点所有Agent共享同一套逻辑更新一次规则所有地方生效。坏处是存在一点其实也常见的不利即需要保证多个Agent在调用Skill时的参数传得对。这个通过输入协议能约束住。这里有一个边界条件可以掰开讲什么时候应该把Skill升级成Agent我的判断标准是如果这个能力需要维护独立的对话状态、需要跨多个会话持续跟踪审计结论、需要处理用户的主动追问和多轮澄清那它就需要Agent级的状态管理。但如果只是给你一个任务跑完返回结果Skill就足够了。安全审计目前属于后者——它不需要跟用户多轮聊天给出证据链和结论就行了。所以我在项目里坚持不单独建Agent只维护Skill。4.3 Agent编排一个审计Skill的实际效果我举个例子团队里的代码生成Agent每次生成完一个新模块会自己调用security-audit-skill做一次快速扫描。它做的事情非常简单确认生成的代码路径传给SkillSkill内部执行依赖检查、密钥检查、危险函数调用检查拿回结果把critical和high的问题直接追加到交付注释里medium和low的问题整理成待处理列表整个链路里Agent没有做任何安全判断它只是编排判断全在Skill里。安全团队只需要维护Skill里的规则和工具不用去管每个Agent怎么写的。这个分工是这套架构里最有价值的部分——安全能力从跟某个Agent绑定变成了跟整个Agent系统绑定。5. 实测记录从误报泛滥到勉强可用我踩过的几个坑5.1 坑一模型会为了完成感编造证据第一个版本上线测试的时候我发现一个非常严重的问题模型在找不到明确漏洞时有时候会编一个不那么精确的证据链。比如把package.json里某一行的版本号抄错了或者把某个配置文件的路径写了不存在的目录。排查之后我确认根因是Prompt里请输出审计报告这个指令太开放模型误以为报告必须找出问题才算完成。我做的修改是Skill里明确写了一句没有发现问题的项直接输出未发现风险不要补修饰语如果证据不完整使用unresolved标记。这句简单的约束直接让编造证据的比例下降了80%。这个教训非常通用任何让模型做检查类任务的Skill都必须显式允许没问题这个答案并且要让没问题和有问题在输出结构上同样容易产生。否则模型的默认行为会偏向于找出点毛病来证明自己的工作价值。5.2 坑二全量扫描时token消耗暴增大仓库直接超时一开始做依赖审计时我用bash命令把扫描器输出直接丢给模型解析。遇到一个大型monorepo仓库扫描结果有几万行JSON模型直接上下文溢出。我后来的做法是分阶段截断依赖扫描和密钥扫描等自动化工具的输出先由Skill内部的脚本预处理我只让模型看到一个聚合后的摘要规则是扫描器命中的每个问题只保留文件路径、行号、命中规则ID、一行说明这4个字段全部命中条目超过200条时强制分组统计不再逐条给模型看对高分险项再要求模型读取对应文件的上下文。这个设计并不复杂但极其重要。Skill不光是给模型加能力也是给模型减负担。好的Skill应该尽量把机器擅长的事大规模匹配、正则扫描交给脚本把模型擅长的事上下文判断、结论生成留给模型而不是让模型硬吞海量原始数据。5.3 坑三多文件关联的漏洞单文件审计发现不了单个文件级别的检查做了三周后我开始遇到一类新问题真正的安全隐患往往藏在文件调用链里。比如A模块调用了B模块的某个函数B函数内部的参数校验做得很差而A模块把外部输入直接传了进去。如果Skill只按文件扫描B函数那层是看不到这个输入不可信的必须沿调用链追溯。这类问题我把解决方案分成了两层。第一层是规则层增加一个跨文件调用检查的流程要求模型在发现危险函数如eval、child_process.exec、System.exec等时向上追溯一层调用来源如果来源是外部接口、用户输入解析模块、反序列化函数就升级为高风险。第二层是靠人工抽查补充Skill的报告模板里增加了一个manual_reviewed字段每次审计随机抽5个低风险项要求人工复核用抽检结果来反推规则能不能再收紧。5.4 坑四Skill的更新很容易影响其他Agent的行为最后一个坑也是所有做Skill的人都会踩的Skill文件更新后之前测试过没问题的场景可能突然就挂了。有一次我更新了规则库里的一条判定条件把某个配置从中危改成高危结果下游的配置自动修复Agent看到高危就直接改了生产配置差点出事故。从那以后我在Skill里加了一个behavior段明令禁止修改任何配置文件审计Skill只输出报告不做变更。安全审计跟安全修复必须解耦Skill和Agent的边界也在这里体现得淋漓尽致审计Skill负责发现问题自动修复是另一个Skill或者另一个Agent的工作两者永远不要混在一起。这不是技术做不到而是责任边界必须清晰——出了事故才能追溯。6. 从代码仓库到业务逻辑安全审计Skill的下一步扩展6.1 从规则驱动走向资产驱动的审计代码仓库审计做扎实之后我开始把同样的思路迁移到业务安全审计。你会发现安全审计的本质不是找毛病而是建立一套可复用的审查标准。业务侧的安全审计跟代码侧不太一样它没有lockfile可以做版本比对也没有正则能扫出密钥。它的核心是梳理资产目录和授权矩阵哪个服务可以访问哪个数据库、哪个边界网关暴露了哪些内部接口、某个离职员工的Token是不是还在有效期内。这类工作极度依赖整理结构化的信息而Skill正好适合做这件事把信息收集的规范、字段定义、检查步骤固定下来模型照着做就行。6.2 人机协同的审计闭环怎么搭Skill化的另一个衍生价值是让审计结果变得可追溯、可复核。以前的AI审计是一锤子买卖模型输出一段结论人看完就完了。现在的Skill输出是一份结构化报告每条发现都有证据、有判断依据、有建议操作人可以直接在报告基础上回复这条误报这条升级——反馈内容再次进入规则库Skill就变成了一套不断学习的系统。我最近就在做这件事把人工复核结果的标注数据每周同步一次到规则库生成新的判定规则。比如有开发反馈这个服务用的Token本来就是要公开展示的不是泄露我就把这条加入Skill的allowed_patterns排除规则里。几轮迭代下来误报率肉眼可见地下滑。6.3 给打算自建Skill的人几条实操建议结合这段时间的维护经验最后给几条实在的建议先花两天时间梳理自己的安全审计流程把每一步的输入输出写下来Skill只是把这段流程数字化流程不清技能包写出来也没用不要追求大而全先覆盖两三个最高频的审计场景跑通了再加在SKILL.md里写清楚禁止做什么有时候比要做什么更重要每次迭代只改一个变量改规则就只改规则改流程就只改流程方便定位是哪次改动影响了结果一定要保留旧版输出记录方便前后对比不然你根本说不清改动是变好了还是变差了这几个月在Agent工程化上最深的体会是模型本身的能力提升已经很快了但真正拉开效果差距的永远是你喂给它的那套方法论。Skill就是方法论的载体security-audit-skill只是其中的一个例子但它的设计思路——能力清单、协议先行、规则显性化、人机闭环——可以平移到你自己的任何领域里。如果你正在做Agent开发我建议从今天开始想一想你手头有什么任务是值得技能化的然后动手做一个原型出来跑几轮你就明白这里面的门道了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进