关于 dependency-audit
用于评估新引入依赖及主版本升级的供应链审计流程,适合负责项目依赖的开发者。流程涵盖身份核验、漏洞与行为检查,并要求在隔离的 Docker 容器中解析和检查依赖树。
主要功能
- 01核验包名、版本、来源与许可证
- 02检查已知漏洞及依赖风险
- 03分析安装脚本和包行为
- 04在 Docker 隔离环境检查依赖树
- 05提供采用或拒绝的判断条件
适用场景
- 01添加第三方包之前进行审查
- 02升级依赖主版本前评估风险
- 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 addgo 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 密钥、 环境 或代码仓库。
容器要完成两项工作:
- 枚举软件包引入的完整传递依赖树—— 这才是真正的影响范围, 阶段 0–3 只检查了顶层依赖。 一个只有两行代码的软件包,也可能引入上百个传递依赖; 每个依赖都有各自的供应链风险。
- 安全地观察安装时的行为—— 先不运行安装脚本(侦察), 然后,仅在确有需要时,运行脚本但隔离网络, 观察哪些脚本试图向外联网。
操作步骤见下一节。
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)/ 拒绝(原因)/ 上报操作人员。