「最新版を即インストール」は危険:Python開発者が見落とす「クールダウン」という発想

Pythonの依存関係クールダウンという考え方:サプライチェーン攻撃から身を守る小さな工夫 プログラミング

PyPIやnpmへのサプライチェーン攻撃のニュースが、ここ最近やけに目立ちます。
Reddit の r/Python でも、関連する話題が活発に議論されていました。

その中で「依存関係クールダウン(dependency cooldown)」という考え方が紹介されていたので、本記事で整理してみます。
シンプルなのに効果が大きい、コスパのよい対策です。

1. 依存関係クールダウンとは何か

考え方はとても単純です。
リリース直後の新しいバージョンには手を出さない、というルールを設けます。

そして一定期間、たとえば1週間や10日ほど寝かせてからインストールします。
「公開されたばかりのパッケージは使わない」と決めておく、と言い換えてもよいでしょう。

なぜこれが効くのか

悪意のあるパッケージは、コミュニティやセキュリティ研究者によって早期に発見されることが多いからです。
通報までの時間は、数時間から数日というケースが目立ちます。

つまり自分が踏む前に、地雷が掃除されている確率が高くなるわけです。
シンプルな時間差作戦と言えます。

2. 「みんなが使えば意味がなくなる」という誤解

議論の中で目立っていたのが、ある懸念の声でした。

全員がクールダウンを設定したら、誰も悪いパッケージを引かなくなる。
結局、誰にも検出されなくなるのでは?

一見もっともらしい話です。
ただし、これは検出のモデルを取り違えた意見だと指摘するコメントがありました。

検出を担っているのは「一般ユーザー」ではない

新しい悪意あるリリースを実際に検出しているのは、たまたまインストールしてしまった一般ユーザーではありません。
専門のセキュリティ研究者や、自動スキャナです。

彼らは公開直後の人気パッケージを能動的に取得します。
そして不審な挙動を監視しています。たとえば次のようなものが対象です。

  • 妙な外部通信
  • ハニーポット用のクラウドトークンの利用
  • 想定外のファイルアクセス

OSVやGitHubアドバイザリーデータベースへの登録、PyPI管理者によるテイクダウンといった仕組みもあります。
これらはユーザーがインストールするかどうかとは無関係に回り続けます。
だから利用者側がクールダウンを入れても、検出スピードは落ちません。

クールダウンはあくまで「自分が踏むまでの時間を稼ぐ」ための工夫です。
検出装置そのものではない、と理解しておくとスッキリします。

3. uv と poetry での設定例

uvとpoetryは、すでにクールダウン期間の指定をネイティブにサポートしています。

uv の設定

uvの場合は、設定ファイルに次のように書くだけです。

[tool.uv]
exclude-newer = "10 days"

これだけで、10日以内に公開された新しいバージョンは選ばれなくなります。

pip の対応

pipについても、新しいバージョンで同様のオプションが追加される方向のようです。
コメント欄では26.1以降に言及がありました。
使い方は次のように、ISO 8601形式の期間で指定する形になります。

pip install --uploaded-prior-to P7D -r requirements.txt

設定方法はいくつかあるそうです。

  • CLI から直接渡す: –uploaded-prior-to P7D
  • 環境変数で指定する: PIP_UPLOADED_PRIOR_TO=P7D
  • グローバル設定: pip config set global.uploaded-prior-to P7D

いずれの方法も可能だと紹介されていました。

4. 社内パッケージはどうする?

社内のPyPIから自前のライブラリを使っているのに、10日も待たされるのは困る

そんな声もありました。
現実的な悩みです。
ただ、これにはちゃんと回避策があります。

特定パッケージだけ除外する

uvでは、特定のパッケージだけクールダウン対象から外す設定が可能です。

exclude-newer-package = { my-package = false }

ビルド時コード実行のリスクにも対策を

さらに、悪意あるコードがビルド時に実行されるリスクへの対策も併せて紹介されていました。

no-build = true
no-binary-package = ["your_internal_package_name"]

前者は「ローカルでビルドコマンドを走らせない」という設定です。
つまりインストール時に、得体の知れないPythonコードを起動させないようにします。

