lockvet について
lockvet は、ロックファイルやワークフローのバージョン固定、プルリクエスト、インストール前のパッケージを確認する開発者向けの依存関係・サプライチェーンリスク検査ツールです。61 形式を解析し、バージョン変更、アドバイザリ、公開時期、非推奨、ライセンス変更などを確認でき、MCP サーバーも内蔵しています。
主な機能
- 0161 種類の依存関係形式を解析
- 02直接・推移依存関係の変更を説明
- 03OSV.dev でアドバイザリと修正版を確認
- 04公開時期・非推奨・ライセンス変更を検出
- 05PR と依存関係キューを検査
- 06SBOM とパッケージを比較
活用シーン
- 01マージ前の依存関係変更レビュー
- 02Dependabot または Renovate のキュー整理
- 03インストール前のパッケージ確認
- 04コンテナイメージの SBOM 比較
MCP 対応
内蔵の `lockvet mcp` MCP サーバーを使うと、MCP クライアントから会話中に PR URL、ローカルリポジトリ、ファイル、パッケージ、依存関係キューを検査できます。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
docker run -i --rm -e GITHUB_TOKEN -e GITLAB_TOKEN -e BITBUCKET_TOKEN -e GITEA_TOKEN -e AZURE_DEVOPS_TOKEN ghcr.io/matteo-sung/lockvet:0.6.20GITHUB_TOKENシークレットGitHub token — private repos and higher API rate limits for vet_url/queueGITLAB_TOKENシークレットGitLab token (read_api) — private projects and self-hosted instancesBITBUCKET_TOKENシークレットBitbucket Cloud access token or app password — private repos, workspace queue scansGITEA_TOKENシークレットGitea/Forgejo/Codeberg token — private reposAZURE_DEVOPS_TOKENシークレットAzure DevOps PAT (Code: Read) — private projectsローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。
ghcr.io/matteo-sung/lockvet:0.6.20stdioツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
lockvet
マージする前に、あらゆるロックファイルの変更内容を確認しましょう。
▶ ブラウザーで試す — Dependabot/Renovate の PR URL を貼り付ける、2 つのロックファイルをドロップして差分を比較する、1 つのファイルをドロップして現在のピン留め内容を監査する、インストール前にパッケージを検証する、といったことができます。インストールは不要です。レポートはリンクで共有できます。
任意の PR 監査を URL として共有する、
またはパッケージを検索する
(これは chalk のスペルミスを狙ったマルウェアのパッケージです)。

実例:sharkdp/fd の jiff に対する dependabot の「パッチ」更新で、気付かれないまま推移的な crate が 7 個追加され、そのうち 1 つに RUSTSEC の警告が付いていました。
lockvet なら検出できたでしょうか? — event-stream、chalk/debug の乗っ取り、Shai-Hulud ワーム(および keyv/Cacheable を通じた2026 年 8 月の再来 — ブラウザーで再現する)、ultralytics のマイナー、strong_password gem の乗っ取り、2021 年の依存関係混同攻撃、tj-actions のワークフローピン留め攻撃、Codecov の改ざんダウンロードの手口を、再現可能なフィクスチャとともに、実際のアドバイザリに基づいて再現しています。
ロックファイルの差分は読みにくいものです。通常の npm install でも数千行が書き換えられることがあり、Dependabot の PR には 1 つ のパッケージについてしか記載されていない一方で、ロックファイルでは気付かれないまま 40 個のパッケージが変更されることがあります。lockvet は実際のロックファイルの差分を読み取り、実際に何が起きたのかを示します。
- 何が更新されたか — 追加/削除/アップグレード/ダウングレードされたパッケージをすべて、メジャー/マイナー/パッチに分類し、影響の大きい順に表示します
- なぜ変更されたか — 変更ごとに
(direct)またはvia <the dependency that dragged it in>のラベルを付けるため、40 パッケージの差分も「直接的な更新が 1 件と、それに伴う依存関係」として把握できます - どのようなリスクがあるか — 新しいバージョンで発生した脆弱性、更新によって修正された脆弱性、両方のバージョンに影響するアドバイザリを表示します(OSV.dev から最新情報を取得し、GHSA/CVE/PYSEC の別名を重複排除します)。未解決のアドバイザリにはすべて、修正されるバージョンがアドバイザリ独自のバージョン範囲から読み取られて表示されるため(
· fixed in 4.17.21)、検出内容と同じ行で修正方法を確認できます - 疑わしい点は何か — 新たに導入される各バージョンの経過期間を表示し、過去 7 日以内に公開されたものに ⏱ フラグを付けます(乗っ取られたリリースの多くは数日以内に発見されるため、クールダウンは手軽な保険になります)。上流の非推奨通知や、⚖ ライセンスの変更も表示します。MIT から BUSL や「非標準」ライセンスへ密かに切り替わる更新にはフラグを付けます(deps.dev 経由)
- サプライチェーンの危険を見つける仕組み — 独自のレジストリインデックスに存在しないバージョン(未公開マルウェアの特徴)、名前が人気パッケージと1 文字違いの新しい依存関係(タイポスクワッティング。埋め込み済みの人気パッケージリストを使ってオフラインで確認)、更新によって新たに npm install スクリプトが追加されたパッケージ、そして若いリリースで署名なしのまま sigstore の来歴情報が削除されたパッケージを検出します
- 上流で実際に何が変わったか — 新しいバージョンごとに、ソースリポジトリ内の正確なタグ間差分へのリンクを表示します(
…/compare/v1.2.3...v1.3.0)。リポジトリの実際のタグに照らして検証するため、リンクが 404 になることはありません。npm の[email protected]モノレポタグ、release-please のname-v1.2.3タグ、Go のサブモジュールのdir/v1.2.3タグ、Go の疑似バージョンのコミットハッシュにも対応します - クローンせずに、あらゆる PR、MR、比較、コミットを確認 —
lockvet pr owner/repo#123、lockvet mr group/project!123、lockvet compare owner/repo v1...v2を実行するか、GitHub/GitLab/Bitbucket/ Gitea/Codeberg/Azure DevOps の URL を貼り付けるだけです(セルフホストの GitLab、Gitea、Forgejo、Azure DevOps Server にも対応)。API から直接検査します - Dependabot のキュー全体を一度に確認 —
lockvet queue <org>は、リポジトリ、ユーザー、または組織の未解決の Dependabot/Renovate PR をすべて振り分け、1 つの表にまとめます。GitHub、GitLab、Bitbucket、Gitea/Forgejo、Azure DevOps に対応し、脆弱性を持ち込むもの、メジャー更新や新規パッケージの更新、定型的な更新と思われるものを示します - SBOM にも対応 — コンテナーイメージを 2 つ比較 — CycloneDX または SPDX 形式の JSON SBOM を 2 つ渡します(
lockvet diff old.cdx.json new.cdx.json。たとえばsyftで生成したもの)。イメージ内のあらゆるエコシステムを横断して、1 つのレポートにまとめます。npm、PyPI、Go に加え、Alpine/Debian OS パッケージも対象となり、ディストリビューションのセキュリティアドバイザリ(ALPINE-CVE-…、DEBIAN-CVE-…)は適切なリリースブランチに照らして解決します - インストールする前に確認 —
lockvet pkg npm:left-padを使うと、まだどのロックファイルにも含まれていないパッケージを検査できます。アドバイザリ(悪意のあるパッケージの記録を含む)、リリースからの経過期間、非推奨情報、タイポスクワッティングの疑いを確認できます。レジストリの最新バージョン、または指定した任意のバージョンが対象です - AI アシスタントから利用可能 —
lockvet mcpは組み込みの MCP サーバーです。Claude Code、Cursor、その他の MCP クライアントから、会話中に PR の URL、ローカルリポジトリ、2 つのファイル、追加予定のパッケージ、Dependabot のキュー全体を検査できます - あらゆるエコシステムに対応する、1 つの静的バイナリ — 61 種類の形式に対応します:
npm、pnpm、yarn(classic と berry)、bun、Deno、Cargo、uv、poetry、PDM、pipenv、
requirements.txt、pylock.toml(PEP 751)、Go modules(go.mod+go.sum)、Composer、Bundler、Hex(mix と rebar3)、pub/Flutter、 Gradle(ビルドスクリプト —build.gradle/build.gradle.kts— とロックファイル、バージョンカタログ、検証メタデータ、gradle-wrapper.properties)、 Maven POM(pom.xml— プロパティから解決されるバージョン固定、親、BOM のインポート、プラグイン — とmaven-wrapper.properties)、 sbt ビルド定義(build.sbt、plugins.sbt、project/Dependencies.scala、project/build.properties— Scala ではマニフェストがロックファイルです)、NuGet、Swift Package Manager、CocoaPods、Conan、vcpkg(マニフェストのベースラインとバージョンオーバーライド)、R/renv、 conda/pixi、Julia、Haskell(stack と cabal)、Gleam、Terraform/OpenTofu、 Helm、Ansible Galaxy(requirements.yml)、Nix flakes、Zig(build.zig.zon)、Bazel modules(bzlmod)、GitHub Actions ワークフロー、 GitLab CI 設定(include: component:カタログのバージョン固定、ジョブのimage:/services:参照)、 CircleCI 設定(orbs:レジストリのバージョン固定、Docker executor のimage:参照)、 (uses:のバージョン固定)、コンテナーのベースイメージ(Dockerfile/Containerfile/ Compose のimage:のバージョン固定)、Dev Containers(devcontainer.jsonのイメージとfeatures:の OCI バージョン固定)、Kubernetes マニフェストと kustomization(コンテナーイメージのバージョン固定、kustomization.yamlのnewTag:/digest:オーバーライド、helmCharts:エントリー、Flux のHelmRelease/OCIRepositoryのバージョン固定、Argo CD のApplicationチャートのバージョン固定)、 Helm の values ファイル(values.yamlのイメージのバージョン固定)、 pre-commit フックのバージョン固定 (.pre-commit-config.yamlのrev:)、asdf/mise のツールチェーンのバージョン固定 (.tool-versions、mise.toml、mise.lock— プラットフォームごとのチェックサムによる完全性のバージョン固定を含む)、単一ツールのバージョンファイル(.nvmrc、.node-version、.python-version、.ruby-version、.go-version、.java-version、.terraform-version、.terragrunt-version)と SDKMAN の.sdkmanrc— CycloneDX および SPDX 形式の SBOM にも対応します
🤖 このプロジェクトは、AI エージェントのMatteo Sungが開発・保守しており、 すべての変更を公開しています。人間からのバグ報告やPRも 大歓迎です。
例
$ lockvet HEAD~1 # what did that "upgrade express" commit really do?
package-lock.json (npm)
↑ express 4.17.1 → 5.1.0 MAJOR (direct) (15mo old)
▼ fixes GHSA-rv95-896h-c2vc (moderate) Express.js Open Redirect in malformed URLs
▼ fixes GHSA-qw6h-vgh9-j6wx (low) express vulnerable to XSS via response.redirect()
↑ body-parser 1.19.0 → 2.3.0 MAJOR via express ⏱ published 5 days ago
▼ fixes GHSA-qwcr-r2fm-qrc7 (high) body-parser vulnerable to denial of service ...
↑ path-to-regexp 0.1.7 → 8.4.2 MAJOR via express (3mo old)
▼ fixes GHSA-9wv6-86v2-598j (high) path-to-regexp outputs backtracking regular expressions
▼ …and 2 more fixed
↑ qs 6.7.0 → 6.15.3 minor via express (27d old)
▼ fixes GHSA-hrpp-h998-j3pp (high) qs vulnerable to Prototype Pollution
↑ lodash 4.17.20 → 4.17.21 patch (direct) (5y old)
● 2 known advisories affect both versions (worst: high, GHSA-r5fr-rjxr-66jc)
+ left-pad 1.3.0 (added) (direct) (8y old)
● deprecated upstream: use String.prototype.padStart()
- minimist 1.2.5 (removed) via mkdirp
64 packages changed · 21 major · 9 minor · 4 patch · 23 added · 7 removed
· 3 direct · 61 transitive · vulnerabilities: 0 introduced, 15 fixed, 3 unresolved
· 1 fresh (<7d old) · 1 deprecated
インストール
Homebrew(macOS / Linux):
brew install matteo-sung/tap/lockvet
Scoop(Windows):
scoop bucket add matteo-sung https://github.com/matteo-sung/scoop-bucket
scoop install matteo-sung/lockvet
aqua(lockvetは標準レジストリに登録されています):
aqua g -i matteo-sung/lockvet
mise(aquaバックエンド経由):
mise use -g aqua:matteo-sung/lockvet
GitHub CLI拡張機能(gh-lockvet —
gh lockvetは、既存のgh認証を再利用して、現在作業中のPRを検査します):
gh extension install matteo-sung/gh-lockvet
Debian / Ubuntu(.deb、.rpmと.apkにも対応 — amd64 & arm64):
curl -fsSLO https://github.com/matteo-sung/lockvet/releases/download/v0.6.20/lockvet_v0.6.20_linux_amd64.deb
sudo dpkg -i lockvet_v0.6.20_linux_amd64.deb
Fedora / RHEL:
sudo rpm -i https://github.com/matteo-sung/lockvet/releases/download/v0.6.20/lockvet_v0.6.20_linux_amd64.rpm
Alpine(パッケージには署名がありません。代わりにチェックサムと Sigstoreによる証明が付与されているため、必要に応じて事前に検証してください):
curl -fsSLO https://github.com/matteo-sung/lockvet/releases/download/v0.6.20/lockvet_v0.6.20_linux_amd64.apk
apk add --allow-untrusted lockvet_v0.6.20_linux_amd64.apk
Go:
go install github.com/matteo-sung/lockvet@latest
または、リリースページ からビルド済みバイナリを入手できます (Linux / macOS / Windows、amd64 & arm64):
curl -fsSL https://raw.githubusercontent.com/matteo-sung/lockvet/main/install.sh | sh
Docker(linux/amd64 & arm64、git同梱 — CIで便利です):
docker run --rm -v "$PWD:/repo" -w /repo ghcr.io/matteo-sung/lockvet:0.6.20 lockvet
シェル補完とmanページ
Homebrewではbash/zsh/fishの補完とman lockvetが自動でインストールされます。
リリースのtarballにはcompletions/とman/に同梱されています。別の方法でインストールした場合も、バイナリからすべて表示できます:
lockvet completion bash > /etc/bash_completion.d/lockvet # or:
lockvet completion zsh > "${fpath[1]}/_lockvet"
lockvet completion fish > ~/.config/fish/completions/lockvet.fish
lockvet man > /usr/local/share/man/man1/lockvet.1
リリースの検証
lockvetはビルドの来歴を失う依存関係の更新にフラグを立てるため、自身にも同じ基準を適用しています。v0.4.5以降、各リリース成果物(各バイナリアーカイブ、checksums.txt、Dockerイメージ)は、ビルド時に公開Sigstoreログで証明されています。ダウンロードしたものがこのリポジトリのリリースワークフローによってビルドされたことを証明できます:
gh attestation verify lockvet_v0.6.20_linux_amd64.tar.gz --owner matteo-sung
gh attestation verify oci://ghcr.io/matteo-sung/lockvet:0.6.20 --owner matteo-sung
各リリースにはSigstoreバンドルもアセットとして同梱されています(lockvet_<tag>.intoto.jsonl。すべての成果物を対象とするバンドルが1つです)。そのため、オフラインでも検証できます:gh attestation verify <file> --owner matteo-sung --bundle lockvet_<tag>.intoto.jsonl。
checksums.txtも証明されており、install.shはこれを使ってダウンロードを検証します。そのため、検証済みのchecksums.txtによって、そこに記載されたすべてのファイルも間接的に保証されます。
使い方
lockvet # working tree vs HEAD — "what did I just do?"
lockvet HEAD~5 # working tree vs 5 commits ago
lockvet main my-branch # any two revisions
lockvet main..my-branch # range syntax works too
lockvet -md # markdown, ready to paste into a PR comment
# (package names link to npmjs/crates.io/PyPI/…)
lockvet -json # machine-readable, full vuln ID lists
lockvet -sarif # SARIF for GitHub Code Scanning — alerts on the
# exact lockfile line (see "In CI" below)
lockvet -offline # no network calls (skips vuln + metadata lookups —
# unless -osv-db gives it a local vuln database)
lockvet -only jiff # one package's story: jiff itself plus everything
# it dragged in (matches names AND via-chains;
# globs ok: -only "@babel/*" or -only "*sys*")
lockvet queue myorg # triage EVERY open Dependabot/Renovate PR
lockvet queue owner/repo # of an org, user, or single repo (see below)
lockvet queue gitlab.com/grp # same for a GitLab group or project,
lockvet queue codeberg.org/o # a Gitea/Forgejo owner or repo, a Bitbucket
# workspace, or an Azure DevOps project
lockvet audit # not a diff: check everything you pin RIGHT NOW
lockvet audit web/ -fail-on vuln,unlisted # (see "Audit" below)
lockvet diff old.cdx.json new.cdx.json # two files on disk, no git — SBOMs
lockvet diff Cargo.lock.orig Cargo.lock # or any two lockfiles (see below)
lockvet -changelogs # pull upstream release notes inline (see below)
lockvet -fresh-days 14 # widen the "recently published" window (default 7)
lockvet -fail-on major,vuln # CI gate: exit 1 on major bumps or new vulns
lockvet -fail-on fresh # CI gate: enforce a release cooldown
任意のgitリポジトリ内で実行してください。lockvetは、2つのリビジョン間で変更されたすべてのロックファイルを自動で検出します。設定は不要で、「どのパッケージマネージャーを使っているか」を示すマニフェストも必要ありません。
GitHub、GitLab、Bitbucket、Gitea、Azure DevOps の PR をクローンなしでチェック
lockvet にプルリクエストを指定すると、変更されたすべてのロックファイルについて、GitHub API 経由で変更前後の両方を取得します。
lockvet pr sharkdp/fd#1723 # owner/repo#number
lockvet https://github.com/npm/cli/pull/9793 # or just paste the URL
Dependabot/Renovate の PR を確認する最速の方法です。チェックアウトは不要で、あらゆるパブリックリポジトリで動作し、すべてのフラグ(-md、-json、-only、-fail-on)を適用できます。プライベートリポジトリやより高いレート制限が必要な場合は、GITHUB_TOKEN、GH_TOKEN、またはログイン済みの gh CLI を自動的に使用します。フォークからの PR、サブディレクトリにあるモノレポのロックファイル、追加・削除・名前変更されたロックファイルにも対応しています。
-comment を追加すると、lockvet はレポートをPR または MR 自体にコメントとして投稿します。再実行時には新しいコメントを重ねず、同じコメントを更新します。
lockvet pr sharkdp/fd#1723 -comment # needs a token that can
lockvet mr my-group/app!42 -comment # write comments
lockvet pr https://bitbucket.org/ws/repo/pull-requests/7 -comment
GitLab のマージリクエストも同じように使えます。gitlab.com またはセルフホストのインスタンスに対応し、ホストは URL から自動的に取得します。
lockvet mr gitlab-org/gitlab!245360 # group/project!iid
lockvet https://gitlab.com/gitlab-org/gitlab/-/merge_requests/245360
lockvet https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/4232
フォークからの MR やサブグループにも対応しています。プライベートプロジェクトでは、設定されている場合に GITLAB_TOKEN(GitLab CI 内では CI_JOB_TOKEN)を使用します。パブリックプロジェクトでは認証は不要です。
Bitbucket Cloud のプルリクエストにも対応しています。URL を貼り付けてください。
lockvet https://bitbucket.org/atlassian/aui/pull-requests/5394
フォークからの PR にも対応しています。プライベートリポジトリでは、設定されている場合に BITBUCKET_TOKEN(アクセストークン)、または BITBUCKET_USERNAME と BITBUCKET_APP_PASSWORD を使用します。
Gitea と Forgejo のプルリクエスト(codeberg.org、gitea.com、またはセルフホストのインスタンス。ホストは URL から取得)と、コミット URL にも対応しています。
lockvet https://codeberg.org/forgejo/forgejo/pulls/13594
lockvet https://gitea.com/gitea/tea/pulls/1057
lockvet https://codeberg.org/forgejo/forgejo/commit/714ddd0044f3
フォークからの PR にも対応しています。-comment を指定すると、レポートを PR に投稿・更新します(GITEA_TOKEN、FORGEJO_TOKEN、または CODEBERG_TOKEN を使用)。パブリックリポジトリでは認証は不要です。
Azure DevOps のプルリクエスト(dev.azure.com、*.visualstudio.com、またはセルフホストの Azure DevOps Server)にも対応しています。URL を貼り付けてください。
lockvet https://dev.azure.com/org/Project/_git/repo/pullrequest/128
パブリックプロジェクトでは認証は不要です。プライベートプロジェクトでは AZURE_DEVOPS_TOKEN(Code: Read 権限を持つ個人用アクセストークン)、または Azure Pipelines 内では SYSTEM_ACCESSTOKEN を使用します。-comment を指定すると、レポートを PR のクローズ済みスレッドとして投稿・更新します(Code: Read & Write 権限が必要)。そのため、コメントの解決を必須とするブランチポリシーがレポートによって妨げられることはありません。
GitHub、GitLab、Bitbucket、Gitea/Forgejo、Azure DevOps のリポジトリで任意の 2 つのリビジョンを指定する場合(たとえば「2 つのリリース間で依存関係にどのような変更があったか?」)や、単一のコミットを指定する場合も同様に使えます。
lockvet compare sharkdp/fd v10.1.0...v10.2.0 # two releases
lockvet https://github.com/sharkdp/fd/compare/v10.1.0...v10.2.0
lockvet https://github.com/npm/cli/commit/f055ce68 # one commit
lockvet https://gitlab.com/veloren/veloren/-/compare/v0.17.0...v0.18.0
lockvet https://bitbucket.org/atlassian/aui/commits/8c4205a86de7
lockvet https://codeberg.org/forgejo/forgejo/compare/v11.0.0...v11.0.1
lockvet "https://dev.azure.com/org/Proj/_git/repo/branchCompare?baseVersion=GBmain&targetVersion=GBnext"
lockvet https://dev.azure.com/org/Proj/_git/repo/commit/da22be91c073
比較 URL(main...user:branch のようなフォーク構文を含む)とコミット URL は自動検出されるため、ブラウザーからそのまま貼り付けられます。
Dependabot のキュー全体を一度にトリアージする
ボットの PR を1つずつ確認するのは本末転倒です。問うべきなのは、この30件の PR のうち、人間による確認が必要なのはどれかです。lockvet queue は、リポジトリ、ユーザー、または組織全体の すべてのオープンな Dependabot/Renovate PR を検査し、警告度の高い順に並べ替えます。
lockvet queue mastodon/mastodon # one repo
lockvet queue grafana # a whole org (or user)

