
safer-dependencies v0.6.1
自动化依赖安全层,专为AI编码助手设计,可审计npm、PyPI、RubyGems、Maven、Go及Rust生态系统中的包,检测CVE、打字劫持、废弃、版本年龄问题及哈希完整性。
面向 Claude Code 的更安全依赖管理
当 Claude 等 AI 编程助手向你的项目添加包时,它们往往会选择听起来合适的版本——而不会检查该包是否存在已知安全漏洞、是否仍在积极维护,或者包名是否与恶意仿冒包仅有一字之差。
safer-dependencies 是 Claude Code 的安全层:它位于 Claude 与你的清单文件之间,自动执行安全检查:存在漏洞的安装会在运行前被拒绝,写入清单的危险版本会在写入后立即在磁盘上被纠正。它会检测并修复有风险的依赖——CVE、仿冒包(typosquats)、已弃用包和版本过旧问题,以及对全新发布版本的冷却期——覆盖 npm、PyPI、RubyGems、Maven、Go、Rust 和 PHP(Composer)。有关具体覆盖范围,请参阅 CAPABILITIES.md。
新手入门? GETTING-STARTED.md 可在大约五分钟内带你从零开始完成安装。
安全与隐私: 请参阅 SECURITY.md(漏洞披露)、PRIVACY.md(数据外发、无遥测)以及 CAPABILITIES.md(该工具防御什么、不防御什么)。
许可证(源代码可用——非 OSI“开源”): 可免费用于自身目的的使用和修改,包括营利性/公司内部使用以及构建你销售的产品。仅当你要将软件本身商业化时才需要单独的付费许可证——即销售它、将其打包进销售的产品或服务中,或向第三方有偿提供其功能(包括托管/SaaS/API)。再分发和衍生作品必须保留许可证并注明本项目来源。请参阅 LICENSE(第 4 节为商业限制);商业许可证申请请通过 github.com/robert-auger。
目录
快速开始
GETTING-STARTED.md 可在大约五分钟内带你从零开始完成安装——包括前置条件、交互式安装和验证。如需完整的安装参考(全局/项目/手动安装、Windows 特定说明、权限白名单、更新和卸载),请参阅 INSTALLATION.md。
日常使用: 钩子安装完成后无需运行任何命令——safer-dependencies 会在后台自动工作。当 Claude 添加或安装包时,它会标记有风险的依赖并将存在漏洞的版本就地升级为安全版本——并在已知存在漏洞的安装运行前就将其阻止——因此不安全的包会在无需你主动要求的情况下被捕获并纠正。你仍可随时直接调用它:“[email protected] 安全吗?”、“检查 safer-dependencies 设置” 或 “显示 safer-dependencies 统计信息”。
功能说明
当 Claude 即将向你的项目添加包时,safer-dependencies 会拦截并运行 5 项检查:
- 来源 —— 官方注册表、仿冒包检测(npm/PyPI/RubyGems/Maven/crates.io)、包年龄
- 版本年龄 —— 选择发布于 7 天以上(冷却窗口)的最新稳定版本
- 漏洞扫描 —— OSV API,并在可用时使用生态系统原生工具(npm audit、pip-audit、bundle audit)
- 哈希固定完整性 —— 对于带有
--hash=sha256:...固定的 PyPIrequirements.txt行,声明的哈希会与 PyPI 发布的哈希进行验证;不匹配会发出警告 - 已弃用与过时包 —— 已知已弃用的包(例如
paperclip、request、pycrypto、github.com/dgrijalva/jwt-go)会立即被硬阻止并给出建议替代品;超过 2 年没有稳定版本的包会收到建议性的STALE:警告。被硬阻止的包会从清单中移除,Claude 将询问如何处理;仅过时的包会保留在原处。
如果发现问题,Claude 会发出警告,并可能回退到更安全的版本。所有检查都会记录到 ~/.claude/safer-dependencies-audit-YYYY-MM.log(每个自然月一个文件)。
触发条件
该技能在 Claude 执行以下操作时自动触发:
清单 / 安装操作
- 在
package.json、requirements.txt、Gemfile、pom.xml、build.gradle、Cargo.toml、go.mod或任何其他受支持的清单中添加或更新包 - 为清单中尚未声明的包写入
import、require或use - 生成或更新锁文件(仅检查新增/变更条目)
- 通过 Bash 运行包管理器安装(
npm install、bundle install、poetry install、uv sync、go mod tidy等)——安装前审计命令参数,安装后审计生成的锁文件 - 写入嵌入了固定包管理器安装步骤的
Dockerfile或 CI 工作流(.github/workflows/*.yml等)
选择与推荐问题
- 库/框架比较:“我应该用 axios 还是 node-fetch?”、“moment 还是 dayjs?”、“X 和 Y 哪个更好?”
- 推荐请求:“Python 有什么好的 HTTP 客户端?”、“推荐一个 Go 的日志库”、“Node 中哪个包处理 CSV?”
- 版本选择:“我应该用哪个版本的 Django?”、“最新的稳定版 Flask?”
使用意图表达(添加前)
- “我想用 FastAPI 做这个”、“我在考虑添加 Celery”、“我们打算用 Prisma 作为 ORM”、“我们用 Tailwind 吧”
包健康与信任问题
- “moment.js 还在维护吗?”、“这个 gem 还在活跃开发吗?”、“X 被弃用了吗?”、“X 到达生命周期终点了吗?”、“我能信任这个包吗?”、“faker 上次更新是什么时候?”
脚手架命令
npx create-react-app、npm create vite@latest、django-admin startproject、rails new、cargo new+cargo add、“引导一个新的 FastAPI 项目”
隐式包添加(暗示新依赖的功能请求)
- “为应用添加 Redis 缓存”、“连接 Postgres”、“添加 JWT 认证”、“编写发送邮件的代码”——当清单中尚无该功能的包时触发
迁移与移植
- “从 requests 迁移到 httpx”、“从 CRA 迁移到 Vite”、“从 moment 移植到 date-fns”——审计引入的包
它不会在以下情况触发:
- 标准库导入(
os、fs、java.util.*等) - 未被更改的已声明依赖
- 关于包内部工作原理的学术讨论(“解释 React 的 reconciler”、“webpack 的模块解析是如何工作的?”)——比较和选择类问题仍会触发
- 安装操作系统级应用、运行时或 IDE 扩展(Python 本身、Docker、Homebrew、VS Code 扩展)
仓库内容
这是一个技能 + 钩子捆绑包,而非单个技能文件。完整安装会部署以下组件:
| 文件 | 作用 |
|---|---|
skills/safer-dependencies.md | 技能(安装后为 SKILL.md)。描述审计流程,并包含用于安装/统计的管理模式。 |
skills/safer-dependencies-shim.sh | PostToolUse:Write/Edit 钩子——审计清单和锁文件写入,并就地自动纠正存在漏洞的版本(拦截模式)。 |
skills/safer-dependencies-pretooluse-bash.sh | PreToolUse:Bash 钩子——对包管理器安装命令进行安装前 OSV 审计;在安装运行前拒绝存在漏洞的具体固定版本(安装前模式)。 |
skills/safer-dependencies-posttooluse-bash.sh | PostToolUse:Bash 钩子——Bash 命令执行后的安装后审计;捕获新写入锁文件中的传递性 CVE、通过 sed/jq/脚本编辑的清单,以及普通 pip install 的解析环境(安装后模式)。 |
skills/safer-dependencies-pretooluse-agent.sh + skills/safer-dependencies-posttooluse-agent.sh | PreToolUse:Agent + PostToolUse:Agent 钩子对——弥补子代理覆盖缺口。模式 2–4 仅对根会话的工具调用触发,因此子代理写入的任何清单都会绕过它们。代理后模式会在每次 Agent 工具调用返回后审计子代理写入的内容(代理后模式)。 |
skills/scripts/ | 共享 Python 库(safedep/)以及所有钩子使用的独立解析器脚本。 |
skills/scripts/safer_dependencies_manager.py | 用于交互式安装、使用统计和设置验证的管理模块。 |
仅技能文件是不够的——没有钩子,自动调用取决于 Claude 是否决定使用该技能。请安装全部五个组件以获得完整覆盖;许多技能和斜杠命令会在内部派发子代理,因此即使你从不显式生成子代理,代理后钩子对也很重要。(有关为何仅靠技能无法保证覆盖的说明,请参阅 FAQ.md。)
支持的生态系统
| 生态系统 | 清单 | 锁文件 |
|---|---|---|
| npm | package.json | package-lock.json、yarn.lock、pnpm-lock.yaml |
| PyPI | requirements.txt、pyproject.toml、Pipfile、setup.py、setup.cfg | Pipfile.lock、poetry.lock、uv.lock |
| RubyGems | Gemfile、*.gemspec | Gemfile.lock |
| Maven | pom.xml、build.gradle、libs.versions.toml | -- |
| Go | go.mod | go.sum |
| Rust | Cargo.toml | Cargo.lock |
| PHP(Composer) | composer.json | composer.lock |
安装
刚接触该项目?请从 GETTING-STARTED.md 开始。简要版本:```bash git clone https://github.com/robert-auger/safer-dependencies /tmp/safer-dependencies python3 /tmp/safer-dependencies/skills/scripts/safer_dependencies_manager.py interactive_install
安装程序会提示选择作用范围(全局 vs 项目)以及要启用哪些钩子,然后为你写入 `settings.json`——既包含钩子条目,**也**包含权限白名单,让该技能的检查命令在每次审计时无需审批提示即可运行。
其他与安装相关的内容都位于 **[INSTALLATION.md](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md)** 中,这是安装机制的唯一参考文档:手动逐文件安装(全局和项目级)、Windows 特定说明、Post-Agent 钩子、[权限白名单](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist)、验证安装、更新、固定到发布标签以及卸载。
安装完成后,日常管理可通过自然语言与 Claude 交互——`install safer-dependencies`(重新运行/更改钩子)、`show safer-dependencies stats`、`check safer-dependencies setup`——或通过 `/safer-dependencies` 菜单。更新也可以在会话内完成:`/safer-dependencies update` 应用最新版本(`update --check` 为试运行,`update --rollback` 用于回滚);信任模型请参阅 [INSTALLATION.md](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#in-session-self-updater-safer-dependencies-update)。
> **平台说明:** 支持 macOS、Linux 和 Windows。Windows 需要 Git for Windows(提供 bash)以及位于 `PATH` 上的 Python 3——无需 WSL。迄今为止的实操测试主要集中在 **macOS 和 Windows** 上;Linux 支持由自动化 CI 矩阵进行验证。
### 配置
安装后有两项内容可配置:
- **权限白名单** — 预先批准该技能的只读检查命令(精确形式的 `npm audit` / `bundle audit` 规则以及该技能自身的解析器脚本),使审计无需每次都弹出审批提示即可运行;`curl` 永远不会被预先批准,而 `npm view` / `pip-audit` 可通过 Convenience 配置文件选择启用。交互式安装程序会为你写入核心条目;手动安装则需要手工添加完整块。完整块及理由说明:[INSTALLATION.md → 权限白名单](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist)。
- **安全策略** — 发布年龄冷却窗口/模式,以及每种检查类型的 `off`/`warn`/`block` 分级设置,通过 `/safer-dependencies config` 编辑,并存储在 `~/.config/safer-dependencies/config.toml` 中。架构和分级语义:[`skills/references/configuration.md`](https://github.com/robert-auger/safer-dependencies/blob/main/skills/references/configuration.md)。
### 更改冷却期
冷却期(在配置中称为 **cooloff**)是发布版本必须达到的最低年龄,之后该技能才会选择它——默认为 **7 天**。要更改它,请询问 Claude 或直接运行配置命令:```
/safer-dependencies config set cooloff.days 14 # require releases to be 14+ days old
/safer-dependencies config set cooloff.mode block # gate strength: off | warn | block (default: warn)
/safer-dependencies config unset cooloff.days # revert to the 7-day default
/safer-dependencies config # show effective values and where each comes from
相同的动词在 Claude 会话之外同样适用:```bash python3 skills/scripts/safer_dependencies_manager.py config set cooloff.days 14
该设置持久化在 `~/.config/safer-dependencies/config.toml`(`[cooloff]` 部分);`SAFE_DEP_COOLOFF_DAYS` 和 `SAFE_DEP_COOLOFF_MODE` 环境变量会在每次会话中覆盖该文件。需要了解三种行为:`mode = "off"` 会完全从版本选择中移除年龄过滤器;CVE 驱动的重写会绕过该门槛,因此安全修复绝不会因过新而被阻止;该门槛覆盖 npm、PyPI、RubyGems 和 crates.io——Maven 和 Go 有意不设门槛。完整语义请参阅:[`skills/references/configuration.md`](https://github.com/robert-auger/safer-dependencies/blob/main/skills/references/configuration.md)。
## 警告级别
| 级别 | 含义 | 示例 |
|-------|---------|---------|
| CRITICAL | 停止并询问用户 | 检测到拼写劫持、签名被篡改 |
| HIGH | 警告并继续 | 已知 CVE、包发布不足 30 天 |
| MEDIUM | 警告并继续 | 版本发布不足 7 天、缺少签名 |
| LOW | 警告并继续 | 未签名的 Ruby gem(预期情况) |
## 工作原理
该技能以五种模式运行(总结如下;最深入的设计原理见 `skills/safer-dependencies.md`):
### 正常模式(手动)
当 Claude 即将写入 `import`、向清单添加包或更新锁文件时,该技能会在你的会话中内联运行:
1. 查询包注册表以获取稳定版本
2. 自动选择发布 7 天以上的最新版本(确定性——不依赖 LLM 判断)
3. 通过生态系统工具和 OSV API 检查已知漏洞
4. 在可用时验证包签名
5. 如果发现问题则发出警告,并固定精确版本
6. 将结果记录到审计跟踪中
版本选择由随技能捆绑的独立 Python 脚本处理,而非由 LLM 解释规则。命令输出 `SELECTED: <version>`,Claude 会精确使用该版本。
### 拦截模式(自动)
配置 `.claude/settings.json`,添加 `PostToolUse` 钩子以启用自动、透明的包验证:
1. Claude 使用最初请求的版本写入清单文件(例如 `package.json`)——文件会落到磁盘上
2. `PostToolUse` 钩子在写入完成后立即触发,并调用 `safer-dependencies-shim.sh`
3. 该 shim 读取文件、解析声明的包,并运行所有安全检查(拼写劫持、废弃、CVE、过时、哈希固定)
4. 如果需要修正,shim 会**就地重写清单**,使用安全版本(或移除没有安全版本的条目)
5. shim 通过 stdout 上的 `hookSpecificOutput.additionalContext` 发出信号(`UPDATED:`、`BLOCKED:`、`WARNING:`、`STALE:`、`MAJOR-UPDATE-CONFIRM:`、`REFACTOR-REQUIRED:`、`REGRESSION:`、`TYPOSQUAT-CONFIRM:`、`VERIFY:`、`CLEAN:`)。当审计日志显示同一(文件、包)此前已被修正到相同的安全目标时,`REGRESSION:` 会先于 `MAJOR-UPDATE-CONFIRM:` 出现——也就是说,子代理或过时计划重新引入了已知易受攻击的版本,编排器应恢复先前已批准的版本,而不是重新决定主版本升级。
6. Claude 会以系统提醒的形式接收这些信号,并执行后续工作(查找受影响的导入、运行测试、为破坏性变更进行重构)
**设计说明——形状 C(写入后修正):** 该钩子不会阻止写入。每个易受攻击的版本会先落到磁盘上,然后在同一工具使用周期内被自动修正。这是相对于 `PreToolUse` 阻止式设计的有意选择——权衡取舍请参阅 [FAQ.md](https://github.com/robert-auger/safer-dependencies/blob/main/FAQ.md#why-posttooluse-post-write-corrective-instead-of-pretooluse-pre-write-blocking-for-the-manifest-path)。
**示例信号:**```
UPDATED: aiohttp 3.8.5 → 3.9.0 (HIGH: 33 CVEs fixed)
父代理使用这些信号来识别受影响的代码并按需重构。
预安装模式(Bash 钩子)
在 .claude/settings.json 中配置 PreToolUse:Bash 钩子,以启用
包管理器安装命令的预检审计。这补充了(而非替代)拦截模式——两者共同构成分层防御。
- Claude 尝试调用 Bash 工具(例如
npm install [email protected]) PreToolUse钩子在调用执行前触发,并调用safer-dependencies-pretooluse-bash.sh- 一个纯 bash 早期过滤器在约 115 毫秒内短路非包管理器命令
(不调用 Python),因此
git status/ls/npm test在热路径上 的开销可忽略不计 - 对于识别出的包管理器安装(
npm/pnpm/yarninstall/i/add),辅助脚本通过shlex进行分词,提取每个pkg@version参数,并 POST 到 OSV - 任何存在漏洞的具体固定版本 → 钩子返回
permissionDecision: "deny",附带每个发现的 GHSA-id + CVSS + 摘要,以及调用 safer-dependencies 技能的提示 - 安装永远不会执行——无网络抓取,无 postinstall 脚本
为何在拦截模式之外还需要此模式: 写入后垫片
对 Bash 不可见。npm install [email protected] 会完整执行(并且
postinstall 脚本会运行)之后审计才会触发;npm install -g typosquat-pkg 根本不会写入任何项目清单。预安装模式
在结构上弥补了这些缺口。
预安装模式只能看到用户输入的内容(命令行上的 pkg@version 参数)。
它无法看到解析器实际将安装的传递依赖树。安装后模式(下文)
在安装完成后审计锁文件——两种模式互补,而非冗余。
范围: 此处涵盖的包管理器 CLI 横跨五个生态系统
(npm/pnpm/yarn/bun/npx/deno、pip/pip3/pipx/pipenv/uv/uvx/poetry、gem/bundle、
go、cargo),外加通过拦截模式覆盖的 Maven(Maven 依赖通常
在 pom.xml/build.gradle 中声明,而非通过 CLI 动词添加)。
已知缺口: Maven CLI 确实支持通过
mvn dependency:get -Dartifact=group:art:version和mvn dependency:copy直接下载。 此钩子尚不能识别这些调用。如果您经常使用它们, 现有的写入后垫片仍会捕获落入清单中的任何内容, 但预取保护仅适用于上述列出的生态系统。已作为后续事项跟踪。
各生态系统可识别的语法:
| PM | 动词 | 具体固定版本语法 |
|---|---|---|
npm、pnpm、yarn、bun | install、i、add(外加 yarn/pnpm dlx、bun x、yarn create) | [email protected]、@scope/[email protected] |
npx | (无动词——包为第一个位置参数) | [email protected] |
deno | add、install | npm:[email protected](npm 前缀规范) |
pip、pip3、pipx、pipenv、uv、uvx、poetry | install(pip/pip3/pipx/pipenv)/ add(uv/poetry)/ 无动词(uvx) | pkg==1.2.3(额外依赖 pkg[extra]==X 也已处理) |
gem、bundle | install(gem)/ add | -v 1.2.3、--version 1.2.3、--version=1.2.3(独立标志) |
go | get、install | [email protected](按 Go modules 规则必须包含 v 前缀) |
cargo | add、install | [email protected] |
范围固定版本(npm ^4.17、pip >=、poetry ^/~、Go @latest)以及
未指定版本会在安装后传递到拦截模式——写入后垫片会审计解析器选择的任何内容。
自动重写为安全版本已作为后续事项排队。
故障模式: 故障开放。任何错误(Python 缺失、网络波动、 格式错误的输入)都会以 0 退出且无输出,允许 bash 继续执行。 拦截模式仍在安装后运行,因此预检失败会优雅地降级到现有保护。
拒绝示例:``` safer-dependencies pre-flight audit blocked this install. Vulnerable pinned version(s) detected:
- [email protected] → GHSA-35jh-r3h4-6jhm (CVSS:7.4): Command Injection in lodash Re-run with a patched version, or invoke the safer-dependencies skill for a recommended pin.
### 安装后模式(Bash 钩子)
在 `.claude/settings.json` 中配置 `PostToolUse:Bash` 钩子,以在 Bash 命令执行后启用飞行后审计。它会针对命令的 `cwd` 运行**三项独立扫描**,每项扫描弥补其他钩子无法覆盖的缺口:
- **扫描 A — 锁文件。** 在成功的安装动词(`npm install`、`bundle install`、`poetry install`、`uv sync`、`go mod tidy` 等)之后,审计新修改的锁文件(`package-lock.json`、`Gemfile.lock`、`poetry.lock`、`uv.lock`、`go.sum`、`yarn.lock`、`pnpm-lock.yaml`、`Pipfile.lock`)。这弥补了 Pre-Install 无法看到的**传递性 CVE 缺口**:用户输入了 `pkg@version`,但解析器可能拉入了数十个无人指定的传递依赖。
- **扫描 B — 清单文件。** 在任何*不在*只读黑名单(`ls`、`cat`、`git status` 等)上的 Bash 命令之后,审计新修改的清单文件。这是通过 `sed -i`、`jq` 或脚本进行清单编辑时的**唯一**兜底方案——这些操作绕过了 Intercept Mode 所钩挂的 `Write`/`Edit` 工具。
- **扫描 C — 解析后的环境。** 普通的 `pip install` / `pip install -r requirements.txt` 不会写入锁文件,因此扫描 A 永远看不到解析后的依赖树。在 pip 形态的安装之后,扫描 C 会以只读的 `list --format=json` 重新调用相同的 pip,并对完整解析后的环境(直接 + 传递依赖)进行 OSV 检查。
扫描的运行方式:
1. Claude 执行一次 Bash 工具调用
2. `PostToolUse` 钩子在命令完成*之后*触发,并调用 `safer-dependencies-posttooluse-bash.sh`
3. 一个纯 Bash 早期过滤器会在约 115 ms 内短路不匹配任何扫描门控的命令(与 Pre-Install 相同的快速路径约定),因此 `ls` / `git` / `cat` 的开销可忽略不计
4. 每项扫描使用 `find -maxdepth 5` 遍历 `cwd`(覆盖 monorepo 布局;排除 `node_modules`、`.git`、`.venv`、`venv`),查找最近 60 秒内修改的文件——可通过 `SAFE_DEP_POSTINSTALL_MTIME_WINDOW` 覆盖
5. 对于每个新修改的文件(扫描 A/B),钩子会伪造一个合成的 `PostToolUse:Write` 载荷并将其管道传递给现有 shim——shim 的锁文件和清单文件审计器原样运行,无需重复逻辑
6. 每个文件的信号被拼接并作为单个 `hookSpecificOutput` JSON 发送给父代理
**它能捕获而 Pre-Install 不能的内容:** 传递性漏洞。一次看似正常的 `bundle install` 可能将 `[email protected]`(CVE-2025-27610)作为 `sinatra` 的传递依赖拉入——用户从未输入过 `rack`,因此 Pre-Install 无法看到它,但 Post-Install 会读取解析后的 `Gemfile.lock` 并报告该 CVE。
**范围:** 扫描 A 不会重写解析后的版本——自动纠正契约仅适用于 Claude 直接写入的清单文件。对于传递性 CVE,修复方式通常是“更新拥有该传递依赖的直接依赖”,这需要人工判断。扫描 B *会*自动纠正,因为它通过与 Intercept Mode 相同的 shim 路径审计清单文件。当 `transitive` 检查层级设置为 `off`(`config set checks.transitive off`)时,扫描 A 会跳过。
**故障模式:** 故障开放,与其他钩子相同。任何错误(shim 缺失、载荷格式错误、Python 不可用)都会静默地以退出码 0 结束。
**WARNING 示例:**```
WARNING: [email protected] in lock file has GHSA-29mw-wpgm-hmr9, GHSA-35jh-r3h4-6jhm
代理后模式(Agent Hook 配对)
上述四种模式仅在根会话的工具调用时触发。当根会话通过 Agent 工具派发子代理时(许多技能和斜杠命令在内部都会这样做),子代理的 Write/Edit/Bash 调用会绕过所有这些模式。代理后模式正是针对这一缺口而设的反应性安全网。
- 一个
PreToolUse:Agent钩子(safer-dependencies-pretooluse-agent.sh)会在每次 Agent 派发前立即运行,并在/tmp/.safer-deps-agent-<PPID>-<session_id>.sentinel处创建一个哨兵文件(当没有可用的会话 ID 时,回退为仅含 PPID 的文件名) - 子代理运行,并可能写入 manifest 或 lockfile
- 一个
PostToolUse:Agent钩子(safer-dependencies-posttooluse-agent.sh)会在 Agent 调用返回后运行,find出所有比哨兵文件更新的 manifest 和 lockfile,并通过相同的 shim 路径逐一审计 - 发现结果以
additionalContext的形式呈现给根会话的下一轮;哨兵文件随后被移除
嵌套子代理会被自动覆盖——根会话的 PostToolUse:Agent 只会在外层代理的所有工作(包括它自身派发的任何内容)落盘之后才触发。唯一的缺口是全局安装且不写入任何 manifest 或 lockfile 的情况(npm install -g …):此时没有可扫描的内容。与其他钩子一样,它采用失败开放策略——任何错误(哨兵缺失、shim 缺失、payload 不可读)都会静默地以退出码 0 结束。完整的设计原理见 skills/safer-dependencies.md。
审计日志
每次检查都会以单行 JSON 的形式记录到 ~/.claude/safer-dependencies-audit-YYYY-MM.log(每个自然月一个文件,其中 YYYY-MM 为 UTC 年月)。可通过 SAFE_DEP_AUDIT_LOG 环境变量覆盖完整路径(设置后不再追加日期后缀)。当文件超过 SAFE_DEP_LOG_MAX_BYTES(默认 10 MiB;设为 0 可禁用)时,还会按大小进行轮转。设置 SAFE_DEP_MODEL 可覆盖写入每条记录中 source.model 的模型值——便于在不同模型版本之间进行 A/B 对比。
所有五种模式都会追加到同一个文件。每条记录都带有一个 source 块(schema 2.2),用于标识写入它的组件:
source.component | 写入方 | 触发时机 |
|---|---|---|
shim.posttooluse | shim.sh | Manifest 或 lockfile 写入(拦截模式、安装后派发) |
shim.install_error | shim.sh | shim 预检安装失败 |
bash.pretooluse | pretooluse-bash.sh | Bash 安装命令(预安装模式) |
bash.posttooluse | posttooluse-bash.sh | 安装后 Bash 钩子本身,在到达 shim 之前失败开放时 |
agent.pretooluse | pretooluse-agent.sh | 保留用于预代理失败开放事件(该钩子本身在成功时保持静默) |
agent.posttooluse | posttooluse-agent.sh | 代理后钩子失败开放事件(例如 shim 缺失、python_missing) |
manual.skill | 运行普通模式的 Claude | 内联手动审计调用 |
source.model 记录会话中处于活动状态的 Claude Code 模型(例如 "claude-sonnet-4-6")。自 schema 2.1+ 起存在;由旧版本安装写入的记录会省略该字段。当该字段缺失时,stats 命令会优雅地降级为 "unknown"。
使用 jq 按 source.component 过滤:```bash
jq -r '.source.component' audit.log | sort | uniq -c | sort -rn
jq -c 'select(.source.component == "bash.pretooluse")' audit.log
Surface every silent fail-open across all hooks:
jq -c 'select(.source.mode == "fail_open") | {component: .source.component, reason: .fail_open.reason, ts}' audit.log
为了更方便地进行分析,可以请 Claude 提供使用统计信息,而无需手动解析日志:```
"Show safer-dependencies stats for the last month"
此内容提供了从这些审计日志中提取的活动、安全影响和性能指标的人类可读摘要。
条目形态(schema 2.2)。 三种不同的形态共享相同的 ts / schema / source 头部:
| 形态 | 写入时机 | 区分字段 |
|---|---|---|
| 审计条目 | Manifest / lockfile / bash-install 审计 | file、ecosystem、checked、findings、abandoned、stale、typosquat、unknown、signatures、notes、clean |
| 安装错误条目 | Shim 预检安装错误(组件 shim.install_error) | install_error、shim_dir、scripts_dir |
| 故障开放条目 | 任何钩子入口点因 helper_missing / shim_missing / python_missing 而提前退出。source.mode 为 "fail_open" | fail_open: { reason, detail? } |
审计条目:拦截模式运行完整流水线(来源、版本年龄、OSV、废弃/过时、typosquat、签名),因此所有数组均可填充。预安装模式目前仅运行 OSV,因此 abandoned / stale / typosquat / signatures 始终为空。安装后分发(lockfile 审计)在 shim.posttooluse 下写入,findings 由 lockfile 审计器中的 WARNING: 字符串填充。notes 数组携带信息性 NOTE: 信号(例如,因未固定版本而跳过 manifest)。
Schema 2.2 以增量方式为 lockfile 审计条目新增了四个字段:lockfile、manifest_ref、relation_summary(对每个被标记包相对于同级 manifest 的直接/传递/未知分类),以及一个记录生效中 transitive 层级的 policy 块。此升级向后兼容:读取 2.1 条目的读取器可容忍新字段,且 source.model 字段自 2.1 起持续存在。```json
{
"ts": "2026-04-19T12:34:56Z",
"schema": "2.2",
"source": {
"component": "shim.posttooluse",
"script": "shim.sh",
"hook": "PostToolUse:Write",
"tool": "Write",
"mode": "intercept",
"model": "claude-sonnet-4-6"
},
"file": "/path/to/project/package.json",
"ecosystem": "npm",
"checked": ["[email protected]", "[email protected]"],
"findings": ["UPDATED: express 4.18.2 → 4.22.1 (HIGH: 1 CVE fixed)"],
"abandoned": [],
"stale": [],
"typosquat": [],
"unknown": [],
"signatures": [],
"notes": [],
"clean": ["[email protected]"]
}
Pre-Install 模式示例(Bash 钩子,易受攻击的引脚被拒绝):```json
{
"ts": "2026-04-23T06:56:21Z",
"schema": "2.2",
"source": {
"component": "bash.pretooluse",
"script": "pretooluse-bash.sh",
"hook": "PreToolUse:Bash",
"tool": "Bash",
"mode": "intercept",
"model": "claude-sonnet-4-6"
},
"file": "bash:npm install [email protected] [email protected]",
"ecosystem": "npm",
"checked": ["[email protected]", "[email protected]"],
"findings": [
"BLOCKED: [email protected] GHSA-35jh-r3h4-6jhm (CVSS:3.1/...): Command Injection in lodash"
],
"abandoned": [],
"stale": [],
"typosquat": [],
"unknown": [],
"signatures": [],
"notes": [],
"clean": ["[email protected]"]
}
失败开放模式示例(安装后 Bash 钩子被调用时旁边没有垫片——安装损坏):```json { "ts": "2026-05-03T07:14:11Z", "schema": "2.2", "source": { "component": "bash.posttooluse", "script": "safer-dependencies-posttooluse-bash.sh", "hook": "PostToolUse", "tool": "Bash", "mode": "fail_open", "model": "claude-sonnet-4-6" }, "fail_open": { "reason": "shim_missing", "detail": "/home/alice/.claude/skills/safer-dependencies" } }
一个fail-open条目表示:“此钩子已触发,但因缺少某些前置条件而提前退出,未执行审计。”使用上述jq过滤器(`select(.source.mode == "fail_open")`)来找出日志中每一次静默的保护失效事件。
当shim以dry-run模式运行时(`SAFE_DEP_DRY_RUN=1`),条目还会包含`"mode": "dry_run"`,以便事后分析可以过滤掉仅审计的调用。
## 要求
- Python 3.9+(钩子会探测此版本,并在较旧的解释器上fail-open)
- `curl`(用于注册表API调用和OSV漏洞检查)
- 生态系统工具(可选,若缺失则技能回退到OSV API):
- `npm`,用于npm包
- `pip-audit`,用于Python包
- `bundle`,用于Ruby包
- `dependency-check`,用于Java包
## 常见问题
设计决策的理由(为何使用`PostToolUse`而非`PreToolUse`、为何不验证签名、为何脚本和shim会重复、技能加载的注意事项等)记录在[`FAQ.md`](https://github.com/robert-auger/safer-dependencies/blob/main/FAQ.md)中。