後者は、自前パッケージだけはこのルールから除外するためのものです。
これで外部パッケージは慎重に、内部パッケージはスピーディに、という使い分けができます。

内部プロキシで強制する手も

なお、内部プロキシを運用しているチームには別の選択肢もあります。
プロキシ側でクールダウンを強制してしまう方法です。
開発者ごとの設定漏れを防げる、という点では有力でしょう。

5. クールダウンだけでは足りない理由

クールダウンは銀の弾丸ではありません。
その指摘も議論の中で非常に重要でした。

静かに長期間仕込まれる攻撃には無力

特に説得力があったのが、xz-utilsのような「静かな」攻撃の話です。
長期間にわたって仕込まれるタイプの攻撃には、クールダウンだけでは対応できません。

数時間で発覚するような派手な攻撃なら、時間差で防げます。
しかし何ヶ月も水面下で進む工作には、別の防御層が必要です。

多層防御という発想

そこで重要になるのが多層防御です。
コメント欄で挙がっていた組み合わせを整理すると、以下のようになります。

対策役割
バージョン固定(pinning)依存ライブラリのバージョンを明示的に固定し、勝手に最新版へ追従しないようにする
ハッシュピン留めロックファイルや --hash 付き requirements.txt を使い、中身がすり替わっていたら失敗させる
ビルド時コード実行の抑止no-build などで、インストール過程での任意コード実行を避ける
脆弱性スキャナの併用Dependabotのようなツールで、既知の脆弱性が出たときに個別に更新する

それぞれの役割を整理しておきましょう。

  • クールダウンは「公開された悪意あるパッケージが検出されるまでの時間を稼ぐ」役割
  • ハッシュピン留めは「あとからアーティファクトがすり替わっても気付ける」役割

カバーする攻撃シナリオが違うので、両方入れるのが現実解と言えます。

6. それでも残るリスク

ここまでの対策をすべて入れても、リスクはゼロにはなりません。
最近のサプライチェーン攻撃には、いくつか目立つ経路があります。

  • メンテナーのアカウントが乗っ取られる
  • CI/CDのキャッシュが汚染される
  • ソーシャルエンジニアリングで権限を握られる

正規のリリースフローに沿って公開された「正規バージョン」に、すでに悪意あるコードが混ざっているケースもあります。
この場合、クールダウンとハッシュピン留めだけでは検知しきれません。

だからこそ、依存ライブラリを増やすときの「本当に必要か」という問いが効いてきます。
地味な習慣ですが、ツール設定では補えない部分です。
新しいパッケージを安易に追加しない、という発想は意外と大切でしょう。

まとめ

依存関係クールダウンは、設定一行で導入できる対策です。
それでいて効果は小さくありません。

ポイントの振り返り

  1. 効果
    新しいバージョンを数日から10日ほど寝かせてから使う。
    これだけで、smash and grab 型のサプライチェーン攻撃に対する耐性がぐっと上がります。
  2. よくある反論への答え
    「みんながやれば意味がなくなる」という直感的な反論もあります。
    しかし検出を担っているのは一般ユーザーではなく、セキュリティ研究者と自動スキャナです。
    その前提を踏まえると、この反論は成立しません。
  3. ツール対応状況
    uv、poetry、pipのいずれも、設定方法が用意されています。
    特定パッケージだけ除外することもできるので、社内ライブラリも問題なく扱えます。
  4. 単体での限界
    ただしクールダウン単体では限界もあります。
    xz-utils型の長期潜伏攻撃や、メンテナーアカウント乗っ取りには無力です。
    そのため、バージョン固定・ハッシュピン留め・ビルド時コード実行の抑止・脆弱性スキャナと組み合わせるのが現実解になります。
  5. 運用面の判断
    最終的には「使うライブラリを増やしすぎない」という運用面の判断も効いてきます。

完璧なセキュリティはあり得ません。
だからこそ、コストの低いものから順に積み上げていく姿勢が大切でしょう。

「今日から1行追加するだけでできる対策」として、依存関係クールダウンはまず試す価値があると感じました。

タイトルとURLをコピーしました