mcpField
D
dependency-audit

dependency-audit

github.com/petr-korobeinikov安全137 次安装14 周前更新
代码仓库

关于 dependency-audit

用于评估新引入依赖及主版本升级的供应链审计流程,适合负责项目依赖的开发者。流程涵盖身份核验、漏洞与行为检查,并要求在隔离的 Docker 容器中解析和检查依赖树。

主要功能

  1. 01核验包名、版本、来源与许可证
  2. 02检查已知漏洞及依赖风险
  3. 03分析安装脚本和包行为
  4. 04在 Docker 隔离环境检查依赖树
  5. 05提供采用或拒绝的判断条件

适用场景

  1. 01添加第三方包之前进行审查
  2. 02升级依赖主版本前评估风险
  3. 03调查可疑安装脚本或传递依赖

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petr-korobeinikov/skills --skill dependency-audit

安装后在 Claude Code 中输入/dependency-audit确认已加载。

附带文件references/ecosystems.mdreferences/services.md

详细介绍

dependency-audit

添加第三方依赖是一项供应链决策, 而不只是为了方便。 你引入的每个包都会在你的计算机和 CI 中运行代码, 携带其传递依赖, 并继承你给予它的信任。 因此,任何东西都不能进入项目—— 无论使用何种语言, 无论是直接依赖还是传递依赖—— 都必须先通过以下审计。

在包通过这套流程之前, 切勿随手运行 npm install / go get / pip install / cargo add, 也不要手动编辑清单文件。 这条规定不容破例: 未经审查的包,即使只是为了快速在本地试用, 其安装脚本也照样会在你的主机上运行。 如果必须试用,请先在沙箱(阶段 4)中尝试。

有疑虑时,询问操作人员

如果有任何不明确之处、 缺少工具, 或某个信号介于粗心疏忽与恶意行为之间—— 请停下来询问操作人员,不要凭猜测采纳依赖。 有问题的依赖很难移除, 而且在你发现之前,它可能已经执行了代码。 问一个问题,总比锁定文件遭到入侵代价低。 以下每个步骤都适用此规则。

何时运行(关卡)

每当准备引入新的第三方依赖, 或将现有依赖升级跨越主版本 (跨主版本升级会带来全新的传递依赖范围)时, 都要运行此流程。 具体来说,在进行以下任何操作之前:

  • npm install <pkg> / yarn add / pnpm add / bun add
  • go get <module>
  • pip install <pkg> / poetry add / uv add / 在 requirements.txt 或 pyproject.toml 中添加一行
  • cargo add <crate>
  • 编辑 build.gradle / build.gradle.kts / pom.xml 以添加 implementation(...) / <dependency>
  • gem install / bundle add、composer require、 dotnet add package,以及其他生态系统中的所有等效操作

这同样适用于生产、开发、测试和构建工具/插件依赖—— 开发依赖的 postinstall 脚本 与生产依赖一样,会在你的计算机和 CI 中运行。

此流程不适用于语言标准库, 或来自你自己的可信注册表的第一方/内部包。 将已审计依赖重新固定到某个补丁版本 无需运行完整流程; 跨主版本升级则需要。

审计流程

共五个阶段。 身份确认后,阶段 0–3 只读取信息, 可以并行运行; 阶段 4 是唯一会执行包代码的阶段, 且始终在隔离环境中运行。 任一阶段发现阻断性问题,都必须停止采纳, 直至问题得到解释或拒绝该包。

各生态系统的命令位于 references/ecosystems.md; 服务的语法和 URL(osv-scanner、OSV.dev、GitHub Advisories、 Socket、Snyk)位于 references/services.md。

阶段 0 — 身份与卫生检查

开始扫描前,先明确你要审计的对象—— 供应链攻击就发生在 “我原本想用的包”和“我输入的包名”之间:

  • 确认确切的包名、注册表 / 命名空间, 以及你打算采用的具体版本。 留意仿冒拼写、 依赖混淆 (公共包冒充内部名称), 以及容易混淆的作用域名称。
  • 确认这是正版项目: 仓库链接、主页、下载量、发布频率、 最近一次发布日期、维护者, 并确认该版本未被撤回或弃用。
  • 许可证——是否与本项目兼容? 即使包没有漏洞,许可证不合适也无法采用。

仅此阶段发现的危险信号就足以终止审计: 一个毫无历史的新包冒充热门包、 维护者突然变更、 某个版本在数小时前发布且活动异常激增。

