关于 unripe
unripe 检查锁文件和 GitHub Actions 中版本的发布时间,并可为多种包生态设置版本冷却规则。它支持 CI、pre-commit、Python 和 MCP,让编码 Agent 在添加依赖前检查版本。
主要功能
- 01检查依赖版本的发布时间
- 02支持多种包生态
- 03可配置冷却规则和例外
- 04检查 GitHub Actions 引用
- 05支持 CI、pre-commit、Python 和 MCP
适用场景
- 01在 Pull Request 中拦截新发布版本
- 02添加软件包前检查依赖版本时长
- 03在多语言仓库统一版本策略
MCP 相关
unripe 可作为 MCP 服务运行,使 AI 编码 Agent 在添加依赖前检查版本发布时间。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
unripe
unripe 会检查你的锁定文件,如果其中有任何依赖版本的发布时间过近,就会检查失败。默认期限为 3 天。它支持 npm、pnpm、yarn、bun、Deno、PyPI、Cargo、Go、Composer、RubyGems、NuGet、Gradle、Dart 和 Flutter、CocoaPods、Swift、Terraform、Conan、R、Elixir、Nix、Bazel 和 vcpkg,也能检查 GitHub 工作流使用的 Actions。一个包含 Python 后端、JavaScript 前端和 Flutter 应用的仓库,只需一项检查和一套策略,无需在每个工具中分别设置。
它可以在 CI 中运行,作为 pre-commit 钩子运行,从 Python 调用,也可以作为 MCP 服务运行,让 AI 编码 Agent 在添加版本前检查该版本的发布时间。
下面是在一个包含 Deno、Python 和 PHP 部分的小型测试项目中的运行结果,时间为 2026 年 10 月 21 日晚上。uv add 刚刚锁定了一个两天前发布的 certifi 版本:
$ unripe check
unripe: checked 867 versions in 4 lockfiles, minimum age 3d
unripe: 2 versions published less than 3d ago
composer laravel/framework 12.70.1 13h old, published 2026-10-21 12:55 UTC, old enough at 2026-10-24 12:55 UTC
php/composer.lock:1
12.70.0 is old enough (published 2026-10-13)
pypi certifi 2026.10.19 47h old, published 2026-10-20 02:24 UTC, old enough at 2026-10-23 02:24 UTC
api/pylock.toml:8, api/uv.lock:6
2026.10.18 is old enough (published 2026-10-18)
fail: 2 unripe, 0 unknown, 865 ok, 0 excluded
这两个版本都没有问题。这很正常,大多数新版本都没问题。关键在于,当某个版本出了问题时,你能在别人发现之后,而不是之前得知。而如果某个版本太新,unripe 会告诉你哪个较旧版本符合要求。
为什么要在意
当热门软件包遭到劫持时,恶意版本通常会在几小时内被发现并撤下。2026 年 8 月,一个蠕虫侵入 npm,在约四小时内发布了444 个软件包的恶意版本,窃取所有安装这些版本的用户的 CI 和云凭据。3 月,两个植入后门的 LiteLLM 版本在 PyPI 上存在了约40 分钟到 3 小时。如果推迟几天再采用新版本,这两起事件都能避开。
你可以验证这一说法。下面是一个锁定文件,其中包含 2025 年 9 月 npm 事件中受影响的 debug 和 chalk 版本,检查时间为它们发布两小时后:
$ unripe check --as-of 2025-09-08T15:00Z
unripe: checked 3 versions in 1 lockfile, minimum age 3d
unripe: 2 versions published less than 3d ago
npm chalk 5.6.1 1h old, published 2025-09-08 13:13 UTC, old enough at 2025-09-11 13:13 UTC
package-lock.json:3
5.6.0 is old enough (published 2025-08-17)
npm debug 4.4.2 1h old, published 2025-09-08 13:12 UTC, old enough at 2025-09-11 13:12 UTC
package-lock.json:2
4.4.1 is old enough (published 2025-05-13)
fail: 2 unripe, 0 unknown, 1 ok, 0 excluded
如今运行检查时,这两个版本都会被标记为已撤销;npm 就是通过这种方式下架恶意版本的。因此,即使锁定文件仍包含其中一个版本,也会检查失败。
过去一年里,大多数包管理器都新增了相关设置;如果你的包管理器支持,就应该启用。unripe 用于弥补这些设置的不足。它会检查锁定文件中已有的版本,包括由机器人、Agent 或旧工具写入的版本。它只报告拉取请求新增的版本。它会对仓库中的所有生态系统应用同一套策略。它还支持 Go、Composer、NuGet、Gradle、Dart、CocoaPods、Swift、Terraform、Conan、R、Nix、Bazel、vcpkg 和 GitHub Actions,这些目前都还没有相应设置。
docs/case-studies.md 以相同方式回顾了更多事件,docs/threat-model.md 则明确说明了冷却期无法防范哪些风险。
安装
你需要 Python 3.11 或更高版本。unripe 刻意不依赖其他软件包:检查供应链的工具不应该给它增加负担。
uvx unripe check
也可以运行 pipx install unripe 或 pip install unripe。若要一次性创建配置和 GitHub 工作流:
unripe init --github
unripe doctor 会检查设置:找到的配置和锁定文件、每个锁定文件是否可读取,以及它们使用的每个注册表是否有响应。
示例
检查当前目录下的所有内容:
unripe
只检查分支新增的版本,如同拉取请求检查:
unripe check --since origin/main
添加版本前先挑选一个版本。如果没有指定版本,explain 会列出近期发布版本,并标出其中符合最短等待时间的最新版本:
$ unripe explain pypi certifi
pypi certifi: 77 versions, minimum age 3d
2026.10.19 2026-10-20 02:24 UTC 2d 8h ago too new, old enough at 2026-10-23 02:24 UTC
2026.10.18 2026-10-18 16:21 UTC 3d 19h ago old enough <- newest old enough
2026.7.22 2026-07-22 03:35 UTC 13w ago old enough
...
在 .unripe.toml 中,为一个生态系统设置更严格的规则,为类型包设置更宽松的规则:
min-age = "3d"
[min-age-for]
npm = "7d"
"npm:@types/*" = "1d"
自己的包始终视为新包;跳过它们:
unripe check --exclude "npm:@myorg/*"
现在就需要的版本,可放行至指定日期,并在报告中注明原因:
[[allow]]
package = "npm:[email protected]"
reason = "fixes the redirect bug we hit in production"
until = 2026-11-15
供脚本使用的结果:
unripe check --format json | jq -r '.findings[] | select(.state == "unripe") | "\(.name) \(.version)"'
用于 Git 钩子或 cron 作业,除非出错,否则保持安静:
unripe check --quiet
它读取的锁文件
| 文件 | 发布日期来自 |
|---|---|
package-lock.json、npm-shrinkwrap.json | npm registry |
pnpm-lock.yaml | npm registry |
yarn.lock(classic 和 berry) | npm registry |
bun.lock | npm registry |
deno.lock | npm registry 和 jsr.io |
uv.lock | 锁文件自身的上传时间 |
pylock.toml、pylock.*.toml | 锁文件自身的上传时间,否则为 PyPI |
poetry.lock、Pipfile.lock | PyPI |
requirements*.txt、constraints*.txt | PyPI(仅精确的 == 固定版本) |
Cargo.lock | crates.io 稀疏索引 |
go.mod | Go 模块代理和校验和数据库 |
composer.lock | 锁文件自身的 time 字段,否则为 Packagist |
Gemfile.lock | rubygems.org |
mix.lock | Hex(hex.pm) |
flake.lock(Nix) | nixpkgs 发布版本对应的 NixOS 渠道;GitHub 输入对应 GitHub |
MODULE.bazel.lock(Bazel 7.2+) | Bazel Central Registry |
vcpkg.json、vcpkg-configuration.json | GitHub,用于 microsoft/vcpkg 基线 |
packages.lock.json | nuget.org |
gradle.lockfile、gradle/dependency-locks/*.lockfile | Maven Central、Google 的 Maven 仓库、Gradle 插件门户 |
pubspec.lock(Dart 和 Flutter) | pub.dev |
Podfile.lock | CocoaPods trunk |
Package.resolved(Swift) | GitHub 上软件包对应的 GitHub releases |
.terraform.lock.hcl | Terraform registry |
conan.lock(Conan 1 和 2) | ConanCenter,按配方修订版本 |
renv.lock | CRAN |
.github/workflows/*.yml、action.yml(使用 --workflows 时) | GitHub releases |
来自 git、本地路径、工作区或其他注册表的依赖项会被跳过,因为没有公开信息可供查询。旧版二进制文件
bun.lockb 无法读取;bun 1.2 及更高版本会写入文本格式的 bun.lock。
NuGet 的 packages.lock.json 不记录软件包来自哪个源,因此 unripe 会在 nuget.org 上查询所有软件包。来自私有源的软件包会显示为未知;请将它们排除。对于 Gradle,可以在配置中的
maven-repositories 里添加其他 Maven 仓库(例如 Confluent 的仓库),以查找其中的构件。
对于 Go,它读取的是 go.mod,而不是 go.sum。从 Go 1.17 开始,go.mod 会列出构建中的所有模块,而 go.sum 还会记住未使用的版本。
Swift 软件包的日期计算方式与下文的 GitHub Actions 相同,因此只会检查托管在 GitHub 上的软件包。其他注册表中的 Terraform 提供程序(包括 OpenTofu 的提供程序)会被跳过。Conan 版本的日期取决于锁文件固定的配方修订版本,因为旧版本的新修订意味着新代码;包含用户和渠道的软件包不在 ConanCenter 上,因此会被跳过。
在 flake.lock 中,来自 NixOS 渠道的 nixpkgs,其日期取自渠道发布该版本的时间,这通常比提交日期晚一天。GitHub 上的输入按照 GitHub Actions 的方式计算日期;其他主机、普通 git 服务器和本地路径都会被跳过。MODULE.bazel.lock 会列出 Bazel 在解析时读取的所有模块版本,而 Bazel 会采用每个模块的最高版本,因此检查的就是该版本;如果某个版本已被注册表撤回,则会报告为未知。vcpkg 没有锁文件:清单会固定一个 builtin-baseline,即 microsoft/vcpkg 中每个 port 所用版本的来源提交。项目通过更新基线来采用新版本,因此 unripe 会检查基线提交是否足够旧。
renv.lock 中的软件包从 CRAN 读取,或从提供 CRAN 内容且路径为 /cran/ 的 Posit Package Manager 镜像读取,仓库名称不限。来自 Bioconductor、GitHub、本地路径或其他仓库的软件包会被跳过,因为 CRAN 是 unripe 唯一可以查询的公共注册表。
GitHub Actions
启用 --workflows(或在配置中设置 workflows = true)后,unripe 还会读取工作流和复合操作中的
uses: 行。每个版本都会根据指向同一提交的 GitHub 发布标签确定日期,因此
actions/checkout@v7 和 actions/checkout@3d3c42e... 都会根据该提交的发布时间进行判断。没有发布的提交则使用
提交时间。如果操作使用的是分支(例如 @main),就没有可依据的发布时间,因此结果会标为未知。设置
GITHUB_TOKEN 可突破 GitHub 每小时 60 次请求的限制;在 Action 中会自动为你设置。
提交时间取决于提交作者填写的时间,因此通过此方式判定的版本会在文本报告中标记为:by its commit time,在 JSON 中标记为
"dated_by": "commit"。
在 GitHub Actions 中
name: unripe
on: pull_request
permissions:
contents: read
pull-requests: write # only needed for comment: true
jobs:
unripe:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: glumly/[email protected]
with:
min-age: 3d
comment: true
对于拉取请求,它只会检查该 PR 相对于基准提交新增的版本。如果检出的提交是浅克隆,它会获取该提交,因此无需设置 fetch-depth: 0。对于推送,它会检查所有内容。
每个过新的版本都会在锁文件对应行上标记,显示在拉取请求的文件视图中。报告会写入作业摘要;设置 comment: true 后,还会在拉取请求中发布评论,让审查者无需打开日志就能看到结果。只会发布一条评论:有过新或未知版本时发布,后续运行会就地更新,不会不断累积。如果作业无法发表评论(例如,拉取请求来自复刻仓库),日志会说明原因,但检查结果不变。
如果可以,请将操作固定到完整的 commit sha,而不是标签。该操作会从自身的检出目录运行 unripe,因此运行的代码就是你固定的提交,不会从 PyPI 下载任何内容。
输入:
| 输入 | 默认值 | |
|---|---|---|
min-age | 3d | 90m、72h、3d、1w |
since | PR 基准提交 | git 引用,或使用 none 检查所有内容 |
paths | 整个仓库 | 文件或目录,以空格或换行分隔 |
exclude | 匹配模式,每行一个 | |
unknown | fail | 设置为 warn,即可让未知时长通过 |
fail | true | 设置为 false,即可只报告而不使检查失败,供试用 |
workflows | false | 同时检查工作流使用的操作 |
annotations | true | 在锁文件对应行上标记问题 |
summary | true | 写入作业摘要 |
sarif | 同时将 SARIF 写入此路径 | |
comment | false | 有过新或未知版本时在拉取请求中发表评论 |
github-token | github.token | 用于发表评论和确定操作发布时间的令牌 |
config | 非默认配置文件 |
输出:result(pass、fail 或 error)、unripe 和 unknown(版本数量),以及 report 和 json(Markdown 和 JSON 报告的路径)。
如果希望将结果作为代码扫描警报,请生成 SARIF 并上传(这需要 security-events: write):
- uses: glumly/[email protected]
with:
sarif: unripe.sarif
- uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: unripe.sarif
作为 pre-commit 钩子
repos:
- repo: https://github.com/glumly/unripe
rev: v0.10.2
hooks:
- id: unripe
锁文件变更时会运行。如果只想检查提交新增的内容,请改用 unripe-changed 钩子。
在 GitLab CI 中
include:
- remote: https://raw.githubusercontent.com/glumly/unripe/v0.10.2/ci/gitlab.yml
这会在 test 阶段添加一个 unripe 作业。对于合并请求,它只会检查该合并请求新增的版本;对于默认分支,它会检查所有内容。发现的问题会显示在合并请求的代码质量小组件和测试报告中,Markdown 报告则会作为作业构件保留。你可以在自己的 variables: 中设置 UNRIPE_MIN_AGE 或 UNRIPE_ARGS(例如 --exclude npm:@myorg/*)来调整设置。
在其他 CI 系统中
docs/ci.md 提供了 Azure Pipelines、Jenkins、CircleCI 和 Bitbucket 的流水线配置。--format junit 适用于上述所有系统,而
--report FORMAT:FILE 会将任意格式的报告写入一个文件,与常规输出并列。docs/bots.md 介绍了 Renovate 和 Dependabot:也为它们设置冷却期,避免它们创建 unripe 会判定为失败的拉取请求。
与 AI 编码 Agent 配合使用
claude mcp add unripe -- uvx [email protected] mcp
unripe mcp 是一个 Model Context Protocol 服务,提供三个只读工具:
check_package(查询某个版本的发布时间,或查询已足够旧的最新版本)、check_lockfiles 和 cooldown_settings。它适用于 Claude
Code、Codex、VS Code 中的 Copilot、Cursor、Claude Desktop、Windsurf 和 Gemini
CLI;docs/agents.md 提供了各自的配置方法,以及一行可添加到
AGENTS.md 的内容,确保 Agent 在添加依赖前使用它。
配置
将设置写入 .unripe.toml,或写入 pyproject.toml 中的
[tool.unripe]。unripe 会从当前目录开始查找,然后逐级向上查找,直到 Git
仓库顶层;也可以读取 UNRIPE_CONFIG 指定的文件。命令行标志优先级更高。两处配置中的排除项会合并。
min-age = "3d"
unknown = "fail"
paths = ["web", "api"]
maven-repositories = ["https://packages.confluent.io/maven"]
exclude = [
"npm:@myorg/*",
"[email protected]",
]
# check workflow actions too
workflows = true
# let a version through early when a security advisory names it as the fix
allow-security-fixes = true
# ask your own registries instead of the public ones
[registries]
npm = { url = "https://artifactory.example.com/api/npm/npm-remote", token-env = "NPM_TOKEN" }
pypi = "https://pypi.example.com"
# a different minimum age for an ecosystem or some packages
[min-age-for]
npm = "7d"
"npm:@types/*" = "1d"
# one version let through early, with a reason and an end date
[[allow]]
package = "npm:[email protected]"
reason = "fixes the redirect bug we hit in production"
until = 2026-11-15
排除项可以是软件包名称或 glob。添加生态系统前缀
(npm:、pypi:、cargo:、go:、composer:、rubygems:、nuget:、
maven:、jsr:、actions:、pub:、cocoapods:、swift:、terraform:、
conan:、cran:、hex:、nix:、bazel:、vcpkg:)可将其限定到一个生态系统;添加 @version 可将其限定到特定版本,例如 @types/node@20.*。Gradle 软件包的名称格式为
group:artifact,例如 maven:com.mycorp:*。PyPI 名称会按 PyPI 的规则规范化,因此 Typing_Extensions 和 typing-extensions 是同一个软件包;NuGet 名称不区分大小写。
排除项适用于你永远不想检查的软件包,例如自己的软件包。对于当前需要使用的版本,请改用 [[allow]]。它只会在版本过新时允许该版本通过,并会在报告中将其标记为已允许;until 日期过后,该条目就不再生效。若某个允许条目已不再需要,因为该版本已经超过最低期限,unripe 也会告知你。
[registries] 可将 npm、PyPI、Conan 或 Go 代理指向其他服务,例如 Artifactory、Nexus 或 Verdaccio 镜像。每项可以是一个 URL,也可以是附带 token-env(存放令牌的环境变量名称,会以 bearer token 的形式发送)或 basic-env(存放 user:password 的环境变量名称)的 URL。机密信息本身绝不会写入文件。它只会通过 https 发送到该 URL 的主机和路径,且绝不会跟随重定向到其他主机。以下环境变量的优先级高于文件中的设置,凭据也只会用于设置它们的 URL。unripe config 和 unripe doctor 会说明各变量是否已设置,但不会显示其值。
min-age-for 接受生态系统名称或软件包模式。软件包模式优先于生态系统名称,并使用第一个匹配的模式。
allow-security-fixes 会针对每个过新的版本查询 osv.dev。如果某条公告将该确切版本列为修复版本,则会允许该版本通过,并在报告中注明公告。修复版本之后恰好发布的较新版本不算,恶意软件报告也不算。此功能默认关闭,因为它会为每个过新的版本额外发送一次请求。
unripe config 会显示当前使用的设置及其来源。它还会读取你的包管理器和更新机器人已有的冷却设置(.npmrc、pnpm-workspace.yaml、.yarnrc.yml、bunfig.toml、uv、
Poetry、Deno、Cargo、Bundler、Dependabot 和 Renovate),指出它们与 unripe 不一致之处,并打印可用于补齐缺失设置的配置行。
命令行
unripe check [paths...]
--min-age AGE default 3d
--since REF only versions added since this git ref
--exclude PATTERN can be repeated
--unknown fail|warn default fail
--allow-security-fixes let through versions an advisory names as the fix
--workflows also check GitHub Actions in workflows
--format FORMAT text, json, sarif, markdown, github, codequality or junit
--output FILE write the report here instead of stdout
--summary FILE also append a markdown report, e.g. $GITHUB_STEP_SUMMARY
--sarif FILE also write a SARIF report
--json FILE also write a JSON report
--report FORMAT:FILE also write any other format, can be repeated
--annotate also print GitHub annotations
--as-of TIME check as if it were then, like 2025-09-08T15:00Z
--exit-zero report, but don't fail
--offline use only cached publish times
--no-suggest don't look for an older version that passes
--config FILE
--no-cache
--verbose list the versions that passed too, and the timing
--quiet print nothing when the check passes
unripe explain ECOSYSTEM NAME [VERSION]
unripe config
unripe doctor [paths...]
unripe init [--github] [--dry-run]
unripe mcp
不带命令运行 unripe 时,会执行 unripe check。所有软件包都已足够旧时,退出码为 0;有软件包过新或发布时间未知时,退出码为 1;无法读取锁文件、未找到锁文件或选项有误时,退出码为 2。JSON 报告的字段说明见 docs/json.md。
explain 用于回答单个问题:
$ unripe explain npm left-pad 1.3.0
npm left-pad 1.3.0: published 2018-04-09 01:10 UTC, 8y ago
old enough: the minimum age is 3d
省略版本号可查看近期发布版本,以及今天可以使用的最新版本:
$ unripe explain composer laravel/framework
composer laravel/framework: 1303 versions, minimum age 3d, 11 prereleases not shown
12.70.1 2026-10-21 12:55 UTC 13h ago too new, old enough at 2026-10-24 12:55 UTC
13.38.0 2026-10-20 10:45 UTC 39h ago too new, old enough at 2026-10-23 10:45 UTC
13.37.0 2026-10-16 04:59 UTC 5d 21h ago old enough <- newest old enough
值得了解的细节
如果注册表无法告知 unripe 某个版本的发布时间(例如注册表宕机、受到速率限制,或从未听说过该版本),那么该版本会被视为未知,而未知会导致检查失败。如果注册表无法访问时检查却悄悄通过,攻击者就能利用这一点绕过检查。如果你更倾向于不这样做,可以使用 --unknown warn。npm 已撤下的版本也会被视为未知,因为 npm 就是通过这种方式下架恶意版本的。
发布时间在未来超过一小时,就不算有效时间,因此该版本也会被视为未知。
当某个版本过新时,unripe 会在同一发布系列中查找最新的、发布时间足够久远的版本(相同的主版本号;低于 1.0 时则是相同的次版本号),跳过预发布版本以及任何已撤回或已下架的版本,并优先选择未标记为弃用的版本。它会对 npm、PyPI、Cargo、Composer、RubyGems、NuGet、JSR、pub.dev、CocoaPods、Terraform、CRAN 和 Hex 执行此操作。
发布时间不会改变,因此查询结果会永久缓存在磁盘上,位置为 ~/.cache/unripe(Windows 上为 %LOCALAPPDATA%\unripe\cache),或 UNRIPE_CACHE 指定的位置。再次运行并处理相同的锁文件时,不会发送请求,只有遇到过新的版本时才会查找更旧的版本;而 --offline 完全不会发送请求。即使多个锁文件中包含同一版本,该版本也只会查询一次;查询每批并发 16 个,并且每个查询都会在请求之间保持与注册表的连接。对于 npm、Cargo、Composer、RubyGems、JSR、pub.dev、CocoaPods、Terraform、CRAN 和 Hex,每个请求都会涵盖某个软件包的所有版本。GitHub Actions 标签(例如 v7)会移动,因此每次运行都会重新查询。
NuGet 和 Maven 也需要特别处理。nuget.org 会将未列出的版本报告为发布于 1900 年,因此 unripe 会改为从 NuGet 目录读取其实际时间;否则,在发布当天就被列出的版本会看起来已经有 126 年历史。Maven 仓库不会更改已发布的文件,因此 POM 的 Last-Modified 标头就是该版本的发布时间;文件一出现,这个标头就会存在,无需等待搜索索引更新。
还可以通过 UNRIPE_NPM_REGISTRY、UNRIPE_PYPI_URL 和 UNRIPE_CONAN_REMOTE(任何提供 Conan v2 API 的服务),以及 UNRIPE_MAVEN_REPOS(以逗号分隔)设置镜像;后者会像 maven-repositories 一样添加 Maven 仓库。对于 Go,unripe 会使用 GOPROXY 中的第一个代理,然后使用 [registries] 中的代理。
在企业代理之后,HTTPS_PROXY 和 NO_PROXY 会照常生效;也可以通过 SSL_CERT_FILE、REQUESTS_CA_BUNDLE 或 UNRIPE_CA_BUNDLE 指定公司 CA 证书包。遇到证书错误时会直接报告,而不会重试。
Go 需要额外处理。模块代理只会报告版本的提交时间,而提交时间由作者自行设置,因此今天推送的旧提交标签(或故意标注为去年日期的提交)会看起来很旧。因此,unripe 还会查询 Go 校验和数据库 sum.golang.org;它会为每个模块版本分配一个按首次发现顺序递增的编号,而且该编号无法改写。将该编号与 Go 模块索引的时间线对照,就能大致确定该版本首次出现的时间。如果提交所标注的时间更早,unripe 就会以首次出现时间为准。对于较新的版本,估算误差在几分钟以内;对于无法进一步缩小范围的旧版本,则采用无法排除的最短时间。它会遵循 GOSUMDB、GONOSUMDB 和 GOPRIVATE 的设置;私有模块则会回退到提交时间。docs/go.md 说明了其工作原理。
uv.lock、pylock.toml 和 composer.lock 本身包含发布时间,unripe 会信任这些时间。能够编辑锁文件的人也能更改这些时间,但他们也可以让锁文件指向任意软件包,因此 unripe 不会尝试防范这种情况。
内置设置之外
截至 2026 年 10 月:
| 设置 | |
|---|---|
| npm 11.10+ | .npmrc 中的 min-release-age,单位为天 |
| pnpm 10.16+ | pnpm-workspace.yaml 中的 minimumReleaseAge,单位为分钟(从 pnpm 11 起默认启用,时长为一天) |
| yarn 4.10+ | .yarnrc.yml 中的 npmMinimalAgeGate |
| bun 1.3+ | bunfig.toml 中的 minimumReleaseAge,单位为秒 |
| Deno | deno.json 中的 minimumDependencyAge(从 Deno 2.9 起默认启用,时长为 24 小时) |
| uv | exclude-newer,可设为日期或类似 7 days 的时长 |
| pip 26.1+ | --uploaded-prior-to P3D |
| Poetry 2.4+ | solver.min-release-age,单位为天 |
| Bundler 4.0.13+ | bundle config set cooldown 3,单位为天 |
| Hex 2.5.0+ | mix hex.config cooldown 3d |
| Cargo | registry.global-min-publish-age,目前不稳定,预计在 Rust 1.100 中稳定 |
| Composer | 提案中 |
| Go、NuGet、Maven、Gradle、GitHub Actions | 暂无 |
| Dart、CocoaPods、Swift、Terraform、Conan、R、Nix、Bazel、vcpkg | 据我们所知,暂无 |
这些设置会在安装时阻止新版本,而 unripe 无法做到这一点,因此请尽可能使用它们;unripe config 会输出适用于你仓库的配置行。unripe 则是在 CI 中随后运行的检查:针对整个仓库使用统一策略,并检查实际锁定的内容。还有一点区别:某些内置设置在无法找到版本的发布时间时,仍会放行该版本。unripe 则会失败。
cooldowns.dev 收录了更完整且持续更新的设置列表,并附有示例。
在 Python 中使用
import unripe
for finding in unripe.scan(["."], min_age="3d", exclude=["npm:@myorg/*"]):
if finding.state != "ok":
print(finding.package, finding.state, finding.reason)
每项发现的状态为 ok、unripe、unknown、allowed 或 excluded。如果无法读取锁文件,或者 since 引用不存在,scan() 会抛出 unripe.check.Problem。
常见问题
我们自己的包总是新的。 将它们排除,例如 npm:@myorg/*。
我们需要一个昨天刚发布的版本。 为那个确切版本添加一条 [[allow]] 条目,并注明原因和 until 日期。如果这是安全修复,设置 allow-security-fixes = true 即可,无需添加条目。
我们使用私有注册表。 在配置中通过 [registries] 指定它;如果需要令牌,可从环境变量读取。代理公共注册表的私有注册表会对所有内容提供响应。如果你的注册表只存放自己的包,则改为将它们排除。
这能替代 npm audit 或类似工具吗? 不能。审计工具了解已报告的漏洞。unripe 对任何包都一无所知。它只是在某个版本发布一段时间之前不信任它。它们能发现不同的问题,因此请同时运行两者。
我们可以先试用而不影响构建吗? 在 Action 中使用 fail: false,或在命令行中使用 --exit-zero。所有结果仍会照常报告。
它运行得慢吗? 不算慢。Discourse 的 Gemfile.lock、Cargo 的 Cargo.lock 和 Vite 的 pnpm-lock.yaml 合计包含 2,048 个版本,未使用缓存时耗时 7 秒,之后耗时三分之一秒。
更多内容
- docs/agents.md:AI 编码 Agent 和 MCP
- docs/ci.md:Azure Pipelines、Jenkins、CircleCI、Bitbucket
- docs/bots.md:Renovate 和 Dependabot
- docs/case-studies.md:过去的攻击案例,使用
--as-of重现 - docs/threat-model.md:它能防护什么,以及不能防护什么
- docs/json.md:JSON 报告
- docs/go.md:Go 版本的日期判定方式
- ROADMAP.md:后续计划,以及未列入计划的内容
贡献
欢迎提交 Bug 报告和拉取请求。CONTRIBUTING.md 介绍了如何运行测试,以及如何添加新的生态系统;这是目前任何人都能做的最有价值的贡献。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。