# 面向 Claude Code 的更安全依赖管理 当 Claude 等 AI 编程助手向你的项目添加包时,它们往往会选择听起来合适的版本——而不会检查该包是否存在已知安全漏洞、是否仍在积极维护,或者包名是否与恶意仿冒包仅有一字之差。 safer-dependencies 是 Claude Code 的安全层:它位于 Claude 与你的清单文件之间,自动执行安全检查:存在漏洞的安装会在运行前被拒绝,写入清单的危险版本会在写入后立即在磁盘上被纠正。它会检测并修复有风险的依赖——CVE、仿冒包(typosquats)、已弃用包和版本过旧问题,以及对全新发布版本的冷却期——覆盖 npm、PyPI、RubyGems、Maven、Go、Rust 和 PHP(Composer)。有关具体覆盖范围,请参阅 [CAPABILITIES.md](https://github.com/robert-auger/safer-dependencies/blob/main/CAPABILITIES.md)。 > **新手入门?** [GETTING-STARTED.md](https://github.com/robert-auger/safer-dependencies/blob/main/GETTING-STARTED.md) 可在大约五分钟内带你从零开始完成安装。 > **安全与隐私:** 请参阅 [SECURITY.md](https://github.com/robert-auger/safer-dependencies/blob/main/SECURITY.md)(漏洞披露)、[PRIVACY.md](https://github.com/robert-auger/safer-dependencies/blob/main/PRIVACY.md)(数据外发、无遥测)以及 [CAPABILITIES.md](https://github.com/robert-auger/safer-dependencies/blob/main/CAPABILITIES.md)(该工具防御什么、不防御什么)。 > **许可证(源代码可用——非 OSI“开源”):** 可免费用于自身目的的使用和修改,**包括营利性/公司内部使用以及构建你销售的产品**。仅当你要将软件*本身*商业化时才**需要**单独的付费许可证——即销售它、将其打包进销售的产品或服务中,或向第三方有偿提供其功能(包括托管/SaaS/API)。再分发和衍生作品必须保留许可证并注明本项目来源。请参阅 **[LICENSE](https://github.com/robert-auger/safer-dependencies/blob/main/LICENSE)**(第 4 节为商业限制);商业许可证申请请通过 [github.com/robert-auger](https://github.com/robert-auger)。 ## 目录 - [快速开始](#getting-started) — 约五分钟完成安装 - [功能说明](#what-it-does) - [触发条件](#what-triggers-it) - [仓库内容](#whats-in-this-repo) - [支持的生态系统](#supported-ecosystems) - [安装](#install) - [配置](#configuration) - [修改冷却期](#changing-the-cooldown-period) - [警告级别](#warning-levels) - [工作原理](#how-it-works) - [普通模式(手动)](#normal-mode-manual) - [拦截模式(自动)](#intercept-mode-automatic) - [安装前模式(Bash 钩子)](#pre-install-mode-bash-hook) - [安装后模式(Bash 钩子)](#post-install-mode-bash-hook) - [代理后模式(代理钩子对)](#post-agent-mode-agent-hook-pair) - [审计日志](#audit-log) - [系统要求](#requirements) - [常见问题](#faq) ## 快速开始 **[GETTING-STARTED.md](https://github.com/robert-auger/safer-dependencies/blob/main/GETTING-STARTED.md)** 可在大约五分钟内带你从零开始完成安装——包括前置条件、交互式安装和验证。如需完整的安装参考(全局/项目/手动安装、Windows 特定说明、[权限白名单](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md#permissions-allowlist)、更新和卸载),请参阅 **[INSTALLATION.md](https://github.com/robert-auger/safer-dependencies/blob/main/INSTALLATION.md)**。 **日常使用:** 钩子安装完成后无需运行任何命令——safer-dependencies 会在后台自动工作。当 Claude 添加或安装包时,它会**标记有风险的依赖并将存在漏洞的版本就地升级为安全版本**——并在已知存在漏洞的安装运行前就将其阻止——因此不安全的包会在无需你主动要求的情况下被捕获并纠正。你仍可随时直接调用它:*“`[email protected]` 安全吗?”*、*“检查 safer-dependencies 设置”* 或 *“显示 safer-dependencies 统计信息”*。 ## 功能说明 当 Claude 即将向你的项目添加包时,safer-dependencies 会拦截并运行 5 项检查: 1. **来源** —— 官方注册表、仿冒包检测(npm/PyPI/RubyGems/Maven/crates.io)、包年龄 2. **版本年龄** —— 选择发布于 7 天以上(冷却窗口)的最新稳定版本 3. **漏洞扫描** —— OSV API,并在可用时使用生态系统原生工具(npm audit、pip-audit、bundle audit) 4. **哈希固定完整性** —— 对于带有 `--hash=sha256:...` 固定的 PyPI `requirements.txt` 行,声明的哈希会与 PyPI 发布的哈希进行验证;不匹配会发出警告 5. **已弃用与过时包** —— 已知已弃用的包(例如 `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](https://github.com/robert-auger/safer-dependencies/blob/main/FAQ.md#why-a-skill-alone-is-not-sufficient)。) ## 支持的生态系统 | 生态系统 | 清单 | 锁文件 | |-----------|----------|-----------| | 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](https://github.com/robert-auger/safer-dependencies/blob/main/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` 钩子,以启用 包管理器安装命令的预检审计。这补充了(而非替代)拦截模式——两者共同构成分层防御。 1. Claude 尝试调用 Bash 工具(例如 `npm install [email protected]`) 2. `PreToolUse` 钩子在调用执行前触发,并调用 `safer-dependencies-pretooluse-bash.sh` 3. 一个纯 bash 早期过滤器在约 115 毫秒内短路非包管理器命令 (不调用 Python),因此 `git status` / `ls` / `npm test` 在热路径上 的开销可忽略不计 4. 对于识别出的包管理器安装(`npm`/`pnpm`/`yarn` `install`/`i`/`add`),辅助脚本通过 `shlex` 进行分词,提取每个 `pkg@version` 参数,并 POST 到 OSV 5. 任何存在漏洞的具体固定版本 → 钩子返回 `permissionDecision: "deny"`,附带每个发现的 GHSA-id + CVSS + 摘要,以及调用 safer-dependencies 技能的提示 6. 安装永远不会执行——无网络抓取,无 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 调用会绕过所有这些模式。代理后模式正是针对这一缺口而设的反应性安全网。 1. 一个 `PreToolUse:Agent` 钩子(`safer-dependencies-pretooluse-agent.sh`)会在每次 Agent 派发前立即运行,并在 `/tmp/.safer-deps-agent-<PPID>-<session_id>.sentinel` 处创建一个哨兵文件(当没有可用的会话 ID 时,回退为仅含 PPID 的文件名) 2. 子代理运行,并可能写入 manifest 或 lockfile 3. 一个 `PostToolUse:Agent` 钩子(`safer-dependencies-posttooluse-agent.sh`)会在 Agent 调用返回后运行,`find` 出所有比哨兵文件更新的 manifest 和 lockfile,并通过相同的 shim 路径逐一审计 4. 发现结果以 `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", --- [Read more](https://github.com/robert-auger/safer-dependencies)