26.1% 的 Agent 技能带漏洞:skills 霸榜 GitHub 当天,NVIDIA 和 OpenAI 同时掏出安检仪

0 阅读13分钟

三万一千一百三十二份 AI Agent 技能摆上扫描台,结果是其中 26.1% 带漏洞、5.2% 有恶意嫌疑。这不是安全厂商的恐吓营销,是 NVIDIA 在 SkillSpector 仓库里公开的研究数据集结论。同一天,GitHub 趋势榜被 skills 类仓库刷屏:addyosmani/agent-skills 一天涨 812 星、总量逼近 12 万星,而 NVIDIA 的 SkillSpector 和 OpenAI 的 codex-security 两个安全工具同时挤进日榜前列。技能生态爆发和安检工具走红发生在同一天,这不是巧合,是生态撞上了自己的信任天花板。当任何人都能把一段指令包装成技能分发出去,装机量的每一次增长都在同步放大攻击面,安检从可选项变成了入场券。

先把数字摆上桌

研究数据集的样本量是 31,132 份技能,来自真实的技能分发渠道。26.1% 的漏洞率意味着随手装四个技能,平均就有一个带着可被利用的缺陷;5.2% 的恶意嫌疑意味着每装二十个技能,大约就有一个是冲着你的环境来的。这两个数字放在一起,足以改变「装个技能而已」的心理预期。

技能生态的膨胀速度同样有数字可查。2026 年 11 月 12 日的 GitHub 趋势档案里,addyosmani/agent-skills 以 116,581 星排在 JavaScript 类第一,单日新增 812 星;last30days-skill 累计 67,432 星;marketingskills 59,944 星;claude-code-templates 33,420 星。技能仓库占据榜单多个席位,这在半年前是不可想象的。

需要先对齐一个概念:这里说的技能,是一段 SKILL.md 说明文件加上配套脚本的组合包,Claude Code、Codex CLI、Gemini CLI 这类 agent 工具加载后会按其中的指令行动。它和 npm 包不同,没有注册表审核,没有锁文件,安装动作往往就是克隆一个仓库或者复制一段文本。

正因为没有审核环节,技能包长期处在隐式信任状态。你让 agent 读一份技能文件,等于把陌生人写的操作说明直接交给一个有终端权限的执行者。过去半年大家忙着享受技能带来的能力扩展,几乎没人停下来问一句这份说明书里写了什么。

所以这一天两个安全工具同时上榜才值得记录。SkillSpector 单日新增 302 星,codex-security 单日新增 663 星、冲到总榜第二。供给侧在拼命生产技能,需求侧开始愿意为安检付费,两边在同一时间交汇,说明市场已经意识到裸奔期结束了。

SkillSpector 的十七类七十一项

SkillSpector 是 NVIDIA 开源的专项扫描器,Apache 2.0 协议,2026 年 3 月创建,到 11 月中旬已经积累两万五千多星、发了 22 个版本。它的核心检测能力是 71 个漏洞模式,覆盖 17 个风险类别,专门针对技能包这种「指令加脚本」的混合载体设计。

十七个类别里有几个是软件供应链的老面孔,比如提示注入、数据外泄、权限提升和供应链风险。也有 agent 时代才出现的新物种:记忆投毒针对的是 agent 的长期记忆文件,流氓 agent 检测的是技能诱导执行者脱离既定任务的行为,反拒绝模式专门找那些教 agent 绕过安全限制的写法。

触发器滥用这一类值得单独说。恶意技能可以把危险动作藏在特定关键词后面,平时扫描看不出问题,只有当用户说出某个词时才触发。静态文本审阅很难发现这种设计,因为它在语法上完全正常,恶意体现在语义和时序上。

针对 MCP 协议的技能,SkillSpector 还单独设了最小权限和工具投毒两个类别。工具投毒指的是技能篡改 MCP 工具的返回内容,让 agent 基于被污染的数据做决策。本账号之前写过 MCP 协议本身,这里只提示一点:协议层的安全设计和技能层的内容安全是两回事,后者长期没人管。

技术实现上,扫描器走两阶段路线。第一阶段是快速静态分析,包括危险代码的 AST 检测、污点追踪和 YARA 签名匹配,纯本地完成,不需要任何 API key。第二阶段是可选的 LLM 语义评估,把静态分析拿不准的样本交给模型判断,处理那些藏在自然语言指令里的恶意。

