Pythonの人気パッケージに、見た目では気づきにくい悪意あるコードが混ざった——そんな話です。SafeDepは、PyPIに載ったLiteLLM 1.107.0が、資格情報を盗み出すコードと遠隔操作用の裏口を含んでいたと報告しました。しかも、それらは litellm を直接 import しなくても、Pythonが起動するたびに動く仕組みでした。ライブラリの中身だけでなく、配布経路そのものを疑わないといけない局面に来ている、という意味で重い事例です。
SafeDepの説明によると、PyPIに公開されたLiteLLM 1.107.0には、Windows、macOS、Linux向けの7つのwheelすべてに悪意ある3つのファイルが入っていました。ひとつは .pth ファイルで、Pythonの起動時に自動実行される仕組みを使って別のPythonコードを読み込みます。ここで動く cook.py は資格情報の窃取役で、SSH鍵、クラウド認証情報、CI/CDの秘密情報、AIツールのトークン、ブラウザの保存パスワード、さらにLiteLLM proxyのデータベースに入ったLLMプロバイダの鍵まで収集するとされています。集めたデータは暗号化され、www.litellm[.]dev に送信されます。
もうひとつは sitecustomize.py です。これもPythonの起動時に自動で読まれるため、アプリが litellm を使っていなくても、ホスト上のPythonプロセスなら何でも巻き込みます。SafeDepによると、このファイルはバックグラウンドのデーモンを立ち上げ、1時間ごとにGitHub上のファイルからサーバーアドレスを読み取り、改めて情報窃取を走らせたうえで、そのサーバーからコードをダウンロードして exec() で実行します。つまり、単なる盗み見ではなく、あとから任意コードを流し込める裏口です。
侵入経路についてもかなり具体的に分析されています。1.107.0のsdist内の pyproject.toml には、maturin のビルド設定に data = "cook" という1行が追加されていました。これにより cook/ フォルダがwheelへ入り、pipで site-packages に配置されます。しかもsdistには cook/ 自体が含まれていないため、見た目上は差分が小さい。公開されたGitHub上のソースにはこの設定もフォルダも見当たらず、PyPIの1.107.0は上流のソースと一致しませんでした。
SafeDepはタグ v1.107.0 が指すコミットと、sdistの中身も照合しています。その結果、sdistは別のmainブランチのコミットにかなり近い一方で、いくつかのファイルが編集され、そこに cook/ が足されていたとみられます。さらに、1.107.0は litellm-proxy-extras==0.4.110 と litellm-enterprise==0.1.78 を参照していましたが、これはrc2と同じで、依存関係の差分では異常が目立たないようになっていました。PyPI側ではこのリリースは削除済みです。
ビルドの痕跡も興味深い点です。wheelの RECORD は整合しており、maturin 1.9.4 で作られた形跡も残っていました。Windows版では2,804個の .py にCRLF改行が入っており、Linux版にはそれがない。GitのチェックアウトがWindowsで改行変換されたようなパターンだとSafeDepは述べています。加えて、公開GitHub Actionsの実行ログからは、このリリースを作った形跡が見つからなかったともしています。最終的にSafeDepは、BerriAIの私設リリースパイプラインが、攻撃者に改変されたgitソースからwheelを作った可能性が高い、とみています。ただし、これはあくまで推定で、原因の確定にはBerriAI側のパイプラインログが必要だと明言しています。
この件でまず引っかかるのは、「importしなければ安全」という感覚が完全に崩れていることです。通常、Pythonのサプライチェーン攻撃は、悪意あるパッケージを入れた瞬間に何かが起きる、と考えがちです。でも今回は、.pth や sitecustomize.py のような、Pythonの起動経路そのものに乗るファイルが使われています。アプリがLiteLLMを直接触らなくても、同じ環境でPythonが立ち上がれば巻き込まれる。IDEの言語サーバー、pip、CIジョブまで対象になるのはかなり厄介です。私はここに、この手口の嫌らしさがあると思います。被害が「そのライブラリを使ったアプリ」に閉じないからです。
もうひとつ気になるのは、盗まれる情報の幅の広さです。SSH鍵やクラウド認証情報だけでなく、ブラウザの保存パスワード、各種CLIの認証ファイル、KubernetesやDockerの設定、さらにはAI開発ツールのトークンまで含まれています。つまり、攻撃者が欲しいのは単一サービスのログイン情報ではなく、開発者の作業環境に散らばった“再利用可能な鍵”です。今の開発現場では、AIツールも含めて認証情報がいろいろな場所に散っているので、一度抜かれると連鎖的に広がりやすい。単なる漏えいではなく、開発者の作業空間そのものを丸ごと奪う設計だと見るべきでしょう。
SafeDepの分析でさらに重いのは、公開GitHubとPyPIの中身が一致しなかった点です。タグ、sdist、wheelの関係がずれ、しかも見た目の差分が小さい。依存関係のバージョンもrc2と似せてあり、表面的なレビューでは見抜きにくい。これは、個々のコード行を眺めるだけでは防げないタイプの侵害だと思います。問題は「悪いコードがある」ことより、「正規のリリース工程に混ざった結果として悪いコードが正規品の顔をして配られる」ことにあります。
こうなると、守る側はソースコードレビューだけで安心できません。公開リポジトリ、ビルド環境、PyPIへの公開権限、秘密鍵、Botの権限、リリース自動化の経路まで全部が監査対象になります。しかも、今回のようにwheelの生成後に改ざんされたのでなく、最初から汚染されたソースからビルドされたように見えるなら、署名やハッシュだけでも追い切れない。私は、これからの依存管理は「このパッケージを入れてよいか」だけでは足りず、「このリリースが、どの経路で、誰の権限で、どのソースから作られたか」を追えるかどうかに移っていくと思います。
この事件はLiteLLMの利用者向けの注意喚起で終わる話ではありません。Pythonエコシステム全体にとって、起動時フックを悪用した攻撃が現実に成立し、しかも開発者が日常的に使う環境まで横断して広がることを示しました。特に、クラウド資格情報やAI開発ツールの認証情報が同じ端末に並ぶ今の環境では、ひとつの感染が複数のサービスへ波及しやすい。被害はアプリ本体より、むしろ開発端末やCI、秘密情報の保管場所に出るのではないかと思います。
対策としては、特定のライブラリを疑うだけでは足りません。パッケージの provenance、リリースパイプラインの権限分離、.pth や sitecustomize.py のような自動実行ファイルの監視、そしてPyPIから入ったものをそのまま開発機に広げない運用が必要になります。地味ですが、こういうところに手を入れないと、次も似た形で来るでしょう。今回の件は、Pythonの便利さがそのまま攻撃面にもなっていることを、かなり露骨に見せた例だと受け止めています。
参考: Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor