本文内容
README ZIP 恶意软件:审查 AI 工具的实际安装内容
你批准了一个 AI 技能。后来,它的 README 指向了未经你审查的压缩包,SKILL.md 却没有变化。原来的文件检查仍然通过,但这次安装已经依赖那项检查从未覆盖的内容。
README ZIP 恶意软件诱导可以保留原样的技能指令,同时改变安装去向。将仓库身份、准确提交、README 指向的目标、压缩包内容和所需权限一起审查。保存可信的记录;源码、下载字节、复制后的文件或执行权限范围发生变化时,重新作出安装决定。
安装审查针对的是特定软件内容,以及允许它使用的权限。下方的离线实验展示了这项决定的边界,其中也包含一次无害的重新打包:它需要解释,却不足以判定文件恶意。
资料核对日期:2026年11月5日。攻击活动的描述依据研究者公开的证据。我们实际运行的比较使用生成的文本文件、ZIP 和本地 Git 提交,不发送网络请求,也不运行任何待审安装程序。
独立性与商标声明: 本页由 Wavect 发布,Wavect 自身也是服务商,因此我们对本页存在商业利益。我们与本页提及的其他公司没有关联,未获得其背书,也不是其合作伙伴;所有第三方公司名称、品牌与商标均归各自所有者所有。关于其他服务商的陈述来自公开可查的来源,主要是其自己发布的页面,以本页标注的核查日期为准,此后可能已经发生变化。做决定前请自行直接核实。本页依据我们所知的情况撰写,并力求保持客观。如果你认为其中有不准确或不公平之处,请写信告诉我们,我们会更正: [email protected]
73 个假冒 AI 工具仓库的报告说明了什么?
2026年11月4日,SecurityBreak 的 Thomas Roccia 发布了一份涵盖 73 个 GitHub 仓库的 IOC 清单,这些仓库冒充 AI 智能体工具。当天复查时,清单记录了 51 个可访问、22 个返回 404。这些数字描述当时观察到的仓库状态,不代表感染数量,也不说明它们现在是否可访问。
Roccia 描述了仿冒 mcp-superset、hermes-agent 和 ContinuousClaude 等项目的副本。检查过的技能与设置文件看起来正常,但 README 指示读者获取仓库中已提交的 ZIP 文件。
这些指示还要求 Windows 用户绕过安全警告。有一个压缩包位于 .claude/hooks/config/。报告中的 Windows 安装包组合了批处理启动器、LuaJIT 运行时,以及伪装成文本的混淆 Lua 代码。
研究者的公告将其识别为 SmartLoader,这类恶意软件常与 StealC 关联。现有证据并未证明每个仓库都投递了该载荷。IOC 清单区分了样本中已确认的指标,以及尚未在这些版本中确认的其他指标。受害者数量和各账号操作者的身份仍不明确。这些证据也不能证明被仿冒的合法上游项目遭到入侵。
SKILL.md 没有异常,为什么仍可能漏掉 README ZIP 恶意软件?
SKILL.md 检查没有发现异常,只能说明被检查的这个文件。安装还可能依赖其他指令、随附脚本、下载的压缩包和启动配置。如果审查止步于技能文件,就不足以批准运行这些额外组件。必须明确:这次批准究竟覆盖哪些文件和操作?
Agent Skills 规范将技能定义为一个目录,SKILL.md 是必需的入口文件,旁边可以有脚本、参考资料、资源及其他文件。可以据此划定审查范围:列出目录中的内容,并继续追踪它引入的安装依赖,不能用入口文件代替整个软件包。
假设审查者读到的是文档排版指令,没有发现可疑命令,于是批准使用。另一个 README 却要求安装者获取某个运行时。这个新增依赖需要单独说明:为什么需要它,为什么选择这个发布者和版本,又为什么要授予这些访问权限?第一个文件里熟悉的措辞无法回答这些问题。
文件如何被执行同样关键。Netskope 对相关 SmartLoader 样本的分析显示,批处理启动器会把含有 Lua 的文本文件传给解释器。只看扩展名,会漏掉命令如何处理文件内容。文本文件可以提供代码,合法解释器也可以执行这些代码。
审查结论要保留明确的范围。“这些指令已经审查”是有用的证据;将这个结论扩大到之后的所有下载和命令,就留下了缺口。共享技能指令随时间变化时,也要回答同样的范围问题,不过那类工作流还需要自己的评估机制。
AI 工具安装审查应该覆盖哪些内容?
安装审查应把发布者、源码快照与准备启用的文件、启动操作和权限对应起来。我们建议将记录保存在待审工具可写的目录之外。每项记录都要说明检查了什么,以及发生哪种变化时,需要由负责人重新决定。
| 审查对象 | 应保留的证据 | 回答的问题 |
|---|---|---|
| 发布者与仓库 | 准确的所有者/仓库名称,以及用于确认身份的独立参考来源。 | 审查的是我们原本要使用的项目吗? |
| 源码快照 | 完整提交 ID,以及纳入审查的源码文件。 | 审查者检查的是哪个版本? |
| README 指向的目标 | 解析后的压缩包路径或最终下载 URL,包括相关重定向。 | 安装指令实际选择了什么? |
| 压缩包 | 压缩包的 SHA-256、成员路径和已审查的内容。 | 这些字节与文件经过审查吗? |
| 安装后的副本 | 预期安装路径与文件哈希的清单。 | 复制或打包是否改变了将被加载的内容? |
| 启动操作 | 准确的命令、参数、工作目录和配置。 | 启用工具时会执行什么? |
| 执行权限 | 所需的文件系统访问、网络目标和凭据。 | 执行过程能够访问什么? |
| 决定负责人 | 审查者、日期、允许的用途及例外理由。 | 谁能批准发生变化的内容? |
从实际安装路径开始检查。提交在仓库内的 ZIP 属于该源码快照。README 如果指向别处,就引入了一个需要单独获取的对象。发布标签、下载按钮或压缩包名称,都不足以证明选中的字节已经纳入审查。
预期值要由团队掌控。将待审压缩包与同一个未知发布者随附的校验值比较,可以验证两者内部一致,却不能证明审查者批准过其中任何一项。审查完成后再记录预期摘要,并防止待审工具及其安装程序改写这份记录。
我们的 MCP 安装风险扫描器可以初步检查粘贴的命令或配置模式,但不会获取仓库,也不会检查压缩包字节。按它实际检查的范围使用结果,缺少的证据仍需另行保留。
离线实验中,哪些变化需要重新审查?
在 6 个离线场景中,比较全部已记录的实验输入,只有未变化的场景与基线一致。只检查 SKILL.md,所有场景都会匹配。比较 Git 提交能发现两种已提交的仓库变化,但发现不了快照之外的变化。这些结果衡量的是与先前记录是否一致,不能判定文件是否恶意。
实验使用 Python 3.12.14 和 Git 2.51.1,完成了 24 项已记录检查。脚本创建无害的仓库,每个仓库包含技能文件、README,以及两个相同的仅含文本文件的 ZIP。它还用本地文件模拟外部下载,并将一个已知的文本成员复制到单独的安装目录。
每个场景都从同一基线开始,只改变一种输入。记录包含提交、源码文件哈希、压缩包及其成员哈希,以及安装文件哈希。发布者身份和执行权限不在实验范围内。“一致”表示与基线记录相等;“不同”表示再次沿用原审查结论之前,需要先解释这次变化。
| 独立场景 | SKILL.md 哈希 | Git 提交 | 全部实验输入 | 发生的变化 |
|---|---|---|---|---|
| 基线未变 | 一致 | 一致 | 一致 | 无。 |
| README 改为指向另一个已提交的 ZIP | 一致 | 不同 | 不同 | 只有 README 内容及其新提交变化;两个 ZIP 原本就包含相同的无害文本。 |
| 已提交 ZIP 的内容变化 | 一致 | 不同 | 不同 | 压缩包字节与成员内容变化,并提交为新版本。 |
| 外部 URL 标签不变,模拟下载内容变化 | 一致 | 一致 | 不同 | 外部压缩包字节与成员内容。 |
| 复制后的安装文件变化 | 一致 | 一致 | 不同 | 已安装的文本文件。 |
| 无害地重新打包外部 ZIP | 一致 | 一致 | 不同 | 只有压缩包注释变化,成员内容完全相同。 |
README 场景刻意只做一项小改动:修改真实的 Markdown 相对链接,让它在两个已提交的压缩包之间切换。两个压缩包都不含恶意软件。只检查技能文件会漏掉指令变化,而比较提交能够发现它。因此,这项实验并未暗示:正确固定提交后,仍无法绑定仓库中已提交的压缩包。
外部下载和重新打包场景,是对审查问题的假设性延伸,不是关于 Roccia 所列仓库的额外发现。实验没有请求任何 URL,而是用本地文件代替下载字节。
重新打包是一个有用的阴性对照:压缩包摘要变了,内部文本却没有变化。如果流程立即将其标记为“恶意软件”,结论就超出了证据。应重新打开审查,比较打包变化,再决定是否签发新的记录。
每个场景执行 4 项检查:实际变化的字段、比较结果、基线记录保持原样,以及 Git 工作树没有未提交修改。6 个场景乘以 4 项检查,共计 24 项。因此,只比较提交所漏掉的变化,并非通过暗中修改未提交源码制造出来的。
如何复现实验,而不运行未知安装程序?
先阅读独立的实验源码,再用 Python 和 Git 运行其生成数据的比较过程。脚本创建自己的无害待审文件,并在你指定的工作目录中写入 JSON 结果。它不接受目标仓库或下载 URL,也不会启动生成的文件。
下载可阅读的 Python 实验源码和已记录的 JSON 结果。源码需要 Python 3.10 或更高版本及 Git,不依赖第三方 Python 包。检查源码后,在保存脚本的目录执行:
python3 readme-zip-review-lab.py \
--work-dir .agent-cache/readme-zip-review-lab
python3 -m json.tool \
.agent-cache/readme-zip-review-lab/results.json逐个场景对照 observed_controls 与 expected_controls。结果还包含实际变化的字段、文件清单、工具版本及实验源码的 SHA-256。任一检查失败,运行就会停止;下次开始运行前,脚本会删除旧的成功结果文件。
基线记录存放在待审目录和安装副本目录之外。在实际部署中,还需要阻止对该记录的越权写入。本实验只验证运行期间自己的记录保持原样。
文件清单覆盖普通文件的路径与内容,不评估文件权限、符号链接、任意压缩包的解压过程、签名、智能体行为,以及检查到执行之间的变化。安装文件场景先安全地复制已知文本,不能据此先运行未知安装程序,再回头检查损害。
固定提交或校验值一致,究竟能证明什么?
固定提交可确定选中的源码快照;校验值一致可证明字节与可信参考值相符。它们是否有用,取决于审查了什么,以及安装实际使用了什么。准确匹配也可能沿用一项错误决定;无害的打包变化则可能产生需要解释的不匹配。
要区分三个对象:作为仓库文件保存的 ZIP、GitHub 生成的源码压缩包,以及从其他位置获取的下载内容。第一个属于选定提交;第二个是围绕源码内容生成的打包产物;第三个需要单独保留产物证据。
GitHub 关于源码压缩包稳定性的文档说明,只要提交仍可访问且仓库名称不变,基于提交生成的压缩包就能保持文件内容不变。分支和标签则可以移动。文档也区分了解压后内容的稳定性与压缩包字节本身:压缩设置变化时,后者可能改变。
因此,实验同时记录压缩包摘要和成员清单。遇到摘要不匹配,不要悄悄将预期值替换为新下载文件的值。保留旧记录,查明变化,再由负责的审查者接受或拒绝替换。否则,比较过程只是不断记录最后收到的内容。
来源证明回答的是审查中的另一个问题。GitHub 产物证明指南说明,验证过的证明可以将产物关联到源码和构建指令,但文档也明确指出,证明不保证安全。发布者、构建过程和软件行为是否符合你的要求,仍需判断。
还要确保验证对象与实际启动对象相同。如果启动器在审查后重新解析一个可变引用,先前的比较就无法说明这次获取的内容。我们建议由宿主环境保管已审查的产物,并防止启动前被替换。这项离线实验没有测试该限制是否得到执行。
如何在智能体指令之外落实安装审查?
通过待审工具指令之外的控制,约束获取、文件修改和执行。发现工具的过程可以提出建议;是否启用哪些内容和权限,应由受保护的决定控制。待审工具不能写入校验器或预期记录,并且要在实际宿主环境中测试拒绝路径。
在指令文件中写下“只安装已审查的工具”,可以说明政策。要落实政策,则需要客户端或运行环境阻止违规操作。Claude Code 权限文档明确区分了宿主强制执行的权限规则,以及提示词和 CLAUDE.md。后两者影响模型行为,却不改变宿主实际授予的访问能力。
Claude Code 的 allowed-tools 技能设置需要仔细理解:它会在调用该技能的当前轮次中预先批准列出的工具,并不意味着只能调用这些工具。其他工具仍受普通权限规则管理。若把这个字段当成排他的允许列表,审查就会建立在错误的控制假设上。
进程边界也要单独核对。Claude Code Bash 沙箱文档覆盖 shell 命令及其子进程,但也列出了在该边界之外运行的钩子、本地 MCP 服务器和其他辅助程序。是否覆盖整个进程,需要另作部署选择。我们的 Claude mods 与函数钩子评估进一步说明了这些扩展能力。
在 Codex 中,审批策略和沙箱模式是两个独立设置。取消确认提示本身不会移除仍在生效的沙箱;一次批准也不能证明产物安全。应检查真正运行工具的环境中,哪些配置实际生效。
Codex 的 MCP 配置参考同样区分了工具过滤、逐工具审批行为,以及本地服务器的命令、参数、环境和工作目录。限制可调用的 MCP 工具名称,既没有评估服务器的启动过程,也没有为它加上沙箱。先批准这个进程,再决定智能体可以调用哪些方法。
MCP 对一键配置本地服务器的安全指引要求展示准确的命令和参数,并取得明确同意,也建议采用最小权限沙箱。这些是对客户端实现的要求,不能证明每种安装路径已经符合要求。
上述内容是文档说明的控制边界,不是我们的 Python 实验测试过的厂商功能。运行时隔离需要另行审查,可参考智能体沙箱安全检查清单。安装前的文件比较不能代替它。
启用工具前,团队应保存哪些验收证据?
保留一份安装决定,写明审查对象、执行范围、审查者和重新审查的触发条件,并附上比较证据与宿主环境的拒绝测试结果。另一位工程师应能据此判断已安装版本为何在批准范围内,无需依赖智能体对自己批准行为的描述。
起步时可以使用一份简短记录。以下字段描述需要你提供的证据,不是生产配置,也不是实验生成的值:
decision: reviewed-for-local-test
source: <owner/repository and full commit>
artifact: <reviewed relative path or final URL>
archive_sha256: <digest of reviewed archive bytes>
installed_inventory: <protected file-manifest reference>
launch: <exact command, arguments and working directory>
scope: <filesystem, network and credential policy>
reviewer: <responsible person and review date>
recheck_on: [source, artifact, installed-files, launch, scope]随后,在实际安装环境验证这项决定。我们建议保留下列验收证据;这些属于拟议的部署检查,与上文已经观察到的离线结果分开。
| 建议检查项 | 应保留的证据 |
|---|---|
| 已审查的基线版本能完成预期任务。 | 仅使用约定访问权限取得的正向结果。 |
| 发生变化的内容到达安装检查关口。 | 宿主在启动前拒绝沿用旧决定,并标明发生变化的字段。 |
| 待审工具尝试替换预期记录。 | 独立控制拒绝写入,且记录保持原样。 |
| 启动器请求新的目标、凭据或文件系统路径。 | 拒绝结果或另行授权的决定,以及实际导致该结果的生效策略。 |
| 文件在验证后、执行前发生变化。 | 证明暂存对象无法被替换,或再次比较会阻止启动的证据。 |
为每项失败指定负责人。字节不匹配可能需要工程师检查重新打包的差异;新增凭据请求可能需要系统负责人判断用途能否接受。两类问题都不能被一个笼统的“继续”操作掩盖。
如果团队需要在开发流程中落实这些控制,我们的 DevSecOps 服务负责仓库、构建和发布控制。验收标准应具体到:在约定范围内启用已审查的安装内容;内容或范围变化时,要求重新作出决定。