漏洞库方面,它的 SC4 模块会实时查询 OSV.dev 拿到最新 CVE 数据,网络不可用时自动降级到离线模式继续扫。这个设计很务实:安检工具自己不能因为断网就放行,也不能因为断网就罢工。

装之前先扫一遍

上手路径很短。机器上有 uv 的话一行装好,没有的话用 pip 也一样。装完就能对本地目录、单个 SKILL.md、远程 Git 仓库地址甚至 zip 压缩包发起扫描,不需要先搭建什么环境。

uv tool install git+https://github.com/NVIDIA/skillspector.git
skillspector scan ./my-skill/
skillspector scan https://github.com/user/my-skill --no-llm
skillspector scan ./my-skills/ --recursive --max-skills 100 --format json --output report.json

第一条命令完成安装,第二条扫本地技能目录,第三条直接扫远程仓库并跳过 LLM 阶段,第四条递归扫描整个技能集合。递归模式默认上限是 32 个技能,集合更大时用 max-skills 参数调高,但仓库自己也提醒,调高不代表能扫完,发现数量和报告体积仍有预算限制。

预算限制是显式的:远程下载和压缩包解压总量上限 100 MiB,单个 zip 的成员数上限一万,单个分析文件上限 1 MB。触碰任何一个上限都会以 IngestLimitExceededError 拒绝继续,宁可扫不成也不半截开工,这个态度对安全工具来说是对的。

输出格式有四种:终端直读、JSON、Markdown 和 SARIF。日常自用看终端输出就够;想留档或者接自动化就落 JSON;SARIF 是给 CI 流水线和 IDE 用的标准格式,GitHub 的代码扫描面板能直接吃。

需要批量扫整个技能库的话,仓库 contrib 目录里有现成的 batch_scan 模块,支持多进程并发和中日韩多语言检测。默认的 LLM 后端配置瞄准 DeepSeek,理由是公开服务里最便宜,不过文档也坦白 DeepSeek-Chat 预计下线,欢迎社区贡献更通用的后端。

有一个 Windows 用户要注意的细节:扫描报告写文件依赖 POSIX 的 no-follow 目录操作,Windows 上文件输出不可用。解决办法是把输出省掉走标准输出重定向,或者干脆用仓库自带的 Dockerfile 跑容器,挂载当前目录进去扫,不依赖本机 Python。

风险分、退出码和门禁

扫描结果不是一长串看不懂的告警,而是一个 0 到 100 的风险分,配上等级标签和明确建议。分数背后是按严重度加权的发现项,每一项都带类别、位置和证据,能顺着报告找到技能文件里的具体行。

真正让工程团队能用的是退出码契约。风险分不超过 50、且没有触发任何严格门禁时,进程退出码为 0;超过 50 或者命中失败条件则返回 1。这意味着你可以把扫描塞进任何脚本,用最简单的退出码判断决定要不要继续安装,不需要解析报告正文。

CI 集成的标准做法是把输出切成 SARIF,配上一行失败条件。扫远程仓库、生成报告、按退出码卡断,三步就是一条完整的安装前流水线。

skillspector scan https://github.com/user/my-skill --format sarif --output report.sarif --fail-on-incomplete
echo "exit=$?"

误报处理也想好了。用 baseline 子命令把当前所有发现项存成基线文件,之后的重扫只报告新增问题,已知且接受的风险不再刷屏。基线文件本身用 0600 私有权限写入,替换走原子写,防止扫描工具自己的产物成为新的攻击面。对长期维护多个技能的团队来说,基线机制是避免告警疲劳的关键,没有它,扫描报告撑不过两周就会被所有人无视。

fail-on-incomplete 参数解决的是另一个隐蔽问题:技能集合太大时扫描可能不完整,默认行为是照常出报告但在报告里标注覆盖不全。CI 里加上这个参数,覆盖不完整直接判失败,不让「没扫完」冒充「扫干净」。

SkillSpector 和 codex-security 不是一回事

同一天上榜的两个安全工具经常被放在一起说,但它们的扫描对象完全不同。SkillSpector 扫的是你要装的技能包,回答的问题是「这份来自陌生人的指令集能不能信任」;codex-security 扫的是你自己仓库里的代码,回答的是「我写的系统有没有漏洞」。一个管入口,一个管自家。