无需安装任何内容即可获取这些信息: deps.dev(https://deps.dev/<ecosystem>/<name>) 涵盖 npm / PyPI / Go / Maven / Cargo / NuGet 的版本、许可证、安全公告和维护者信息, 而每个注册表都有自己的元数据命令 (npm view、PyPI JSON API、go list -m -versions 等)—— 参见 references/services.md。

阶段 1 — 已知漏洞(CVE / OSV)

检查该包及其传递依赖 是否存在已知且已公开的漏洞。 候选包尚未加入你的项目, 因此没有主机锁文件可供扫描—— 也不得通过在主机上解析依赖来创建锁文件。 以下两种方式不会违反这一要求:

  • 按名称 + 版本查询——无需锁文件,也无需安装: OSV.dev 的 /v1/query、 GitHub Advisory Database, 或生态系统的单包检查工具 (例如,snyk test <pkg>@<ver>,仅适用于 npm)。
  • 在阶段 4 的沙箱中扫描完整的已解析依赖树, 锁文件会在沙箱中生成,且绝不会离开容器: 对其运行 osv-scanner, 或运行生态系统原生审计工具—— npm audit、pip-audit、govulncheck(Go,具备可达性感知)、 cargo audit、OWASP dependency-check(JVM)—— 均在解析依赖的同一个临时容器中执行。

无论运行哪种查询, 都要确认工具接受该生态系统标识符: OSV 会对无效标识符返回 HTTP 400 Invalid ecosystem, 而看似合理但实际错误的标识符会返回空结果—— 这两者都不能证明安全,只说明查询目标有误。

已知、未修补且可达,并且没有修复版本的漏洞 属于阻断性发现。

阶段 2 — 供应链行为(Socket)

CVE 数据库只掌握 已有人报告的漏洞。 Socket 能发现另一类风险—— 包的行为: 安装 / postinstall 脚本、 网络 / 文件系统 / 环境变量访问、 Shell 访问、 混淆或仅有压缩代码、 原生代码、 仿冒拼写、 以及维护者或功能的突然变化—— 这些都是恶意或已遭入侵包的特征, 甚至早于任何 CVE 被提交。

在查明原因之前,将高风险警报视为阻断项: 会访问网络的安装脚本、 混淆的有效载荷、 与包声明用途不符的功能 (例如,一个字符串辅助工具却会打开套接字)。

阶段 3 — 第二意见(Snyk)

Snyk 的漏洞数据库和包健康状况视图 是对阶段 1 的有力独立交叉核验: 包含严重性、CVSS 和修复 / 升级路径的已知 CVE、 许可证问题、 以及健康评分 (安全性 / 流行度 / 维护情况 / 社区)。 使用 snyk test(需要免费账户), 或访问 security.snyk.io 上的公开包页面(无需登录)。

阶段 4 — 隔离安装与传递依赖检查

这一步是安全门真正发挥作用的地方, 也是硬性要求所在: 绝不在宿主机上解析或安装未经审查的软件包。 应在一次性 Docker 容器中执行, 且不挂载任何宿主机文件系统, 这样安装脚本—— 它们会运行任意代码—— 就无法访问你的机器、 SSH 密钥、 环境 或代码仓库。

容器要完成两项工作:

  1. 枚举软件包引入的完整传递依赖树—— 这才是真正的影响范围, 阶段 0–3 只检查了顶层依赖。 一个只有两行代码的软件包,也可能引入上百个传递依赖; 每个依赖都有各自的供应链风险。
  2. 安全地观察安装时的行为—— 先不运行安装脚本(侦察), 然后,仅在确有需要时,运行脚本但隔离网络, 观察哪些脚本试图向外联网。

操作步骤见下一节。

Docker 沙箱

经过加固的基础调用方式—— 根据生态系统替换镜像和安装命令 (references/ecosystems.md):

docker run --rm \
  --network bridge \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --pids-limit 512 \
  --memory 1g \
  <ecosystem-image> sh -c '<install-and-inspect>'

每个标志都有其用处:

  • 不使用 -v / --mount。 这是最重要的一项: 宿主机文件系统绝不会传入容器 (“不映射磁盘”)。 容器可以随意操作自己的临时层; 宿主机不会受到影响。
  • --rm — 容器及其可写层会在退出时销毁。 不会留下任何内容。
  • --network bridge — 侦察(阶段 A)必须连接软件包仓库 以解析依赖树,因此容器在此阶段保持联网。 真正重要的隔离—— 在无外网连接的情况下运行安装脚本—— 是下面的阶段 B: 先在联网状态下下载, 再在执行任何代码之前断开网络。
  • --cap-drop ALL — 移除所有 Linux 能力; 安装过程不需要这些能力。
  • --security-opt no-new-privileges — 阻止 setuid 权限提升。
  • --pids-limit / --memory — 限制恶意安装脚本引发的 fork 炸弹或内存炸弹。
  • 可选的额外加固: 使用 --read-only,并为工具链写入的每个路径配置 --tmpfs — 不仅是 /tmp,还包括缓存和工作目录 (npm 的 ~/.npm、pip 的 ~/.cache、安装目标目录), 或通过 npm_config_cache / PIP_CACHE_DIR 重定向这些路径; 如果镜像支持,还可以使用非 root 用户运行 --user。

两阶段方法:

阶段 A — 侦察(在生态系统允许的情况下不执行代码)。 解析并查看依赖树,不运行安装脚本。 下载需要使用 --network bridge; 其他所有标志都保持锁定状态。

# npm — --ignore-scripts skips pre/post-install hooks entirely
docker run --rm --network bridge --cap-drop ALL \
  --security-opt no-new-privileges --pids-limit 512 --memory 1g \
  node:lts-alpine sh -c \
  'cd "$(mktemp -d)" && npm init -y >/dev/null \
     && npm install --ignore-scripts --no-audit --no-fund <pkg> \
     && npm ls --all'

查看依赖树: 传递依赖有多少, 是否有任何意外情况, 是否有你认为存在风险的依赖, 是否有单个维护者控制着一条很深的依赖链。 (Go 和 Cargo 在解析时不会运行代码; pip 没有干净的 --ignore-scripts 选项, 因此应优先使用 wheel — 参见各生态系统的说明。)

阶段 B — 引爆(仅当你必须查看安装脚本的行为时)。 运行过程中无法更改 --network, 而 --rm 会丢弃下载内容 — 因此应运行一个分离模式容器, 在脚本仍被禁用时获取制品, 断开网络, 然后离线运行脚本:

cid=$(docker run -d --network bridge --cap-drop ALL \
  --security-opt no-new-privileges --pids-limit 512 --memory 1g \
  node:lts-alpine sleep 600)
docker exec "$cid" sh -c 'cd /tmp && npm init -y >/dev/null \
  && npm install --ignore-scripts --no-audit --no-fund <pkg>'   # fetch only
docker network disconnect bridge "$cid"                         # cut egress
docker exec "$cid" sh -c \
  'wget -T3 -qO- https://registry.npmjs.org >/dev/null 2>&1 \
     && echo REACHABLE || echo "egress blocked"'                # confirm
docker exec "$cid" sh -c 'cd /tmp \
  && npm rebuild --foreground-scripts'                    # run hooks, offline
docker rm -f "$cid"

如果 postinstall 因无法连接网络、 读取了环境变量 或找不到 SSH 密钥而报错, 它就已经准确地告诉了你它想做什么 — 而它从未有机会访问你的宿主机。 这里特意直接使用 npm rebuild: 它会运行整个已解析依赖树的安装钩子, 包括传递依赖 — 恶意软件往往藏在这些依赖中。 npm rebuild <pkg> 只会引爆顶层软件包,遗漏传递依赖。 将该命令替换为相应生态系统中的等效命令 (用于运行安装钩子的步骤)。

决策:采用还是拒绝

只有在以下条件全部满足时才采用:

  • 没有已知且未修补、可被触及的漏洞(阶段 1、3)。
  • Socket 没有无法解释的高风险行为(阶段 2)。
  • 已了解传递依赖的影响范围,且可以接受(阶段 4)。
  • 许可证兼容。
  • 软件包真实存在、仍在维护,且身份已正确确认(阶段 0)。

出现以下任何情况时,拒绝或升级处理:

  • 存在已知可利用的 CVE,且没有修复版本。
  • 安装脚本会访问网络,或读取机密信息 / 环境变量,却没有明确合理的原因。
  • 载荷经过混淆处理,或仅提供压缩后的源码。
  • 存在拼写仿冒、依赖混淆或冒充身份的迹象。
  • 功能与预期不符 (一个会打开套接字的「left-pad」)。
  • 未维护,且自身带有高风险传递依赖链。
  • 许可证不兼容。
  • 传递依赖的影响范围与其价值严重不成比例 (一个日期辅助工具却拉取了 200 个软件包)。

如果情况难以判断, 请咨询操作人员、 提出经过审查的替代方案, 或将审查过的子集打包并固定版本。 不要凭猜测采用。

通过之后

  • 固定到确切的版本 — 对刚刚审查过的软件,绝不要使用浮动版本范围。
  • 提交锁文件,并包含完整性哈希。
  • 在生态系统支持的情况下, 优先使用固定了哈希 / SHA 的引用。
  • 记录已检查的内容 — CVE 状态、Socket 结论、传递依赖数量、沙箱观察结果。 在 PR 中写一行说明,就能免去下一个人的重复审查。
  • 尽量缩小依赖范围: 依赖越少越好,尤其是直接依赖。

安装工具

尽可能通过项目的 mise 技能安装 osv-scanner、snyk 和 socket CLI, 以及原生审计工具 (pip-audit、govulncheck、cargo audit、OWASP dependency-check); 否则请按照 references/services.md 操作。

不要因为缺少某个 CLI 就阻止审计: OSV.dev、security.snyk.io 和 github.com/advisories 可通过普通 HTTP 查询,无需安装; Docker 则可用于阶段 4。 Socket 是例外 — socket.dev 只向浏览器提供服务(对 Agent 返回 403), 因此自动运行需要 Socket CLI(附带令牌), 否则应将该阶段升级交由人工处理; 403 不等于「未发现问题」。 Docker 是阶段 4 的唯一硬性前提; 如果无法使用,请明确说明, 不要通过在主机上安装来假装实现了隔离。

未列出该生态系统时

这五个阶段与语言无关; 只有工具链会有所变化。 如果某个包所属的生态系统未涵盖在 references/ecosystems.md 中 — 例如较新的语言或小众包管理器 — 不要跳过审计, 也不要退而求其次,在主机上安装。 将列出的生态系统作为模板, 按相同的五个阶段逐一执行, 并替换为等效工具:

  • 身份信息: 当 deps.dev 无法覆盖时,使用该生态系统的原生注册表 (pub.dev、hex.pm 等) — 查询维护者、下载量、发布频率和撤回状态。
  • 已知漏洞: 使用 osv-scanner 扫描该生态系统的锁定文件(它支持跨生态系统), 使用该生态系统自带的审计工具(如果有), 并检查 OSV.dev / GitHub Advisories / Socket / Snyk 对它的覆盖情况。 先确认准确的标识符: 各服务对生态系统的命名方式不同 (OSV 使用 Pub / Hex,GitHub 使用 pub / erlang), 如果标识符被拒绝或有误,则不会返回任何结果 — 这不代表扫描结果干净,只代表尚未运行扫描。 规范字符串见 references/services.md。
  • 沙箱(阶段 4): 使用该工具链的官方基础镜像, 并采用与列出的生态系统相同的强化标志 (不挂载主机目录、移除功能、限制 pid 数量 / 内存), 执行相同的两轮扫描 — 第一轮在 --network bridge 上进行侦察, 第二轮通过分离容器 / docker network disconnect 流程进行引爆 — 使用该生态系统自己的命令来安装、 枚举完整的传递依赖树, 并观察安装时的行为。

无论使用哪种语言,方法都不变: 干净的容器、 不挂载主机目录、 解析、 枚举传递依赖、 扫描、 观察。 如果确实找不到某个阶段所需的工具, 请说明缺少哪个阶段并询问操作人员 — 不要默默跳过, 也不要把「这种语言没有工具」当作 盲目采用的借口。

范围

  • 每种生态系统: js / ts、go、python、kotlin / java / jvm、rust、ruby、php、.net、 swift、dart — 无一例外。
  • 直接依赖和传递依赖: 阶段 4 的存在,正是因为风险隐藏在传递依赖中。
  • 每种依赖类型: prod、dev、test、build-tool、plugin。
  • 新增采用和主要版本升级; 已审计依赖的补丁版本升级可免于审计。
  • 不包括标准库, 也不包括来自可信来源的第一方内部包。

报告

审计后,请总结:

  • 已审计的确切包名 + 版本。
  • 阶段 0 身份信息(真实包、维护者、许可证)。
  • 阶段 1 / 3 已知漏洞结果(按严重程度列出 CVE;是否可修复)。
  • 阶段 2 Socket 警报。
  • 阶段 4 传递依赖树规模、 值得关注或有风险的传递依赖, 以及沙箱观察到的情况。
  • 结论: 采用(固定到 X.Y.Z)/ 拒绝(原因)/ 上报操作人员。

评论

支持 Markdown