先说赛道:2026 年 3 月起,skill 成了新的 npm
工坊写过 agent-skills、agentic-awesome-skills、superpowers——那时大家关心的是「去哪找 skill」。2026 年上半年冒出来的新问题是「装的这个 skill 有没有毒」。SkillSpector 引用的那篇研究给了个足够吓人的数字:42,447 个 skill 的实证研究里,26.1% 至少含一个漏洞,5.2% 表现出明显恶意意图,而带可执行脚本的 skill 中招概率是纯文档 skill 的 2.12 倍。
这个恐惧催生了一条清清楚楚的赛道,而且是大厂集体下场:
| 项目 | Star | 出身 / 建仓 | 路线 |
|---|---|---|---|
| NVIDIA/SkillSpector | 17,069 | NVIDIA,2026-03 | LangGraph 27 节点扇出 + YARA/AST/污点 + LLM 精修,接 NVIDIA 签名与 skills catalog |
| Tencent/AI-Infra-Guard | 6,255 | 腾讯,2024-12 | 全栈 AI 红队平台:Agent Scan + Skills Scan + MCP Scan |
| snyk/agent-scan | 3,034 | Snyk,2025-04 | 「发现并扫描你机器上所有 agent 组件」,uvx 一把梭,README 自己标 CLI 输出 experimental |
| cisco-ai-defense/skill-scanner | 2,518 | Cisco,2026-01 | YAML + YARA-X 模式、AST/数据流、可选 LLM 裁判,外加一层 cel-go 有界决策层;SARIF 进 GitHub Code Scanning、有 pre-commit hook |
四家的技术路线其实高度重合(模式匹配 + AST/污点 + LLM 语义 + SARIF),差别在工程取向:Snyk 主打「扫全机器」,腾讯主打「一个平台全包」,Cisco 主打「进 GitHub Code Scanning 的 CI 集成」,NVIDIA 主打「扫完给签名、进官方目录」——它不只是个扫描器,是 NVIDIA 那个 skill 应用商店的质量闸门,这也是它 Star 最高的原因之一。
它是怎么实现的:27 个分析器扇出,一台 LangGraph 流水线
装完之后我直接扒了包内的源码(uv tool install 装到 ~/.local/share/uv/tools/skillspector/,包内 4.1MB / 49,721 行 Python)。执行图极简,没有条件边,纯扇出扇入:
resolve_input → build_context → [ 27 个 analyzer 并行 ] → meta_analyzer → report
- analyzers 是自动发现的:
nodes/analyzers/__init__.py扫包内模块注册节点,实测ANALYZER_NODE_IDS长度 27(README 说「11 个静态分析器」,开发文档说「22 个节点」,两个数字都过期了); - 四层检测叠着上:regex 模式(19 个
static_patterns_*.py)→ YARA 签名 → Python AST + 污点追踪(source→sink 数据流)→ 可选 LLM 语义(三个分析器:安全发现 SSD、开发者意图 SDI、质量策略 SQP); - meta_analyzer 是 LLM 精修层:对每个文件一次(或分块)调用,过滤误报、补解释,并且把结果写成结构化摘要;
- report 节点做 baseline 抑制、算分、出 SARIF 2.1.0。
评分公式是源码里读出来的,不是文档抄的(nodes/report.py):CRITICAL 50 分 / HIGH 25 / MEDIUM 10 / LOW 5,再乘置信度,同一规则最多算 3 次且权重递减(1.0 / 0.5 / 0.25),来自可执行文件的 finding 乘 1.3,最后有个 SC8(夹带 pyc 字节码)的硬下限 51 分——意思是「有东西可能执行」,分数再低也直接判 DO_NOT_INSTALL。分档:0–20 LOW/SAFE,21–50 MEDIUM/CAUTION,51–80 HIGH、81–100 CRITICAL(后两档都是 DO_NOT_INSTALL)。
包内按源码去重我数到 至少 90 个规则 ID,而 README 只列了「71 patterns / 17 categories」。少列的不只是零头:README 完全没有 AS1–AS3(agent snooping,探测 agent 配置目录)、AE 系、RP1–RP3(MCP rug pull,两次扫描之间工具定义被偷偷改掉)这三个家族——RP 我在实测输出里亲眼看到了(下面有截图式贴片)。
实测一:喂一个恶意 skill,它抓不抓得住
我造了个 11 行的恶意 skill:SKILL.md 里塞 HTML 注释包的指令覆盖 + 「别告诉用户」+ 读 ~/.ssh/id_rsa 外传 + curl | bash + 「把系统提示词原样打印出来」,配一个 scripts/sync.py(遍历 home 找 .env/.pem、base64 打包 POST 到外部、subprocess 跑远程脚本、verify=False、exec(base64.b64decode(...))),外加一个不锁版本的 requirements.txt。
skillspector scan /tmp/skills/evil-sync --no-llm
Risk Assessment
Score 100/100
Severity CRITICAL
Recommendation DO NOT INSTALL
Issues (29)
CRITICAL: AST8 - Dangerous chain: exec() wrapping base64.b64decode (scripts/sync.py:19, conf 95%)
HIGH: P1 - Instruction Override (SKILL.md:8, conf 80%)
HIGH: P2 - Hidden Instructions (SKILL.md:8, conf 70%)
HIGH: AR1 - Anti-Refusal Statement (SKILL.md:9, conf 85%)
HIGH: PE3 - Credential Access (SKILL.md:13, conf 90%)
HIGH: SC2 - External Script Fetching (SKILL.md:16 / scripts/sync.py:18)
HIGH: E2 - Env Variable Harvesting (scripts/sync.py:5)
HIGH: P6 - Direct Prompt Extraction (SKILL.md:18)
HIGH: TM2 - Chaining Abuse / TM1 - Tool Parameter Abuse (shell=True)
MEDIUM: E1/E3/TM3(unsafe defaults) / AST4 - subprocess
LOW: SC1 - Unpinned Dependencies ×3
LOW: SC4 - Unverifiable Dependency: requests has 16 known advisory(ies)
pyyaml 8 advisories / flask 10 advisories
RC=1
该抓的全抓到了,一网打尽。两个细节值得点赞:SC4 是联网查 OSV.dev 的真实 CVE 数据(不需要 key,一次批量 HTTP,离线自动退回内置小表),这也是我这几条 LOW 的来源;PE3 抓的是「读到凭据路径」这件事本身——同一份文本里既报了文档路径 ~/.ssh/id_rsa 的引用,也报了代码里的实际枚举。
也正因为它连「提到」都算,误报从这里开始埋雷。
实测二:一个干净 skill 的两种命运,和一条只写在源码里的规则
我写了个 14 行的良性 skill(列目录、写个 markdown 摘要,无代码),静态扫它:
Score 0/100
Severity LOW
Recommendation CAUTION ← 0 分,却建议「谨慎」
Status partial ← 覆盖率 100%,但状态是 partial
Ledger exceptions
- reference_unresolved SKILL.md:12-12: A local path-like reference
could not be resolved unambiguously.
0 分应该是 SAFE 才对(README 的分档表也这么写)。我去源码里找原因,report.py:1529 有一段注释写得很直白——「Fail closed for any incomplete/degraded scan while preserving the honest score and severity」:
degraded or fatal_exception or entirely_uninspected > 0 or incomplete or transitive_truncation_reasons
→ risk_recommendation 从 SAFE 强制改成 CAUTION
也就是说:分数是诚实的,判定是保守的。我把同一个 skill 里那句「写入 summary.md」(一个不存在的文件引用)改成「打印到 stdout」,什么都不变,只改这一句:
# 带一个不存在的文件名引用 Score 0/100 LOW CAUTION is_complete=false status=partial # 去掉它 Score 0/100 LOW SAFE is_complete=true status=complete
这就是这次实测里最阴的一条:判定档位可以被「正文里提了一个不存在的文件名」这种无关紧要的东西拉低,而且分数表上完全看不出来(都是 0)。文档型 skill 大量引用脚本、模板、子目录,命中 references 解析失败太容易了。
顺带两个实测事实:
--no-llm的静态扫描永远拿不到 SAFE——三个语义分析器被标disabled_by_configuration,于是is_complete=false,所有干净 skill 都变 CAUTION。如果你在 CI 里靠recommendation == SAFE放行,或者按 README 建议对 CAUTION 弹告警,结果就是「全量告警」;- 用 LLM 跑通后,同一个 skill 立刻变 SAFE——但得先有 key(见下一节)。
顺手的正解是它藏得比较深的那个 flag:--fail-on-incomplete。实测对上面那个 partial 的 skill,不加 flag RC=0,加上 RC=1——要拿它当门禁,门禁条件应该是「退出码 + 完整性」,而不是「退出码」。
实测三:把 Anthropic 官方仓库当靶子,5 分钟、100 分、90.4% 覆盖
更有意思的是拿真货试。Anthropic 官方 anthropics/skills(176,089 Star,9 月 10 日还在推)我扫了两遍。第一遍直接扫 Git URL 整仓:
skillspector scan https://github.com/anthropics/skills --recursive --no-llm real 5m02.853s Score 100/100 CRITICAL DO NOT INSTALL Components (420) Issues (238) Coverage 90.4% Status partial Fully inspected 329 / Partially inspected 35 Scope exclusions: vcs_metadata .git/HEAD, .git/branches/ ... Limitations: ... 13 个分析器 status: degraded
整仓扫了 5 分 03 秒,把 .git/config、THIRD_PARTY_NOTICES.md 全算成组件,命中 238 条,覆盖率 90.4% 且状态 partial——连它自己都没扫完,13 个分析器 degraded(资源上限:官方 ANALYSIS_RESOURCE_BOUNDS.md 写明单次图执行 600 秒、单 bundle 64MiB、遍历深度 64)。这种用法唯一正确的结论是:别扫整仓。
第二遍我按官方目录结构(skills/ 下 20 个 skill 各带 SKILL.md)批量扫:
| Skill | 分数 | 严重度 | 发现数 |
|---|---|---|---|
| claude-api | 100 | CRITICAL | 55 |
| xlsx | 92 | CRITICAL | 11 |
| docx / pptx | 100 | CRITICAL | 13 / 12 |
| mcp-builder | 100 | CRITICAL | 11 |
| skill-creator | 100 | CRITICAL | 8 |
| webapp-testing | 64 | HIGH | 5 |
| theme-factory | 37 | MEDIUM | 1 |
| pdf / slack-gif-creator / canvas-design / frontend-design / discernment-nudge | 7–17 | LOW | 1–6 |
| academy-guide / algorithmic-art / brand-guidelines / doc-coauthoring / internal-comms / web-artifacts-builder | 0 | LOW | 0 |
19 个 skill 里 6 个满分 CRITICAL(RC=1)。我最想看的是那个 55 条的 claude-api,所以单独再扫一次:
skillspector scan .../skills/claude-api --no-llm real 2m01.904s ← 静态模式,单个 skill 两分钟 Rules: E1 ×51 AE1 ×21 EA2 ×12 P9 ×7 PE3 ×6 E4 ×5 AS3 ×5 TM1 ×3 EA5 ×3 TM2 ×2
claude-api 是干什么的?教 agent 怎么调 Anthropic API 的文档型 skill。而 E1(External Transmission,外部传输)命中了 51 次——因为文档里写满了 api.anthropic.com 之类的 URL 示例。同理 xlsx/docx 命中 AST4 subprocess(脚本里正常调用工具)、skill-creator 命中 EA5(文档里提到模型选择)、mcp-builder 命中 RP1(npx @modelcontextprotocol/... 示例没锁版本)。
这不是「NVIDIA 的规则写得烂」,而是这类扫描器的结构性难题:静态规则无法区分「一份文档在描述危险操作」和「一份文档在命令 agent 做危险操作」。Cisco 那版 README 把话说得更诚(「best-effort,没报问题不代表安全」),NVIDIA 这版把 LLM 精修层和 baseline 当成解法——但如果 LLM 层没配好,你看到的就是这一屏红。
实测四:符号链接会被静默跳过,然后报「SAFE」
我先把本机 109 个 skill 目录用符号链接收拢到一个目录里批量扫,得到的是:
Warning: --recursive specified but no sub-skills detected. Scanning as single skill. Skill: unknown Components (0) Score 0/100 LOW SAFE ← 什么都没扫,给出 SAFE RC=0
导火索是发现阶段不跟随符号链接(单扫一个 symlink 目录同样得到 0 组件)。这会真实咬人:用 dotfiles 管理的、或者把 ~/.claude/skills 软链到别处的环境,扫描器会安静地给你一张「0 问题、SAFE、退出码 0」的白纸。换成 cp -rL 解引用后的实体目录再扫,同一个 skill 立刻变成 100/100 CRITICAL。所以:扫描前先确认组件数不是 0,这比看分数重要。
实测五:自建 mock 打穿 LLM 语义层(含 3 个只有抓包才看得见的事实)
没有 API key 就拿不到 SAFE,我只能自己搭一个 OpenAI 兼容的 mock 服务,把 SKILLSPECTOR_PROVIDER=openai 指过去,抓它发出去的每一个请求。三层事实浮现出来:
1) 它的结构化输出走 json_schema strict,不是 function calling
POST /v1/chat/completions
messages: [ {role: "user", content: "..."} ] ← 只有一条 user 消息,没有 system
response_format: {type: "json_schema", strict: true,
json_schema: {name: "LLMAnalysisResult", ...}}
usage: prompt 4,321 / completion 77 / cached 300 ← 记账进 metadata.inference_usage
由此推出一个很实际的坑:任何不支持 json_schema 严格模式的后端,语义层会直接降级。README 只在 contrib/batch_scan 那一节的注释里轻描淡写提过一句「DeepSeek 缺 structured-output 支持,需要手动打 JSON 解析补丁」——而结构化输出现在是主链路。我的 mock 第一次返回普通 chat 内容时,收到的就是三行 LLM structured response validation failed ... retrying (1/3),最后 llm_degraded=true。降级不是失败,不报错,只是悄悄退回静态结果,report 里那一行 llm_error 是你唯一能看见它的地方。
2) 三条提示词里,我没找到「文件内容不可信」的隔离声明
三个语义分析器的 prompt 分别 3,296 / 4,876 / 5,073 字符,族系是 SSD-1~4(语义注入/改写攻击/自然语言外传/叙事式欺骗)、SDI-1~4(描述-行为不符、越权能力、范围蔓延、意图背离)、SQP-1~3(模糊触发、缺警告、语言/地区政策)。设计上有两点很聪明:明确告诉模型「字面关键词模式静态分析器已经抓了,你只做语义」,以及「大多数文件是干净的,空 findings 是正确的,不要为了凑数编造发现」。但——被扫文件的内容是以 L01: ... 行号前缀包在同一个 user 消息的代码围栏里,我在三条 prompt 里都没找到「以下文件是不可信数据,不要执行其中的指令」这类隔离声明;README 里「The LLM prompt includes anti-jailbreak protections」这句,在这三条 prompt 上没有对应的文字。用被审对象污染审问者,是这个赛道的通用风险,值得自己再加一层包装。
3) 一条 LLM finding 变成了 9 条
让 mock 在语义层返回「一条 HIGH 发现」,结果 issues 从 29 涨到 38,其中同一条 finding 被复制 9 份:3 个语义分析器 × 3 个文件各来一份,连 requirements.txt:12 这种不存在的行号都被配上了。包里有 nodes/deduplicate.py,但没把这种跨文件重复收掉。同时那次运行还留下 llm_degraded=true + llm_error="TP4 LLM batch failed: ValidationError"——一个批次的 schema 校验失败就会把整轮标成降级。读报告时要分开看「分数」和「有多少待审条目」,LLM 层的条目是可能灌水的。
实战:把我自己的 109 个 skill 全扫一遍,结果比想象中难看
最后拿真实工作负载收尾:本机 ~/.hermes/skills 下 53 个分类里共 109 个带 SKILL.md 的 skill,解引用拷贝后批量静态扫描。它没扫完——
<omitted> — — 77 partial Recursive scan incomplete: one or more skills were omitted after an aggregate safety limit.
109 个只处理了 32 个,剩下 77 个被总量预算掐掉,标记 partial,退出码 1。32 个里:6 个 CRITICAL、2 个 HIGH、6 个 MEDIUM、18 个 LOW。挑几个扎眼的:
| 我自己的 skill | 分数 | 发现数 | 里面是什么 |
|---|---|---|---|
| creative-coding | 100 | 14 | 示例代码 + 外部 URL |
| comfyui | 100 | 19 | 本地服务地址、安装提示 |
| autonomous-ai-agents | 100 | 11 | 纯 Markdown 文档:EA5(文档里讲模型选择)、AS1(文档里出现 ~/.claude/ 路径)、MP3、TM1/TM2(文档里的命令示例)、PE3(文档里的凭据路径)、RP1(npx 示例没锁版本) |
| hermes-agent | 89 | 8 | PE3 ×3 全是文档里写的配置路径 |
| static-site-deploy | 88 | 10 | 部署命令与 URL |
| content-publishing | 81 | 12 | 发布接口与 curl 示例 |
一个纯文档型的 skill(我的多 agent 委派说明文档)拿了 100/100「不要安装」,11 条发现全部来自文档正文里的路径、命令示例和 URL。这就是前面结构性问题在工作负载上的真实后果。如果你打算把它接进 CI 直接卡门禁,第一周你会拦下自己所有的 skill。
怎么用才不踩坑:我实际跑通的四条命令
这台机器上验证过的完整路径(Ubuntu 无头环境,Python 3.12 由 uv 提供):
# 1) 装(CLI-only;要 MCP server 就加 [mcp] extra) uv tool install git+https://github.com/NVIDIA/skillspector.git uv tool install --force 'skillspector[mcp] @ git+https://github.com/NVIDIA/skillspector.git' # 2) 先跑纯静态(不泄露文件内容,秒级到分钟级) skillspector scan ./my-skill/ --no-llm # 3) 接 LLM 语义层(任何 OpenAI 兼容端点;也可用 ollama / 本地 claude|codex|gemini CLI 复用订阅) export SKILLSPECTOR_PROVIDER=openai # 默认是 nv_build,没 NVIDIA key 就静默降级 export OPENAI_BASE_URL=https://.../v1 export OPENAI_API_KEY=... skillspector scan ./my-skill/ -f json -o report.json # 4) 当门禁用(退出码 0=≤50分 1=>50分 2=错误;再加完整性门槛) skillspector scan ./my-skill/ --fail-on-incomplete || exit 1 skillspector scan ./my-skill/ --format sarif -o report.sarif # 进 GitHub Code Scanning # 批量:扫目录下每个带 SKILL.md 的子目录 skillspector scan ./skills/ --recursive --no-llm # 已接受的误报落成 baseline,之后只报新增(实测:100/CRITICAL → 29/CAUTION,28 条被抑制) skillspector baseline ./my-skill/ -o .skillspector-baseline.yaml skillspector scan ./my-skill/ -b .skillspector-baseline.yaml --show-suppressed # 让 agent 自己调用(MCP 单工具 scan_skill,可 gate 安装) claude mcp add skillspector -- skillspector mcp
MCP 我实测握手成功:initialize 返回 protocolVersion 2025-06-18,tools/list 只有一个 scan_skill(参数 target / use_llm / output_format)——README 里自己挂着 issue #199「initialize hang」,我这次没复现,但既然作者标注了,接进生产前值得自己压一轮。HTTP 传输没有鉴权(官方明说,且会拒绝本地路径和 file:// 防任意文件读取),要暴露就自己套 mTLS 反代。
对比:四个 skill 扫描器,我为什么还留着 SkillSpector
| 维度 | SkillSpector | Cisco skill-scanner | Snyk agent-scan | 腾讯 AI-Infra-Guard |
|---|---|---|---|---|
| Star | 17,069 | 2,518 | 3,034 | 6,255 |
| 许可 | ✅ Apache-2.0 | ⚠️ 自定义(NOASSERTION) | ✅ Apache-2.0 | ✅ Apache-2.0 |
| 检测栈 | regex + YARA + AST + 污点 + LLM | YAML + YARA-X + AST/数据流 + CEL 决策层 + LLM 裁判 | 模式匹配 + 机器全量发现 | 平台化:Agent/Skill/MCP 三合一 |
| LLM 层可换后端 | ✅ 11 种(含 ollama / 本地 CLI 复用订阅) | ✅ 可选 LLM-as-a-judge | — | ✅ 平台内配置 |
| 实时 CVE | ✅ OSV.dev 免 key(实测 requests 16 / pyyaml 8 / flask 10 条) | — | ✅ Snyk 自家库 | — |
| 签名 / 供应链闭环 | ✅ OpenSSF Model Signing + NVIDIA 官方目录 | — | — | — |
| 误报处理 | ✅ baseline(指纹/glob)+ LLM 精修 | ✅ Meta-analyzer | — | 平台内 |
| CI 友好度 | SARIF 2.1.0 + 退出码 + --fail-on-incomplete | SARIF + GitHub Actions + pre-commit hook | uvx 一条命令 | 平台化 |
| 实测印象 | 证据链最全(inspection ledger、覆盖率、降级标记),但静态层在文档型 skill 上狂暴误报、慢(单个大 skill 2 分钟) | README 对局限最诚实,工程集成最顺 | 胜在「扫全机器」,输出自称 experimental | 胜在覆盖面,不是一个轻量 CLI |
选型我给三句话:想在本地/CI 里对单个 skill 做一次「份量够重」的验货(带覆盖率、证据账本、CVE 查询、可换 LLM 后端、结果可签名)→ SkillSpector;想把它塞进 pre-commit 或 GitHub Code Scanning 让 PR 自动卡 → Cisco 那个更贴合(它有 pre-commit hook,许可要自己过一眼法务);想知道「我这台机器上到底装了多少危险东西」 → Snyk agent-scan 的机器级发现更好用。
最后说几句实话
- 它不是「装了就安全」的工具,是「帮你把审查变便宜」的工具。静态层的信噪比在文档型 skill 上很差(官方仓库 19 个里 6 个满分这套数字已经说明问题),但它把证据(文件、行号、置信度、规则解释、修复建议)摆得整整齐齐,加上 baseline 和 LLM 精修,人工过一遍的成本确实降下来了。真正能用起来的姿势是:
--no-llm先看一遍红点 → 落 baseline → 之后只盯「新增」→ 判定档位不单独用,配合--fail-on-incomplete看完整性。 - 文档漂移比较多,别信 README 的数字。71 条规则 vs 代码里至少 90 个 ID;11 个静态分析器 vs 27 个注册节点;MCP server 自报版本
1.30.0而 CLI 是2.11.2;文档承诺 JSON 里有safe_to_install、llm_used、scan_mode,实测 2.11.2 的 JSON 里一个都没有(只有llm_requested/llm_available/llm_degraded/llm_error/meta_analysis_applied);README 说 LLM prompt 有 anti-jailbreak 保护,我没在那三条 prompt 里找到对应文字。以代码和实测为准。 - 小毛病不少但都不致命:每条命令(连
--version)都刷三行「Skipping analyzer ... required API key is missing」的 WARNING;默认 provider 是 NVIDIA 自己的nv_build,不配 key 就静默降级成静态(这可能是设计者心里的默认用法,但很容易被误解为「工具没生效」);符号链接不跟随会给出 0 组件的 SAFE 假阴性。 - 但对做 agent 的人来说,它值得装。Apache-2.0、无商用陷阱、Python 3.12 + uv 一条命令、能当 CLI、能当 MCP server 让 agent 自己在装 skill 前先验一次、能进 CI 出 SARIF。17,069 Star 里没有水分——它最大的价值不是那个分数,而是把「skill 是不是可信」这件事从「凭感觉」变成了「有账本可查」。我现在的做法是:新 skill 先
--no-llm扫一遍看红点,可疑的自己读那几行,确定是文档误报就落 baseline,然后才claude mcp add装上用它当常驻闸门。
Apache-2.0 许可,但有一条附加条款要记住:它自己不沙箱化你(README 的 Trust model 章节自己写了这条),扫描行为本身不执行被扫代码,但开 LLM 时会把文件内容发给你配的 provider——内网环境记得 --no-llm。仓库 github.com/NVIDIA/skillspector,文档 docs.nvidia.com/skills/。skill 生态正在变成新的 npm,而 2026 年敢给 skill 建「验货流水线」的,目前就这几家——NVIDIA 这条是链条最完整的一条。