各件数は、各 PR のロックファイルを実際に差分比較して算出されます(まとめて1回の OSV / deps.dev バッチ処理を行うため、組織全体のキューでも数秒で完了します)。-md を指定すると表が Markdown に変換され、週次のトリアージ課題に使えます。-json はダッシュボードにデータを渡し、-only left-pad は特定のパッケージに触れる PR を見つけます。また、-fail-on vuln を指定すると、オープンな PR のいずれかが脆弱性を導入した場合に終了コード 1 で終了します。
デフォルトでは、app/dependabot と app/renovate による PR を検索します。-author my-bot でこの設定を上書きできます(-author any = すべてのオープンな PR)。また、-limit 100 で PR の上限を引き上げられます(デフォルトは30件)。利用可能な場合は GITHUB_TOKEN / gh 認証を使用します。API のレート制限内に収めるため、PR が約5件を超える場合に推奨します。
GitLab のキューにも対応しています — グループまたはプロジェクトの URL を指定します(gitlab.com またはセルフホスト環境に対応。サブグループのプロジェクトも含まれます)。
lockvet queue gitlab.com/gitlab-org/gitlab -author gitlab-dependency-update-bot
lockvet queue https://gitlab.example.com/platform # a whole group
GitLab のボットのユーザー名はインスタンスごとに異なります(Renovate の標準アプリユーザーはありません)。そのため、デフォルトの検索対象である renovate-bot、dependabot では見つからないことが多く、-author <your bot's username> の指定が必要です。GITLAB_TOKEN が設定されている場合はこれを使用します。
Gitea / Forgejo / Codeberg にも対応しています — オーナーまたはリポジトリの URL を指定します(codeberg.org、gitea.com、またはセルフホスト環境に対応。不明なホストは匿名 API プローブ1回で自動検出されます)。
lockvet queue codeberg.org/forgejo -author viceice-bot # a whole org
lockvet queue https://git.example.org/team/app # one repo
ここでもボットのユーザー名はさまざまです(Forgejo 独自の Renovate は viceice-bot として実行されます)。そのため、-author を指定するか、ロックファイルに触れるオープンな PR をすべて検査するには -author any を指定してください。GITEA_TOKEN / FORGEJO_TOKEN / CODEBERG_TOKEN が設定されている場合はこれらを使用します。
Bitbucket Cloud — ワークスペースまたはリポジトリの URL を指定します。
lockvet queue bitbucket.org/atlassian # a whole workspace
lockvet queue bitbucket.org/atlassian/aui # one repo
Bitbucket のボットは、表示名しか持たないアプリユーザーとして実行されることがよくあります。そのため、作成者の指定は表示名にも緩やかに一致します。デフォルトの renovate-bot で atlassian-renovate-bot が見つかります。サーバー側で作成者を検索できない場合、lockvet はワークスペース内で最近更新されたリポジトリをスキャンします。ここでは未認証時のレート制限が厳しいため、簡単な確認を超えて利用する場合は BITBUCKET_TOKEN(またはアプリパスワード)を設定してください。
Azure DevOps にも対応しています — プロジェクトまたはリポジトリの URL を指定します。
lockvet queue dev.azure.com/myorg/myproject # a whole project
lockvet queue dev.azure.com/myorg/myproject/_git/api # one repo
Azure DevOps でもボットの ID はさまざまなため、作成者の指定は表示名にも緩やかに一致します(デフォルトの renovate で「Renovate Bot」が見つかります)。または -author any を指定してください。AZURE_DEVOPS_TOKEN / SYSTEM_ACCESSTOKEN が設定されている場合はこれらを使用します。
週次トリアージ課題 — このワークフローは、リポジトリ内に「依存関係 PR のトリアージ」という常に最新の課題を1件維持し、毎週月曜日に更新します(実際の例)。
name: dependency triage
on:
schedule: [{cron: '0 8 * * 1'}]
workflow_dispatch:
permissions:
issues: write
pull-requests: read
jobs:
triage:
runs-on: ubuntu-latest
steps:
- run: curl -fsSL https://raw.githubusercontent.com/matteo-sung/lockvet/main/install.sh | sh -s -- -b /usr/local/bin -v v0.6.20
- env: {GITHUB_TOKEN: '${{ github.token }}'}
run: lockvet queue "$GITHUB_REPOSITORY" -md > queue.md
- env: {GH_TOKEN: '${{ github.token }}'}
run: |
title="Dependency PR triage"
n=$(gh issue list -R "$GITHUB_REPOSITORY" --state open --search "in:title \"$title\"" --json number --jq '.[0].number')
if [ -n "$n" ]; then gh issue edit -R "$GITHUB_REPOSITORY" "$n" --body-file queue.md
else gh issue create -R "$GITHUB_REPOSITORY" --title "$title" --body-file queue.md; fi
(ボット以外の PR も含めるには、lockvet queue の行に -author any を追加してください。)
2つの SBOM(またはコンテナイメージ)を差分比較する
lockvet diff は ディスク上の2つのファイルを検査します。git リポジトリは不要です。2つの CycloneDX または SPDX JSON SBOM を指定すると(ファイル名は任意で、形式は内容から判別されます)、ドキュメント内のすべてのエコシステムをまとめて比較し、何が変わったかを説明します。
syft -q alpine:3.18 -o cyclonedx-json > old.cdx.json
syft -q alpine:3.19 -o cyclonedx-json > new.cdx.json
lockvet diff old.cdx.json new.cdx.json
new.cdx.json (SBOM)
↑ ca-certificates-bundle 20241121-r1 → 20250911-r0 MAJOR via apk-tools
↑ zlib 1.2.13-r1 → 1.3.1-r0 minor via apk-tools
↑ busybox 1.36.1-r7 → 1.36.1-r20 patch via alpine-baselayout › busybox-binsh
▼ fixes ALPINE-CVE-2023-42363 A use-after-free vulnerability was discovered in xasprintf…
▼ fixes ALPINE-CVE-2023-42364 A use-after-free vulnerability in BusyBox v.1.36.1 allows…
…
12 packages changed · 1 major · 1 minor · 10 patch · 2 direct · 10 transitive
· vulnerabilities: 0 introduced, 5 fixed, 4 unresolved
パッケージは purl で照合されます。言語エコシステム(npm、PyPI、Go、Cargo、…)は、OSV アドバイザリ、リリース経過期間、検証済み変更履歴リンクなど、すべての情報が確認されます。OS パッケージについては、適切なリリースブランチ(Alpine:v3.18、Debian:12、Wolfi)に照らしてディストリビューションのアドバイザリを解決するため、ベースイメージの更新によってどの CVE が修正または導入されるかを正確に把握できます。バージョンの比較方法もディストリビューションに準拠します。apk の -r7 → -r20 リビジョン、_git スナップショット接尾辞、Debian のエポック(1:3.10-4)、~deb13u1 のプレリリースは、いずれも正しく比較されます。
リポジトリ外の通常のロックファイル(lockvet diff Cargo.lock.orig Cargo.lock)にも対応します。また、git にコミットされた SBOM(bom.json、*.cdx.json、*.spdx.json)は、他のあらゆるモード(lockvet、lockvet pr、GitHub Action)で、他のロックファイルと同様に検出されます。
現在固定している依存関係を監査する — lockvet audit
これまで説明してきたのは、すべて「変更」についてです。lockvet audit が答えるのは、もう一つの疑問、つまり 「現在依存しているものに、既知の問題があるものはないか?」 です。これは、サプライチェーン攻撃のニュースを受けたとき、引き継いだばかりのコードベースを確認するとき、または定期的な健全性チェックを行うときに尋ねる質問です。
ツリーをたどり(node_modules、vendor、.git などはスキップ)、見つかったすべてのロックファイルを読み込みます。61 種類すべての形式に加え、SBOM、CI ワークフロー、Dockerfile、Kubernetes マニフェストも対象です。そして、現在固定されているバージョンに対して、パイプライン全体を実行します。問題が見つかったものだけを表示します。

この再生は、事例研究 7の「ニュースが報じられた直後」の一斉監査です(再現可能なフィクスチャも同ページにあります)。日常的に問題のないリポジトリなら、期待どおり静かな結果になります。
$ lockvet audit # in sharkdp/fd
Cargo.lock (crates.io · 126 packages)
• anyhow 1.0.102 (direct) (5mo old)
▲ affected by RUSTSEC-2026-0190 Unsoundness in `Error::downcast_mut()` · fixed in 1.0.103
• crossbeam-epoch 0.9.18 via ignore › crossbeam-deque (2y old)
▲ affected by RUSTSEC-2026-0204 Invalid pointer dereference in `fmt::Pointer` impl… · fixed in 0.9.20
• proc-macro-error2 2.0.1 via jiff › … › defmt-macros (23mo old)
▲ affected by RUSTSEC-2026-0173 proc-macro-error2 is unmaintained
audited 126 packages across 1 lockfile · 24 direct, 102 transitive · 3 advisories affecting 3 packages
監査では、固定されているバージョンごとに次の問題を検出します。
- 既知の勧告:現在使用しているバージョンに影響するもの(OSV.dev — 差分モードと同じ、エイリアスを重複排除したフィードです。そのため、
MAL-*の悪意あるパッケージに関する勧告も検出されます)。 - レジストリのインデックスに存在しないバージョン:そのパッケージの他のバージョンは掲載されている場合に検出します。公開が取り消されたリリース(悪意あるリリースの場合も多い)は、このように表示されます。2025 年 9 月の
[email protected]の内容を固定したままのロックファイルでは、この検出と MAL 勧告の両方が発生します。 - 非推奨/取り消し済み/削除済み/放棄済みの固定バージョン:すべてのレジストリ連携について、上流の理由と推奨される代替バージョンを表示します。
- 公開から数日しか経っていない固定バージョン(⏱ クールダウン、
-fresh-days)。
差分モードと同様に、各種オプションを組み合わせて使用できます。-md、-json、-only "@babel/*"、-fail-on vuln,unlisted、-sarif に対応しているため、スケジュールされたワークフローで、問題のあるものを固定している正確なロックファイルの行に対する Code Scanning アラートを維持できます。
# .github/workflows/lockvet-audit.yml — nightly dependency audit
name: lockvet audit
on:
schedule: [{cron: '14 6 * * *'}]
workflow_dispatch:
permissions:
contents: read
security-events: write
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
curl -fsSL https://raw.githubusercontent.com/matteo-sung/lockvet/v0.6.20/install.sh | sh -s -- -b .
./lockvet audit -sarif > audit.sarif || true
- uses: github/codeql-action/upload-sarif@v3
with: {sarif_file: audit.sarif}
変化に基づくシグナル(追加された ⚙ インストールスクリプト、削除された ⛨ プロベナンス)には変更前後のペアが必要なため、差分モードでのみ検出されます。監査では履歴ではなく現在の状態を報告します。
インストールせずに試すことができます。ブラウザプレイグラウンドにはロックファイルを監査するモードがあり、ロックファイル(または SBOM)を 1 つ以上ドロップすると、同じ監査をブラウザ内だけで実行します。
インストールする前にパッケージを審査する — lockvet pkg
依存関係管理で最も危険な瞬間は、見たこともないパッケージに対して npm install something を実行するときです。lockvet pkg は、まだどのロックファイルにも含まれていないパッケージに対して、判断を下すまさにその時点で同じパイプラインを実行します。

