关于 safeinstall
SafeInstall 是面向 npm 和 pnpm 的本地优先安装策略门禁,适用于安装包的开发者和 AI Agent。它会在安装前检查策略与制品绑定,也可监控 Agent 配置文件。
主要功能
- 01在 npm 或 pnpm 安装前检查策略
- 02验证锁文件版本与制品哈希
- 03拦截发布过新及生命周期脚本风险
- 04可选的拼写仿冒和来源检查
- 05防护 Agent 安装命令
- 06跟踪 Agent 配置变更
适用场景
- 01管控 AI Agent 发起的软件包安装
- 02按本地策略审查直接依赖
- 03核对软件包来源是否符合可信发布者
- 04检测已提交 Agent 配置的偏移
MCP 相关
提供供 Agent 查询 SafeInstall 策略的 MCP 工具。还提供 shell 防护,可拦截 Claude Code、Codex 和 Cursor 中的安装命令。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
SafeInstall
面向 AI Agent 时代的供应链安全。
面向输入 install 的用户、替他们运行命令的 AI Agent,以及用于配置这些 Agent 的配置文件,为 npm 和 pnpm 提供与制品绑定的安装闸门。
先运行策略,再运行包管理器。顺序不能颠倒。
本地优先 · 无云端 · 无需账号 · MIT · 永久免费
功能介绍
SafeInstall 先于包管理器运行策略——在本地执行,默认阻止操作。一个工具,三层防御:
- 🧑💻 面向安装软件包的用户 — 为受支持的安装命令添加前缀:
safeinstall pnpm add axios。先运行策略;然后在冻结安装前,根据已批准的版本和 SHA-512 检查解析后的锁文件。发布时间、安装脚本、不可信来源、仿冒包名和加密来源证明均为策略检查。 - 🤖 面向替你安装软件包的 AI Agent — Agent 可调用的 MCP 工具,以及一个命令行防护程序,它会在 Claude Code、Codex 和 Cursor 中的安装命令运行之前拦截这些命令。Shell 拦截尽力而为——它是一层即使 Agent 不配合也会触发的防御,并非万无一失的保证。
- 🔒 面向用于配置 Agent 的文件 — Agent Trust Surface:对配置、钩子、规则和 MCP 文件建立已提交的哈希基线,在本地协调并在 CI 中重新验证。这样,规则被篡改时会显示为偏移,而不是在之后的每次会话中悄无声息地取得控制权——但如果篡改后重写内容完全一致,仍需人工审核差异才能发现,自动检查本身无法发现。
为什么选择 SafeInstall
AI 编码工具几秒内就能推荐软件包。它们不会检查发布时间,不会阅读安装脚本,也不会验证来源。你输入“是”,然后继续。
SafeInstall 是建议与执行之间的闸门。
$ safeinstall pnpm add [email protected]
Install blocked.
- [email protected]
Blocked: release too new (published 3 hours ago; minimum is 72 hours).
Blocked: install script present (has postinstall).
无需仪表板,无需账号,也无需云端。只需在命令前加一个前缀——策略在本地运行,默认阻止操作,然后调用实际工具。
防范维护者账号被攻陷的攻击
有效的 Sigstore 签名并不足够。攻击者若攻陷 npm 维护者账号,就能发布你已信任的软件包的恶意版本,而该恶意版本上的证明也会通过密码学验证——签名来自攻击者在真实代码库的分叉中控制的 GitHub Actions 工作流。
SafeInstall 可以防范这种情况。使用 provenance.trustedPublishers 固定预期的源代码库,任何来自其他位置的构建都会被拦截,即使签名有效:
$ safeinstall check
Using config: ./safeinstall.config.json
Check blocked.
- [email protected]
Blocked: publisher mismatch for axios (expected axios/axios, got evil-org/axios).
Suggestion: Verify the package source. Update provenance.trustedPublishers only if the change is intentional.
这是安装时策略门禁中唯一此类检查。CVE 扫描器用于查找已知漏洞,内容分析器用于查找可疑代码。SafeInstall 会确保密码学信任链指向你同意信任的代码库,并拒绝其他一切内容,无论它看起来多么可信。
SafeInstall 自身通过 Sigstore 证明发布。你可以亲自实践:启用来源验证,将 safeinstall-cli 固定到 Mickdownunder/SafeInstall,然后观察 SafeInstall 如何根据公开的 Sigstore 透明度日志验证自身的信任链。
$ safeinstall check
Using config: ./safeinstall.config.json
Info: safeinstall-cli: provenance verified from Mickdownunder/SafeInstall via .github/workflows/release.yml.
Check passed: no direct dependency policy violations found.
安装
npm install -g safeinstall-cli
Node.js >=20 · macOS、Linux、Windows · 命令:
safeinstall
快速入门
safeinstall init # config + agent guard hooks + trust lock, in one command
safeinstall pnpm add axios # policy runs, then pnpm
safeinstall npm install # lockfile-aware project install
safeinstall check # audit direct deps against policy
工作原理
Resolve → Policy → Lockfile-only preparation → Verify artifact binding → Frozen install
Any failed policy or binding check → Exit 2 (blocked)
- 解析请求的版本,并记录注册表中的 tarball URL 和 SHA-512
- 获取注册表元数据(发布时间、声明的脚本)
- 评估策略规则
- 对于显式添加的软件包,固定已评估的版本,并运行禁用脚本、仅处理锁文件的准备步骤
- 检查受影响锁文件范围内的每个直接注册表依赖项,核对其已评估版本、SHA-512,以及有记录时的 tarball URL;应用已配置的传递依赖项检查
- 在禁用脚本并强制使用已配置注册表的情况下运行
npm ci或pnpm install --frozen-lockfile;若评估或安装期间清单/锁文件发生变化,则拒绝操作
没有注册表代理。不会重写 tarball。无云端依赖。
默认策略
| 规则 | 默认值 | 拦截消息 |
|---|---|---|
| 发行版本等待时间 | 至少 72 小时 | Blocked: release too new |
| 生命周期脚本 | 禁止 preinstall、install、postinstall | Blocked: install script present |
| 源类型 | 允许 registry、workspace、file、directory | Blocked: untrusted source |
| 信任级别降级 | 更新时检测 registry→git/url 或新增脚本 | Blocked: trust level dropped |
| 拼写仿冒检测 | 默认关闭;通过 typoSquat.mode 启用 | Blocked: suspected typo-squat |
| 来源验证 | 默认关闭;通过 provenance.mode 启用 | Blocked: attestation missing/invalid/publisher mismatch |
| 传递依赖 | 默认关闭;通过 transitive.mode 启用 | Blocked: transitive install script / untrusted source |
| 来源连续性 | 默认关闭;通过 continuity.mode 启用 | Blocked: provenance downgrade / identity discontinuity |
所有规则均可配置。元数据含糊不清或不完整时,会拦截而非放行。
来源连续性——捕捉 npm 默认机制无法发现的问题
这是 SafeInstall 最具特色的检查,也是其他面向使用者的工具都不具备的能力。npm 会在发布时验证来源证明,并将软件包绑定到源代码仓库,但不会强制要求不同版本之间保持连续性。遭到入侵的维护者账户可以发布一个没有来源证明的新版本(使用被盗令牌发布),也可以从不同的仓库发布,而 npm 不会发出任何警报。这正是 2026 年攻击浪潮(Mastra、休眠账户重新发布)的典型特征。
连续性检查会根据近期版本的来源证明身份,为每个软件包建立信任基线,然后拦截偏离基线的情况:
provenance-downgrade— 近期版本都有来源证明,但当前版本没有。这是使用个人令牌发布、账户遭入侵的典型信号。(Mastra 案例即属此类。)identity-discontinuity— 当前版本的来源证明来自与既定基线不同的源代码仓库。
由于基线是按软件包建立的,因此绝大多数从未采用来源证明的软件包不会产生误报——它们没有基线,检查就会保持静默。无需采用全局性的“强制要求来源证明”硬性措施。
该功能会读取 npm 发布的来源证明元数据,因此无需安装可选的 sigstore 软件包即可运行。将 continuity.mode 设为 "warn" 或 "block" 即可启用。
如实说明限制: 如果攻击是通过合法但已遭入侵的 CI 工作流实施,且来源证明有效并来自真实仓库(例如 Shai-Hulud 蠕虫这一类攻击),连续性检查无法发现。因为这里不存在可检测的身份不连续。SafeInstall 提高了抵御 2026 年主要攻击模式的门槛,但无法堵住所有漏洞。
传递依赖
默认情况下,SafeInstall 会检查直接依赖。不过,大多数供应链攻击都是通过传递依赖入侵你的项目——也就是你从未主动选择、由其他依赖在多层嵌套中引入的软件包。启用 transitive 模式即可遍历完整的锁文件依赖树。
有两项检查会沿传递依赖执行,且都直接读取锁文件,不会额外调用注册表:
install-script— 标记声明了生命周期脚本的传递依赖(即ua-parser-js这类攻击:深层嵌套的依赖在安装时运行代码)。npm 会在锁文件中记录此信息;pnpm 锁文件不会,因此目前此检查仅适用于 npm。untrusted-source— 标记从 git、url 或 tarball 来源解析,而非从注册表解析的传递依赖。适用于 npm 和 pnpm。
为避免产生大量噪声或需要为每个软件包往返查询注册表,发布时效、拼写仿冒和来源证明检查不会沿传递依赖执行。传递依赖检查适用于 safeinstall check、项目安装,以及显式添加 npm/pnpm 依赖时准备的锁文件。
{
"transitive": {
"mode": "warn",
"checks": ["install-script", "untrusted-source"]
}
}
拼写仿冒检测
标记与知名热门软件包名称近似但不完全相同的安装请求。这是最常见的供应链攻击模式:用 lodsh 替代 lodash、用 axois 替代 axios、用 raect 替代 react,等等。
- 算法: Damerau-Levenshtein 距离(相邻字符换位按一次编辑计算)
- 目标列表: 精选的热门生态系统软件包列表,构建时嵌入(运行时不从网络获取)
- 默认模式:
"off"——通过typoSquat: { "mode": "warn" }或"block"启用 - 误报缓解: 与列表完全匹配的名称永远不会被标记;跳过短名称(少于 4 个字符);支持按项目配置
ignore列表
来源验证
通过密码学验证 registry 安装的 npm 来源证明,并可选择通过每个包的可信发布者模式固定源代码仓库。
- 从 npm registry 的
/-/npm/v1/attestations/<pkg>@<version>端点获取证明包 - 通过官方
sigstore包验证 Sigstore 包(签名、Rekor 透明日志、Sigstore 公共信任根) - 从 SLSA v1 来源证明声明中提取源代码仓库、提交引用和工作流路径
- 将源代码仓库标识与每个包的
trustedPublishers模式进行匹配——不仅能发现被篡改的 tarball,还能防范维护者遭入侵攻击:攻击者从其控制的 fork 重新发布一个看似合法的包 - 发布者不匹配时始终会阻止操作,即使处于
"warn"模式也不例外,因为来自错误仓库的有效签名正是维护者遭入侵的典型表现
默认模式为 "off"。将 provenance.mode 设为 "warn" 或 "require" 即可启用。
用法
# Ad-hoc installs
safeinstall pnpm add axios
safeinstall npm install [email protected]
# Project installs (lockfile-aware for npm/pnpm)
safeinstall pnpm install
safeinstall npm ci
# Monorepo — select a package; shared-lockfile installs review all importers
safeinstall pnpm -C packages/app install
safeinstall npm --prefix packages/app ci
# Utilities
safeinstall check # direct dependency audit
safeinstall check --json # machine-readable
safeinstall init # one-command onboarding: config, guard hooks for
# detected agents (.claude/, .codex/, .cursor/), trust lock
safeinstall init --force # overwrite an existing config (hooks/lock stay idempotent)
safeinstall init --client codex # force specific agents instead of detection
safeinstall init --no-guard --no-lock # config only (the old behavior)
safeinstall mcp # MCP server for AI coding agents
safeinstall guard install # register agent shell hooks (Claude Code, Codex, Cursor)
safeinstall trust lock # baseline the Agent Trust Surface
safeinstall trust lock --ci github # ...and scaffold the CI re-verification workflow
safeinstall trust status # reconcile the trust surface (exit 2 on drift; read-only)
safeinstall trust approve # review drift and re-baseline (interactive)
safeinstall trust unlock # remove the baseline (lock, ledger, head mirror)
safeinstall --help
safeinstall --version
# JSON output (CI/automation)
safeinstall --json pnpm add axios
# Explicit config file (skips upward discovery)
safeinstall --config ./ci/safeinstall.config.json check
项目安装
对于 pnpm install 和 npm install / npm ci,依赖版本取自 lockfile,而不是 package.json 中的宽泛版本范围。
- pnpm 使用
pnpm-lock.yaml - npm 使用
package-lock.json或npm-shrinkwrap.json - lockfile 条目过时、缺失或不匹配时,会默认拒绝
- 如果
package.json中设置了packageManager,则会阻止使用不同的 CLI - 会阻止指定工作区的标志(
--filter、--workspace)——请使用-C或--prefix - 在实现与制品绑定的 Bun 安装前,
bun add/bun install会默认拒绝;check和 MCP 策略查询仍可用
与制品绑定的安装约定
安装要求已检查的 registry 清单中包含一个规范的 SHA-512 摘要,且 lockfile 中有匹配的条目。仅包含 SHA-1 或缺少完整性信息的条目会被阻止。使用不同的命令行 registry、启用脚本的标志、全局安装以及未知安装选项都会被阻止。环境变量或作用域 .npmrc 设置中的 registry 选择无法授权使用其他制品:最终 lockfile 仍必须匹配已检查的绑定。
显式添加的依赖会先解析为包含经过评估的精确版本的 lockfile,然后再进行安装。即使后续绑定/策略检查阻止了操作,准备阶段仍可能更新 package.json 和 lockfile,并填充包管理器的缓存。这些文件会保留供检查,不会自动回滚。npm 的冻结阶段使用 ci,会替换 node_modules。共享工作区 lockfile 会使系统根据所选策略审查其中记录的所有导入方的直接依赖,而不仅是所选包。
始终禁用生命周期脚本,即使 packageManagerDefaults.*.ignoreScripts 为 false 也不例外。allowedScripts 仅允许策略例外,不会执行脚本。pnpm 的可执行 .pnpmfile.cjs 钩子也会被禁用。当前会阻止显式添加非 registry 依赖;现有本地/工作区依赖仍受 allowedSources 管理,但不声称这些依赖已与 registry 制品绑定。
MCP 服务 / AI Agent
CLI 面向输入 safeinstall 的用户。MCP 服务面向 AI 编码 Agent——Claude Code、Codex、Cursor、Windsurf、Cline——因此,策略引擎可以在 Agent 建议或运行安装命令之前接受查询,无需任何人输入内容。
safeinstall mcp 会通过标准输入/输出启动一个 Model Context Protocol 服务,并提供一个工具 check_package。该工具由与 CLI 完全相同的引擎提供支持(版本发布时间、安装脚本、不可信来源、仿冒包、Sigstore 来源证明和来源证明连续性)。
重要
MCP 工具仅供参考。安装服务后,
check_package会变为可用,但不会强制 Agent 调用它。“安装一次,永久保护” = 一段 MCP 配置 + 一段规则片段(mcp/agent-rule.md),告知 Agent 在每次安装前调用该工具。
将其接入你的 Agent
Claude Code / Claude Desktop — 添加到你的 MCP 配置(claude_desktop_config.json 或 .mcp.json)中:
{
"mcpServers": {
"safeinstall": { "command": "npx", "args": ["safeinstall-cli", "mcp"] }
}
}
Cursor — 将相同的配置块添加到 .cursor/mcp.json。
然后将 mcp/agent-rule.md 中的规则粘贴到你的 CLAUDE.md / Cursor Rules 中。完整设置详情:mcp/README.md。
Agent 会看到什么
check_package 接受 name(必填)、version(可选,默认为最新版本)和 manager(可选,仅供参考),并返回 JSON 判定结果:
{
"verdict": "block",
"name": "raect",
"version": null,
"reasons": [
{ "code": "typo-squat-suspected", "message": "Blocked: Suspected typo-squat: \"raect\" is 2 edit(s) away from popular package \"react\".", "suggestion": "Verify you meant to install \"react\"." }
],
"warnings": [],
"infos": [],
"sourceRepository": null,
"ageHours": null
}
当任何策略原因阻止安装时,verdict 为 "block";否则为 "allow"。
未找到 safeinstall.config.json 时,MCP 服务会使用推荐的安全预设——内置默认值,并将 typoSquat 和 continuity 提升为 "block",因为 Agent 使用场景需要最大程度地提供风险信号。找到配置文件时,则会严格遵循该文件(与 CLI 使用相同的解析方式)。
MCP SDK 是可选依赖项,仅在运行 safeinstall mcp 时才会延迟加载——仅使用 CLI 的用户无需安装它。如果缺少该依赖项,命令会显示安装提示并以非零状态码退出。
Agent 防护——强制执行,而非提供建议
MCP 工具仅供参考:Agent 可以查询它。防护层级更高:它会接入 Agent 的 shell 层,在命令运行之前进行筛查,即使 Agent 不知道 SafeInstall 的存在,也会触发。这是尽力而为的 shell 拦截——是纵深防御的一层,并非完整的安全边界;其命令解析并不全面(参见防护限制)。
safeinstall guard install # Claude Code, Codex, and Cursor
safeinstall guard install --client codex # or just one client
这会写入项目级钩子配置(以非破坏性方式合并,重复运行时保持幂等):
- Claude Code — 在
.claude/settings.json中为Bash工具配置PreToolUse钩子;原始安装命令会通过 SafeInstall CLI 使用updatedInput原地重写,且不设置permissionDecision,因此 Claude 仍会显示常规权限提示,并向用户展示重写后的命令以供批准 - Codex — 在
.codex/hooks.json中为Bash配置PreToolUse钩子;原始安装命令会通过 CodexupdatedInput使用 SafeInstall CLI 原地重写。安装后,在 Codex 中打开/hooks并信任新的项目钩子,然后它才会运行。 - Cursor — 在
.cursor/hooks.json中配置beforeShellExecution钩子,并以failClosed: true注册,因此防护程序崩溃或超时时会阻止操作,而不是悄然放行
注册后,CLI 会报告与项目状态相符的 Trust Surface 步骤。没有基线的项目会被引导运行 safeinstall trust lock。如果基线已存在且防护程序对其进行了更改,请使用 safeinstall trust status 查看完整差异,并且仅在确认所有更改都是有意为之时运行 safeinstall trust approve。重复运行已注册的防护程序无需重新批准,除非状态报告存在差异。
防护程序的决策方式
防护程序本身不会评估策略——它会检测软件包安装操作,并将其转交给负责决策的 CLI:
| Agent 执行的命令 | 防护程序的响应 |
|---|---|
git status、npm test,…… | 允许——不是安装操作 |
npm install axios(也包括 npm i、pnpm add、bun a、npm ci、corepack pnpm add、pnpm --dir app add,……) | Claude/Codex:执行前就地改写为 safeinstall npm install axios(权限提示中会显示转交后的命令)。Cursor:使用 SafeInstall 替代命令拒绝 |
cd app && npm i axios && npm test | 仅改写安装命令片段前缀;Claude/Codex 会直接应用改写,Cursor 会将其返回给 Agent |
safeinstall npm install axios | 允许——已转交给策略引擎处理 |
npm install $(cat list.txt)、bash -c "npm install ..." | 拒绝——无法安全分析(默认拒绝) |
yarn add axios | 拒绝——SafeInstall 无法对 yarn 进行策略检查 |
已在本地安装 typescript 时执行 npx tsc | 允许——npx 会解析本地二进制文件,不会下载任何内容 |
npx create-next-app、pnpm dlx ...、bunx ...、yarn dlx ... | Claude/Cursor:询问。Codex:拒绝,因为 Codex PreToolUse 目前不支持 ask 决策 |
将操作转交给 CLI,比简单地允许或拒绝更重要:即使未经 SafeInstall 处理的 npm install 已通过审查,仍会执行生命周期脚本。通过 SafeInstall 执行时,同一安装操作会经过完整的策略评估,并且会在禁用安装脚本的情况下运行。
Claude 和 Codex 通过 updatedInput 就地应用改写,因此用户看到的常规权限提示会显示转交给 SafeInstall 的命令,而不是原始安装命令。Cursor 会在拦截消息中返回完整的改写后命令,以便行为良好的 Agent 一步完成自我修正。防护程序无需访问网络,响应时间仅为几毫秒。
防护程序的局限性
safeinstall二进制文件必须位于 Agent 的PATH中(npm install -g safeinstall-cli)。- Codex 要求用户通过
/hooks审查并信任新的或已更改的项目钩子;在此之前,Codex 会跳过该钩子。Codex 目前只会拦截受支持的简单 Shell 调用,而非每条unified_exec路径,因此该钩子仍是纵深防御措施,而不是完整的安全边界。 npx及类似工具不会经过策略检查:SafeInstall 引擎评估的是安装操作,而不是一次性执行。Claude/Cursor 会请求批准;Codex 则默认拒绝,因为其PreToolUse钩子目前不支持ask。本地二进制文件的快速路径与运行器自身的解析方式一致(最近的node_modules/.bin),因此经批准的项目工具不会触发远程运行器门控。- 有意绕过检查的攻击者可以避开 Shell 层分析——安装操作可以通过其他方式运行(例如先写入脚本文件、使用
node -e等),而且命令行解析本身也无法穷尽所有情况。不要只依赖 Shell 层:它作为纵深防御的一层提高了攻击门槛——应结合 MCP 工具和 CI 检查使用。
Agent 信任表面——自我防御策略
防护机制会阻止直接安装。但遭到提示词注入的 Agent 不必对抗防护机制——它可以直接改写规则:削弱 safeinstall.config.json、删除 Agent 钩子、在 .codex/config.toml 中禁用 Codex 钩子、在 AGENTS.md 中植入不可见 Unicode 指令,或注册恶意 MCP 服务。这些文件会为下一次 Agent 会话编写程序;在 Agent 时代,它们就是持久化机制,是新的 crontab。
safeinstall trust lock 会记录该信任表面的哈希基准。随后,SafeInstall 会在做出防护决策之前以及每次安装/检查之前,将实际状态与基准进行核对,并可在 CI 中重新验证。
safeinstall guard install # register the hooks
safeinstall trust lock --ci github # baseline the surface AND scaffold CI re-verification
safeinstall trust status # reconcile — exit 2 on drift (use in CI); read-only
safeinstall trust approve # review drift and re-baseline (interactive only)
safeinstall trust unlock # remove the baseline (lock, ledger, head mirror)
--ci github 会写入 .github/workflows/safeinstall-trust.yml,在每个拉取请求上通过运行 safeinstall trust status --require-lock 重新验证已提交的基准;该工作流会将 CLI 固定到一个精确版本,并且固定到其 sha512 内容哈希,该哈希在生成脚手架时从注册表记录(首次使用时建立信任)——如果注册表之后为该版本提供了不同的字节内容,检查就会失败,而不会静默替换验证器。该工作流使用 pull_request_target,因此 GitHub 会从受保护的基分支加载其定义;它会检出提议的修订版本,但不使用凭据,将其仅视为数据,且绝不执行 PR 代码。请一并提交 .safeinstall/ 和该工作流。已有的工作流文件绝不会被覆盖。因此,PR 无法削弱负责审查它的验证器——但如果策略和基准被完全一致地改写,仍需人工审查差异(见下方的 CODEOWNERS 说明)。
要让检查真正发挥约束作用,你必须完成以下两项设置,因为 CLI 无法替你设置:
- 在分支保护中将此工作流设为必需状态检查——否则,失败的检查不会阻止合并。
- 要求审查对
.safeinstall/和.github/workflows/的更改(例如通过CODEOWNERS)。自动检查会捕获不一致的篡改;如果在一个 PR 中完全一致地改写了整个基准,则只能通过人工审查.safeinstall/的差异来发现。
三个区域
| 区域 | 文件 | 发生偏移时 |
|---|---|---|
| 强制执行 | safeinstall.config.json、.claude/settings.json、.codex/hooks.json、.codex/config.toml、.cursor/hooks.json、.github/workflows/safeinstall-trust.yml、.safeinstall/ | 拒绝每条 Agent 命令,直到获得人工批准 |
| 指令 | AGENTS.md、CLAUDE.md、.cursorrules、.cursor/rules/**、.github/copilot-instructions.md | 始终阻止隐藏 Unicode;内容偏移会发出警告(在 --mode strict 下会阻止) |
| 工具 | .mcp.json、.cursor/mcp.json、.claude/settings.json 中的 MCP 区块 | 拒绝安装和运行器,直到获得人工批准;标记未固定版本的 MCP 服务 |
指令内容偏移默认只会发出警告,因为 Claude Code 会自行写入 CLAUDE.md(其记忆功能)——阻止这种行为会破坏正常工作流。另一方面,这些文件中的隐藏 Unicode 没有任何正当用途,因此始终会被阻止。
它如何保护自身
- 验证器来自受保护的基础分支;有意重新建立基线仍由人工审核把关。 运行
safeinstall trust lock --ci github,即可搭建一个工作流:在每个拉取请求中,由 Agent 无法控制的机器使用精确版本及其 sha512 内容哈希固定的 CLI,将候选信任面与已提交的锁文件重新哈希比对。pull_request_target会使验证器定义不受 PR 控制,而候选检出内容则是不会执行、也不含凭据的数据。这能阻止 PR 修改工作流来豁免自身的失败。需要坦诚说明的边界:PR 仍可一致地重写策略和基线,因此这些差异必须经过独立审核。(直接接入 Action 也可行,但要固定其version:输入;默认值latest会浮动。) - 在拦截之上进行对账。 防护钩子是快速防护层;每次
safeinstall安装或检查也都会与基线对账。删除钩子来让防护静默,或编辑受保护文件,都会在下次本地运行 SafeInstall 时显示为偏移;无论如何,都会导致 CI 重新验证失败。 - 变更须经人工批准。 放宽强制措施的两条命令都需要人工操作:
safeinstall trust approve会从控制终端(/dev/tty)读取确认,而不会从 stdin 读取;trust unlock同样需要控制终端。两者在 CI 或已知的 Agent 钩子上下文中都会拒绝运行,因此 Agent 无法悄悄重设基线或移除信任面。trust status为只读(可在 CI 中安全运行,绝不会修改仓库)。 - 哈希链式账本。 基线决策会链接记录在
.safeinstall/ledger.jsonl中;锁文件绑定到一条账本记录(包括其强制执行mode),因此,编辑已提交的锁文件以删除文件或将 strict 降级为 warn 会破坏这种绑定。~/.safeinstall/下的本地头部镜像可捕捉简单或意外的历史改写;如果镜像消失,下次无误运行时会根据已验证的头部自动修复,因此新克隆会自动建立镜像,不产生额外提示。
信任面的局限——请仔细了解
- 最强的防护层是经过 PR 审核的已提交锁文件,而不是本地镜像。 在本地,这套机制只能防止可察觉的错误和非定向篡改,无法防止篡改。 在你自己的用户账户下运行的、了解该机制的 Agent 可以绕过拦截(例如通过
node -e驱动安装),将仓库内的锁文件和账本重写为相互一致的状态,并删除本地头部镜像——用户空间无法阻止这种行为,因为 Agent 可以读取并删除你能读取和删除的任何内容。CI 重新验证会在另一台机器上运行,并由基础分支拥有的工作流驱动,因此 PR 无法禁用自己的验证器。但策略和基线被一致重写后,仍可能自动通过。对于有能力的对手,剩余的保障措施是在每个依赖项 PR 中,对信任面差异(工作流、锁文件和配置)进行独立人工审核;待有第二位可信审核者后,通过规则集强制执行。请运行 CI 检查并要求进行此类审核;两者涵盖不同的故障模式。 - 它监视的是 Shell 命令和文件,而非意图:如果 Agent 编写并运行某个程序来执行安装,该操作会在下次 SafeInstall/CI 运行时通过对账被发现,而不是在调用发生时被阻止。
- 本地头部镜像缺失的原因并不明确(新克隆也没有镜像),因此干净验证时会根据已提交的账本头部自动修复,而不会发出警告或阻止操作——镜像只是尽力提供的本地便利,绝非保障。
配置
可选的 safeinstall.config.json——从项目目录开始向上查找,或通过 --config <path> 显式指定。无法读取显式指定的路径时会报错(退出码为 1),绝不会静默回退到默认值。
{
"minimumCliVersion": "0.12.0",
"minimumReleaseAgeHours": 72,
"registryUrl": "https://registry.npmjs.org",
"allowedScripts": {
"esbuild": ["postinstall"]
},
"allowedSources": ["registry", "workspace", "file", "directory"],
"allowedPackages": [],
"packageManagerDefaults": {
"npm": { "ignoreScripts": true },
"pnpm": { "ignoreScripts": true },
"bun": { "ignoreScripts": true }
},
"typoSquat": {
"mode": "warn",
"minNameLength": 4,
"ignore": []
},
"provenance": {
"mode": "warn",
"requireFor": [],
"trustedPublishers": {
"axios": "axios/axios"
},
"offlineBehavior": "fail-closed",
"toolingUnavailable": "warn"
},
"transitive": {
"mode": "warn",
"checks": ["install-script", "untrusted-source"]
},
"continuity": {
"mode": "warn",
"baselineSize": 5
}
}
| 字段 | 用途 |
|---|---|
minimumCliVersion | 可选。此项目的保护措施所依赖的最低 safeinstall-cli 版本(精确的 semver)。运行中的 CLI 版本更旧时会发出警告——在 guard、install/check 和 trust status 中均如此——但绝不会硬性失败 |
minimumReleaseAgeHours | 注册表版本的最小发布时长(小时) |
registryUrl | 用于获取元数据和安装的兼容 npm 的注册表 URL(镜像、Artifactory、Verdaccio) |
allowedScripts | 针对各个包的生命周期脚本例外 |
allowedSources | 允许的来源类型 |
allowedPackages | 跳过发布时长、安装脚本和拼写仿冒检查的名称(会发出警告)。来源、信任降级和来源证明检查仍会执行。 |
packageManagerDefaults | 旧版配置;即使 ignoreScripts 为 false,生命周期脚本仍会保持禁用 |
typoSquat.mode | "off" / "warn" / "block"——如何处理疑似拼写仿冒 |
typoSquat.minNameLength | 执行检查所需的最短包名称长度(更短的名称将跳过检查) |
typoSquat.ignore | 应跳过的已知合法近似名称;加载时会转换为小写 |
provenance.mode | "off" / "warn" / "require"——是否验证 Sigstore 证明 |
provenance.requireFor | 即使处于 "warn" 模式,也必须提供来源证明的包名称(支持 glob) |
provenance.trustedPublishers | 包名称模式 → 预期的 owner/repo 标识符映射;不匹配时始终阻止 |
provenance.offlineBehavior | "fail-closed" 在获取失败时阻止操作;"allow-cached" 则回退到缓存的证明 |
provenance.toolingUnavailable | "warn"(默认)在未安装 sigstore 验证器时发出醒目警告并继续;"fail-closed" 将缺少验证器视为可疑,并阻止所有安装,直到验证器可用(安装 sigstore 本身仍为例外,因此绝不会陷入死锁) |
transitive.mode | "off" / "warn" / "block"——评估完整的锁文件依赖树,而不只是直接依赖 |
transitive.checks | 要对传递依赖执行的检查:"install-script" 和/或 "untrusted-source" |
continuity.mode | "off" / "warn" / "block"——根据学习到的各包基线,检测来源证明降级和源代码仓库变更 |
continuity.baselineSize | 学习基线时要抽样的近期版本数(默认值为 5) |
固定最低 CLI 版本
不同版本的 guard 判定会有所变化(修复误报、新增检查),但 CLI 通常是全局安装的——因此,项目可能依赖 0.12.0 的行为,而 Agent 的机器上仍运行着 0.11.0。将 minimumCliVersion 设为策略所依赖行为的最低版本;所有更旧的 CLI 都会醒目地提示版本不匹配:
Warning: This project's safeinstall.config.json expects safeinstall-cli >= 0.12.0, but version 0.11.0 is running — protections this project relies on may be missing or behave differently. Update with `npm install -g safeinstall-cli@latest`.
此检查完全离线(将运行中的版本与 semver 进行比较),并且有意只发出警告,绝不硬性失败:硬性失败会导致每次发布后,在全局 CLI 更新之前,所有 Agent 会话都无法运行。请注意以下两个易踩的地方:
- 此字段必须是精确版本(
"0.12.0"),不能是范围——它是最低版本,而不是约束条件。任何其他格式都会导致配置解析失败并采取拒绝策略。 - 在此字段加入之前发布的 CLI 会拒绝未知配置键,因此会拒绝整个配置(失败时采取拒绝策略),而不是发出警告。设置此字段实际上要求协作者使用支持该字段的 CLI。
运行 safeinstall init 以生成初始配置。
退出码
| 代码 | 含义 |
|---|---|
0 | 已允许/检查通过 |
1 | 运行时或配置错误 |
2 | 被策略阻止 |
在 CI 流水线中,像处理其他失败步骤一样处理退出码 2。
JSON 输出
在命令中的任意位置传入 --json。结构化输出会发送到标准输出。
safeinstall --json pnpm add axios
字段:command、commandString、packageManager、decision、summary、reasons、warnings、affectedPackages、exitCode、exitCodeMeaning。允许安装时会包含 execution.stdout 和 execution.stderr。
示例
想查看每项检查的实际效果?运行
bash demo/run.sh——这是一个可复现脚本,针对真实软件包,为每项策略检查演示一个拦截场景。请参阅 demo/README.md 了解操作步骤。想看看它如何应对真实攻击?运行
pnpm replay mastra——它将 2026 年 6 月 Mastra 遭入侵时记录的攻击状态输入 SafeInstall 的真实策略引擎,并展示哪些检查本可以拦截攻击。请参阅 demo/replay/README.md。
拦截新发布版本
$ safeinstall pnpm add axios
Using config: built-in defaults
Install blocked.
- [email protected]
Blocked: release too new ([email protected] is 6 hours old; minimum is 72 hours).
Suggestion: Retry later or lower minimumReleaseAgeHours if this package is intentionally urgent.
拦截 Git 源
$ safeinstall npm install github:axios/axios
Using config: built-in defaults
Install blocked.
- github:axios/axios
Blocked: untrusted source (git).
Suggestion: Use a registry release or allow this source intentionally.
包管理器不匹配
$ safeinstall npm install
Using config: built-in defaults
Install blocked.
- Project install blocked: package.json declares pnpm as packageManager, but this command uses npm.
锁文件过期
$ safeinstall pnpm install
Using config: built-in defaults
Install blocked.
- Project install blocked: axios is declared in package.json but missing from pnpm-lock.yaml.
识别仿冒包名
$ safeinstall pnpm add raect
Using config: ./safeinstall.config.json
Install blocked.
- raect
Blocked: Suspected typo-squat: "raect" is 1 edit(s) away from popular package "react".
Suggestion: Verify you meant to install "react". If this package is intentional, add "raect" to typoSquat.ignore.
发布者不匹配(防范维护者账号遭入侵)
$ safeinstall pnpm add [email protected]
Using config: ./safeinstall.config.json
Install blocked.
- [email protected]
Blocked: publisher mismatch for axios (expected axios/axios, got evil-org/axios).
Suggestion: Verify the package source. Update provenance.trustedPublishers only if the change is intentional.
GitHub Action
在每个拉取请求上自动运行 SafeInstall 策略检查。只需 5 行 YAML,无需配置——默认设置会立即生效。
# .github/workflows/safeinstall.yml
name: SafeInstall Policy Check
on: [pull_request]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: Mickdownunder/SafeInstall@v1
此 Action 会针对直接依赖运行 safeinstall check,并将作业状态设为通过或失败。被拦截的依赖会显示在 GitHub Actions 作业摘要中,其中包含确切的拦截原因和建议。
安装模式
若要在实际安装步骤中强制执行策略(而不仅仅是检查):
- uses: Mickdownunder/SafeInstall@v1
with:
mode: install
package-manager: pnpm
args: "--frozen-lockfile"
输入
| 输入 | 默认值 | 描述 |
|---|---|---|
mode | check | check 审计依赖;install 通过 SafeInstall 运行包管理器 |
package-manager | pnpm | 安装时使用 npm 或 pnpm;bun 会失败关闭 |
args | 传递给包管理器的其他参数 | |
config-path | safeinstall.config.json 的显式路径(若省略,则自动发现) | |
version | latest | 要安装的 SafeInstall CLI 版本 |
输出
| 输出 | 描述 |
|---|---|
decision | allow 或 block |
summary | 人类可读的单行摘要 |
exit-code | 0(允许)、2(拦截)或 1(错误) |
json | SafeInstall 的完整 JSON 结果 |
局限性
- 不是 CVE 扫描器 — 请搭配
npm audit或 Snyk 获取漏洞数据 - 启用
transitive.mode时,会检查传递依赖的安装脚本和不受信任的来源。发布时长、仿冒包和来源验证仍仅适用于直接依赖。 - 传递依赖安装脚本检测仅支持 npm — pnpm lockfile 不会记录安装脚本是否存在。传递依赖的不受信任来源检测则同时支持两者。
- **原生构建(
binding.gyp)**会通过安装脚本检查捕获:npm 会在发布时将binding.gyp规范化为node-gyp rebuild安装脚本,因此 SafeInstall 已读取的注册表元数据中会包含该脚本 — 无需下载 tarball。剩余的边缘情况是:通过非标准客户端发布的软件包未进行此规范化,但仍包含binding.gyp;要检测这种情况,需要检查 tarball 内容,这超出了本项目的范围(参见 不做的事)。 - 除非同时声明为直接依赖,否则不会检查
peerDependencies - 信任降级检测需要
node_modules中存在先前的安装状态 - Bun 安装会以失败关闭;尚未实现制品绑定适配器
- 可信执行边界 — 这不是操作系统沙箱。它依赖可信的 npm/pnpm 二进制文件来强制执行 frozen-lockfile 完整性、选定的注册表元数据,并假定本地文件系统不受并发攻击者控制。它无法证明软件包代码是良性的,无法根据注册表策略检查所有传递制品,也无法隔离运行时执行
- 仿冒包目标列表由人工整理,并在各个版本发布之间手动更新;过去一天内新发布的软件包可能尚未列入
- 来源验证仅支持公共 Sigstore 根上的 GitHub Actions 可信发布者(GitLab CI、自托管 Sigstore 目前不在支持范围内)
- Git 来源会按 URL 识别,以用于允许列表,而不是推断软件包名称 — 将注册表中的
axios与github:any-fork/axios混为一谈会很危险 - 遇到有歧义的元数据时会阻止操作,而不是猜测 — 这是有意为之
不做的事
- 漏洞扫描或 CVE 数据库
- 注册表代理或 tarball 重写
- 恶意软件检测或软件包内容分析
- 选择性执行生命周期脚本(始终强制使用
--ignore-scripts)
适用工具
SafeInstall 适用于任何会运行包管理器命令的工具 — 包括 AI 编码助手:
Cursor · Codex · GitHub Copilot · Cline · Claude Code · Windsurf · Aider · Devin · Continue
只需在安装命令前加上 safeinstall。工作流不变,多一层安全保障。
参与贡献
pnpm install
pnpm typecheck
pnpm test
pnpm build
欢迎提交问题和 PR。作者会自行决定是否合并 — 本项目由个人维护。
许可证
MIT — 参见 LICENSE。
免责声明
SafeInstall 按照 MIT 许可证按原样提供。它是一款策略工具,可在软件包安装时强制执行可配置的规则。它不保证任何软件包的安全性,不会检测所有供应链攻击,也不能替代专业的安全审查。使用风险由您自行承担。作者不对因使用本软件而产生的任何损害承担责任。
最后验证时间:2026-10-01
safeinstall.dev · npm · GitHub
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。