本节摘要:治理之前先认清对手。本节用保守底账描述恶意 Skill 现状:能确证的是官方 CVE 记录 CVE-2026-100602——Skill 审批链路中缺失「预览」环节,人在没看清将执行什么的情况下点了同意;「824 个恶意 Skill 遍布 5 个市场」的规模数字为单一来源,仅供参考;不点名具体恶意 Skill、不转述 payload,只归纳三类形态:安装钩子类、读取越权类、混淆载荷类。然后给出装任何外来 Skill 前的四步自查——安装(它装了什么)、读取(它读什么)、预览(让它先干跑)、差异比对(更新了什么)——每步配可执行的检查命令与判读标准,最后附一张可直接抄走的自查清单卡。第 3.2 节的审计日志是整个自查的取证底座。
供应链事件的表述必须分层,混在一起就会以讹传讹。三层底账如下:
| 层次 | 内容 | 属性 | 使用纪律 |
|---|---|---|---|
| 官方确证 | CVE-2026-100602:Skill 审批链路中「预览」环节缺失,批准动作与实际执行内容脱节 | 官方 CVE 记录 | 可作为治理依据直接引用 |
| 规模数字 | 「824 个恶意 Skill 遍布 5 个市场」 | 单一来源,仅供参考 | 每次引用必须紧邻标注;不据其做精确决策 |
| 形态归纳 | 安装钩子类 / 读取越权类 / 混淆载荷类 | 本书归纳(教学分类) | 用来组织检查思路,不是扫描器签名 |
这个分层本身就是供应链情报的方法论:**治理决策建立在「恶意供给确实存在」这个方向性事实上(官方 CVE 已足够证明),而不是建立在某个未经复核的精确数字上。**社区侧的补充信号同样支持这个方向:OWASP 已把代理式 AI 的供应链入侵列为独立威胁类别,指出被攻陷的 Skill 会继承 Agent 运行时的全部凭据面(公开资料);2026 年公开的运行时验证研究显示,纯静态扫描对恶意 Skill 漏检明显(社区资料,量级参考)。换句话说:威胁是真的,扫描器是不够的,人审是不可省的。
三类形态的含义(只描述行为模式,不指向任何具体实例):
| 形态 | 行为模式 | 检查时机 |
|---|---|---|
| 安装钩子类 | 在安装、初始化、首次运行等时机执行声明之外的动作 | 装前(第一步) |
| 读取越权类 | 访问功能声明之外的敏感路径、环境变量、凭据文件 | 装前(第二步) |
| 混淆载荷类 | 把真实意图藏在编码、拼接、多层数据里,绕过肉眼与扫描器 | 装前全流程 + 干跑(第三步) |
传统依赖投毒(抢注近似名称、恶意更新、构建链污染)的整套手法可以平移过来,但 Skill 与 MCP 还多出三层结构:
MCP 服务器比 Skill 还多一层「常驻」:它作为进程跑在 Agent 生命周期之外,装的时候审过一次,不代表运行时还是审过的那个版本——这正是 4.2 节版本锁定要解决的问题。另外,npm、PyPI 这类老牌仓库已有多年沉淀的锁文件与校验生态,Skill 与 MCP 生态还年轻,自动化工具少、人肉比例高——流程立得越早,越不受制于工具缺位。
💡 一条实用的心智模型:把每个外来 Skill 当成「一段会自动获得部分执行权的陌生代码 + 一段直接对模型说话的提示词」。前者用传统代码审查对待,后者用第 0.1 节的不可信输入原则对待——两条线都要查。
以下命令以 Skill 目录 ./skill-xyz 为例(MCP 服务器把仓库根目录当同一对象处理),全部标注写法示意,思路通用。
第一步 · 安装检查——「它装了什么」。 看三样东西:文件清单是否与官方发布一致、可执行位是否只出现在声明的脚本上、有没有钩子类写入。
# check_install.sh —— 安装面检查:清单、可执行位、钩子与配置改写(写法示意) find ./skill-xyz -type f | sort # 全量文件清单 find ./skill-xyz -type f -perm -u+x # 哪些文件能直接执行 grep -rn -E "bashrc|zshrc|profile|git/hooks|sitecustomize|postInstall" ./skill-xyz # 判读:清单之外多出的文件、未声明的可执行位、钩子写入命中 —— 均为安装钩子类红旗
第二步 · 读取检查——「它读什么」。 拿声明面对照实际读取面:代码里引用了哪些敏感路径与环境变量。
# check_reads.sh —— 读取面检查:敏感路径与凭据引用(写法示意) grep -rn -E "\.ssh|\.aws|\.gnupg|/etc/passwd|/etc/shadow|id_rsa|credentials|\.env" ./skill-xyz grep -rn -E "environ|getenv|process\.env|API_KEY|_TOKEN" ./skill-xyz # 判读:命中项必须能被功能声明解释;解释不了的就是读取越权类红旗
第三步 · 预览检查——「让它先干跑」。 静态查不出的(混淆载荷类、运行时才分支的逻辑),放到断网沙箱里跑一遍,看它实际做什么。这正是 CVE-2026-100602 缺失的「预览」环节的通用化:人看的不是描述文案,是实际行为。
# check_preview.sh —— 预览检查:断网沙箱内干跑并回看审计(写法示意) python sandbox_run.py python ./skill-xyz/scripts/run.py --help python sandbox_run.py python ./skill-xyz/scripts/run.py sample-input.txt python replay_query.py <本次 run_id> # 第 3.2 节回放:干跑期间有无越界尝试 # 判读:时间线里出现 deny / needs_approval / 路径越界记录 —— 深查后再谈安装
第四步 · 差异比对——「更新了什么」。 更新不是重装,是 diff:新版本与已审版本的差异必须逐行人读。
# check_diff.sh —— 差异检查:新旧版本逐文件比对(写法示意) diff -ruN ./skill-xyz-1.0 ./skill-xyz-1.1 | tee skill-xyz.update.diff # 判读:变更应集中在声明的功能文件;diff 中新增可执行文件、新增外联或读取引用 # —— 一票升级为全新审查(重走第一到第三步)
四步的顺序有讲究:先看「装了什么、读什么」(静态、便宜),再「干跑」(动态、贵但见真章),最后「比对」(只对更新场景)。四步全过才有资格进入 4.2 节的入库流水线。
⚠️ 自查的诚实边界:四步能拦住粗制滥造的大多数,拦不住专门针对你环境定制的高级载荷——混淆载荷类的研究结论正是「静态扫描漏检明显」(社区资料,量级参考)。所以自查是必要条件,不是充分条件;真正的兜底是第 2 章的沙箱边界与第 5 章的运行时信号,本章管的是把进料口的成本抬上去。
把四步固化成一张卡,贴在入库流程里(纯文本,可直接放进团队文档):
# skill-checklist.txt —— 外来 Skill / MCP 装前自查清单卡(可直接抄) [ ] 01 来源:官方或组织内部仓库?陌生来源一律先进沙箱试跑 [ ] 02 安装:文件清单与官方发布一致;无可执行位异常;无钩子写入 [ ] 03 读取:敏感路径与凭据引用检索为空,或每条命中都能被功能声明解释 [ ] 04 预览:断网沙箱干跑通过,审计时间线无越界记录 [ ] 05 差异:更新场景 diff 已逐行人读;新增可执行与外联零容忍 [ ] 06 锁定:name / version / digest 已写入锁定文件(见第 4.2 节) [ ] 07 记录:自查结论与 diff 已存档,审批人确认后才允许入库
单件商品的安检手法已经就位,但一个人手工安检撑不起一个团队的进料口。下一节把四步自查升级成制度:来源分级决定通道,manifest 锁定钉死版本,入库流水线与更新差异审查让每次进料都有据可查。