sharkyger/composer-cve-gate

GitHub: sharkyger/composer-cve-gate

一个 Composer 供应链安全插件,在依赖安装和升级前基于多源 CVE 数据库和新版本保留策略拦截含有漏洞或恶意代码的包。

Stars: 2 | Forks: 0

# composer-cve-gate **状态 — 预发布版 (0.x)。** API 和退出码已稳定;但在我们根据真实世界反馈进行迭代期间,默认设置、检测启发式规则和建议逻辑可能会在次要版本中发生变化。 **新版本保留功能**(下文第 2 点的缺口)是有时间限制的:当 Composer 发布 `minimum-release-age`(其策略路线图中的保留名称)时,该特定功能将被废弃。**基于锁文件安装的建议缺口**(下文第 1 点)是该工具存在的根本原因,因此只要该缺口存在,**工具本身就会保留** —— 只有当 Composer 在上游也填补了这一缺口时,它才会被归档。 Composer 2.10+ 内置了 `config.policy.advisories.block`(默认为 true)用于在 `composer update` / `require` / `remove` 期间进行安全建议拦截,以及 `config.policy.malware.block`(默认为 true)用于在 `composer install` 期间通过 Aikido 数据源进行恶意软件拦截。**composer-cve-gate 填补了 `composer.policy` 未涵盖的三个缺口:** 1. **在 `composer install` 时拦截安全建议** —— 当锁文件在提交时是干净的,但随后针对锁定版本发布了漏洞,后续的 `composer install`(典型的 CI 部署方式)会毫无阻拦地加载该易受攻击的版本。`composer audit` 可以选择在安装后运行,但无法阻止安装。 2. **3 天新版本保留** —— 在安全研究人员能够检查并报告建议之前,防御零时差发布攻击。有时间限制(直到 Composer 发布 `minimum-release-age`,其路线图中的保留名称 —— 当其发布时我们会归档此工具)。 3. **安装后 IoC 扫描** —— 当新闻中出现供应链安全事件后,`safe-scan` 会遍历 `vendor/` 以寻找已知的入侵指标(C2 域名、数据外泄 URL、攻击者注入的文件路径)。 ## 检查内容 扫描器会查询多个 CVE 数据库并应用基于时间的过滤。 **每个信号覆盖的范围不同 —— 有关准确性,请参见下文:** 1. **OSV.dev** —— Google 的聚合安全建议源。 - 范围:**顶级 + 传递依赖**(批量查询) - 涵盖:Google 的数据,包括生态系统原生的披露信息 2. **GitHub Advisory Database** —— GitHub 的 GHSA 披露。 - 范围:**仅限顶级** - 涵盖:composer 生态系统安全建议,按版本范围过滤 - 注意:Composer 原生的 `config.policy.advisories.block` 主要从 GHSA 获取数据,因此这与原版 Composer 部分重复。我们进行查询是为了让基于锁文件安装的拦截门能够对锁定的集合提供 GHSA 覆盖。 3. **NIST NVD** —— 国家漏洞数据库。 - 范围:**仅限顶级**(在 OSV 传递依赖干净时的预算回退方案) - 涵盖:上游 CVE 元数据、OSV 可能遗漏的 CPE 版本匹配 - 注意:查询速度慢(受速率限制);我们为前 N 个传递依赖包分配了预算以避免超时。 4. **Packagist 新版本保留** —— 基于发布时间的门控。 - 范围:**仅限顶级** - 阈值:发布时间少于 3 天的包将被保留 - 原理:零时差恶意版本通常在发布后的 72 小时内被标记。需要时可使用 `--min-age 0` 覆盖。 - 生命周期:**临时的**。当 Composer 将 `minimum-release-age` 作为原生策略发布时,我们将归档此工具。 5. **OSSF Malicious Packages** —— OpenSSF [`ossf/malicious-packages`](https://github.com/ossf/malicious-packages) 注册表(本地快照)。 - 范围:**顶级 + 传递依赖** - 涵盖:已知恶意软件,由 OSSF 社区确认 - 注意:与使用 Aikido 数据源的 `composer.policy.malware` 分开。我们包含 OSSF 以扩大覆盖面。 ### 我们跳过的包(及原因) 有两种包形态没有可查询的安全建议数据,因此拦截门会在任何扫描运行**之前**跳过它们 —— 在安装拦截门中是静默跳过的,而在 `safe-scan` 中会输出一行信息。它们都不会阻止安装: - **Path 类型包**(`composer.json` 中 `"type": "path"` 的仓库,或者任何锁文件条目中 `dist.type` 为 `"path"` 的包) —— 你项目自己的定制 `clientname/site-package`、Drupal 自定义模块、内部 Laravel 包。不在 Packagist 上,也不在任何漏洞数据库中。 - **Composer 开发分支引用**(`dev-main`、`dev-feature/x`、`1.x-dev` 等) —— 通常是来自分支的私有/开发中的扩展。Composer 建议是基于已发布版本和标签的,而不是任意分支。 如果你想关注被跳过的内容,`composer safe-scan` 会在其报告中列出每个被跳过的包及其原因,并在摘要行中将它们计为 `N skipped`。 ## 为什么预安装很重要 Composer 依赖代码可能会在下一次 autoload 引导时执行,或者在加载 `composer-plugin` 类型包时执行 —— 这两件事都发生在 `composer install` 本身期间,早于 `composer audit` 检查任何内容。如果一个易受攻击(或恶意)的版本**在安装时没有通过锁文件被拦截**,代码就会在你有机会审计它之前运行。 预安装和安装时拦截是生命周期中仅存的、让拦截依然有用的节点。安装后运行 `composer audit` 是一个有用的后盾,但如果恶意代码已经执行,那就太晚了。 ## 用法 该插件添加了三个命令:`safe-install`、`safe-upgrade`(别名为 `safe-update`)和 `safe-scan`。 ### 安装一个新包,优先进行扫描 ``` composer safe-install monolog/monolog ``` 插件会解析 `monolog/monolog` 及其完整的传递树,根据 OSV / GHSA / NVD 加上新版本保留规则查询每个包,只有在一切正常的情况下才会继续实际安装。扫描正常时的输出: ``` safe-install: scanning monolog/monolog [standard composer require output follows] ``` 如果有内容被拦截,你将看到一份结构化的报告,并且**不会安装任何内容**: ``` safe-install: scanning evil/pkg BLOCKED: evil/pkg@1.0.0 — status=vulnerable [CRITICAL] CVE-2026-XXXX — info-stealer in post-install script safe-install: blocked 1 of 1 package(s). Nothing installed. ``` 退出码为 `1`。你的项目未受影响 —— 没有下载,没有写入 `vendor/`,也没有运行安装后脚本。 ### 安装一个开发依赖 ``` composer safe-install --dev phpstan/phpstan ``` `--dev` 被传递给 `composer require`,因此该包会按预期落入 `require-dev` 中。 ### 升级所有依赖项 ``` composer safe-upgrade ``` (也可作为 `composer safe-update` 使用 —— 此别名为了易于发现)。扫描 `composer.json` 中的每个直接依赖项,然后不带任何包参数委托给 `composer update` —— composer 会解析整个图(包括仅涉及传递依赖的更新)。 ### 升级单个包 ``` composer safe-upgrade vendor/pkg ``` 扫描后运行 `composer update vendor/pkg`。与 `safe-update` 的工作方式相同。 ### 安装一个全新的发布版本 3 天的新版本保留规则会阻止安装发布时间不到 72 小时的包 —— 这正是受感染的版本最常出现在 Packagist 上但尚未进入任何 CVE 数据库的时间窗口。如果你确信某个全新的版本没问题(例如,你一直等待的、来自你信任的维护者的补丁),请固定到该版本并禁用保留: ``` composer safe-install --min-age 0 vendor/just-released:1.2.3 ``` ### 审计已安装的内容 ``` composer safe-scan ``` 读取 `composer.lock` 以枚举每个已安装的依赖项,对每一个运行完整的预安装扫描,此外还会遍历 `vendor//` 以寻找来自任何已知恶意发现(C2 域名、数据外泄 URL、攻击者注入的文件路径)的失陷指标字符串或标记文件。输出将包分类为: ``` === safe-scan report === INFECTED — 1 package(s): evil/pkg@1.0.0 [url] https://evil.test/exfil → vendor/evil/pkg/src/payload.php safe-scan — 12 clean, 0 suspicious, 1 infected (of 13 scanned). ``` | 状态 | 含义 | |-------------|-------------------------------------------------------------------| | `CLEAN` | 无任何发现,无 IoC 匹配。 | | `SUSPICIOUS`| 漏洞数据库命中,但磁盘上无 IoC 字符串。 | | `INFECTED` | 在已安装的包内部发现了 IoC 字符串或标记文件。 | 只读 —— `safe-scan` 绝不会执行、修改或下载任何内容。它是供应链安全事件上了新闻后对“*我是否已经被感染?*”的解答。 ### 读取退出码 `safe-install` / `safe-upgrade`: | 退出码 | 含义 | |-----------|------------------------------------------------------| | `0` | 扫描干净,继续安装 | | `10` | 至少有一个包被拦截,**什么也没有安装** | | `1` | 扫描器错误(网络、Python 缺失等) | `safe-scan`: | 退出码 | 含义 | |-----------|---------------------------------------------------------------| | `0` | 干净 | | `1` | 已感染(在磁盘上发现 IoC 匹配) | | `2` | 可疑(有漏洞发现,但磁盘上无 IoC) | | `3` | 扫描器错误(缺失锁文件、格式错误等) | 当你看到一行 `BLOCKED` 时,下一步是查找它引用的 CVE 或建议 ID,并确定该问题是否确实适用于你的使用情况。如果不适用,你有两种选择: - 显式固定到已修补的版本: `composer safe-install vendor/pkg:^2.1.4` - 为一次性操作禁用新版本保留(仅当拦截来自 `FRESH-HOLD`,而不是来自 CVE 时): `composer safe-install --min-age 0 vendor/pkg` ## 安装 ``` composer require sharkyger/composer-cve-gate --dev ``` 就是这样 —— 插件会自动注册,所有三个子命令会立即出现在 `composer list` 中。没有配置文件,也不需要针对项目进行设置。 ### 环境要求 | 组件 | 版本 | 原因 | |-------------|------------|-------------------------------------------------------| | Composer | `^2.0` | 插件使用现代的 `composer-plugin-api` v2 钩子 | | PHP | `^8.2` | 现代构造函数提升、`readonly`、`enum` | | Python | `≥ 3.11` | 扫描器使用 `datetime.UTC` (Python 3.11+) | 内置扫描器 (`bin/dependency_security_check.py`) 作为子进程被调用 —— `python3` 必须在 `PATH` 中。该扫描器没有任何第三方 Python 依赖(仅标准库,以及 macOS 上用于 SSL 信任的可选 `certifi` 包)。如果在激活时缺少 Python,插件会**立即发出明显报错**,而不是静默禁用自身。 ## 配置基于锁文件安装的拦截门 基于锁文件安装的拦截门**默认在建议模式下开启** —— 普通的 `composer install` 会加载锁文件,扫描锁定的集合,对任何发现发出警告,然后继续。要在发现问题时导致构建失败,请通过根目录的 `composer.json` 切换到 `block` 模式: ``` { "extra": { "composer-cve-gate": { "install-gate": "advisory", "install-gate-min-age": 3, "install-gate-cache-ttl": 21600 } } } ``` 这些键的任意子集都有效 —— 未指定的键将保留其默认值。 | 键 | 类型 | 默认值 | 行为 | |---|---|---|---| | `install-gate` | 字符串 | `advisory` | `advisory` 发出警告并继续 · `block` 会在**任何下载或安装后脚本运行之前**以非零退出码中止安装 · `off` 会永久静默禁用 | | `install-gate-min-age` | 整数 (天) | `3` | 应用于每个锁定包发布日期的新版本保留。`0` 禁用保留(重新引入零时差发布的缺口)。 | | `install-gate-cache-ttl` | 整数 (秒) | `21600` (6小时) | 位于 `~/.cache/composer-cve-gate/install-gate/`(或 Windows 上的 `%USERPROFILE%\.cache\…`)下的按包干净判定缓存。`0` 禁用缓存。只有干净的判定会被缓存;被标记或错误的判定总是会重新扫描。 | 无效或格式错误的值会静默回退到这些默认值 —— 拦截门宁愿使用稍微严格的配置,也不愿被静默禁用。另一方面:像 `"install-gate": "blok"` 这样的拼写错误会静默降级为 advisory,因此如果拦截模式至关重要,请在编辑后通过触发一次已知的问题来检查配置的正确性,并确认拦截门的输出名称是你期望的模式。 ### `COMPOSER_CVE_GATE_DISABLE`(紧急绕过) 要在不编辑 `composer.json` 的情况下为单个命令跳过拦截门,请在环境中设置 `COMPOSER_CVE_GATE_DISABLE=1`: ``` COMPOSER_CVE_GATE_DISABLE=1 composer install ``` 除了字符串 `0` 之外的任何非空值都会启用绕过 —— 因此 `1`、`true` `yes` 都有效,**字符串 `false` 也同样有效**,在这里它*不是* falsy(假值)。要重新启用拦截门,请取消设置该变量或将其设置为 `0`。这种设计是刻意让其引人注目的 —— 构建日志中会打印一条警告,使得被禁用的拦截门保持可见 —— 并且它在所有模式下都有效,包括 `block`。将其用作单条命令的紧急控制杆(例如,你已经验证过漏洞的紧急热修复部署);如果要在 `composer.json` 中永久静默禁用,请改用 `install-gate: off`。 **优先级:** `install-gate: off` 会首先短路(不扫描,不记录日志行)。否则,`COMPOSER_CVE_GATE_DISABLE`(当设置为非 `0` 值时)的优先级高于 `composer.json` 中的 `install-gate` 模式。 ## 验证基于锁文件安装的拦截门 核心主张 —— **在 `composer install` 时,即在任何下载或安装后脚本运行之前,拦截被标记的包** —— 作为一个端到端测试发布,你可以在一次性的容器中自行重现。该测试构建了一个项目,其 `composer.lock` 固定了一个被标记的包(一个惰性的、仅用于测试桩的存根 —— 不是真实的包或恶意软件),运行一次**真实的 `composer install`**,并断言拦截门在建议模式下发出警告并继续,而在拦截模式下**在操作运行前中止**(该包永远不会被写入 `vendor/`)。判定由本地安全建议测试桩驱动,因此它是确定性的,不需要实时网络查询。 任何带有 PHP 8.2+、Python 3.11+、git 和 Composer 2 的 Linux 基础系统都可以工作。启动一个一次性容器(`--rm` 退出时自动移除): ``` docker run --rm -it debian:trixie bash # Debian / Ubuntu (apt) # 或者:docker run --rm -it almalinux:10 bash # RHEL / AlmaLinux / UBI (dnf) ``` 然后,在它的内部: ``` # 1) dependencies — Debian/Ubuntu (apt): apt update && apt install -y php-cli php-mbstring php-xml php-zip git unzip python3 python3-venv python3-pip # 改为 RHEL/AlmaLinux/UBI (dnf): # dnf install -y php-cli php-mbstring php-xml git unzip python3 python3-pip # 2) Composer — hash 验证的官方安装程序: php -r "copy('https://getcomposer.org/installer','composer-setup.php');" php -r "if (hash_file('sha384','composer-setup.php') === trim(file_get_contents('https://composer.github.io/installer.sig'))) { echo 'verified'.PHP_EOL; } else { unlink('composer-setup.php'); exit(1); }" php composer-setup.php --install-dir=/usr/local/bin --filename=composer # 3) 运行 proof: git clone https://github.com/sharkyger/composer-cve-gate.git && cd composer-cve-gate python3 -m venv .venv && . .venv/bin/activate pip install pytest -r requirements.txt pytest tests/integration/ -v # -> 2 passed ``` `2 passed` 确认拦截门在该平台上的真实 `composer install` 中触发了。它已经在两大主要打包系列 —— Debian/Ubuntu (apt) 和包括未注册 UBI (dnf) 在内的 RHEL/AlmaLinux —— 上以这种方式重现过,涵盖 PHP 8.3–8.5 和 Python 3.12–3.14。同样的端到端测试在 CI 中的每次更改时都会运行。 ## 范围 `composer-cve-gate` 是对 `config.policy` 的**补充,而不是替代品**。它不能替代: - **`composer audit`** —— 安装后锁文件扫描,默认包含在每个 Composer 项目中。请定期运行它。 - **`config.policy.advisories.block`** —— `composer update` / `require` / `remove` 期间的原生安全建议拦截(Composer 2.10+,默认为 true)。我们针对其未涵盖的基于锁文件安装的缺口并行运行。 - **`config.policy.malware.block`** —— `composer install` 期间通过 Aikido 进行的原生恶意软件拦截(Composer 2.10+,默认为 true)。我们提供额外的 OSSF 数据摄入。 ### 临时工具 当 Composer 发布 `minimum-release-age`(其策略路线图中的保留名称)时,新版本保留的差异化优势将消失,我们将归档此项目。我们是针对已知缺口的权宜之计,而不是永久性产品。无需担心长期的锁定,放心维护。 ## DDEV 如果你的项目使用 DDEV(TYPO3、Drupal、Laravel、Symfony、Magento 等),请安装此 addon 而不是直接安装 composer 插件。该 addon 会在 web 容器**内部**针对容器的 PHP 版本运行扫描器 —— 这正是你的应用程序实际运行的版本 —— 而不是你主机上恰好存在的 PHP 版本。 ``` ddev add-on get sharkyger/composer-cve-gate ``` 这会注册三个自定义命令,并自动将 composer 插件安装到你的项目中(如果 `composer.json` 存在): ``` ddev safe-install monolog/monolog ddev safe-upgrade ddev safe-scan ``` 每一项都在 web 容器中运行,并应用与普通 composer 命令相同的 5 信号拦截门。无需主机垫片 —— 你的主机 PHP 版本无关紧要。 使用 `ddev add-on remove composer-cve-gate` 移除 addon,这也会从你的项目中移除 composer 插件。 ## 相关项目 `composer-cve-gate` 是 safe-install 家族的一部分: **已发布:** - `homebrew-safe-upgrade` —— `brew safe-install` / `brew safe-upgrade` - `claude-code-cve-gate` —— Claude Code 钩子(拦截 AI 安装) - `mistral-code-cve-gate` —— Mistral Code 钩子 - **本项目** —— `composer safe-install`、`composer safe-upgrade` 以及基于锁文件安装的拦截门(基于锁文件的普通 `composer install`) **路线图:** - `pip-cve-gate` —— `pip safe-install` / `pip safe-upgrade` - `npm-cve-gate` —— `npm safe-install` / `npm safe-upgrade` 所有项目都共享 OSV + GHSA + NVD + 新版本保留模式。Composer 具有原生的插件 API,所以我们在这里使用它。pip 和 npm 将改用带前缀的二进制文件。 ## 许可证 MIT。详见 [LICENSE](LICENSE)。 ## 安全 将漏洞私下报告至 **sharky@augatho.com**。详见 [SECURITY.md](SECURITY.md)。本仓库不接受关于安全主题的公开 bug 报告。
标签:Composer, DevSecOps, ffuf, GPT, OpenVAS, PHP, 上游代理, 依赖审查, 漏洞管理, 逆向工具