レジストリで確認できる情報をすべて取得します。対象バージョンに影響するセキュリティ勧告(MAL-* の悪意あるパッケージに関する記録を含む)、リリースからの経過時間(⏱ 新しいリリースほどリスクが高くなります)、非推奨化/撤回/取り下げとその上流での理由、レジストリのインデックスに存在しないバージョン、そして人気パッケージ名と1文字だけ異なる名前の≈タイプスクワッティングの疑いです。
指定形式は eco:name[@version] です。バージョンを指定しない場合、パッケージ自身のレジストリから「latest」が判断されます。
lockvet pkg npm:left-pad # latest, from the npm registry
lockvet pkg pypi:[email protected] # a specific version
lockvet pkg cargo:serde gem:rails hex:phoenix pub:dio # several at once
lockvet pkg go:github.com/gin-gonic/gin # Go modules
lockvet pkg maven:com.google.guava:guava # Maven group:artifact
lockvet pkg jsr:@std/http terraform:hashicorp/aws pod:Alamofire
lockvet pkg swift:Alamofire/Alamofire # SwiftPM (github.com implied)
lockvet pkg helm:https://charts.bitnami.com/bitnami/postgresql # Helm charts
lockvet pkg ansible:community.general # Ansible Galaxy (collections & roles)
lockvet pkg tool:node tool:terraform # asdf/mise tools, from the tool's own repo tags
lockvet pkg vcpkg:fmt # vcpkg ports, from the registry's versions database
最新バージョンの検索は npm、PyPI、crates.io、RubyGems、Packagist、Go、Hex、pub.dev、JSR、NuGet、Maven、CocoaPods、Terraform、CRAN、Hackage、Bazel Central Registry(bazel:<module>)、vcpkg ポート(vcpkg:<port> — microsoft/vcpkg の versions データベースで最も新しいエントリー。port-version も含みます)、conda(conda:[channel/]name — channel の既定値は conda-forge)、Helm チャート(helm:<repo-url>/<chart> — リポジトリ独自のインデックスから解決し、非推奨のリリースは除外します)、Ansible Galaxy(ansible:namespace.name — まずコレクションを検索し、見つからない場合は従来のロールを検索します)、GitHub Actions(actions:owner/repo)、Swift パッケージ(swift:host/owner/repo — stable タグのうち最も高いバージョンが最新です)、asdf/mise ツール(tool:<name> — ツール独自のリポジトリにある最も新しい stable タグをバージョンとして表示します)に対応しています。その他のエコシステム(conan:、julia:)では、@version の明示的な指定が必要です。ここでもインストールは不要です。ブラウザのプレイグラウンドのパッケージを審査モードでも同じ検索をブラウザ上で実行でき、結果は共有可能な URL になります —
#pkg=npm:chakl では、chalk のタイプミスである名前についてレジストリの「latest」を検索し、そのパッケージに付随するマルウェアの記録を報告します。(RubyGems、Maven、conda、Ansible Galaxy など、一部のレジストリはブラウザからの問い合わせに応答しません。CLI ならすべてに対応できます。)
-fail-on vuln,unlisted,typosquat を指定すると、CI と同じ条件でスクリプトを制御できます。また、-md/-json の出力も、ほかの場合と同じように機能します。
AI アシスタントに依存関係を検証させる(MCP サーバー)
lockvet mcp は、lockvet を標準入出力経由の Model Context Protocol
サーバーとして実行します。これにより、Claude Code、Claude Desktop、Cursor、VS Code など、あらゆる
MCP クライアントが会話の途中でロックファイルの変更を検証できます。「この Dependabot PR は
安全にマージできますか?」 という質問に、アシスタントが勘ではなく OSV のデータに基づいて答えられるようになります。
# Claude Code
claude mcp add lockvet -- lockvet mcp
// Claude Desktop, Cursor, and most other clients (mcpServers config):
{ "mcpServers": { "lockvet": { "command": "lockvet", "args": ["mcp"] } } }
Docker を使えばインストールは不要です:
{ "command": "docker", "args": ["run", "-i", "--rm", "ghcr.io/matteo-sung/lockvet:0.6.20", "lockvet", "mcp"] }。
lockvet は公式の MCP Registry
にも io.github.matteo-sung/lockvet として登録されているため、
レジストリを参照できるクライアントから追加できます。
読み取り専用の 6 つのツールがあり、CLI に対応しています:
| ツール | 機能 |
|---|---|
vet_url | URL を指定して、あらゆる PR/MR、比較範囲、コミットを検証します — GitHub、GitLab、Bitbucket、Gitea/Forgejo、Azure DevOps に対応し、クローンは不要です |
vet_git | ローカルリポジトリを検証します:作業ツリーと HEAD の比較、または任意のリビジョン範囲 |
vet_files | ディスク上にある 2 つのロックファイルまたは SBOM を検証します |
audit | プロジェクトが現在固定しているすべての依存関係を監査します — アドバイザリ、未掲載バージョン、非推奨 |
vet_package | 依存関係をインストールする前に検証します(npm:left-pad、pypi:[email protected]) — アドバイザリ、経過期間、非推奨、タイポスクワットの疑い |
queue | リポジトリ/組織の、オープン中の Dependabot/Renovate PR をすべて 1 つの表でトリアージします |
レポートは Markdown(構造化データの場合は format: "json")で返されます。フォージの
トークンは環境変数(GITHUB_TOKEN、GITLAB_TOKEN など)から読み込まれるため、CLI が対応する場所であれば
プライベートリポジトリでも動作します。次のように試してみてください:「grafana のオープン中の依存関係
PR をトリアージして、最初に確認すべきものを教えてください。」
CI での利用(Dependabot/Renovate PR を自動レビュー)
lockvet は、ロックファイルに変更を加える PR に概要コメントを投稿します —
実際の PR で確認できます:
# .github/workflows/lockvet.yml
name: lockvet
on:
pull_request:
paths:
- '**/package-lock.json'
- '**/pnpm-lock.yaml'
- '**/yarn.lock'
- '**/bun.lock'
- '**/Cargo.lock'
- '**/uv.lock'
- '**/poetry.lock'
- '**/pdm.lock'
- '**/requirements.txt'
- '**/go.mod'
- '**/composer.lock'
- '**/Gemfile.lock'
permissions:
pull-requests: write
contents: read
jobs:
lockvet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: matteo-sung/[email protected]
# optional:
# with:
# fail-on: vuln # or "major,vuln,downgrade,fresh,deprecated,unlisted,scripts,provenance,license"
# fresh-days: '7' # cooldown window for the fresh flag
# changelogs: 'true' # inline release notes for every bump
# sarif: 'true' # code scanning alerts (see below)
(GitHub Actions 以外の場合:lockvet pr <PR-url> -comment -fail-on vuln を使うと、
環境に GITHUB_TOKEN がある任意の CI から、取得、レポート、コメント、ゲート処理を同様に実行できます。)
GitHub Code Scanning(SARIF)
lockvet -sarif は SARIF 2.1.0
を出力します。これにより、脆弱なバージョン、引き続き脆弱なバージョン、非推奨の新規導入バージョンが
コードスキャンのアラートとして表示されます — パッケージを固定しているロックファイル上の正確な行に、
OSV へのリンクと重大度が注釈として付けられます。Action では入力を 1 つ指定するだけです(ジョブには
追加で security-events: write も必要です):
permissions:
pull-requests: write
contents: read
security-events: write
- uses: matteo-sung/[email protected]
with:
sarif: 'true'
または、任意の場所で単独実行できます:
$ lockvet -sarif BASE HEAD > lockvet.sarif # also works with pr/mr/compare modes
その後、github/codeql-action/upload-sarif または
gh api repos/<owner>/<repo>/code-scanning/sarifs でアップロードします。
GitLab では、1 行のコマンドで MR を検証し、レポートを MR のノートとして投稿できます — 再実行すると、ノートがその場で更新されます:
# .gitlab-ci.yml
lockvet:
image: ghcr.io/matteo-sung/lockvet:0.6.20
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes: ["**/*lock*", "**/go.mod", "**/requirements.txt"]
script:
- lockvet mr "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID" -comment -fail-on vuln
-comment には GITLAB_TOKEN CI/CD 変数が必要です(api スコープを持つプロジェクトアクセストークン —
CI_JOB_TOKEN ではノートを投稿できません)。トークンがない場合は -comment を外してください:
公開 MR の取得には認証は不要で、レポートは引き続きジョブログに出力されます。
セルフホストのインスタンスにも対応しています — ホストは URL から取得します。
API ではなくチェックアウトを差分比較する場合は、git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" && lockvet "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" を実行すると、同じ処理をローカルで行えます。
Bitbucket では、同じ 1 行のコマンドを Pipelines で実行できます:
# bitbucket-pipelines.yml
pipelines:
pull-requests:
'**':
- step:
name: lockvet
image: ghcr.io/matteo-sung/lockvet:0.6.20
script:
- lockvet pr "https://bitbucket.org/$BITBUCKET_WORKSPACE/$BITBUCKET_REPO_SLUG/pull-requests/$BITBUCKET_PR_ID" -comment -fail-on vuln
-comment を使うには、BITBUCKET_TOKEN リポジトリ変数を設定してください(pull request: write スコープを
持つリポジトリアクセストークン)。トークンがない場合は -comment を外すと、レポートはパイプラインログに出力されます。
Azure DevOps では、PR をトリガーとするジョブを追加して、レポートをプルリクエストにコメントできます:
# azure-pipelines.yml
jobs:
- job: lockvet
condition: eq(variables['Build.Reason'], 'PullRequest')
pool: { vmImage: ubuntu-latest }
container: ghcr.io/matteo-sung/lockvet:0.6.20
steps:
- checkout: none
- script: >
lockvet pr
"$(System.CollectionUri)$(System.TeamProject)/_git/$(Build.Repository.Name)/pullrequest/$(System.PullRequest.PullRequestId)"
-comment -fail-on vuln
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
-comment を使うには、ビルドサービスアカウントにリポジトリでの Contribute to pull requests 権限が
必要です。権限がない場合は -comment を外すと、レポートはジョブログに出力されます。
Codeberg(または Woodpecker CI を使用する Gitea/Forgejo)では、次のようにします:
# .woodpecker/lockvet.yaml
when:
- event: pull_request
steps:
- name: lockvet
image: ghcr.io/matteo-sung/lockvet:0.6.20
environment:
GITEA_TOKEN:
from_secret: gitea_token # only needed for -comment
commands:
- lockvet pr "$CI_REPO_URL/pulls/$CI_COMMIT_PULL_REQUEST" -comment -fail-on vuln
pre-commit フックとして
リスクの高い更新をコミット前に検出できます。lockvet のデフォルトモード(作業ツリーと HEAD の比較)は、まさに「このコミットで何が変わるか」を確認するもので、ロックファイル(または uses: ピンを含むワークフローファイル)がコミットに含まれる場合にのみフックが実行されます。
# .pre-commit-config.yaml
repos:
- repo: https://github.com/matteo-sung/lockvet
rev: v0.6.20
hooks:
- id: lockvet
# optional: also gate on majors and <7d releases
# args: [-fail-on, "vuln,unlisted,scripts,provenance,typosquat,integrity,registry,major,fresh"]
# optional: skip network lookups for instant commits (still catches
# downgrades, repins, resolution moves, and typosquats)
# args: [-offline]
フックは常に説明を表示します。デフォルトでは、重大な段階に該当する場合にのみコミットをブロックします。対象となるのは、新たに持ち込まれた脆弱性、レジストリのインデックスにないバージョン、新たに追加されたインストールスクリプト、失われた来歴情報、タイポスクワッティングに似た名前、整合性や解決結果の改ざんです。これらは、ほぼノイズが発生しないよう調整されたシグナルです。ゲートの条件を調整するには args を上書きしてください(または無効にしてください。args: [] にすると、フックは情報提供のみを行います)。必要なのは pre-commit だけです(フックは Go でビルドされ、pre-commit が Go を自動的にダウンロードします)。
検出結果の承認 — .lockvetignore
静かにできないゲートは無効にされてしまいます。検出結果を確認し、受け入れた場合(利用方法には当てはまらない CVE、予定していたメジャーアップデート、次のスプリントで移行する予定の非推奨パッケージなど)は、ロックファイルと同じ場所にある .lockvetignore ファイルにその判断を記録してください。検出結果はサマリーや -fail-on の判定対象から除外されます。
# One rule per line; # comments. Globs (* ?) and case don't matter.
GHSA-35jh-r3h4-6jhm # ReDoS — we never pass user input here
[email protected] # everything about this one version
fresh:aws-sdk-go-v2 # daily releases, cooldown is noise
major:react # the React 19 migration PR
deprecated:crossbeam-channel until=2026-12-31 # revisit after Q4 freeze
ルールには、アドバイザリ ID、pkg[@version]、または kind:pkg[@version] を指定します。kind は vuln、fresh、deprecated、unlisted、scripts、provenance、integrity、registry、license、major、downgrade のいずれかです。until=YYYY-MM-DD の期限を指定すると、承認は一時的なものになります。期限を過ぎるとルールは適用されなくなり、その行を削除するか期限を延長するまで、実行のたびに警告が表示されます。そのため、一時的な保留がいつまでも続くことはありません。
抑制の状況は明確に示されます。無視された検出結果はレポートに薄い色の ○ ignored (.lockvetignore) マーカーで表示され、-json では ignored/ignored_vulns として示されます。また、サマリーには承認された検出結果の数が表示されます。このファイルは現在のディレクトリ(または lockvet audit の監査対象ツリー)から自動的に検出され、lockvet pr <url> や GitHub Action を含むすべてのモードで機能します。-ignore-file <path> で別の場所を指定するか、-no-ignore で無効にできます。これにより、CI で「無視を認めない」というポリシーを強制できます。queue モードは複数のリポジトリを対象とし、無視ファイルは適用されません。
非推奨化とライセンスの変更
新たに導入されるすべてのバージョンは、レジストリ(deps.dev 経由)と照合されます。
-
非推奨 — レジストリがそのバージョンを非推奨としてマークしている場合、上流の理由がインラインで表示されます(
● deprecated upstream: use String.prototype.padStart())。 PyPI では、yanked releases(取り下げ理由を含む)とPEP 792 のプロジェクトステータスも対象になります。メンテナーによってアーカイブされたプロジェクトや、PyPI の管理者によって隔離されたプロジェクト(マルウェア審査中の状態)は、引き続きそのプロジェクトを固定しているすべての変更に警告が表示されます。crates.io の取り下げも同様に表示されます。疎インデックス自体を照合するため、数分前の取り下げも検出できます。Composer パッケージにも、Packagist から直接同様の処理が適用されます。放棄されたパッケージへの更新には、メンテナーが提案する代替パッケージを含む警告が表示されます(● deprecated upstream: abandoned; use symfony/mailer instead)。NuGet の非推奨情報も登録インデックスから直接取得され、deps.dev が除外する代替パッケージも含まれます(● deprecated upstream: legacy; use Azure.Storage.Common instead)。また、作者によって非公開にされたバージョンもこの分類に表示されます。Hex の廃止(BEAM の世界におけるバージョンごとの非推奨)も hex.pm から直接ここに表示され、メンテナーの理由とメッセージが含まれます(● deprecated upstream: retired: deprecated — Not really maintained, please check out Tesla)。Dart パッケージの非推奨情報は pub.dev から直接取得されます。パブリッシャーが公開を終了したパッケージ内のバージョンへの更新には、指定された代替パッケージを含む警告が表示されます(● deprecated upstream: discontinued on pub.dev; replaced by flutter_markdown_plus)。また、dart pub自体が新たに解決することを拒否する撤回済みバージョンへの更新にも警告が表示されます。JSR パッケージの非推奨情報は jsr.io から直接取得され、取り下げられたバージョンへの更新や、jsr.io でアーカイブされたパッケージもこの分類に表示されます。Go の撤回もモジュールプロキシから直接ここに表示されます。作者が撤回したバージョンへの更新には、モジュール自身のgo.modにある根拠コメントが表示されます(● deprecated upstream: retracted: https://github.com/klauspost/compress/issues/1114)。また、deps.dev による再インデックス前でも、// Deprecated:の通知があるモジュールには警告が表示されます。CocoaPods の非推奨情報は、CDN 上にある Pod 自身の podspec から取得されます。pod trunk deprecateはすべてのバージョンの podspec を書き換えます。指定された後継パッケージも含まれます(● deprecated upstream: deprecated on CocoaPods; in favor of FirebaseCrashlytics)。Terraform/OpenTofu プロバイダーの情報は、各レジストリから直接取得されます。レジストリが警告するプロバイダー、registry.terraform.io から削除されたプロバイダー、OpenTofu レジストリによってブロックされたプロバイダー、HashiCorp によってアーカイブされたプロバイダーが対象となり、代替案も含まれます(● deprecated upstream: This provider has been archived. Please use the templatefile function or the Cloudinit provider instead)。Maven アーティファクトの情報は POM 自体から取得されます。移転スタブ(Java プロジェクトが移転後の座標を知らせる方法)への更新には、新しい座標と作者のメッセージを含む警告が表示されます(● deprecated upstream: relocated to com.mysql:mysql-connector-j — MySQL Connector/J artifacts moved to reverse-DNS compliant Maven 2+ coordinates.)。この情報は deps.dev ではまったくモデル化されていません。R パッケージの情報は CRAN から直接取得されます。CRAN がアーカイブしたパッケージ内のバージョン(インデックスから削除されているため、install.packages()で解決できなくなっています)への更新には警告が表示されます(● deprecated upstream: archived on CRAN (no longer installable from the index))。 Haskell パッケージの情報は Hackage から直接取得されます。Hackage の非推奨リストにあるパッケージ内のバージョンへの更新には、メンテナーが提案する代替パッケージを含む警告が表示されます(● deprecated upstream: deprecated on Hackage; use crypton, cryptohash-md5 or cryptohash-sha1 instead—cryptoniteの事例)。また、preferred-versionsで非推奨に指定された個別のバージョン(Hackage における取り下げに相当し、ソルバーは使用を避けます)への更新にも警告が表示されます。 Bazel モジュールの情報はBazel Central Registryから直接取得されます。取り下げられたバージョンへの更新には、レジストリ独自の理由を含む警告が表示されます(● deprecated upstream: version 3.19.0 is yanked from the Bazel Central Registry: CVE-2022-3171— BCR は CVE や壊れたアーティファクトを理由にリリースを取り下げます)。また、--allow_yanked_versionsを使って作成されたロックファイルでは、取り下げられたバージョンの選択結果がselectedYankedVersionsに記録され、lockvet はオフラインでもこれを読み取ります。 Conda パッケージの情報は anaconda.org から直接取得されます。brokenラベルが付いたリリースへの更新には警告が表示されます。これは、conda-forge が不良または悪意のあるビルドを削除せずに除外する方法です(● deprecated upstream: marked broken on conda-forge (artifacts moved to the broken label))。すべてのビルドが壊れている場合と、一部のビルドのみが壊れている場合は、異なる表現で示されます。 -
ライセンスの変更 — 更新先のバージョンが、置き換えるバージョンとは異なるライセンスで公開されています:
↑ husky 4.3.8 → 5.0.9 MAJOR (direct) ● license change: MIT → non-standardリリース途中でのライセンス変更(MIT → BUSL、SSPL、「標準外」など)は、40 行のロックファイル差分では誰も気づかない類の問題です。lockvet が変更を報告するのは、レジストリが変更前と変更後の両方のライセンスを報告している場合に限られます。JSON 出力では、対象となるすべての変更について
old_license/new_licenseが含まれます。
どちらもゲートとして利用できます:-fail-on deprecated,license。
レジストリに存在しないバージョン
他のバージョンが登録されているにもかかわらず、取り込まれたバージョンが レジストリのインデックスにない場合、lockvet はその旨を報告します:
+ flatmap-stream 0.1.1 (added) via event-stream
▲ not in registry index: 0.1.1 unknown to deps.dev though other
versions are listed — unpublished/deleted release, or published
minutes ago; verify before trusting
これが重要な理由:レジストリに悪意のあるリリースが取り込まれたとき、問題はこのような形で現れます。当社の事例にある悪意のあるバージョンはすべて、[email protected]、[email protected]、[email protected]、[email protected]、そして 2026 年 8 月の keyv/Cacheable ワーム再現における 6 つすべてのバージョンが、攻撃後に公開取り下げとなりました。そのため、いずれかを固定したままのロックファイルは、レジストリ自体がすでに認めていないバージョンを参照しています。lockvet は、アドバイザリがまだ存在しなくてもこれを検出します。以下は、2025 年 9 月の chalk + debug に対する npm の乗っ取りを、2 時間の実行期間中に再現したものです:

誤検出を防ぐため、フラグは意図的に保守的に設定されており、以下の場合は報告されません:
- レジストリがまったくインデックス化していないパッケージ(プライベートレジストリ、対象外のエコシステム)。他のバージョンが登録されているパッケージだけが検出対象です;
- ワークスペースメンバー、git およびパス依存関係(ロックファイル自体が、レジストリ由来ではないことを示しています);
- Go の疑似バージョンおよび pnpm 形式の装飾付きバージョン文字列。
npm、PyPI、crates.io、RubyGems、Packagist、NuGet、Hex、Pub、JSR、CocoaPods、Go、Maven、Hackage、Bazel、conda のパッケージ、および Terraform/OpenTofu プロバイダーについては、このフラグは レジストリ自体に照合して二重確認されます。deps.dev の情報はレジストリに数日遅れる場合があるため、何かを報告する前に、lockvet はパッケージの実際のバージョン一覧を registry.npmjs.org / PyPI の simple API / crates.io の sparse index / RubyGems の compact index / Packagist の Composer メタデータエンドポイント / NuGet の registration index / hex.pm と pub.dev の packages API / jsr.io の meta.json(Deno 自体が解決時に参照するものと同じドキュメントです。取り下げられたバージョンもここには残るため、見つからないことは実際のシグナルになります)/ CocoaPods のシャーディングされた CDN インデックス(pod install が解決時に参照するものと同じファイルです)/ Go モジュールプロキシ / Terraform レジストリのバージョンごとのエンドポイントと OpenTofu レジストリのバージョンインデックス / Maven Central のバージョンごとの POM(androidx の各種パッケージが存在する Google の Maven リポジトリにフォールバックします)/ Hackage のパッケージごとのバージョンマップ(非推奨のバージョンもここには残るため、見つからないことは実際のシグナルになります)/ Bazel Central Registry のモジュールごとの metadata.json(yank されたバージョンもここには残ります。また、このレジストリは git リポジトリであり、バージョンが追加されることはあっても、ひそかに削除されることはないため、見つからないことは実際のシグナルになります)/ anaconda.org のリリースごとのエンドポイント(チャンネルは悪意のあるアップロードを完全に削除し、ビルドを破損済みとしてマークしても一覧には残るため、見つからないことは実際のシグナルになります。パッケージドキュメントへの HEAD リクエストでパッケージ自体の存在を確認した場合に限り報告されます)から取得し、レジストリが配信するバージョンについてはフラグを解除します。残るのは、レジストリ自体の一覧にもう存在しないバージョンです。この違いは重要です。crates.io では yank されたバージョンはインデックスに残る一方、削除された(悪意のある)バージョンは完全に消えます。また、RubyGems では yank するとリリースがインデックスから完全に削除されるため、yank された、または管理者によって削除された gem への更新ではフラグが維持されます(2019 年の strong_password 0.0.7 の乗っ取りを再現すると、現在でも検出されます)。マルウェアだけの問題でもありません。HashiCorp はリグレッションを受けて AWS プロバイダー 5.71.0 を Terraform レジストリから削除しました。GitHub 上にはタグが残っていますが、5.71.0 を固定したロックファイルには、レジストリ自体からの 404 に基づく ▲ フラグが付きます。
NuGet は「非掲載(unlisted)」がネイティブの概念である唯一のレジストリで、lockvet は NuGet と同じ方法でこれを区別します。つまり、安定版のバージョンが登録インデックスにまったく存在しない場合(管理者によって削除された悪意あるパッケージの状態)は▲フラグが維持されます。一方、作者が単に非掲載にした(検索結果には表示されないものの、引き続き復元可能な)バージョンは、非推奨の扱いになります。存在しないプレリリースバージョンはフラグを付けずに除外されます。NuGet では、これらのほとんどが CI フィード上の日次ビルド(Roslyn のナイトリービルドなど)であり、packages.lock.json から実際のフィードを特定できないためです。
数分前に公開されたリリースは、まだインデックスに登録されていない場合もあります。このフラグは、パニックになるのではなく、確認するよう促すものです。-fail-on unlisted でこれを条件に設定できます。JSON には unlisted / unlisted_versions が含まれ、SARIF は unlisted-version 警告を出力し、queue は影響を受ける PR を先頭に並べます。
タイポスクワッティングの疑いがあるパッケージ
あらゆるレジストリで最も古くからある手口です。lodahs、reqeusts、rustdecimal を公開し、タイプミスを待ちます。lockvet は、同じレジストリの人気パッケージ名と編集距離が 1 以内の名前を持つ新しい依存関係にフラグを付けます。リリースから 30 日以内(≤ 30 日)であるか、経過日数が不明な場合が対象です。
+ python3-dateutil 2.9.0 (added)
≈ name resembles python-dateutil: a new dependency one edit away
from a popular package, and the release is young — the shape of
a typosquat; make sure this is the package you meant
この例は実際に 2019 年に PyPI で発生した攻撃です。OSV のアドバイザリには一度も掲載されなかったため、このフラグだけが検出手段です。2022 年の rustdecimal による crates.io への攻撃や、npm の lodahs も検出されます。
このチェックは完全にローカルで実行されます。人気パッケージのリスト(npm の影響力の大きいパッケージ一覧、PyPI の上位パッケージ、crates.io で最もダウンロードされているパッケージ、ecosyste.ms 経由で取得した RubyGems で最もダウンロードされているパッケージ、Packagist の人気パッケージ)はバイナリに組み込まれているため、-offline を指定した場合やブラウザーのプレイグラウンドでも動作します。Ruby 側では、2020 年 2 月の RubyGems キャンペーンの代表例(rspec-mocks に対する rspec-mokcs — 1 回の攻撃で 760 個以上の悪意ある gem)も再現できます。いつもどおり、ノイズの抑制が重要です。
- チェック対象はツリーに新しく追加されるパッケージだけです。更新では名前は変わらず、人気パッケージと何年も共存してきた名前は、攻撃ではなく不運な名前です(経過日数による判定)。
- レジストリが同一パッケージとして扱う名前の組み合わせにはフラグが付きません(PyPI の
-/_/ `` の同等性、crates.io の-/_の衝突禁止)。一方、npm / RubyGems / Packagist では区切り文字を入れ替えた名前も別パッケージとして扱われるため、フラグが付きます(rack-cacheと並んだrack_cacheにはフラグが付きます)。 - 追加されたパッケージ自体が人気パッケージ一覧に含まれていないことが条件です。また、名前が非常に短いパッケージは対象外です。
-fail-on typosquat でこれを条件に設定できます。意図的に人気パッケージに似た名前を使う場合は、.lockvetignore に typosquat:pkgname の行を追加して許可できます。JSON には typosquat_of が含まれ、SARIF は typosquat-suspect 警告を出力し、queue は影響を受ける PR を先頭に並べます。
GitHub Actions のワークフローもロックファイルです
すべての uses: 行は依存関係を固定しており、Dependabot/Renovate はほかの依存関係と同様にその固定先を更新します。lockvet はあらゆるモード(lockvet pr <url>、ローカル差分、queue、audit)で、ワークフローファイル(.github/workflows/*.yml、複合 action.yml ファイル、Gitea/Forgejo のワークフローディレクトリも含む)を読み取ります。アクションの更新には、通常の差分だけでは分からない情報が加わります。
-
SHA 固定をリリースに解決します。 lockvet は匿名の Git smart-HTTP を使ってアクションのリポジトリのタグを取得し(リポジトリごとに GET を 1 回、API もレート制限もなし)、各コミットが示すリリースを報告します。Renovate によるダイジェスト更新も読みやすくなります。
.github/workflows/build-container-image.yml (GitHub Actions) ↑ actions/checkout df4cb1c (=v6.0.3) → d23441a (=v6.1.0) minor (direct) -
浮動するメジャーバージョンも解決します。
v4が「今日」指しているリリースを報告し、実際のバージョンに基づいて更新の種類を判定します。そのため、v5 → v7は、検証済みの比較リンクとともにv5 (=v5.1.0) → v7 (=v7.0.1) MAJORと表示されます。 -
アドバイザリを適切に評価します。 OSV.dev には「GitHub Actions」エコシステムがありますが、API はアドバイザリとバージョンをサーバー側で照合できません。lockvet は影響を受ける範囲を取得し、解決されたリリースに対して自ら評価します。GHSA の影響を受ける SHA 固定は、そのハッシュを記載したアドバイザリがなくても検出されます。また、メジャーバージョンの途中で修正されたアドバイザリについて、浮動する
v4が誤検出されることもありません。 -
▲ はリリースではありません。 アクションのリポジトリにあるどのタグとも一致しない(ブランチでも、そのブランチの先端でもない)固定コミットを示します。これは、2025 年 3 月の tj-actions/changed-files 攻撃とまさに同じ状態です。リポジトリ全体のバージョンタグが、悪意のある孤立コミットに強制的に移動されました。攻撃された固定先を現在再生すると、次のようになります。
$ lockvet diff old/ci.yml new/ci.yml new/ci.yml (GitHub Actions) ↑ tj-actions/changed-files v44 (=v44.0.0) → 0e58ed8 ? (direct) ▲ not a release: 0e58ed8 pinned ref matches no tag in the action's repository — release tags are how actions ship, and the tj-actions attack pinned exactly like this; verify the commit(
0e58ed8は、この攻撃で使われた実際の悪意あるコミットです。) CI では-fail-on unlistedを使ってこれをゲートできます。
ブランチ固定(@main)は意図的な選択として扱われ、警告は出ません。github.com 以外でホストされているアクションについては、リスト外であるという判定を行いません。また、-changelogs を使うと、更新によって取り込まれる各アクションリリースのリリースノートを表示します。
…そして .pre-commit-config.yaml も同様です
すべての repos: エントリーは、フックリポジトリを厳密な rev: に固定しています。pre-commit はこのコードをクローンし、すべてのコントリビューターのマシンで、すべてのコミット時に実行します。pre-commit autoupdate と Renovate は、ほかの依存関係と同様にこれらの固定先を更新します。Git フォージを問わず、ワークフローの固定先と同じ扱いを受けます(名前にはホストが残ります)。
$ lockvet HEAD~1 # after a `pre-commit autoupdate` commit
.pre-commit-config.yaml (pre-commit)
↑ github.com/astral-sh/ruff-pre-commit v0.8.0 → v0.14.14 minor (direct)
↑ github.com/pre-commit/pre-commit-hooks v4.4.0 → v5.0.0 MAJOR (direct)
SHA の rev は一致するリリースに解決され、更新は実際のバージョンに基づいて分類され、-changelogs は各フックのリリースノートを取得します。また、フックのリポジトリにあるどのタグとも一致しない rev は、▲ はリリースではありませんという警告を出します。これは、チーム全員がコミットのたびに実行するコードについて、tj-actions 攻撃と同じパターンを検出するシグナルです。repo: local と repo: meta のエントリーは対象外です。また、フックを追加する前に lockvet pkg pre-commit:owner/repo でそのリポジトリを検査できます。
…そして .gitlab-ci.yml
GitLab パイプラインでは、依存関係を2か所で固定します。lockvet はその両方を読み取ります。.gitlab-ci.yml、サフィックス付きの名前を持つバリアント(backend.gitlab-ci.yml)、および .gitlab/ または .gitlab-ci/ ディレクトリ内の CI フラグメントが対象です。
-
include: component:のピン — CI/CD Catalog コンポーネントの設定は、すべてのパイプラインで実行されます。各ピンをコンポーネントのプロジェクトにある実際のタグと照合し、GitLab の可変形式は「今日」意味するリリースに解決します。@2と@2.0(semver 範囲の短縮形)、および@~latestは具体的なバージョンを表示するため、@2 → @~latestのような更新も不透明にならず、内容を確認できます。.gitlab-ci.yml (Docker) ↑ gitlab.com/components/opentofu/full-pipeline 2.0.0 → 2.4.0 minor (direct) ↑ gitlab.com/components/secret-detection/secret-detection 2 (=2.3.0) → ~latest (=2.3.0) (direct)コンポーネントのプロジェクトに一致するタグがないピンは、▲ not a release を報告します。これは GitLab 版の tj-actions 攻撃パターンです。比較リンクではプロジェクトの実際のタグを使用し、
-changelogsは更新によって取り込まれるバージョンに対応するCHANGELOG.mdのセクションを表示します(GitLab ホストのリポジトリも対象です)。lockvet pkg component:gitlab.com/components/opentofu/full-pipelineで、追加前にコンポーネントを検査できます。 -
image:とservices:の参照 — 各ジョブで実行されるコンテナに対して、Dockerfile のFROMピンと同じレジストリ検証を行います(ダイジェストとタグの違い、不明なタグ、Docker Hub 上での経過期間)。Renovate は両方を更新しますが、どちらかを検査するツールはほかにありません。
include: project: と ref: のピンはバージョン行として追跡されますが、クレームは付きません。ファイルにはプロジェクトをホストしている GitLab インスタンスが記録されないため、lockvet は推測しません。$CI_SERVER_FQDN をプレフィックスとするコンポーネントのピンも、ディスクから読み取る場合はクレームが付きません。ただし、mr、compare、queue モードでは URL 自体がインスタンスを示すため、lockvet はそのホストに対して解決し、完全なタグ検証を行います。テンプレート化された参照($VARIABLES)と include: local:/template:/remote: のエントリーは、何も固定しません。
…そして .circleci/config.yml
CircleCI パイプラインでは、依存関係を2か所で固定します。lockvet はその両方を読み取ります。.circleci/config.yml と、.circleci/ ディレクトリ内の継続設定/フラグメントが対象です。
-
orbs:のピン — CircleCI orb レジストリの再利用可能な CI パッケージは、すべてのパイプラインでコマンドを実行します。orb には OSV エコシステムも deps.dev システムもないため、lockvet はレジストリ自体に問い合わせます。リリース経過期間(数時間前にリリースされた orb への更新は、立ち止まって確認する価値があります)、検証済みの比較リンクと orb 自身のソースリポジトリから取得した-changelogsリリースノートに加え、公開された orb のバージョンは不変であり、その削除はセキュリティ上の問題に限って CircleCI サポートが対応する操作であることから、完全なバージョン一覧にないピンに対して、レジストリで検証された ▲ not in registry index チェックを行います。可変のピンは「今日」取得されるリリースに解決します。.circleci/config.yml (Docker) ↑ circleci/aws-cli 4.1.0 → volatile (=5.4.2) ? (direct) (54d old) ↑ circleci/node 5.1.0 → 7.2.1 MAJOR (direct) (9mo old)dev:*バージョンは設計上変更可能で、期限切れになります。インライン orb 定義やテンプレート化された参照と同様に、これらにはクレームが付きません。lockvet pkg orb:circleci/nodeで、追加前に orb を検査できます。 -
docker executor の
- image:参照 — 各ジョブで実行されるコンテナに対して、Dockerfile のFROMピンと同じレジストリ検証を行います(ダイジェストとタグの違い、不明なタグ、Docker Hub 上での経過期間)。machine:executor のイメージは OCI 参照ではなく CircleCI VM イメージのラベルであるため、スキップされます。
Renovate の circleci マネージャーは両方を更新しますが、どちらかを検査するツールはほかにありません。
…そして .tool-versions、mise.toml、.nvmrc など
プロジェクトが実行するツールチェーン(node、python、terraform、kubectl)は、asdf/mise のファイルにピン留めされています。mise up と Renovate の asdf/mise マネージャーは、ロックファイルのエントリーと同じようにこれらのピンを更新しますが、それを検証するものはありません。lockvet は両方の形式を読み込み、各ツール固有のリポジトリタグと照合してすべてのピンを検証します。各リポジトリのタグの命名規則(go1.23.4、ruby の v3_4_2、Erlang の OTP-27.1.2、jq-1.7.1、kustomize/v5.8.1、swift-6.1-RELEASE など)に対応しています(約 90 種類のツールを選定し、継続的に検証しています)。
.tool-versions (mise/asdf)
↑ erlang 27.0 (=OTP-27.0) → 27.1.2 (=OTP-27.1.2) minor (direct)
↑ golang 1.22.5 (=go1.22.5) → 1.23.4 (=go1.23.4) minor (direct)
↑ ruby 3.3.4 (=v3_3_4) → 3.4.2 (=v3_4_2) minor (direct)
↑ terraform 1.9.2 (=v1.9.2) → 1.9.99 patch (direct)
▲ not a release: 1.9.99 pinned version matches no tag in the tool's repository
この (=tag) は検証済みの解決結果です。比較リンクと -changelogs のリリースノートは、実際のタグに基づいています。バージョン形式のピンがどのタグとも一致しない場合は、▲ リリースではありませんと警告します。これは、ワークフロー内で tj-actions の攻撃パターンを検出するのと同じチェックです。mise のあいまいなピンは、現在インストールされるバージョンに解決されます(node = "22" → (=v22.23.2))。
mise でバックエンドがプレフィックスになっているツールは、実際のレジストリパッケージであり、lockvet によるレジストリの完全な検証を受けます。npm:prettier は npm、cargo:eza は crates.io、pipx:black は PyPI、同様に gem:、dotnet:、go: も対象となり、アドバイザリ、経過日数、非推奨、タイポスクワッティングを検証します。ubi:/aqua:/github:/spm: のツールは、指定された GitHub リポジトリのタグと照合して検証されます。また、gradle、maven、sbt のピンは、lockvet がすでに検証しているレジストリ(Gradle 配布インデックス、Maven Central。ここでは古い sbt のピンに実際の GHSA が付与されています)を利用します。プラグイン由来のツール(asdf:、vfox:)、system、latest、lts、ref: のピン、ベンダープレフィックス付きの Java ビルドについては、検証済みであると偽って主張することはありません。lockvet pkg tool:node を使えば、ツールを切り替える前にリリースを検証できます。
同様の検証は、各バージョンマネージャーが読み込む単一ツール用のバージョンファイルにも対応します。対象は .nvmrc/.node-version(nvm、fnm、nodenv)、.python-version(複数バージョンのフォールバックを含む pyenv)、.ruby-version(rbenv/rvm。ruby-3.3.4 形式にも対応)、.go-version、.java-version、.terraform-version/.terragrunt-version(tfenv、tgenv)です。さらに、SDKMAN の .sdkmanrc も対象で、gradle=/maven=/sbt= のピンは、lockvet がすでに検証しているレジストリを利用します。Renovate はこれらすべてを専用マネージャーで更新しますが、その更新を検証するものはほかにありません。
そして、mise 独自のロックファイルである mise.lock にも、ロックファイルと同等の完全な検証を適用します。正確な解決済みバージョンに加えて、mise がダウンロードするアーティファクトのプラットフォームごとの sha256/blake3 チェックサムを記録し、lockvet はこれらを整合性ピンとして読み取ります。バージョンが変わらないままチェックサムが変わった場合、これはダウンロードの改ざんを示すパターンであり、‼ 再ピン留めとして警告します。新たに追加されたプラットフォームでは警告しません(アーティファクト単位で比較するためです。Gradle の検証メタデータにも同じルールが適用されます)。
mise.lock (mise/asdf)
‼ node 20.11.0 (=v20.11.0) REPINNED (version unchanged) (direct)
この種類のピンが重要である理由については、事例研究 11をご覧ください。これは、Gradle ラッパーの派生パターンも含め、Codecov bash uploader の事例をツールチェーンのチェックサム差し替えとして再現しています。このパターンでは、lockvet がピン留めされた distributionSha256Sum を、Gradle が実際に公開しているチェックサムと照合します。
コンテナーのベースイメージ
Dockerfile でも依存関係が固定されます。FROM 行もほかの依存関係と同様にバージョンが固定され、Renovate と Dependabot が同様に更新しますが、ほかの依存関係と同じようにレビューされることはありません。lockvet は Dockerfile/Containerfile(マルチステージに対応し、ARG のデフォルト値を展開。Dockerfile.alpine や dev.Dockerfile のようなバリアント名も対象)、COPY --from= イメージ、# syntax= BuildKit ディレクティブ、Compose ファイル内のすべての image: を読み取り、イメージには OSV のエコシステムも deps.dev のシステムもないため、イメージレジストリ自体に照らして変更を検証します。
$ lockvet https://github.com/hairyhenderson/dockerfiles/pull/1108
freeciv/Dockerfile (Docker)
↻ debian trixie digest sha256:fac46bff2e02 → sha256:34cd9e9fd437 (direct) ⏱ published 3 days ago
✔ digest verified: the registry serves exactly this digest for the tag today
1 package changed · 1 direct · 0 transitive · 1 fresh (<7d old)
- ダイジェストの固定は、タグが 現在 提供している内容と照合されます。完全一致(インデックスダイジェスト、またはプラットフォームごとのマニフェストダイジェストのいずれか)があれば
✔ digest verifiedと表示されます。タグが提供しなくなったダイジェストは‼ tag mismatchのレーンに入り(イメージタグは実際に移動します。再ビルドされたベースイメージも、タグに由来しない固定もこのように表示されます)、レジストリで一度も確認されていないダイジェストは▲(未登録)になります。 - レジストリが提供していないタグ(イメージリポジトリ自体は既知の場合)は、削除されたタグ、誤ったリポジトリ、または捏造された参照を示します。
▲ not in the registryと表示され、-fail-on unlistedで失敗条件に設定できます。 - 同じタグのダイジェスト更新は、通常の
↻として表示されます(レジストリがタグを再ビルドしただけです)。新しい固定も検証済みとなり、警告を伴う再固定としては表示されません。 - リリースからの経過時間と ⏱ fresh フラグは、Docker Hub でホストされているイメージについて、Docker Hub のタグごとの
last_updatedから取得します。
照会先は、固定された公開レジストリの許可リストです(Docker Hub、ghcr.io、quay.io、mcr.microsoft.com、gcr.io、registry.k8s.io — リクエストがすべてリダイレクトされるため、従来の k8s.gcr.io の固定も registry.k8s.io に対して検証されます — public.ecr.aws、registry.gitlab.com)。それ以外のホストのイメージ(プライベートレジストリ、ミラー、localhost)には照会しません。lockvet は認識していないホストにリクエストを送信しません。ローカルの build: コンテキストからビルドされる Compose サービス、ステージ名の参照、未解決の ${VARIABLE} が残っている参照は、推測せずにスキップされます。
Dev Containers もイメージの固定対象です。 devcontainer.json(および .devcontainer.json。サブフォルダー内のバリアントも含む)は、エディターや Codespaces が実際にビルドするコンテナーを指定します。"image" は Dockerfile の FROM と同様に検証され、"features" 内のすべての Feature(ghcr.io/devcontainers/features/go:1 など)は、同じ Distribution API を通じて取得される OCI アーティファクトなので、そのタグ(または @sha256: ダイジェストの固定)も同様にレジストリに照らして検証されます。Renovate の devcontainer マネージャーは、これらの参照を正確に更新します。それ以外にこれらを検査するものはありません。JSONC に対応しています(VS Code のテンプレートにはコメントが含まれています)。Compose/Dockerfile ベースの構成、ローカルパスの Feature、tarball URI、ホストを含まない従来形式の Feature ID は、ここでは実際には何も固定しません。
Kubernetes マニフェストと kustomization
同じ処理は、クラスターが取得するイメージにも適用されます。containers:/initContainers:/ephemeralContainers: リスト内のすべての image: は固定対象です(Deployments、StatefulSets、CronJobs など、ネストの深さは問いません)。また、kustomization.yaml の images: トランスフォーマー(Renovate と Flux のイメージ自動化が更新する newTag:/digest: の値)も固定対象です。どちらも上記のレジストリ検証をすべて受けます。kustomization.yaml の helmCharts: エントリーは、Chart.yaml の依存関係と同様に、チャートリポジトリ自身の index.yaml に照らして検査されます(リリースからの経過時間、非推奨チャート、prune-guard によって保護された未登録チェック)。
Flux の CR もピン留め対象です。 spec.chart.spec.version が厳密なピン留めになっている HelmRelease は、Helm パッケージとして扱われます。sourceRef の HelmRepository が同じファイルにある場合はその URL も取得し、チャートリポジトリの index.yaml に照らして更新を検証します(経過日数、非推奨のチャート、リストにないバージョン、-changelogs)。別のファイルで定義された sourceRef、または OCI や Git のソースの場合も、バージョン行と更新種別の分類は表示されますが、レジストリに関する情報は表示されません(正確に確認できる index.yaml がないためです)。最新の chartRef 構成も、もう一方の側から対応しています。spec.url: oci://… と spec.ref.tag/spec.ref.digest を持つ OCIRepository はまさにイメージのピン留めなので、Renovate による ref.tag の更新には、上記の Dockerfile と同じレジストリ処理が適用されます。これにはダイジェスト検証の ✔/‼ も含まれます。バージョン範囲(spec.ref.semver、>=1.0.0)は何もピン留めしないため、対象外です。また、HelmRelease の values: 内にある標準的なチャートイメージのマッピング、つまり repository: と tag: の子要素を持つ image:(Bitnami 形式の registry: と digest: も含む)は、イメージのピン留めとして読み取られます。これは home-ops リポジトリで Flux のイメージ自動化と Renovate が更新する正確な形式であり、旧 Flux v1 の Auto-release コミットも含まれます。
Argo CD の Application CR も対象です。 spec.source に chart: を含む Application は、targetRevision: でそのチャートをピン留めします。また、インラインの repoURL: はチャートリポジトリそのものなので、同じ方法でそのリポジトリの index.yaml に照らして更新を検証します(経過日数、非推奨のチャート、リストにないバージョン、-changelogs)。複数ソースの spec.sources リストは項目ごとに処理され、ApplicationSet のテンプレート(spec.template.spec.source/.sources)も同様に読み取られます。ただし、{{ … }} パラメーターは文字どおりのピン留めではなく、パラメーターとして扱われます。厳密なリビジョンのみが対象です。1.2.*、>=1.0.0、* のような targetRevision やブランチ名はリポジトリを追跡するものであり、何もピン留めしません。また、Git ソースの Application(chart: ではなく path: を使用するもの)は対象外です。OCI の repoURL では、レジストリに関する情報を含まないバージョン行が表示されます(確認できる index.yaml がないためです)。
Helm の values ファイルも対象です。 同じチャートイメージの規約、つまり repository: と tag: の子要素を持つ image: マッピング(Bitnami 形式の registry: と digest: も含む)は、values ファイルから直接読み取られます。対象ファイルは、チャートルートにある values.yaml、values-prod.yaml/app.values.yaml などのオーバーレイ、および GitOps ディレクトリ配下にある任意の名前の helmfile または Argo の値オーバーレイです。これは Renovate の helm-values マネージャーが更新する正確な形式であり、それ以外にこれを検証するものはありません。values ファイルからチャートのバージョンは特定できず、チャートには OSV エコシステムもありませんが、内部のイメージのピン留めには上記のレジストリ処理一式が適用されます(ダイジェスト検証、不明なタグ、経過日数)。規約に基づいて名前が付けられた values ファイルでは、タグまたはダイジェストがある場合、単一値の形式(image: ghcr.io/owner/app:v1.2.3)も受け付けます。image: nginx は何もピン留めしません。ディレクトリの規約や内容のスニッフィングで検出されたにすぎないファイルでは、Helm 特有の形式である repository:/tag: の構造が必要です。ブロックスカラーの本文(config: | — 埋め込まれたアプリ設定)は不透明な領域として扱われます。その内部にある image: の行は埋め込まれたアプリに属し、このファイルのピン留め対象ではありません。
プレーンなマニフェストには予約済みのファイル名がないため、lockvet は慣例的なディレクトリ(k8s/、
kubernetes/、manifests/、deploy/、overlays/、base/、
clusters/、gitops/、apps/、infrastructure/、infra/、flux/、
flux-system/、chart/、charts/、argocd/、argo/、argo-cd/、
applications/、applicationsets/)配下の
*.yaml/*.yml ファイル、*.k8s.yaml という名前のファイル、慣例的なベース名(deployment.yaml、statefulset.yaml、
daemonset.yaml、replicaset.yaml、cronjob.yaml、pod.yaml、および Flux の慣例である helmrelease.yaml、helm-release.yaml、release.yaml、
ocirepository.yaml、helmrepository.yaml と、Argo CD の慣例である
application.yaml、applicationset.yaml、appset.yaml)を対象にします。ただし厳密に、ドキュメントの最上位で apiVersion: と kind: が宣言されている場合にのみ対象となり、該当したファイルが Kubernetes YAML でない場合は黙って無視されます。また、Helm の templates/ ディレクトリ配下は対象外です(そこにあるイメージ参照はテンプレートの補間であり、クラスターが解決する値ではありません)。{{ … }}、$(VAR)、${VAR} が残っているイメージ値は推測せずにスキップします。
差分モード(ローカル git、pr/mr/compare、queue)では、検出対象をさらに広げます。変更された他の *.yaml/*.yml ファイルもすべて、同じ厳密な条件で内容を検査するため、任意の構成(default/nzbget/nzbget.yaml など)の GitOps リポジトリも、慣例に一致しなくても動作します。変更ファイルの一覧は小さいため、対象を広げてもコストはかかりません。大規模なリファクタリング PR の処理を軽く保つため、リモートから取得した差分では検査候補を 1 差分あたり最大 20 ファイルに制限します。ディレクトリ走査(lockvet audit)では、引き続き慣例ベースの検出を使います。大規模なツリー内の YAML をすべて検査すると、見つかる数に比べて読み込むファイルがはるかに多くなるためです。
整合性と解決方法の変更
ロックファイルで固定するのはバージョンだけではありません。バイト列(整合性ハッシュ)と、取得先(解決 URL)も固定します。私たちの知る限り、lockvet はこれらを読み取る唯一のロックファイル差分検出ツールです。
バージョンは変わらず、整合性が変化 ‼ — レジストリが公開済みのアーティファクトを変更することはありません。差分で lodash 4.17.21 が維持されているのに sha512-… が差し替わっている場合、そのピンが想定する tarball は置き換えられています。レジストリ側での改ざん、ミラーの乗っ取り、あるいは手作業で編集されたロックファイルが原因です。いずれも「いったん停止して原因を調べる」べき事態です。
‼ lodash 4.17.21 REPINNED (version unchanged) (direct)
‼ integrity changed: 4.17.21 same version, different content hash —
registries never change a published artifact, so the tarball this
pin expects was replaced; do not trust this without finding out why
ハッシュはアルゴリズムごとに比較されるため、ロックファイル形式のアップグレードで sha1 が sha512 に切り替わる場合(または yarn berry のキャッシュキーが更新される場合)にフラグが立つことはありません。また、Python のハッシュ セット は wheel の追加に伴って正当に増えることがあるため、セットが完全に重ならない場合にのみフラグが立ちます。共通する各アルゴリズムは個別に判定されます。同じバイト列が、同じアルゴリズムで異なるハッシュになることはありません。そのため、SRI の sha512 が差し替えられた yarn-classic のピンは、偽装のために sha1 の resolved フラグメントがそのまま残されていてもフラグが立ちます(yarn はインストール時にフラグメントではなく SRI 行を検証します)。同様に、sha256 行がそのままでも conda ロックの md5 が差し替えられればフラグが立ちます(conda ロックにはどちらか一方、または両方のハッシュが含まれることがあり、それぞれのラベルごとに比較されます)。Hex の二重チェックサムも、同じアルゴリズム内で同様に扱われます。mix.lock と rebar.lock はパッケージごとに、異なるバイトストリームをダイジェスト化する sha256 値を 2 つ記録します。tarball の内容(内側の pkg_hash)と tarball ファイル(外側の pkg_hash_ext)です。そのため、それぞれを個別に(contents#sha256:/tarball#sha256:)扱い、一方だけが差し替えられた場合は、もう一方が変更されていないことで隠されずにフラグが立ちます。外側のチェックサムを追加する旧形式から新形式への移行では、フラグは立ちません。ひとつの例外としてまとめて扱われるのが、Terraform の h1/zh の組み合わせです。h1 ハッシュはロックの作成に使われたプラットフォームにのみ存在するため、プラットフォームに合わせてロックを作り直し、h1 セット全体が置き換わってもフラグは立ちません。一方、レジストリで公開された zh セットは引き続きリリースの正当性を証明します。
レンダリング後に Git コミットが省略されるピン(flake input の
date.rev[:8]、vcpkg baseline の sha[:12])では、完全な
リビジョンもポインターハッシュとして記録するため、短い SHA の衝突が
判明しても、「変更なし」のバージョンのままピンが書き換えられることは
ありません。入れ替えには ‼ REPINNED のフラグが付きます。ポインター
ハッシュはコンテンツがどこにあるかを示すもので、バイト列を検証するものでは
ないため、整合性削除の計上対象からは除外されます。また、ポインターが
一致するだけではバイト列が同じであることの証明にはなりません。vcpkg
レジストリが同じコミットのまま別のリポジトリに切り替わると、フラグが付きます
(今後のベースライン更新はすべて新しいリポジトリに由来するためです)。一方、
narHash が一致する flake のオーナー変更は問題になりません。narHash が
実際のバイト列を検証するためです。
整合性情報が削除され、バージョンは変わっていない ‼ — コンテンツ ハッシュがあったピンから、それが一切なくなる場合も、同様に警告します。 アーティファクトを検証するものがなくなります。また、ハッシュを削除したり、 どのアルゴリズムにも該当しない不正な値に置き換えたりするのは、改ざんされた ピンを問題なしに見せかける方法です。
‼ http 1.2.0 REPINNED (version unchanged)
‼ integrity removed: 1.2.0 this version carried a content hash before
and carries none now — nothing verifies the artifact anymore;
restore the hash or find out why it vanished
次の 2 つの正当なケースでは警告されません。同じバージョンの依存関係が git/path override に切り替わった場合(ピンがコミットに移ったため、レジストリ ハッシュがなくなるのは妥当です)、およびファイル全体からハッシュが一度に 削除された場合(ハッシュを書き込まないツールでロックファイルが再生成された ためで、パッケージ単位の攻撃ではなく設定の変更です)。
解決先が公開レジストリに移動 ⇄ — 古典的な
依存関係混同攻撃
では、公開レジストリに内部パッケージ名のより高いバージョンを公開し、
リゾルバーを罠にかけます。ロックファイルの差分では、これは明確に確認できます。
パッケージの解決先ホストが、プライベートレジストリから
registry.npmjs.org / pypi.org / crates.io / rubygems.org に
切り替わります。
↑ acme-metrics 1.2.0 → 99.9.9 MAJOR (direct)
⇄ resolution moved: npm.acme-corp.internal → registry.npmjs.org —
the shape of a dependency-confusion attack; make sure the public
package is really yours
フラグが付くのはプライベートから公開への切り替えだけです(ミラーの採用は 一般的です)。コンテンツハッシュが変わっていないことが証明できる移動には 警告せず、5 つ以上のパッケージが同じ 2 つのホスト間で移動した場合は、 lockvet はレジストリの移行(設定の変更)として扱い、警告しません。
同じチェックはリリースタグのピンも対象にします。Swift の
Package.resolved と composer.lock には、リリースタグが指していたコミットが
記録され、Julia の Manifest.toml には、レジストリのソースツリーハッシュが
記録されます。バージョン番号はそのままでコミットが変わった場合、上流で
タグが移動されたことを意味し、同様にフラグが付きます。
どちらのシグナルも、2つのロックファイルから完全にオフラインで計算されます。-offline、エアギャップ環境の CI、ブラウザのプレイグラウンドで動作します。データを記録する形式は、npm(v1–v3)、pnpm、yarn(classic + berry)、bun.lock、deno.lock(npm + jsr)、Cargo、poetry、uv、Pipfile.lock、requirements.txt --hash、pylock.toml(PEP 751 のアーティファクトハッシュとインデックスホスト。Python の混同パターンも検出します)、Gemfile.lock(解決時のホストに加え、Bundler ≥ 2.6 の CHECKSUMS セクションにある gem ごとの sha256。プラットフォーム gem はそれぞれ独自のハッシュを保持します)、go.sum(ピンのみの台帳として解析されます。バージョンの変化は go.mod の役割であり、重複行は生成されませんが、同じバージョンの h1: ハッシュの編集は、改ざんされたモジュールが go mod verify を通過する唯一の方法である poisoned-go.sum パターン、そして GOPRIVATE モジュールで唯一のチェックとなる場合に、‼を検出します。SARIF アラートは編集された正確な行に紐づきます)、mix.lock、rebar.lock(pkg_hash/pkg_hash_ext)、Gleam の manifest.toml、pubspec.lock(ハッシュとホスト。Dart の混同パターンも検出します)、Podfile.lock(trunk podspec のチェックサム)、Package.resolved、composer.lock、Julia Manifest.toml、.terraform.lock.hcl(プロバイダーの h1:/zh: ハッシュ)、conda/pixi のロックファイル(PyPI の wheel ハッシュとチャネルホスト)、MODULE.bazel.lock(選択された各モジュールのレジストリ source.json ハッシュ。同じバージョンで変化した場合、レジストリによるリリースの説明が知らないうちに変わったことを意味します。また、レジストリホストも記録するため、モジュールがプライベートレジストリから公開 BCR に移動すると混同パターンを検出します。旧形式の Bazel 7.0/7.1 ロックファイルでは、アーカイブ自体の SRI ハッシュがピン留めされます)、build.zig.zon(Zig にはレジストリが存在しないため、マニフェスト独自の url と hash のピンがすべてを示します。同じバージョンのハッシュ差し替えや、同じバージョンのソースの再指定は、まさに持ち込まれた編集の痕跡です。一方、依存関係を新しいピンを伴うフォークに差し替えるのは通常の、見える変更であり、検出されません)、および flake.lock(Nix。同じリビジョンの narHash が変化した場合、ピン留めされたツリーが置き換えられたことを意味します。git リビジョンの内容は変化しません。入力先が別のリポジトリに変更されると ⇄ パターンを検出します。リポジトリ名の単純な変更のように、再指定後の narHash で内容が同一と証明される場合は検出されません。ただし、表示上の8文字の短縮形ではなく、ロックされた完全なリビジョンを比較します。そのため、短縮リビジョンの衝突を狙って作られた紛らわしいコミットも、「変更なし」に見せかけて隠れるのではなく、ピンの変更として検出されます)。実際の履歴から誤検出が多いと分かったため、意図的に除外しているものが2つあります。NuGet の contentHash(NuGet の2018年のリポジトリ署名変更により、以前のすべてのパッケージのハッシュが変わりました)と、conda のアーティファクトハッシュ(conda は新しいビルド番号で同じバージョンを頻繁に再ビルドします)。-fail-on integrity,registry でゲートできます。JSON には integrity_changed/integrity_changed_versions/old_host/new_host/registry_moved が含まれます。SARIF は integrity-changed(エラー)と registry-moved(警告)を出力します。queue は影響を受ける PR を先頭に並べます。同じバージョンの亜種を含め、ほかの差分では検出できない、2021年の依存関係混同攻撃の完全な再現については、ケーススタディ9をご覧ください。
アップストリームタグに対して検証される SwiftPM のピン
このファミリーのチェックのうち、1 つはオンラインで行います。Package.resolved にはバージョンと、そのタグが解決されたコミット、さらにそれらの取得元リポジトリが記録されているため、信頼できる情報源に照らしてピンを検証できます。
lockvet はリポジトリの実際のタグ一覧を取得し(リポジトリごとに匿名の git smart-HTTP リクエストを 1 回送信します。git fetch と同じ通信経路を使い、API は使わず、レート制限もありません)、新たに追加される各ピンについて次の 2 点をチェックします。
- バージョンのタグが現在も存在すること。バージョンのピンはタグからのみ解決されるため、ピン留めされたバージョンに一致するタグがアップストリームにない場合、誰かが解決した後でタグが削除または改名されたことを意味します → ▲ リリースではありません;
- ピン留めされたコミットが現在のタグの参照先であること。リリースタグは不変であるべきです。不一致は、解決後にタグの参照先が変更された(tj-actions 攻撃はまさにこの方法で配布されました)か、無害そうなバージョンを表示しながら別のコミットを取得するようロックファイルが編集されたことを意味します → ‼ タグの不一致:
↑ github.com/Alamofire/Alamofire 5.9.0 → 5.9.1 patch
‼ tag mismatch: 5.9.1 pinned at deadbeefdead, upstream tag 5.9.1 is
at f455c2975872 — released tags are immutable; either the tag has
been moved since this was resolved, or the lockfile was edited;
verify the commit before trusting it
同じタグ一覧の検証は、ワークフローの uses: ピンにも適用されます。注釈付きタグはコミットまでたどり、v が付いたタグは接頭辞なしのバージョンにも一致します。匿名で取得できないリポジトリ(プライベート、移動済み)については、検証結果を一切出しません。-fail-on integrity(タグの不一致)と -fail-on unlisted(削除されたタグ)でゲートできます。SARIF は tag-mismatch(エラー)を出力します。lockvet audit は現在使用中のすべての Swift のピンを検証し、lockvet pkg swift:Alamofire/Alamofire は追加前に Swift パッケージを検査します(latest は最も高い安定版タグです)。
更新で追加されたインストールスクリプト
npm パッケージはインストール時に任意のコードを実行できます(preinstall/install/postinstall)。ほとんどのパッケージは実行しません。そのため、通常に見える更新でインストールスクリプトが突然追加された場合は、マージ前に慎重に確認する必要があります。インストール時に実行されるコードを追加することは、Shai-Hulud ワームと、その2026 年 8 月の keyv/Cacheable での再来、そして数多くの小規模な npm 攻撃がペイロードを配布するために使った手口です。
lockvet は npm レジストリに、どのバージョンがインストールスクリプトを実行するかを問い合わせ、変更を検出します。
↑ core-js 2.6.5 → 2.6.6 patch (direct)
⚙ install scripts added: 2.6.6 the old version ran no install scripts,
this one does — a favourite payload vehicle for hijacked npm
packages; review before trusting
検出されるのは変更があった場合のみなので、不要な警告は発生しません。インストールスクリプトのある新規依存関係は一般的なもの(ネイティブビルドなど)であり、常にスクリプトがあったパッケージから新しい情報は得られません。92 件の変更がある npm/cli のリリース差分と、165 件の変更がある React の差分では、どちらも何も検出しません。
-fail-on scripts でゲートできます。JSON には install_scripts_added/scripted_versions が含まれ、SARIF は install-scripts-added 警告を出力します。queue は該当する PR を先頭に並べます。現時点では npm のみが対象です(他のエコシステムにはインストールフックがないか、レジストリのメタデータで公開されていません)。
バージョン更新で失われた来歴
npm パッケージのうち、sigstore の来歴証明を付けて公開するものが増えています。PyPI プロジェクトでは PEP 740 の証明、crates.io のクレートでは信頼された公開、RubyGems のリリースでは sigstore の証明が使われています。これらは、リリースがプロジェクト自身の公開リポジトリから、その CI によってビルド・公開されたことを示す暗号学的証明です(crates.io の場合は、レジストリに記録された保証です)。この証明には、レビュー担当者にとって有用な特性があります。公開トークンを盗めばリリースをアップロードできますが、プロジェクトのパイプラインに証明を生成させることはできません。
そのため、継続して証明を付けているパッケージが、突然、来歴なしでバージョンを公開すると、lockvet はそれを検出します。
↑ acme 1.4.2 → 1.4.3 patch (direct) (2d old)
⛨ provenance dropped: 1.4.3 every previous version was published with
sigstore provenance, this one wasn't — legitimate CI keeps
attesting, a stolen publish token can't; verify the release
誤検知を抑えるため、検出には次の 3 つの条件があります。公開されるバージョンに証明が付いていること、パッケージで来歴の付与が定着していること(対象バージョンより前の安定版のうち、上位のものすべてに証明が付いていること。一度だけ採用した例は対象外です)、そして新しいリリースであること(30 日以内)です。これは勧告が出る前の期間を対象とした、発生中の問題を検出する仕組みであり、過去の監査を目的としたものではありません。この条件では、現在の上位 npm パッケージ約 100 件のバージョン更新では検出はなく、上位 PyPI パッケージ 1,000 件では 1 件だけ検出されます。これは、プロジェクトで途切れることなく続いていた証明の付与を中断した、実際に 7 日前に公開されたリリースです(おそらく問題のない手動公開ですが、まさにこのフラグが「確認する価値あり」として示すケースです)。上位 100 件の crates では検出はゼロで、その 10 分の 1 はすでに信頼されたパイプラインを通じて公開されています(cc、time、getrandom など)。トークンを盗んだ者が、プロジェクトの CI を通さずにリリースを公開した時点で検出されます。
-fail-on provenance で検出を必須にできます。JSON には provenance_dropped/unattested_versions が含まれ、SARIF では provenance-dropped 警告が出力されます。queue では該当する PR が上位に表示されます。インストールスクリプトと非リスト対象のチェックと同じレジストリ文書を利用するため、追加のリクエストは発生しません。npm、PyPI、crates.io、RubyGems に対応しています(PyPI では、リリース内のすべてのファイルに来歴が付いている場合にのみ、そのバージョンに証明が付いていると見なします。また、証明付きファイルがひとつもないリリースだけを検出します。証明の有無が混在するアップロードは公開設定の問題であり、検出の手がかりとはしません。crates.io では、各バージョンのレジストリにある trustpub_data が信頼できる情報源です。RubyGems では、リリースごとの証明 API を使います。RubyGems での採用はまだ少ないため、現時点では検出されませんが、gem に証明が付けられ始めると自動的に保護が有効になります)。
リリースノートをインラインで表示(-changelogs)
バージョンを上げるたびに、検証済みの上流タグ間の差分へのリンクが付いています。-changelogs を追加すると、lockvet はリリースノートも取得します。複数バージョンを一度に上げる際に飛ばすリリースの分も含まれます。
↑ body-parser 1.19.0 → 1.20.1 minor via express (3y old)
▤ 1.20.1
* deps: [email protected]
* perf: remove unnecessary object clone
▤ 1.20.0
* Fix internal error when inflated body exceeds limit
* Prevent loss of async hooks context
…
▤ 1.19.2
▤ 1.19.1
つまり、1.19.0 → 1.20.1 では、1.19.1、1.19.2、1.20.0、および 1.20.1 で追加された内容が表示されます(パッケージごとに最大5リリース。残りは比較リンクで確認できます)。
-md モードでは、各パッケージのノートが折りたたみ式の <details> ブロックになります。Dependabot のリリースノートセクションのように見える PR コメントで、トランジティブ依存関係も含め、差分に含まれるすべてのパッケージを対象とします。すべてのモード(pr、mr、compare、diff、changelogs: 'true' を指定した Action、"changelogs": true を指定した MCP)で動作します。
ノートは(検証済みの)ソースリポジトリの GitHub Releases から取得します。プロジェクトがリリースを公開していない場合(Phoenix は2020年に停止しました。また、リリースを一度も公開していない crate や gem も多数あります。GitLab、Codeberg、Bitbucket でホストされているプロジェクトにもありません)、lockvet はリポジトリの変更履歴ファイルにフォールバックします。検証済みタグ時点の CHANGELOG.md/CHANGES.md/NEWS.md/HISTORY.md を取得し、今回のバージョンアップで含まれる正確なバージョンのセクションを抜き出します。どちらの場合も、抜粋は短く整えられ、制御文字がサニタイズされます。リリースノートの取得には GitHub API を使います。匿名でも少数のパッケージなら利用できますが、GITHUB_TOKEN またはログイン済みの gh を使うと上限が引き上げられます。変更履歴ファイルは forge の raw エンドポイントから取得するため、レート制限はありません。
対応するロックファイル
| エコシステム | ファイル |
|---|---|
| JavaScript | package-lock.json、npm-shrinkwrap.json、pnpm-lock.yaml、yarn.lock(v1 と berry)、bun.lock、deno.lock — deno.lock 内の JSR パッケージについては、リリースからの経過期間、yank/アーカイブのフラグ、およびレジストリによる検証済みの未掲載チェックを jsr.io から直接取得します |
| Rust | Cargo.lock |
| Python | uv.lock、poetry.lock、pdm.lock、Pipfile.lock、requirements.txt(== によるピン)、pylock.toml と名前付きの pylock.*.toml(PEP 751 — uv、pip、pipenv、pdm が書き込む標準化されたロックファイル。vcs/ディレクトリ/パスのソースはレジストリチェックの対象外です) |
| Go | go.mod — replace ディレクティブは実効的なピンに解決されます。replace m => m v1.2.3 は要求されたバージョンを上書きします(replace ディレクティブによるダウングレードは、アドバイザリもすべて含めてダウングレードとして表示されます)。replace a => b v1.2.3 は削除と追加として表示されます(解決先を乗っ取る形)。ファイルシステムへの置換(=> ../local)はレジストリに関する主張の対象外です。また、go.sum はピンのみの台帳として扱います。バージョンの変更は引き続き go.mod 側で確認できますが、同じバージョンの h1: ハッシュ編集(go.sum が汚染された形。改ざんされたモジュールは、go.sum を一致するよう編集した場合にのみインストールされ、GOPRIVATE モジュールについてはチェックサムデータベースが異議を唱えることはありません)は ‼ として検出されます |
| PHP | composer.lock |
| Ruby | Gemfile.lock(Appraisal の *.gemfile.lock 形式も含みます。Bundler ≥ 2.6 の CHECKSUMS ハッシュは完全性ピンになります) |
| Elixir | mix.lock — リリースからの経過期間、廃止状況、および未掲載チェックを hex.pm から直接取得します。名前変更されたフォーク({:hex, :ts_chatterbox, …})は実際の Hex パッケージ名で解決されます |
| Erlang | rebar.lock — Hex のアドバイザリ、経過期間/廃止状況/未掲載情報を hex.pm から取得し、直接依存関係はロックファイル自体の階層番号から判定します。pkg_hash/pkg_hash_ext チェックサムはそれぞれ個別にスコープされた完全性ピンとして扱います。同じバージョンでどちらか一方のハッシュが変更されると、もう一方がそのままでも ‼ repinned として表示されます。名前変更されたフォーク({<<"uuid">>,{pkg,<<"uuid_erl">>,…}})は実際の Hex パッケージ名で解決されます。git/path 依存関係は対象外です |
| Dart / Flutter | pubspec.lock — リリースからの経過期間、配布終了/撤回のフラグ、および未掲載チェックを pub.dev から直接取得します(git/path/SDK/プライベートホストのパッケージは対象外です) |
| Java / JVM | pom.xml(Maven には npm 形式のロックファイルがありません。POM のバージョン自体がピンであり、Dependabot はその場で更新します。${property} で解決されるバージョン、spring-boot-starter-parent のような <parent> の更新、BOM のインポート、プラグイン、プロファイルが対象です。${project.version} の兄弟依存関係については、根拠のない主張をしません)、build.gradle/build.gradle.kts(および settings.gradle(.kts) とすべての *.gradle(.kts) スクリプト — 多くの Gradle プロジェクトでは、ビルドスクリプト自体がピンファイルです。座標リテラル、Groovy のマップ形式/Kotlin の名前付き引数形式、plugins { id(…) version … } ブロックを Plugin Portal のマーカーとして解決します。$version の補間は同じファイル内の ext/val 代入を参照して解決し、それ以外の場所で定義された値については根拠のない主張をしません)、gradle.lockfile、libs.versions.toml(Gradle のバージョンカタログ — Renovate と Dependabot が実際に更新するファイルです。プラグインは Gradle Plugin Portal に対して id:id.gradle.plugin マーカーとして解決されます)、verification-metadata.xml(Gradle の依存関係検証 — 誰もレビューしない XML の大規模な差分をパッケージ変更として扱い、同じバージョンのチェックサム変更は ‼ repinned として表示します)、およびビルドツールのラッパー自体 — gradle-wrapper.properties の distributionUrl ピンは services.gradle.org の Gradle 独自のバージョンインデックスに照らして検証されます(リリースからの経過期間、問題があって撤回されたリリース、インデックスに存在しないバージョン、および指定された distributionSha256Sum を Gradle が実際に公開しているチェックサムと照合します。ラッパーは汚染されたチェックサムに対して汚染された配布物を問題なく検証するため、公式チェックサムに一致しないピンは ‼ の対象になります)。一方、.mvn/wrapper/maven-wrapper.properties のピンは通常の Maven 座標として解析され、Maven のすべてのチェックを受けます(どちらも企業ミラーホストについては根拠のない主張をしません)。移行スタブ(mysql:mysql-connector-java → com.mysql:mysql-connector-j)への更新は非推奨の対象になり、未掲載チェックは Maven Central、Google の Maven リポジトリ、Plugin Portal に照らして検証されます |
| Scala | build.sbt(およびすべての *.sbt と project/Dependencies.scala の慣例 — Scala には npm 形式のロックファイルがなく、ビルド定義自体がピンファイルです。scala-steward はその場で座標を更新します)。"org" %% "artifact" % "1.2.3" は同じファイルの scalaVersion から Scala のバイナリサフィックスを解決するため、レジストリ上のアーティファクト(cats-core_2.13)について OSV のアドバイザリ、リリースからの経過期間、未掲載チェックを取得します。バージョンの val はファイル内で解決されます。addSbtPlugin(…) のピンは sbt のクロスサフィックス(Central が実際に提供する名前である sbt-scalafmt_2.12_1.0)で解決されます。CrossVersion.full/for3Use2_13 は文書化されたサフィックスに対応付けられます。ファイルだけでは判別できないもの(scalaVersion が見つからない場合や %%% プラットフォームアーティファクト)は、バージョンの行は維持しますが、根拠のない主張はしません。project/build.properties は sbt 自体をピン留めします。Central 上の org.scala-sbt:sbt が対象となり、実際のアドバイザリも取得されます(lockvet pkg maven:org.scala-sbt:[email protected] をお試しください) |
| .NET | packages.lock.json |
| Swift | Package.resolved |
| C / C++ | conan.lock(Conan 2 のフラット形式のロックファイルと Conan 1 のグラフ形式のロックファイル) — リリースからの経過期間は ConanCenter から直接取得し、各バージョンの最も古いレシピリビジョンを基準日とします。これにより、再エクスポートによって古いリリースが新しく見えることはありません(ユーザー/チャネルを含む参照は対象外です。参照にはリモートが記録されないため、未掲載に関する主張はしません)。各参照の #recipe-revision はピンとして追跡します。バージョンを変えずにリビジョンが変更された場合(レシピ、つまりビルドスクリプトが意図せず変更された場合)は、中立的な ↻ recipe revision の行として表示されます。コンテナーの同じタグに対するダイジェスト更新と同様、これまでは見えなかった変更を可視化しますが、アラートにはしません。ConanCenter では古いバージョンのレシピが定期的に再エクスポートされるためです |
| vcpkg | vcpkg.json + vcpkg-configuration.json — builtin-baseline のコミットがロックファイルの役割を果たします(すべての依存関係は、そのコミット時点でレジストリに記録された内容に解決されます)。ベースラインがレジストリのリポジトリに存在することを検証します(フォークでしか到達できないベースラインは、汚染されたレジストリの形です)。ベースラインの経過期間にはコミット自体の日付を使い、リビジョン間の比較リンクを表示します。overrides のバージョンピンは microsoft/vcpkg の追記専用バージョンデータベースに照らして検査されます。vcpkg-configuration.json のレジストリベースラインにも同じ処理を行い、default-registry の入れ替えは解決先の変更として表示します(overlay-ports やカスタムレジストリ内の override については根拠のない主張をしません) |
| Bazel | MODULE.bazel.lock(最新のレジストリハッシュ形式と Bazel 7.0/7.1 の依存関係グラフ形式の両方)および MODULE.bazel 自体(bazel_dep は正確なバージョンをピン留めし、更新ボットが go.mod と同様にその場で更新します) — レジストリに記載された理由付きの yank 済みバージョン、ソースリポジトリの変更履歴リンク、レジストリによる検証済みの未掲載チェックを Bazel Central Registry から直接取得します(プライベートレジストリのモジュールと git_override/local_path_override は対象外です) |
| iOS / CocoaPods | Podfile.lock — リリースからの経過期間、非推奨 Pod のフラグ、ライセンスの変更、および未掲載チェックを CocoaPods CDN と trunk レジストリから直接取得します(git/path Pod とプライベート specs リポジトリは対象外です) |
| R | renv.lock — CRAN + Bioconductor のアドバイザリ(RSEC-…)、リリースからの経過期間、アーカイブ済みパッケージのフラグ、ライセンスの変更、変更履歴リンク、およびミラーの遅延を考慮した未掲載チェックを CRAN 自体から直接取得します(GitHub/GitLab/ローカル/git によるインストールは対象外です) |
| Julia | Manifest.toml(Manifest-v1.11.toml 形式も含む) — General レジストリのアドバイザリ(JLSEC-…)と、マニフェストの依存関係グラフに基づく依存経路 |
| Haskell | stack.yaml.lock、cabal.project.freeze、cabal.config(Stackage のピン) — Hackage のアドバイザリ(HSEC-…)、リリースからの経過期間、非推奨パッケージ(メンテナーが提案する代替パッケージも含む)と非推奨バージョン、検証済みの変更履歴リンク、レジストリによる検証済みの未掲載チェックを Hackage から直接取得します |
| Gleam | manifest.toml — Hex のアドバイザリ、依存経路、[requirements] に基づく直接依存関係、経過期間/廃止状況/未掲載情報を hex.pm から取得します(git/path パッケージは対象外です) |
| Conda | pixi.lock(v4~v7)、conda-lock.yml(*.pixi.lock、*.conda-lock.yml も含む) — リリースからの経過期間、問題のあるリリースのフラグ、ライセンスの変更、レジストリによる検証済みの未掲載チェックを anaconda.org から直接取得します(conda-forge、bioconda など、ロックファイルが解決するすべてのチャネルが対象です)。内部の pip パッケージには PyPI の脆弱性/経過期間データをすべて適用します。アーティファクトの sha256 ハッシュは、アーティファクトのファイル名をスコープとする完全性ピン(openssl-3.3.1-h4bc722e_2.conda#sha256:…)として読み取られます。そのため、同じアーティファクトのハッシュが入れ替わると ‼ REPINNED として検出されます。一方、conda-forge によるビルド番号を変えた通常の同一バージョンの再ビルドは通知されません。また、ローカルでビルドされたソース(- pypi: ./rerun_py のようなパスによるインストールや git チェックアウト)は、再ビルドごとにハッシュが正当に変わるため、完全性情報を記録しません |
| Terraform / OpenTofu | .terraform.lock.hcl(接尾辞付きの *.terraform.lock.hcl も含む) — リリースからの経過期間、アーカイブ済み/リストから削除済み/ブロック済みプロバイダーのフラグ、検証済みの変更履歴リンク、レジストリによる検証済みの未掲載チェックを Terraform および OpenTofu のレジストリから直接取得します(カスタムレジストリのホストは対象外です) |
| Helm | Chart.lock、Chart.yaml(多くのリポジトリではマニフェストのみをコミットするため、正確なバージョンピンを読み取り、範囲指定の制約は無視します)、requirements.yaml(Helm v2。Ansible Galaxy ファイルとは内容に基づいて判別します)、requirements.lock(Helm v2。rye の pip 形式の requirements.lock は自動検出して PyPI として扱います) — リリースからの経過期間、非推奨チャートのフラグ、検証済みの変更履歴リンク、各チャートリポジトリ自身の index.yaml に対する剪定ガード付き未掲載チェックを直接取得します。これは helm dependency update が解決に使う正確なドキュメントです(oci:// と file:// の参照は対象外です) |
| Ansible | requirements.yml/requirements.yaml — Galaxy コレクション(namespace.name)と従来形式のロールを対象とし、正確なバージョンピンのみを扱います(範囲指定の制約は無視します)。リリースからの経過期間、非推奨コレクションのフラグ、検証済みの変更履歴リンク、レジストリによる検証済みの未掲載チェックを galaxy.ansible.com から直接取得します(v3 コレクションインデックス。ansible-galaxy 自体が解決に使うものと、従来の v1 ロールインデックスの両方が対象です)。git/file/url エントリとプライベート Automation Hub ソースは対象外です。ロールについては、所有者が再インポートしたときにしかインデックスが更新されないため、未掲載の主張は行いません |
| Nix | flake.lock |
| Zig | build.zig.zon — Zig にはこれ以外のロックファイルがなく、レジストリもありません。各依存関係はソース URL とコンテンツハッシュをピン留めし、lockvet はその両方を読み取ります(rev/tag/hash から導出したバージョン、‼ 同じバージョンでのハッシュの入れ替え、⇄ 同じバージョンでのソース参照先の変更、検証済みのタグ比較リンク + 依存関係自身のリポジトリから取得したリリースノート) |
| CI / GitHub Actions | .github/workflows/*.yml、action.yml/action.yaml(コンポジットアクション)、.gitea/workflows、.forgejo/workflows — すべての uses: ピンを対象とします。SHA と変動するタグのピンはアクションリポジトリの実際のタグに照らして解決し、OSV の GitHub Actions エコシステムのアドバイザリはクライアント側で評価します |
| CI / GitLab | .gitlab-ci.yml(backend.gitlab-ci.yml のような接尾辞付きの形式や、.gitlab//.gitlab-ci/ 内のフラグメントも対象です) — include: component: CI/CD Catalog のピンはコンポーネントプロジェクトの実際のタグに照らして検証します(@2/@2.0/@~latest のような変動する形式は、現在取得されるリリースに解決します)。ジョブ/デフォルトの image: と services: の参照は Dockerfile のイメージと同様に検証します。include: project: + ref: のピンは、根拠のない主張をせずに追跡します |
| CI / CircleCI | .circleci/config.yml(継続設定と .circleci/ 内のフラグメントも対象です) — すべての orbs: レジストリピンについて、リリースからの経過期間、volatile/5/5.1 のような変動する形式が現在取得するバージョンへの解決、レジストリによる検証済みの未掲載検出、CircleCI orb レジストリから直接取得した変更履歴リンクを扱います(dev: バージョンとインライン orb は対象外です)。Docker executor の - image: 参照は Dockerfile のイメージと同様に検証します(machine: VM イメージラベルは無視します) |
| コンテナー | Dockerfile/Containerfile(Dockerfile.alpine、dev.Dockerfile のような別名も対象です)および Compose ファイル(docker-compose.yml、compose.yaml、オーバーライド) — すべての FROM ベースイメージ(マルチステージに対応し、ステージ参照は無視し、ARG のデフォルト値を展開し、--platform フラグは無視します)、COPY --from= のイメージ、Renovate が更新する # syntax= BuildKit ディレクティブ、Compose の services: 内のすべての image: を対象とします。ローカルの build: コンテキストからビルドされるサービスは対象外です。タグとダイジェストのピンはイメージレジストリ自体に照らして検証されます。コンテナーのベースイメージを参照してください |
| Dev Containers | devcontainer.json/.devcontainer.json(サブフォルダー内の形式も含み、JSONC に対応します) — "image" のピンと "features" 内で OCI を参照するすべての Feature(ghcr.io/devcontainers/features/…:tag または @sha256: ダイジェストピン)を対象とし、Dockerfile のイメージと同様にレジストリに照らして検証します。ローカルパス/tarball/ホストを含まない旧形式の Feature ID は無視します |
| Kubernetes | マニフェスト(慣例的なディレクトリ内の *.yaml — k8s/、kubernetes/、manifests/、deploy/、overlays/、base/、clusters/、gitops/、apps/、infrastructure/、flux-system/、chart(s)/、… — に加え、*.k8s.yaml や deployment.yaml、helmrelease.yaml、ocirepository.yaml のような慣例的なベース名が対象です。最上位に apiVersion: + kind: が必要で、Helm の templates/ は除外されます):containers:/initContainers:/ephemeralContainers 内のすべての image: を Dockerfile のイメージと同様に検証します。Flux の HelmRelease チャートバージョンピン(同じファイル内の HelmRepository URL はチャートリポジトリの index.yaml に照らして検証されます)と、OCIRepository の ref.tag/ref.digest ピン(イメージと同様に検証されます)。Argo CD の Application/ApplicationSet チャートソース(chart: + 正確な targetRevision:。単一ソースと複数ソースの両方に対応し、インラインの repoURL の index.yaml に照らして検証します。慣例的なベース名の application.yaml、applicationset.yaml、appset.yaml と、argocd/、argo/、applications/ ディレクトリも対象です)。kustomization.yaml/kustomization.yml:images: トランスフォーマーのピン(newTag:、digest:)と、チャートリポジトリ自身の index.yaml に照らして検査する helmCharts: エントリ。Helm values ファイル(values.yaml、values-*.yaml、*-values.yaml、*.values.yaml — および構造が一致する YAML として検出されたファイル):repository:/tag:(+ registry:/digest:)子要素を持つ image: マッピングと、慣例的な名前のファイル内にあるタグ付き image: スカラーを対象とします。ブロックスカラーの本文は不透明なものとして扱い、テンプレート化された値は無視します。Kubernetes マニフェストと kustomizationを参照してください |
| pre-commit | .pre-commit-config.yaml — すべての repos: エントリの rev: ピンを対象とし、どの git フォージも利用できます(名前はホスト名を維持します)。ワークフローのピンと同様に、rev をフックリポジトリの実際のタグに照らして正確に検証します。SHA ピンは一致するリリースに解決され、どのタグにも一致しない rev には ▲ を付けます。検証済みの比較リンク + 更新の変更履歴も表示します(repo: local/meta は対象外です) |
| asdf / mise | .tool-versions、mise.toml(.mise.toml、mise.local.toml、mise/config.toml の場所も含む) — 約90種類のツールを収録し、各リポジトリのタグ形式(go1.23.4、v3_4_2、OTP-27.1.2、REL_18_4、swift-6.1-RELEASE、…)に対応した厳選マップを使い、すべてのツールチェーンのピンをツール自身のリポジトリのタグに照らして検証します。検証済みの (=tag) 解決には比較リンクとリリースノートを表示し、あいまいなピン(node = "22")は現在インストールされるバージョンに解決します。具体的なピンがどのタグにも一致しない場合は ▲ を付けます。mise のバックエンドツールは実際のレジストリパッケージとして扱い、完全なチェックを行います(npm: → npm、cargo: → crates.io、pipx:/pip: → PyPI、gem:、dotnet:、go:)。ubi:/aqua:/github:/spm: は指定された GitHub リポジトリに照らして検証し、gradle/maven/sbt は既存のレジストリを利用します。asdf:/vfox: プラグイン、シンボリックセレクター(latest、lts、system、ref:)、ベンダー接頭辞付きの Java ビルドについては根拠のない主張をしません。単一ツールのバージョンファイルにも同じ処理を適用します:.nvmrc/.node-version、.python-version(複数バージョンの pyenv フォールバックも含む)、.ruby-version(rvm の ruby-3.3.4 形式にも対応)、.go-version、.java-version、.terraform-version/.terragrunt-version、および SDKMAN の .sdkmanrc(gradle=/maven=/sbt=/kotlin= のピンを検証し、ベンダー接尾辞付きの Java ビルドについては根拠のない主張をしません)。mise.lock のプラットフォームごとの sha256/blake3 チェックサムは、アーティファクトをスコープとする完全性ピンとして追加されます。同じバージョンでチェックサムが変わると ‼ REPINNED として検出されますが、プラットフォームの追加では通知されません |
| SBOM | CycloneDX と SPDX の JSON:bom.json、sbom.json、*.cdx.json、*.spdx.json — Alpine/Debian/Wolfi の OS パッケージを含む複数エコシステムに対応 |
注:直接依存/via … の依存元ラベルは、ロックファイルに依存関係グラフが記録されている場合に表示されます。対象は npm、pnpm、yarn、Cargo、uv、poetry、Composer、Bundler、renv、pixi/conda-lock、Julia マニフェスト、Gleam、CocoaPods、Go modules です(go.mod の // indirect マーカーは、依存関係のチェーンなしで直接/間接依存を示します)。バージョンをフラットに固定するだけの形式(requirements.txt、mix.lock、Gradle、…)では、このラベルは省略されます。 | |
Deno の jsr: パッケージ、CocoaPods、conda チャンネル、Terraform プロバイダー、Helm チャート、Bazel モジュール、Nix フレーク、コンテナーのベースイメージ、pre-commit フックのピン留めには、OSV.dev エコシステムがまだないため、これらの差分は脆弱性データなしで説明されます(Conan には「ConanCenter」というエコシステムがありますが、まだほとんどデータがありません。情報が増えれば、conan.lock の差分に対するアドバイザリが自動的に表示されます)。ただし、pixi.lock/conda-lock.yml 内の pip/pypi パッケージは、ほかの Python ロックファイルと同様に PyPI アドバイザリと照合されます。 | |
SBOM パッケージは各 purl のエコシステムを保持します。purl がリリースを示す場合、OS パッケージには OSV データが適用されます(distro=alpine-3.18.4 → Alpine:v3.18)。Ubuntu/RPM パッケージは、そのデータなしで差分が確認されます。 | |
リリース経過期間/非推奨化/ライセンス変更の情報は deps.dev から取得されます。deps.dev が対応しているのは npm、crates.io、PyPI、Go、Maven、NuGet、RubyGems で、その他のエコシステムではこれらのチェックが省略されます。PHP、BEAM 系、Dart、JSR、iOS、C/C++、Terraform、R、Haskell、Bazel、conda、Helm、Ansible は例外です。deps.dev には Composer、Hex、Pub、JSR、CocoaPods、Conan、Terraform、CRAN、Hackage、Bazel、conda、Helm、Ansible のシステムがないため、lockvet は Packagist、hex.pm、pub.dev、jsr.io、CocoaPods レジストリ、ConanCenter、Terraform/OpenTofu レジストリ、CRAN(METACRAN の crandb 経由)、Hackage、Bazel Central Registry、anaconda.org、Ansible Galaxy、および各 Helm チャートリポジトリの index.yaml に直接問い合わせます。リリース経過期間、非推奨の警告(Composer の abandoned、Hex の廃止、pub.dev の提供終了パッケージと撤回済みバージョン、JSR の取り下げ済みバージョンとアーカイブ済みパッケージ、CocoaPods の非推奨ポッドと指定された置換先、アーカイブ済み/掲載解除済み Terraform プロバイダー、CRAN でアーカイブされたパッケージ、Hackage で非推奨となったパッケージと個別バージョン(置換候補を含む)、Bazel Central Registry から取り下げられた Bazel モジュールバージョンとレジストリの理由、破損とマークされた conda リリース、リポジトリのインデックスで deprecated とマークされた Helm チャート、Galaxy で非推奨とマークされた Ansible コレクション)、変更履歴へのリンク、未掲載チェックは、composer.lock、mix.lock、rebar.lock、Gleam の manifest.toml、pubspec.lock、deno.lock の jsr: パッケージ、Podfile.lock、.terraform.lock.hcl、renv.lock、stack.yaml.lock、cabal.project.freeze、MODULE.bazel.lock、MODULE.bazel、pixi.lock、conda-lock.yml、Chart.lock、Chart.yaml、Ansible の requirements.yml にも対応しています(さらに、Composer、CocoaPods、CRAN、conda のライセンス変更も対象です。Hex、pub.dev、jsr.io、Terraform レジストリ、Hackage、Bazel Central Registry、Helm チャートのインデックス、Ansible Galaxy には、リリースごとのライセンス履歴として利用できるデータがないため、そのチェックは正直に省略されます)。Ansible のチェックには、独自の慎重な運用ルールがあります。従来型のロールについては、存在しないという判定は行いません(v1 ロールインデックスはオーナーが再インポートした場合にのみ更新されるため、バージョンが見つからなくても、悪意ではなく遅延を示すにすぎません)。また、Galaxy の 2023 年の移行以前に公開されたバージョンはすべて移行日が記録されるため、過去の経過期間は上限値です。Helm の未掲載チェックには、古いリリースがインデックスから定期的に削除されるチャートリポジトリ向けのガードもあります。そのため、インデックスに残っている最古のリリースと同じかそれより新しいバージョンが見つからない場合にのみ、フラグが立ちます。Helm の経過期間にも再生成に対するガードがあります。リポジトリによっては index.yaml を再生成し、過去のエントリに生成時刻を設定するため(すべてのチャートが今日公開されたように見えてしまいます)、同じチャートの 3 つ以上のバージョンで created の時刻が 1 時間以内に集中している場合、そのタイムスタンプは生成時のものとみなされ、経過期間は報告されません。集中していないバージョンには、実際の経過期間が適用されます。Bazel Central Registry にも公開時刻は記録されておらず(git 履歴にのみ存在します)、そのため Bazel モジュールにはリリース経過期間も ⏱ クールダウンフラグも付与されません。CRAN の未掲載チェックは、ミラーの遅延を考慮しています。何かを報告する前に、cran.r-project.org 自体に照会して存在しないことを再確認します(現行リリースと Archive の両方を確認します。CRAN は古いバージョンをアーカイブしますが、削除はしないため、どちらにもないバージョンは CRAN に存在したことがありません)。 | |
conan.lock ではリリース経過期間のみ確認されます。Conan の参照にはどのリモートから取得したかが記録されないため、ConanCenter に存在しなくても証拠にはならず、lockvet は未掲載や非推奨に関する判定を行いません。 | |
vcpkg マニフェストではベースラインが固定されています。builtin-baseline のコミットによって各依存関係のバージョンが決まるため、lockvet はそのコミットがレジストリのリポジトリに存在することを確認し(フォークでしか到達できないベースラインは、汚染されたレジストリの状態です。攻撃者のチェックアウトでのみビルドが解決され、それ以外では解決できません)、日付を特定し、レジストリリポジトリ上の old...new へのリンクを作成し、overrides のバージョンピン留めを microsoft/vcpkg の追記専用バージョンデータベースと照合します。どちらの判定も、更新後の値が解決できる場合に限って行われます(オーバーレイポートやカスタムレジストリを使うプロジェクトでは判定されません)。また、マニフェストの設定で overlay-ports が宣言されている場合は、オーバーライドの判定が完全に無効になります。 | |
Nix フレークの入力はバージョンではなく git リビジョンを固定します。lockvet は差分を時系列順に読めるよう、これらを <commit-date>.<short-rev> として表示し、各入力の lastModified をリリース経過期間に変換します(⏱ クールダウンフラグは update-flake-lock の PR で完全オフラインで機能します)。また、すべての更新についてフォージの rev...rev 比較ページにリンクするため、「nixpkgs で実際に何が変わったのか」をワンクリックで確認できます。上記の narHash/リポジトリ差し替えチェックは、フレークにも適用されます。 | |
| 気になるものがほかにありますか?issue を作成してください。パーサーはそれぞれ約50行です。 |
仕組み
git diff --name-only <base> <target>で変更されたロックファイルを見つけます。git showで各ロックファイルのバージョンを読み込み、package → pinned versionsに解析します(パッケージごとに複数のバージョンがある場合にも対応します。npm のネストや Cargo のメジャーバージョンの重複などです)。- 2つのスナップショットを比較し、semver、Python のポストリリース、Go の疑似バージョンに対応する寛容なバージョンパーサーで各変更を分類します。
ロックファイルに依存関係のエッジとルート依存関係も記録されている場合、lockvet はグラフを幅優先探索して各変更に
(direct)またはvia <chain>のラベルを付けます。マニフェストファイルもネットワークも必要ありません。 - 変更前と変更後のバージョンを OSV.dev のバッチ API で確認します。新しいバージョンには一致し、古いバージョンには一致しない脆弱性は導入、その逆は修正済み、両方に一致する場合は未解決です。同じ問題を示す別名のアドバイザリ(GHSA/CVE/PYSEC/RUSTSEC)はまとめられます。
- 追加される各バージョンについて、deps.dev のバッチ API で公開日、非推奨ステータス、ライセンスを調べます。削除されるバージョンも調べるため、変更前後でライセンスが変わる場合も検出されます(npm、crates.io、PyPI、Go、Maven、NuGet、RubyGems)。レジストリに掲載されていない追加バージョン(同じパッケージのほかのバージョンは掲載されている場合)には、
未掲載フラグが付けられます。未公開のリリース(悪意のあるものが多く見られます)は、このように表示されます(npm、PyPI、crates.io、RubyGems、Packagist、NuGet、Hex、Pub、Go、Maven、CRAN、Hackage、Bazel、conda では、レジストリ自体でもフラグを再確認します。これにより、lockvet はバージョン更新でインストールスクリプトが突然追加された場合や来歴の証明がひそかに削除された場合、またリリースが取り下げられた場合や、プロジェクトがアーカイブまたは隔離された場合も把握できます)。-fresh-days(デフォルトは7)で指定した日数に満たないバージョンには ⏱ フラグが付きます。サプライチェーン攻撃は通常、公開から数日以内に発見されて取り下げられるため、短い猶予期間を設けることでその大半を除外できます。RubyGems では、コンパクトインデックスのcreated_atの時刻を使って、deps.dev がまだインデックス化していないバージョンの経過時間を補完します。そのため、数分前に公開された gem にも ⏱ フラグが付きます。NuGet の登録情報にあるpublished時刻も、.NET パッケージに対して同様に機能します。Composer、Hex、Dart、JSR、R、Haskell、conda のパッケージでは、経過時間をそれぞれ Packagist、hex.pm、pub.dev、jsr.io、CRAN 独自のバージョン別タイムライン(METACRAN の crandb 経由)、Hackage のアップロード時刻エンドポイント、anaconda.org のリリース別アップロード時刻から直接取得します。これらは deps.dev ではまったく対応していません。pod の経過時間は CocoaPods trunk API のバージョン別タイムスタンプから取得します。 Terraform/OpenTofu プロバイダーについては、レジストリのバージョン別公開時刻から取得します(数時間前に公開されたプロバイダーにも ⏱ フラグが付きます)。数分前に作成された Go のタグは、モジュールプロキシの.infoエンドポイントから時刻を取得します。また、疑似バージョンはバージョン文字列自体にコミット時刻が含まれるため、その経過時間をすぐに判定できます。Maven アーティファクトの場合は、Central または Google の Maven リポジトリにある POM のLast-Modifiedアップロード時刻を代わりに使用します。プロキシの最新のgo.modからは、撤回情報(作成者の理由コメントを含む)と// Deprecated:モジュール通知も取得します。GOPROXYが尊重されるため、プライベートプロキシも使用できます。 - 変更された各パッケージのソースリポジトリ(deps.dev から取得)のタグ一覧を、Git のスマート HTTP プロトコルで取得します。リポジトリごとに匿名 GET リクエストを1回行います。これは
git ls-remoteが送信するものと同じリクエストです。変更前と変更後のバージョンを実際のタグと照合し(v1.2.3、1.2.3、[email protected]、pkg-v1.2.3、Go のdir/v1.2.3、疑似バージョンのコミットハッシュを順に試します)、一致が確認されたものだけが比較リンク/リリースリンクになります。そのため、すべてのリンクが機能します。 **プライバシー:**ネットワーク通信は、OSV.dev と deps.dev へのバッチクエリ(パッケージ名とバージョン)、変更されたパッケージに関する各エコシステムの匿名メタデータ取得(npm レジストリ/PyPI/crates.io/RubyGems/Packagist/NuGet/hex.pm/pub.dev/jsr.io/CocoaPods-CDN-and-trunk/Go-module-proxy/Terraform-and-OpenTofu-registry/Maven-Central-and-Google-Maven/ConanCenter/CRAN(crandb + cran.r-project.org)/Hackage/Bazel-Central-Registry/anaconda.org)、および上記の匿名 git タグ一覧の取得のみです。-offlineを指定すると、これらすべてが無効になります。-no-vulns/-no-metaを指定すると、脆弱性の照合とメタデータ+リンクの照会をそれぞれ個別に無効にできます。タイポスクワッティングのチェックではリクエストは一切送信されません。人気度リストはバイナリに含まれています。 テレメトリーは一切ありません。
エアギャップ環境での脆弱性チェック(-osv-db)
-offline は「脆弱性データなし」を意味する必要はありません。OSV.dev が公開しているエコシステムごとの all.zip ファイル
(OSV.dev の公開データ)を保存したディレクトリを lockvet に指定すれば、脆弱性チェックをすべてディスクから実行できます。
# On a machine with network: downloads (and later revalidates) the
# databases for whatever ecosystems the diff actually touches.
lockvet -osv-db ~/osvdb
# Air-gapped / locked-down CI: same check, zero network.
lockvet -offline -osv-db ~/osvdb -fail-on vuln
重要な詳細:
- 同じ結果が得られます。 範囲の評価はクライアント側で行われ、lockvet が GitHub Actions のアドバイザリにすでに使用しているものと同じ走査を使います。撤回されたレコードは API と同様に除外されます。実際のリポジトリでは、
MAL-…マルウェアレコードも含め、API 経由の場合と出力はバイト単位で一致します。そのため、chalk/debug の再現手順もネットワークを切断した状態で機能します。 - 更新コストを抑えられます。 ダウンロードは条件付き(ETag)です。データベースに変更がなければ、コストは 304 応答 1 回分です。
LOCKVET_OSV_DBでデフォルトのディレクトリを設定できるため、CI ではほかのビルドキャッシュと同様にキャッシュできます。 - 手動でコピーした zip ファイルも使えます。 エアギャップ環境へのファイル転送が転送プロセス経由に限られる場合は、
all.zipファイルを手動でコピーしてください。lockvet は初回使用時にローカルでインデックスを作成します。 - サイズは正直にお伝えします。 ほとんどのエコシステムは数 MB です。npm はすべての
MAL-…マルウェアレコードを含むため、約 200 MB あります。それが目的です。npm のインデックス作成は更新ごとに一度だけ約 15 秒かかり、キャッシュが温まった後の実行は数秒です。 - データベースがない場合は明確に失敗します。 差分に含まれるエコシステムのローカルデータベースがない場合、脆弱性チェックは案内を表示してスキップされ、「脆弱性なし」と黙って報告することはありません。
**キャッシュ:**レジストリとアドバイザリの結果は 1 時間、ディスク(~/.cache/lockvet。LOCKVET_CACHE_DIR で変更可能)にキャッシュされるため、繰り返しの実行が速くなります。たとえば lockvet の後に PR コメント用に lockvet -md を実行しても、各レジストリへの問い合わせは一度で済み、匿名利用時のレート制限内に収まります。Forge のデータ(PR の状態、ファイルの内容)はキャッシュされないため、常に最新の差分を検査できます。また、▲ 未掲載フラグの根拠となるレジストリの否定的な応答は、毎回あらためて確認されるため、新しいバージョンが公開されるとフラグはすぐに解除されます。-no-cache を指定するとキャッシュを迂回し、-cache-ttl 15m で有効期間を調整できます(-cache-ttl 0 で無効になります)。あなたのリポジトリに関する情報がキャッシュに書き込まれることはありません。
ネットワークに到達できない場合(DNS、TCP、TLS、タイムアウト)、lockvet は停止するのではなく、期限切れから最大 24 時間以内のキャッシュエントリを使います。また、フォールバックが使用されたこととデータの経過時間を一度だけ警告します。ステータスを問わず応答するサーバーからの回答は、常に尊重されます。フォールバックの対象は「問い合わせできなかった」場合であり、「回答が気に入らなかった」場合ではありません。キャッシュを無効にすると、フォールバックは行われません。
**依存関係:**ありません。Go 標準ライブラリのみで動作します。
比較
ロックファイルの git diff | whatsdiff v2.6 | dependency-review-action | lockvet | |
|---|---|---|---|---|
| ロックファイル形式 | 任意(生テキスト) | 3(composer、npm、pnpm) | GitHub の依存関係グラフのエコシステム | 30 以上のエコシステムにわたる 60 形式、および CycloneDX/SPDX SBOM |
| パッケージごとの読みやすい概要 | ✗ | ✓ | ✓(ジョブの概要/PR コメント) | ✓ |
| 変更によって導入/修正された脆弱性 | ✗ | ✗ | 導入されたもののみ(GitHub Advisory DB) | ✓ 両方(OSV.dev) |
| リリースからの経過期間と、最新バージョンの⏱クールダウンフラグ | ✗ | ✗ | ✗ | ✓(deps.dev) |
| 非推奨の警告 | ✗ | ✗ | ✗ | ✓(deps.dev) |
ライセンス変更フラグ(MIT → BUSL-1.1) | ✗ | ✗ | ✗(代わりに SPDX 許可リストでゲート) | ✓(deps.dev) |
| 変更された依存関係の OpenSSF Scorecard スコア | ✗ | ✗ | ✓(警告しきい値) | ✗ |
| 配布元レジストリに掲載されなくなったバージョン(公開後に削除されたマルウェア)を検出 | ✗ | ✗ | ✗ | ✓ (unlisted) |
| 人気の名前と 1 文字違いの新しい依存関係(タイポスクワッティング)を検出 | ✗ | ✗ | ✗ | ✓ (≈ typosquat、オフライン、npm/PyPI/crates.io) |
| 更新によって突然追加された npm インストールスクリプトを検出 | ✗ | ✗ | ✗ | ✓ (⚙ scripts) |
| 来歴(sigstore/信頼された公開)が気づかれないまま失われた、リリースから間もないバージョンを検出 | ✗ | ✗ | ✗ | ✓ (⛨ provenance) |
| バージョンが変わっていないのに整合性情報が変更された場合、およびプライベートからパブリックのレジストリへ移動した場合(依存関係の混同)を検出 | ✗ | ✗ | ✗ | ✓ (‼ integrity / ⇄ resolution、オフライン) |
| ローカルデータベースを使ったエアギャップ環境での脆弱性チェック | ✗ | ✗ | ✗ | ✓ (-osv-db、MAL-… マルウェア記録を含む) |
GitHub Actions の uses: ピン(SHA → リリースの解決、タグ攻撃の検出) | ✗ | ✗ | 依存関係グラフ経由の GitHub Actions エコシステム | ✓ (形式 #31、5 つすべてのフォージとローカル環境に対応) |
| SwiftPM のピンを上流タグと照合(タグの移動/リリースの削除) | ✗ | ✗ | ✗ | ✓ (‼ tag mismatch) |
直接依存と推移的依存、および依存関係の取り込み経路(via a › b) | ✗ | ✗ | 直接/間接のラベルのみ。経路なし | ✓ |
| クローンせずに PR/MR/比較 URL を検査 | ✗ | ✗ | GitHub PR のみ(PR のワークフローとして実行) | ✓(GitHub + GitLab + Bitbucket + Gitea/Forgejo + Azure DevOps、セルフホスト環境も含む) |
| 未対応の Dependabot/Renovate PR をすべて一括でトリアージ | ✗ | ✗ | ✗ | ✓(lockvet queue <org>、5 つすべてのフォージに対応) |
| 2 つのコンテナーイメージの SBOM を、ディストリビューションの CVE とあわせて差分比較 | ✗ | ✗ | ✗ | ✓(lockvet diff old.cdx.json new.cdx.json) |
| 変更だけでなく、現在のピンも監査 | ✗ | ✗ | ✗ | ✓ (lockvet audit:アドバイザリー、未掲載、非推奨、最新) |
| インストールする前にパッケージを検査 | ✗ | ✗ | ✗ | ✓ (lockvet pkg npm:left-pad:アドバイザリー、経過期間、タイポスクワッティング。最新または任意のバージョン) |
| CI ゲート | ✗ | パッケージごとの check 終了コード | ✓(fail-on-severity、ライセンスリスト、deny-packages) | ポリシーゲート(-fail-on major|vuln|fresh|deprecated|unlisted|typosquat|scripts|provenance|integrity|registry|license)+ GitHub Action |
| ゲートを無効にせずに検出結果を承認 | ✗ | ✗ | ✓(allow-ghsas、有効期限なし) | .lockvetignore(有効期限付きのアドバイザリー/パッケージごとのルール。無視した検出結果も表示されます) |
| 出力形式 | テキスト | テキスト、JSON、Markdown | ジョブの概要 + PR コメント | テキスト、JSON、Markdown、SARIF(コードスキャンアラート) |
| 上流での変更内容を確認 | ✗ | ✓(変更履歴のテキストを取得) | ✗ | ✓(検証済みのタグ間差分リンク + -changelogs でリリースノートを取得。推移的依存も含む) |
| MCP サーバー(AI アシスタントが PR を検査) | ✗ | ✓ | ✗ | ✓(lockvet mcp:URL、ローカルリポジトリ、ファイル、インストール前のパッケージ、監査、キュー全体を検査) |
| 対話型 TUI | ✗ | ✓ | ✗ | ✗ |
| ローカル/CI 外で実行 | ✓ | ✓ | ✗ | ✓(CLI、ブラウザーのプレイグラウンド、任意の CI) |
| ランタイム | — | PHP(バイナリを提供) | GitHub ランナー上で動作する JS Action。パブリックリポジトリでは無料、プライベートリポジトリでは GitHub Advanced Security が必要 | 依存関係ゼロの単一静的 Go バイナリ |
| composer/npm を使っていて、変更履歴と TUI が欲しいなら、whatsdiff は優れたツールです。GitHub を全面的に使っているなら、GitHub の dependency-review-action は堅実なデフォルトです。依存関係グラフを読み取るため(ロックファイルに記録されないマニフェストのみの変更も検出できます)、OpenSSF Scorecard のスコアも表示します。ただし、チェック対象は既知のアドバイザリとライセンスに限られ、GitHub の PR でしか動作しません。また、プライベートリポジトリには Advanced Security ライセンスが必要です。lockvet の着眼点は異なります。この差分を信頼すべきか?――どのフォージや言語を使うリポジトリでも、アドバイザリデータベースがまだ把握していないレジストリレベルの改ざんシグナルも含め、ローカルでも CI でも確認できます。 |
「これは単に……では?」
osv-scannerでは? Google の osv-scanner は、既知の脆弱性を調べるリファレンススキャナーです。ツリーやコンテナイメージを指定すると、OSV アドバイザリをすべて調べます。実験的な fix モードでは、アップグレードの計画も立ててくれます。lockvet が答えるのは別の問いです。この変更は何をするのか、何か問題の兆候はないか? アドバイザリは判断材料の一項目にすぎません。リリースからの経過期間、非推奨化、ライセンスの変更、レジストリに掲載されなくなったバージョン、タイポスクワッティング、インストールスクリプトや来歴の変化、完全性や解決処理の改ざんも対象です。これらのシグナルは、アドバイザリが存在する前に検知できます(事例を参照)。ツリー全体の CVE を洗い出し、修正の計画も立てたいなら、osv-scanner をお使いください。lockvet audit も対象は重複しており、すべてのアドバイザリの行に修正バージョンを示しますが、マニフェストを編集することはありません。Dependabot の PR をマージすべきか知りたいなら、lockvet です。
npqでは? npq は npm install の前に npm パッケージを監査します。経過期間、インストールスクリプト、タイポスクワッティング、来歴の後退、既知の脆弱性を調べます。対話型プロンプトやパッケージマネージャーのエイリアス機能も備えているため、普段使いの npm として利用できます。npm を使っているなら、普段使いに最適です。lockvet pkg は、約30のエコシステム(npm、PyPI、crates.io、RubyGems、Go、Maven、NuGet、Composer、Hex、pub.dev、JSR、CocoaPods、Terraform、CRAN、Helm、GitHub Actions、CircleCI orbs、ツールチェーンなど)で同種のインストール前チェックを実行します。ただし、これは単発のレポートであり、インストールを仲介するラッパーではありません。また、インストール前の検査は、差分と CI ゲートを主な役割とするツールのサブコマンドの一つです。
Socketでは? Socket は商用プラットフォームであり、重要な点で lockvet より踏み込んだ機能を備えています。パッケージのコードを静的解析し、悪意ある動作やリスクの高い機能を検出します。lockvet はパッケージのコードを読み取りません。ロックファイルのバイト列と公開レジストリのメタデータを、匿名で、アカウントもコードのアップロードも不要な状態で読み取ります。公開レジストリ以外のサービスにも依存しません。信頼のモデルは異なります。マネージドサービスを導入でき、コードレベルの解析を求めるなら、Socket は lockvet では検出できないものを見つけられます。どのフォージや CI でも、あるいはエアギャップ環境(-offline)でも実行できる、アカウント不要の静的バイナリが欲しいなら、lockvet です。
Dependabot / Renovateでは? これらはアップデーターであり、レビュアーではありません。PR を作成するのはこれらのツールで、対する側でレビューするのが lockvet です(lockvet queue <org> を使うと、ボットが作成したすべてのオープン PR を一度にトリアージできます)。互いを補完するように設計されています。
対象外
- コードアナライザーではありません。lockvet はパッケージの内容をダウンロードも検査もせず、ロックファイルとレジストリのメタデータだけを扱います。その役割は Socket のようなツールや人間によるレビューが担います。
- アップデーターではありません。Dependabot/Renovate が PR を作成し、lockvet はそれをマージすべきかを判断します。
- 対話型 TUI はありません(上記の whatsdiff を参照)。lockvet は、出力をそのまま PR のコメントに貼り付けられる単発のコマンドとして動作します。
ライセンス
MIT © Matteo Sung
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。