维度SkillSpectorcodex-security
扫描对象第三方 agent 技能包自己的代码仓库
安装方式uv 或 pip 安装 Python CLInpm 全局安装 cs 命令
核心输出0-100 风险分加退出码扫描报告加修复补丁
凭证要求静态扫描零凭证OpenAI 密钥或 Bedrock 等
CI 形态SARIF 加退出码卡断官方 Action 每周扫
运行环境Python 3.12 及以上Node 22.13 加 Python 3.10

codex-security 的用法同样直接:npm 全局装好后 cs login 登录,仓库根目录执行 cs scan 即可。它支持按路径缩小范围、用 diff 参数只扫某个提交以来的变更,还有深度模式会做重复扫描并去重发现项。官方提供的 GitHub Action 模板默认每次扫描预算上限 5 美元,适合作为周常体检。

凭证层面的差异值得留意。SkillSpector 的静态阶段完全不需要任何密钥,LLM 阶段默认走 DeepSeek 这类廉价公开服务,也支持 Claude、Gemini、OpenCode 的本地 CLI 会话,后者连 API key 都不用配。codex-security 则绑定 OpenAI 生态,可选 Bedrock、OpenRouter、Fireworks 等提供方。

实际工作流里两者是接力关系。往 agent 环境里引入任何第三方技能之前,先过 SkillSpector;给自己的仓库提交代码之前,让 codex-security 过一遍。前者挡住带毒的外部输入,后者拦住自己写出来的洞,缺一边都不完整。

从能跑到敢装

技能包正在变成新的供应链入口,这个位置浏览器扩展站过、npm 包站过、VS Code 插件也站过。每一次的结局都一样:生态先野性生长,然后出几起投毒事件,最终逼出审核机制和扫描工具。agent 技能的特殊性在于它不止是可执行代码,还是给执行者看的自然语言指令,攻击面比传统包更大。

NVIDIA 显然不满足于只做一个扫描器。SkillSpector 是它 Verified Skills 流水线的第一环,完整的链路是扫描、评估、签名,通过的技能进入官方技能目录发布。这个结构和操作系统应用商店的审核加签名如出一辙,只是这次审核员是机器。

签名目录的意义在于把安全责任从个人转移到平台。今天你装技能前自己扫一遍,是在为自己的环境负责;目录机制成熟后,「只装签名技能」会成为团队规范,就像现在没人会禁用操作系统的签名检查一样。目录里每一件技能背后都站着一次可复查的扫描记录,出了问题能追溯到是哪个环节放的行,这种可归责性是野生生态最缺的东西。

对正在写技能的人来说,变化同样直接。技能文件会被各种扫描器当作审计对象,写技能时留下权限最小化注释、避免要求不必要的工具访问,很快就会像写代码时处理 lint 告警一样成为日常。反过来,懂得在技能里显式声明权限边界的作者会获得信任溢价,签名目录也会把这类作者推到更显眼的位置。生态从比谁能跑,进入比谁敢装的阶段,野蛮生长的窗口正在关闭。

以后装 skill 的固定流程

总结成习惯只需要三步:扫、读、记。扫是安装前必跑一遍静态扫描,零凭证零成本,十秒内出结果。读是按类别看发现项,而不是只瞟一眼总分,数据外泄和记忆投毒这两类在任何分数下都值得点开看证据,因为它们直接对应密钥被偷和长期记忆被改写这两种最难察觉的事故。

记是把已接受的发现写进基线。第一次扫某个技能时认真研究每一项告警,确认可接受就存进基线文件,之后版本升级只看重扫报告里的新增部分。这个习惯能把每次升级的安检成本从十分钟压到一分钟,也让「上次那个告警我看过」有了书面凭据,而不是凭记忆拍胸脯。

团队场景再往前一步:把扫描嵌进技能引入流程,没有 SARIF 报告和零退出的记录就不允许合并。这条规则和依赖包要过漏洞扫描是同一性质,区别只是多数人还没意识到技能包也需要同等待遇。等第一次有同事拿一份带外泄模式的技能提交评审时,流程的价值会自己说话,不需要安全负责人再额外布道。

工具已经备好了,而且是免费的,连凭证都可以不配。三万份样本的扫描结果告诉我们,每四个技能里就有一个不值得信任,每二十个里就有一个可能是冲着环境来的。从今天起,装之前先问一句「扫过了吗」,是这个生态送给每个使用者的最低成本保险,也是 Agent 时代装机礼仪的第一课。