unripe について
unripeはロックファイルとGitHub Actionsを調べ、新しすぎるバージョンを検出します。複数のパッケージエコシステムに年齢ルールを適用でき、CI、pre-commit、Python、MCPから利用できます。
主な機能
- 01依存バージョンの公開時期を確認
- 02多数のパッケージエコシステムに対応
- 03年齢ルールと例外を設定
- 04GitHub Actionsの参照を確認
- 05CI、pre-commit、Python、MCPに対応
活用シーン
- 01プルリクエストで新しいバージョンを拒否
- 02パッケージ追加前に公開時期を確認
- 03多言語リポジトリで年齢ポリシーを統一
MCP 対応
unripeはMCPサーバーとして動作し、AIコーディングエージェントが依存を追加する前にバージョンの公開時期を確認できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの 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 ワークフローで使用しているアクションもチェックできます。Python バックエンド、JavaScript フロントエンド、Flutter アプリがあるリポジトリでも、ツールごとに異なる設定を用意する代わりに、1つのチェックと1つのポリシーで済みます。
CI、pre-commit フック、Python から実行できるほか、MCP サーバーとしても実行できます。AI コーディングエージェントは、バージョンを追加する前にその公開からの経過日数をチェックできます。
こちらは、Deno、Python、PHP のパーツを含む小規模なテストプロジェクトでの例です。2026年10月21日の夜、uv add が2日前にリリースされた 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 を経由して約4時間で444個のパッケージに悪意のあるバージョンを公開し、それらをインストールした人から CI とクラウドの認証情報を盗みました。3月には、バックドアが仕込まれた LiteLLM の2つのリリースが、40分から3時間ほど PyPI に置かれていました。新しいバージョンの利用を数日待っていれば、どちらも回避できたはずです。
この主張は実際に確認できます。2025年9月の npm 侵害で公開された debug と chalk のバージョンを含むロックファイルを、公開から2時間後の時点でチェックした例です。
$ 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 は、そうした設定では対応できない部分を補います。ロックファイルにすでに含まれているバージョンをチェックします。ボット、エージェント、古いツールが追加したものも対象です。プルリクエストで追加されるバージョンだけを報告します。リポジトリ内のすべてのエコシステムに1つのポリシーを適用します。また、まだ設定がない 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 以降が必要です。意図的に依存関係をなくしています。サプライチェーンをチェックするツールが、サプライチェーンに何かを追加すべきではありません。
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 で、1 つのエコシステムにはより厳しいルールを、型パッケージにはより緩いルールを設定します:
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 レジストリ |
pnpm-lock.yaml | npm レジストリ |
yarn.lock(classic と berry) | npm レジストリ |
bun.lock | npm レジストリ |
deno.lock | npm レジストリと 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 | microsoft/vcpkg のベースラインについては GitHub |
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 リリース |
.terraform.lock.hcl | Terraform レジストリ |
conan.lock(Conan 1 と 2) | レシピリビジョンによる ConanCenter |
renv.lock | CRAN |
.github/workflows/*.yml、action.yml(--workflows を指定した場合) | GitHub リリース |
Git、ローカルパス、ワークスペース、または別のレジストリからの依存関係は、問い合わせ先となる公開情報がないためスキップされます。古いバイナリ形式の bun.lockb は読み取れません。bun 1.2 以降ではテキスト形式の bun.lock が書き出されます。
NuGet の packages.lock.json にはパッケージの取得元フィードが記録されないため、unripe はすべてのパッケージを nuget.org で検索します。プライベートフィードのパッケージは不明として返されます。それらは除外してください。Gradle では、Confluent など別の Maven リポジトリにあるアーティファクトは、設定の maven-repositories にそのリポジトリを追加すると見つけられます。
Go では go.sum ではなく go.mod を読み取ります。Go 1.17 以降、go.mod にはビルド内のすべてのモジュールが記載されますが、go.sum には使用されていないバージョンも記録されます。
Swift パッケージの日時は、後述する GitHub Actions と同じ方法で確認されるため、GitHub 上でホストされているパッケージのみがチェックされます。OpenTofu のレジストリを含む、他のレジストリの Terraform プロバイダーはスキップされます。Conan のバージョンは、ロックファイルでピン留めされたレシピリビジョンを基準に判定されます。古いバージョンでも新しいリビジョンは新しいコードだからです。ユーザー名とチャンネルが指定されたレシピは ConanCenter にないため、スキップされます。
flake.lock では、NixOS チャンネルの nixpkgs の日時は、そのリリースをチャンネルが公開した日時で判定されます。これは多くの場合、コミットの翌日です。GitHub 上の入力は GitHub Actions と同じ方法で判定されます。それ以外のホスト、通常の Git サーバー、ローカルパスはスキップされます。MODULE.bazel.lock には、Bazel が依存関係の解決時に読み取ったすべてのモジュールバージョンが記載されます。Bazel は各モジュールの中で最も高いバージョンを使用するため、そのバージョンがチェックされます。レジストリによって取り下げられたバージョンは不明として報告されます。vcpkg にはロックファイルがありません。マニフェストでは builtin-baseline として、すべてのポートのバージョンの基準となる microsoft/vcpkg のコミットをピン留めします。ベースラインを更新することでプロジェクトは新しいバージョンを取り込むため、unripe はベースラインのコミットが十分に古いことをチェックします。
renv.lock のパッケージは CRAN から読み取られるか、CRAN を /cran/ で提供する Posit Package Manager のミラーから読み取られます。リポジトリには任意の名前を付けられます。Bioconductor、GitHub、ローカルパス、または別のリポジトリからのパッケージは、unripe が問い合わせできる公開レジストリは CRAN のみであるためスキップされます。
GitHub Actions
--workflows(または設定ファイルの workflows = true)を指定すると、unripe はワークフローとコンポジットアクションの uses: 行も読み込みます。それぞれ、タグが同じコミットを指している GitHub リリースの日付で判定するため、actions/checkout@v7 も actions/checkout@3d3c42e... も、そのコミットがリリースされた時期に基づいて判定されます。リリースのないコミットは、コミット日時が代わりに使われます。@main のようにブランチを指定して使われているアクションには基準となるリリースがないため、結果は不明になります。GitHub の 1 時間あたり 60 リクエストの制限を回避するには、GITHUB_TOKEN を設定してください。GitHub Actions では自動的に設定されます。
コミット日時はコミットの作成者が指定するものなので、その日時を基準に合格したバージョンには、テキストレポートでは 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
プルリクエストでは、ベースコミットと比較して、プルリクエストで追加されたバージョンのみを確認します。チェックアウトがシャローの場合はそのコミットを取得するため、fetch-depth: 0 は不要です。プッシュ時にはすべてを確認します。
新しすぎる各バージョンは、プルリクエストのファイル表示で、ロックファイル内の該当行にマークされます。レポートはジョブのサマリーに出力され、comment: true を指定するとプルリクエストにもコメントされるため、レビュー担当者はログを開かずに確認できます。コメントは1件のみで、新しすぎるバージョンまたは不明なバージョンがある場合に投稿され、以降の実行では同じコメントが編集されるため、コメントが増え続けることはありません。フォークからのプルリクエストなど、ジョブがコメントできない場合はログにその旨が表示され、チェック結果は変わりません。
可能であれば、アクションはタグではなく完全なコミット SHA で固定してください。このアクションは独自のチェックアウトから unripe を実行するため、実行されるコードは固定したコミットのものであり、PyPI から何もダウンロードされません。
入力:
| 入力 | デフォルト | |
|---|---|---|
min-age | 3d | 90m、72h、3d、1w |
since | PR のベース | Git の ref、またはすべてを確認する場合は none |
paths | リポジトリ全体 | ファイルまたはディレクトリ。スペースまたは改行で区切ります |
exclude | パターン。1 行に 1 つ | |
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 コーディングエージェントとの連携
claude mcp add unripe -- uvx [email protected] mcp
unripe mcp は、読み取り専用のツールを3つ備えた Model Context Protocol サーバーです。check_package(バージョンがどれくらい古いか、または十分に古い最新バージョン)、check_lockfiles、cooldown_settings を利用できます。Claude Code、Codex、VS Code の Copilot、Cursor、Claude Desktop、Windsurf、Gemini CLI で動作します。それぞれのセットアップ方法と、依存関係を追加する前にエージェントがこれを使うよう AGENTS.md に記載する一文については、docs/agents.md をご覧ください。
設定
設定は .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:)を先頭に付けると、対象を1つに限定できます。また、@version を付けると、@types/node@20.* のように対象バージョンを限定できます。Gradle パッケージは maven:com.mycorp:* のように group:artifact 形式で指定します。PyPI の名前は PyPI と同じ方法で正規化されるため、Typing_Extensions と typing-extensions は同じパッケージです。NuGet の名前では大文字と小文字が区別されません。
除外設定は、自分のパッケージのように、チェック対象から常に外したいパッケージに使用します。今すぐ必要なバージョンには、代わりに [[allow]] を使います。対象バージョンが新しすぎる間だけ許可され、レポートには許可済みとして表示されます。until の日付を過ぎると、許可設定は適用されなくなります。また、バージョンが最小期間を超えて古くなり、許可設定が不要になった場合も unripe が知らせます。
[registries] を使うと、npm、PyPI、Conan、または Go プロキシの接続先を、Artifactory、Nexus、Verdaccio のミラーなどの別のサーバーに設定できます。それぞれ URL を指定するか、token-env(ベアラートークンとして送信するトークンを保持する環境変数の名前)、または 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 は、1つの質問に回答します。
$ 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 はその方法を使うためです。
公開日時が1時間を超えて未来の場合、それは実際の日時ではないため、そのバージョンも不明として扱われます。
バージョンが新しすぎる場合、unripe は同じリリース系列(同じメジャーバージョン、または 1.0 未満の場合は同じマイナーバージョン)から、十分に古い最新のバージョンを探します。その際、プレリリース版や削除・公開取り消しされたバージョンは除外し、非推奨ではないバージョンを優先します。これは npm、PyPI、Cargo、Composer、RubyGems、NuGet、JSR、pub.dev、CocoaPods、Terraform、CRAN、Hex で行われます。
公開日時は変わらないため、結果は ~/.cache/unripe(Windows では %LOCALAPPDATA%\unripe\cache)、または UNRIPE_CACHE が指す場所に、ディスク上で無期限にキャッシュされます。同じロックファイルを使った2回目の実行では、新しすぎるバージョンの古いバージョンを探す場合を除き、リクエストは行われません。また、--offline を指定すると、リクエストは一切行われません。同じバージョンが複数のロックファイルに含まれていても、各バージョンの検索は1回だけ行われます。検索は同時に16件ずつ実行され、それぞれの接続はリクエストの間もレジストリに対して開いたままになります。npm、Cargo、Composer、RubyGems、JSR、pub.dev、CocoaPods、Terraform、CRAN、Hex では、1回のリクエストでパッケージの全バージョンを確認できます。v7 のような GitHub Actions のタグは変わるため、実行のたびに検索されます。
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(カンマ区切り)でも設定できます。UNRIPE_MAVEN_REPOS は maven-repositories と同じ方法で Maven リポジトリを追加します。Go の場合、unripe は GOPROXY 内の最初のプロキシを使い、次に [registries] 内のプロキシを使います。
社内プロキシの背後では、通常どおり HTTPS_PROXY と NO_PROXY が使えます。また、会社の CA バンドルは SSL_CERT_FILE、REQUESTS_CA_BUNDLE、または UNRIPE_CA_BUNDLE で指定できます。証明書エラーは、再試行されずにその旨が報告されます。
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 以降はデフォルトで有効で、1日に設定されています) |
| 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 はリポジトリ用の設定行を出力します。その後 CI で実行するチェックが unripe です。リポジトリ全体で一つのポリシーを適用し、実際にロックされた内容を対象に実行します。もう一つの違いとして、組み込み設定の中には、公開日を見つけられない場合にバージョンを許可するものがあります。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/* のように除外してください。
昨日公開されたバージョンが必要です。 理由と until 日付を指定して、そのバージョンだけを対象とする [[allow]] エントリを追加してください。セキュリティ修正の場合は、エントリを追加しなくても allow-security-fixes = true で対応できます。
プライベートレジストリを使っています。 設定の [registries] で unripe の参照先を指定してください。トークンが必要な場合は、環境変数から渡します。パブリックレジストリをプロキシするプライベートレジストリなら、すべてのパッケージに応答できます。自社のパッケージしか保持していない場合は、代わりにそれらを除外してください。
npm audit などの代わりになりますか? いいえ。監査ツールは報告済みの脆弱性を把握しています。unripe はどのパッケージについても情報を持ちません。バージョンがしばらく公開されていることを確認するまで、信頼しないだけです。それぞれ検出する問題が異なるため、両方を実行してください。
ビルドを壊さずに試せますか? Action では fail: false を、コマンドラインでは --exit-zero を使ってください。結果はすべて引き続き報告されます。
遅いですか? それほどでもありません。Discourse の Gemfile.lock、Cargo の Cargo.lock、Vite の pnpm-lock.yaml に含まれる合計 2,048 バージョンの処理は、キャッシュがない状態で 7 秒、その後は 3 分の 1 秒かかりました。
詳細情報
- docs/agents.md:AI コーディングエージェントと 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:今後の予定と、予定していないこと
コントリビューション
バグ報告やプルリクエストを歓迎します。CONTRIBUTING.md では、テストの実行方法と新しいエコシステムの追加方法を説明しています。現在、最も役立つ貢献は新しいエコシステムの追加です。
ライセンス
MIT
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。