LiteLLM 1.107.0 が PyPI・Docker で改ざん — 認証情報窃取とバックドアの影響確認と対処手順
2026年10月21日、LiteLLM 1.107.0がPyPIとDockerで改ざんされ、認証情報窃取とバックドアが混入した。影響はwheelとコンテナで1.106.2は安全。Pythonを動かさずにできる確認コマンド、隔離から全認証情報ローテーションまでの手順、再発防止、3月の事件との違いを整理する。
2026年10月21日(UTC)、LLM ゲートウェイ/SDK の LiteLLM のバージョン 1.107.0 が PyPI と Docker(Docker Hub・GHCR)で改ざんされ、認証情報を盗むマルウェアとバックドアが混入した。対象は 1.107.0 の wheel またはコンテナイメージを導入・実行したすべての環境で、今すぐ litellm==1.107.0 と該当 Docker タグの使用有無を確認し、該当すれば Python を再実行せずに隔離して、到達可能だった認証情報をすべて失効・再発行する必要がある。
本記事は当初、2026年10月22日時点で公開されていた SafeDep、StepSecurity、JFrog、Endor Labs、Socket、Chainguard の調査記事をもとに作成した。その後、BerriAI(LiteLLM の開発元)が公式ブログに事後報告「Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised」(2026年10月21日公開、最終更新 同日 20:30 UTC)を出していることを確認したため、10月23日に、影響範囲・時系列・根本原因・推奨される対処を公式の記載に合わせて更新した。公式報告の状態は「封じ込め済み、調査継続中」で、侵入の一部は未確定とされている。公式に明記がない点は、引き続き「公式に明記なし」「情報源により差がある」と書き分ける。
何が起きたのか — 3行でわかる要点
- 何が: LiteLLM 1.107.0 の PyPI 配布物とコンテナイメージに、ソースコードリポジトリには存在しない悪意あるファイルが含まれていた
- どう動くか: Python を起動するたびに実行される仕組みで、litellm を import しなくても動く。SSH 鍵、クラウド認証情報、AI ツールのトークンなどを暗号化して外部へ送信する
- いま何をすべきか: 1.107.0 の使用有無を確認し、該当すれば隔離・再構築・認証情報の全ローテーションを行う。安全な最新版は 1.106.2 で、1.107.0 には戻らない
影響を受けるバージョンと安全なバージョン
影響範囲は、公式報告で確定した。公式が影響対象として挙げるのは、PyPI の wheel 7個すべてと、Docker Hub・GHCR の4つのタグ(v1.107.0、latest、main-latest、main-stable)である。sdist(ソース配布物)は公式の一覧に含まれていない。第三者の分析では、SafeDep が sdist も影響対象に含める一方、StepSecurity・JFrog・Endor Labs・Socket・Chainguard は「sdist にペイロードのファイルは入っていないが、ビルド設定に不正な1行が追加されている」と説明していた。実害が出るのは wheel とコンテナイメージなので、それらを優先して確認すればよい。
| 対象 | 状態 | 備考 |
|---|---|---|
PyPI litellm==1.107.0 の wheel 7個 | 悪意あり | 公式: 11:39:51〜13:23 UTC に公開。macOS(x86_64・arm64)、manylinux/musllinux(x86_64・aarch64)、Windows amd64 |
PyPI litellm==1.107.0 の sdist | 公式の影響一覧に記載なし | ペイロードのファイルは無いが、pyproject.toml に不正な設定が入っていると多数の第三者が報告。PyPI からは削除済み |
Docker Hub の v1.107.0 / latest / main-latest / main-stable | 悪意あり | 公式: 11:46:56〜14:20 UTC に不正なイメージ(index sha256:1bbcb144…)を指していた。現在は削除または正常なイメージへ差し替え済み |
GHCR の同じ4タグ(ghcr.io/berriai/litellm、litellm-database、litellm-non_root) | 悪意あり | 公式: 11:46:56 から、13:51 UTC までに取得できなくなるまで |
litellm==1.106.2 | 安全 | 公式が固定先として推奨(それ以前に公開された明示的なバージョンも可) |
1.107.0rc1 / 1.107.0rc2 | 安全 | 検査して正常を確認したと報告(StepSecurity、Chainguard) |
litellm-proxy-extras 0.4.110 / litellm-enterprise 0.1.78 | 安全 | 正常と報告(第三者) |
| エンタープライズ向けの非公開レジストリ | 影響なし | 公式の記載 |
公式報告によると、エンタープライズ顧客向けの非公開レジストリでは、10月27日に開示予定の脆弱性を修正した版を先行配布しており、こちらは今回の影響を受けていない。オープンソース版の利用者は、当面 1.106.2 に固定したうえで、10月27日前後に LiteLLM が出すセキュリティ情報を確認しておくとよい。
ここで重要なのは、GitHub 上の v1.107.0 タグのソースコードは正常だという点である。コードレビューやタグの差分確認では見つからず、PyPI と Docker に出た成果物だけが汚染されていた。「ソースを読んだから安全」という判断が通用しない典型例になっている。公式報告も、GitHub 上の v1.107.0 タグに悪意あるファイルは含まれず、汚染された成果物は攻撃者が管理するリポジトリからビルドされたと明記している。
時系列
時刻は2026年10月21日(UTC)。日本時間(JST)は +9 時間で、たとえば PyPI への公開 11:39 UTC は 20:39 JST にあたる。BerriAI の公式報告に記載がある出来事は公式の時刻を、記載がないものは第三者の分析を示す。
| 時刻(UTC) | 出来事 | 出典・補足 |
|---|---|---|
| 10月13日 | 偽ドメイン litellm.dev が登録される | StepSecurity。攻撃の準備は数日前から始まっていた |
| 10月19日(月) | 顧客向けの修正版を毎日出すため、社内エンジニア2人にリリース権限を一時的に拡大 | 公式(「月曜日」と記載) |
| 09:48〜10:46 | 偽リポジトリ Berri-AI/litellm(正規は BerriAI/litellm)に不正なコミットが入る | 第三者(StepSecurity は 08:56〜10:46 に10コミット、Endor Labs は 09:48 の最初のコミットと 10:46 の Dockerfile 変更)。公式にコミット時刻の記載なし |
| 10:08:29 | 非公開のリリース workflow への不正な変更を、ブランチ保護が拒否 | 公式 |
| 10:08:34 | 特権を持つ開発者アカウントの認証情報で保護が上書きされ、workflow のコミット 9cf97e9 が適用される | 公式(SafeDep の 10:08 の記載と一致) |
| 10:46:39 | 改変された workflow が、偽リポジトリのコミット 20535d8 を使って正式なステージング実行を開始 | 公式 |
| 11:08 | ステージングが完了し(11:08:09)、本番リリースのパイプラインが開始(11:08:53) | 公式 |
| 11:11:18 | 2人目の開発者アカウントの認証情報で本番の承認ゲートを通過 | 公式 |
| 11:39:51〜11:40:13 | 悪意ある wheel 7個が PyPI にアップロードされる | 公式 |
| 11:46:56 | コンテナの公開ジョブが実行され、悪意あるイメージが公開される | 公式(StepSecurity は 11:48 と記載していた) |
| 11:46〜11:55 | 各社のスキャナが検知 | 第三者(Socket は約7分後、Chainguard は約16分後) |
| 11:53:49〜11:54:01 | 3人目の開発者アカウントの認証情報で、本番とステージングの workflow の実行記録が削除される | 公式。調査に使えるログが減った |
| 12:06 | GitHub issue #47826 で侵害が報告される | issue の作成時刻 |
| 12:09:38〜12:24:52 | 不正な workflow の変更が取り除かれ、元の版に戻される | 公式 |
| 13:23 まで | PyPI が 1.107.0 を削除 | 公式 |
| 13:51 | GHCR の該当タグが取得できなくなったことを確認。Docker Hub はまだ不正なイメージを指していた | 公式 |
| 14:20 | Docker Hub の該当タグとイメージをすべて削除、または正常なイメージへ差し替え | 公式(StepSecurity は Docker イメージの削除を 14:32 と記載していた) |
| 20:30 | 公式報告を更新し、攻撃の経路を特定したと発表 | 公式。状態は「封じ込め済み、調査継続中」 |
PyPI での露出は約1時間45分(11:39〜13:23 UTC)、Docker Hub の浮動タグは約2時間半(11:46〜14:20 UTC)、GHCR は約2時間(11:46〜13:51 UTC)である。短時間に見えるが、LiteLLM は月間のダウンロード数が1億を超え、2,000を超えるパッケージが依存すると Chainguard は記載している。CI が毎回最新版を取得する構成や、latest タグでコンテナを再起動する運用では、この時間内に取り込まれていてもおかしくない。
攻撃の仕組み — import しなくても動く理由

攻撃の起点は、sdist の pyproject.toml の [tool.maturin] 設定に追加された data = "cook" という1行である。この設定により、cook/ ディレクトリの中身が wheel のインストール時に site-packages の直下へ配置される。結果として、次の3ファイルが litellm パッケージとは別に、Python の標準の探索パスに置かれる。
- cook.pth(12バイト): import cook の1行だけを含む。実体は cook.py の読み込み指示
- cook.py(RECORD 上で約22 KB): Base64+zlib で隠蔽された約61 KB の認証情報窃取プログラム(privcollect と呼ばれている)
- sitecustomize.py(25,509バイト): バックドア(リモートコード実行ローダー)と、予備の窃取コードを含む
鍵になるのは Python の起動時の仕様である。Python は起動時に site-packages 内の .pth ファイルを走査し、import で始まる行を実行する。さらに sitecustomize という名前のモジュールが存在すれば、それも自動で import する。つまり悪意あるコードは、litellm を import しなくても、その環境の Python プロセスが1つ起動するたびに実行される。pip の実行、IDE の言語サーバ、テストランナー、無関係なスクリプトも例外ではない。「LiteLLM はこのサーバでは使っていない」と思っていても、同じ仮想環境にインストールされていれば発火する。
なお、配布元の経路についても整理しておく。コンテナのビルド来歴には、正規の BerriAI/litellm ではなく、ハイフン入りの偽リポジトリ Berri-AI/litellm が所有者として記録されていた。StepSecurity によれば、攻撃者のコミットは GitHub の Web エディタ経由で作られたため「Verified」バッジが付いており、これだけでは作成者の正当性を示さない。偽リポジトリは現在 404 を返す。どうやって正規のリリース workflow が偽リポジトリを参照するようになったのかは、次の章で BerriAI の公式報告に沿って説明する。
侵入の経緯 — 公式報告が明かした根本原因
BerriAI の公式報告によると、LiteLLM は3月の事件の後、リリースを非公開リポジトリに移し、Trusted Publishers を使う決まった workflow でのみ公開する、Docker のタグを書き換え不可にする、イメージに署名する、といった形でリリース経路を強化していた。通常は、検証済みのコミットから成果物を作り、テスト環境で再検証したうえで、オフィスに置かれた特定のノート PC に紐づくアカウントだけが公開できる。今回の攻撃は、この複数の防御を次の手順で崩した。
- 権限の一時拡大: 直前の月曜日、顧客向けの修正版を毎日出すため、社内エンジニア2人にリリース権限を一時的に広げていた
- 開発者3人分の GitHub CLI 認証情報: 攻撃者は3人分の GitHub CLI の OAuth トークンを持っていた。うち1つは GitHub の workflow を実行でき、1つは GitHub 組織に2人いるオーナーの一方のものだった
- ブランチ保護の解除: リリース用 workflow が別のソースリポジトリを参照できるようにする変更は、通常の保護で拒否された。そこで攻撃者は、組織オーナーのトークンでブランチ保護を無効にし、変更を force push した
- 偽リポジトリからの正式リリース: 改変した workflow を偽リポジトリ Berri-AI/litellm に向けて実行し、正規の手順で成果物を署名・公開させた。さらに別の開発者の認証情報で、workflow の実行記録を削除した
トークンの入手経路について、BerriAI は次のように説明している。調査の結果、Claude Code が特定の状況でユーザーの GitHub CLI の OAuth トークンを応答本文に埋め込むことを確認したとし、応答本文をログに残す設定では、そのトークンが LiteLLM のログに記録されうるという。そして、インターネットに公開されていた社内の LiteLLM 環境に、無害そうな名前の見覚えのないユーザーアカウントが見つかった。攻撃者はこの環境に入り、ログから GitHub CLI のトークンを取得して、リリース workflow の侵害に使ったとみられている。
ただし、攻撃者が社内環境にどう入ったかは未確定である。BerriAI は「最近修正された脆弱性を突かれた」と疑っているが、確認はできていないとしている。公式報告の最終更新は10月21日 20:30 UTC で、続報を出すと予告しているが、本稿の更新時点(10月23日)で追加の更新は確認できていない。対応としては、PyPI からの削除に加え、調査が終わるまでメンテナの権限を必要最小限に絞ったと記載している。第三者の分析のうち、SafeDep の説明は公式報告とほぼ一致しており、Socket・Endor Labs・JFrog が「確立できていない」「不明」としていた点の大部分は、公式報告で説明された。
ペイロードの動作 — 窃取、送信、居座り
第1段階(窃取)では、cook.py が別スレッドで 16 の収集モジュールを動かす。多重実行を避けるため、環境変数 PRIVCOLLECT_ACTIVE=1 を設定する。収集した結果は JSON にまとめ、AES-256-CBC で暗号化し、その鍵を埋め込みの RSA-4096 公開鍵(OAEP)で包んだうえで、tar.gz にして http://www.litellm[.]dev/healthcheck へ HTTP POST する(X-Token ヘッダ付き、最大4回再試行)。このドメインは正規の litellm.ai に似せた偽物で、通信は暗号化されていない平文 HTTP である。
送信が HTTP 200 で成功すると、/tmp/.pc_once_72129118c59f5636c0bb というマーカーファイルを作り、その後は再実行しない。裏を返せば、マーカーが無いことは感染していない証拠にならない。送信に失敗した場合、コンテナを作り直した場合、そして Windows でパスが解決できない場合は、マーカーが残らず、Python が起動するたびに再び収集を試みる。
第2段階(居座り)では、sitecustomize.py が /tmp/remote_code_loader.lock でロックを取り、fork と setsid() でデーモン化して端末から切り離す(標準入出力は /dev/null、作業ディレクトリは /)。その後は1時間ごとに、GitHub 上の letters9813/pythonclient-test リポジトリの domain.txt からホスト名を読み、窃取コードを再実行し、http://<ホスト名>/codepy から Python コードを取得して 署名や整合性の検証なしに exec() する。つまり、攻撃者が任意のコードを後から送り込める経路になっている。
ただし現状の第2段階は、少なくとも観測時点では武装解除されていたと報告されている。domain.txt は空(0バイト)で、SafeDep は GitHub アカウント letters9813 が 404 になったことを確認し、攻撃者のサーバのアドレスは回収できなかったとしている。過去の値は localhost:18909 や test.test.com:18909 で、本番サーバだったとは断定されていない。注意点が2つある。1つは、ローダーを止めても第1段階の窃取(cook.pth 経由)は止まらないこと。もう1つは、StepSecurity が指摘するとおり、感染ホストは削除済みのアカウント名の URL を見続けるため、第三者が同名のアカウントとファイルを再作成すれば、感染ホストでコードを実行させられる可能性があることである。感染ホストを放置してよい理由にはならない。
プラットフォームにより挙動が違う。Windows では fcntl と os.fork が使えないため、バックドアのローダーは黙って失敗するが、窃取自体は動く。macOS では、Socket の分析によれば LiteLLM のデータベース収集モジュールが通常の環境で失敗し、Chromium 系のパスワード復号も平文では取れないが、ブラウザのデータベースファイル自体はコピーされ、ほかのモジュールは有効である。
盗まれる情報
収集対象は広く、開発者のワークステーション、CI ランナー、LiteLLM プロキシのサーバのいずれでも、その Python プロセスから読めたもの全部が対象になる。主な項目は次のとおり。
- 鍵類: SSH 秘密鍵、GPG 秘密鍵、TLS 秘密鍵、.pem/.key/.p12/.pfx ファイル
- クラウド・コンテナ: AWS、Azure、Google Cloud の認証情報、Kubernetes の設定とサービスアカウントトークン、Docker と Swarm のシークレット、GitHub CLI の認証情報
- クラウドのメタデータ: IMDS(169.254.169.254)やECS/GCP/Azure のメタデータ経由の一時認証情報。AWS キーは GetCallerIdentity で有効性も検証される
- CI/インフラ: GitLab Runner、Jenkins、Vault、Terraform、Packer の秘密情報、/run/secrets
- パッケージ・Git: .git-credentials、.npmrc、.pypirc、.netrc、.env ファイル
- AI ツール: Claude、Cursor、Copilot、Codex、Gemini、Windsurf、Kiro、Zed、Continue、Hugging Face などの設定とトークン(~/.claude/.credentials.json、~/.codex/auth.json など)
- ブラウザ: Chromium 系と Firefox のパスワード、Cookie、履歴
- 履歴・環境変数: シェル履歴、データベースのクライアント履歴、自プロセスと(Linux では)読めるすべてのプロセスの環境変数(/proc/<pid>/environ)
- LiteLLM プロキシ: DATABASE_URL が設定されていれば、LiteLLM_CredentialsTable と LiteLLM_ProxyModelTable を丸ごと取得する。ここには、プロキシ配下の全チームが使うLLM プロバイダの API キーが入っている
LiteLLM は複数の LLM プロバイダのキーを集約するゲートウェイとして使われることが多く、1台のプロキシが侵害されると、OpenAI、Anthropic、Google など配下のすべてのプロバイダのキーが一度に漏れる。AI ツールのトークンまで狙う点は、開発者個人のマシンで LiteLLM を使うケースでも、被害がクラウドや社内システムに波及しうることを意味する。AI エージェントを使った開発の運用については、Claude Code とエージェント SDK の解説も参考になる。
影響確認の手順
最初に守るべき原則は、疑わしい環境で Python や pip を実行しないことである。pip show や pip uninstall を含め、インタプリタを起動するたびに cook.pth が発火し、窃取が再試行されるためだ。Chainguard、JFrog、Endor Labs は、シェルコマンドなどインタプリタを使わない手段で調べることを求めている。次のコマンドはどれも Python を使わない。Linux/macOS で、可能なら調査専用のマシンから対象を読み取り専用でマウントして実行するのが望ましい。なお、公式報告は確認方法の1つとして pip show litellm を挙げているが、同じ報告の対処手順は「pip を含め、影響を受けた環境で Python を起動すると cook.pth と sitecustomize.py が再び動く」と注意している。疑わしい環境では、Python を使わない確認を優先するのが安全である。
# 1. 依存の記述に 1.107.0 が無いか(リポジトリのルートで)
grep -rnE 'litellm.{0,6}1\.107\.0' requirements*.txt pyproject.toml poetry.lock uv.lock Pipfile.lock 2>/dev/null
# 2. インストール済みのメタデータ(Python 不要)
find / -xdev -type d -name 'litellm-1.107.0*' 2>/dev/null
# 3. 悪意ある3ファイルの有無(site-packages 配下)
find / -xdev -type f \( -name cook.pth -o -name cook.py \) -path '*site-packages*' 2>/dev/null
find / -xdev -type f -name sitecustomize.py -path '*site-packages*' 2>/dev/null
# 4. 見つかったファイルをハッシュで照合(sitecustomize.py は正規の環境にも存在し得る)
sha256sum <見つかったファイル>
# cook.pth 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb
# cook.py 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25
# sitecustomize.py 34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa
# (Windows 版は改行コードの違いで別のハッシュ。各社の記事を参照)
# 5. 実行の痕跡(マーカー、ロック、一時ファイル)
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock /tmp/.pc_sq_* /tmp/.pc_e_* 2>/dev/null
# 6. バックドアの常駐(ロックを保持しているプロセス)
lsof /tmp/remote_code_loader.lock 2>/dev/null
ps -eo pid,ppid,sid,tty,args | awk '$2==1 && /python/'
# 7. 環境変数 PRIVCOLLECT_ACTIVE=1 を持つプロセス(Linux)
grep -l 'PRIVCOLLECT_ACTIVE=1' /proc/[0-9]*/environ 2>/dev/null
# 8. pip/uv のキャッシュに 1.107.0 が残っていないか
find ~/.cache/pip ~/.cache/uv -iname '*litellm-1.107.0*' 2>/dev/null
# 9. Docker:ローカルのイメージと稼働中コンテナのダイジェスト
docker images --digests | grep -i litellm
docker ps --format '{{.ID}} {{.Image}}' | grep -i litellm
docker inspect --format '{{.Image}} {{join .RepoDigests " "}}' <イメージ名>
# 悪意あるイメージのダイジェスト
# index: sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e
# amd64: sha256:bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5
# arm64: sha256:56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf
# 10. Kubernetes:稼働中の Pod が使うイメージID
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" "}{.status.containerStatuses[*].imageID}{"\n"}{end}' | grep -iE 'litellm|1bbcb144|bd1aeee4|56b74573'上のダイジェストは Chainguard と StepSecurity が公表したもので、index の値は公式報告とも一致する。GHCR の litellm-database と litellm-non_root は別のダイジェストで、公式報告は先頭部分だけを sha256:c00f9d92c427…(litellm-database)、sha256:7b00e91cd2fe…(litellm-non_root)と示している(完全な値は Chainguard の記事に一覧)。確認は完全なハッシュで行うこと。また、ネットワークログでは www.litellm.dev への通信、raw.githubusercontent.com/letters9813/ へのアクセス、/codepy で終わるパスへのリクエストを探す。/codepy へのリクエストが1件でもあれば、第2段階のコードが実際に実行された可能性があり、被害の判断が一段重くなる。GitHub 全体のブロックは業務への影響が大きいため、ブロックはこのリポジトリのパスに限定する。
マーカーが無くても安心はできない。前述のとおり、送信に失敗したケースやコンテナの作り直しではマーカーが残らない。公式報告も、マーカーがあれば送信は確定だが、無いことは何も証明しないとしている。1.107.0 を実際にインストール・実行した事実が確認できた時点で、感染として扱うのが安全である。CI のログとキャッシュは 10 月 21 日分を見直し、latest などの浮動タグでコンテナを起動する構成は、その日に取得したイメージのダイジェストを確認する。
感染していた場合の対処 — 隔離、再構築、全ローテーションの順
対処の順番が重要である。先に認証情報を回しても、感染環境が残っていれば新しい認証情報も盗まれる。次の順で進める。
1. 隔離する: 該当のホスト、コンテナ、CI ランナーをネットワークから切り離す。可能なら調査用にディスクやコンテナを保全し、DNS・プロキシ・EDR・クラウド監査ログを保存する
2. 常駐を止める: /tmp/remote_code_loader.lock を保持するプロセスや、親が PID 1 で作業ディレクトリが / の Python プロセスを終了させる
3. ファイルを手で消す: cook.pth、cook.py、対応する __pycache__、マーカー(/tmp/.pc_once_...)とロックを、シェルで削除する。pip uninstall は Python を起動して再発火させ、しかも RECORD に頼るため使わない。sitecustomize.py は正規の環境にも存在するので、中身かハッシュを確認してから削除する(ディストリビューション付属のものを消すと別の不具合を起こす)
4. 再構築する: 可能なら、その場の掃除ではなく、検証済みの成果物から環境、イメージ、ホストを作り直す。litellm==1.106.2(または検証したバージョン)を、後述のハッシュ固定で入れる。社内のミラーやプロキシ、レジストリにキャッシュされた 1.107.0 も消し、取得をブロックする
5. 認証情報を全部ローテーションする: 必ずきれいな別のマシンから行う。対象は後述の一覧のとおり
6. 悪用の痕跡を調べる: 10月21日(UTC)以降のクラウド、リポジトリ、レジストリ、ID 基盤の監査ログで、見覚えのない API 呼び出し、新しい鍵やトークンの作成、ロールの変更を探す
7. 通信を遮断する: litellm.dev(www.litellm.dev)を DNS かエグレスでブロックし、/codepy へのリクエストを検知対象に入れる
ローテーションの対象は、そのプロセスから読めた可能性のあるものすべてと考える。パッケージを消しても、すでに送信された情報は戻らない。最低限、次を見直す。
- クラウドの鍵・ロール(AWS、Azure、Google Cloud)、インスタンスロール、Kubernetes と Vault のトークン
- SSH、GPG、TLS の鍵(公開側の authorized_keys や証明書の失効・再発行も含む)
- GitHub、GitLab などの個人アクセストークン、デプロイキー、レジストリ(PyPI、npm、Docker)のトークン
- CI/CD のシークレット、.env の値、環境変数に置いていた値
- Claude、Cursor、Copilot、Codex などの AI ツールのトークンとセッション
- ブラウザに保存していたパスワードと、ログイン済みのセッション
- LiteLLM プロキシでは、マスターキー、ソルトキー、仮想キー、LiteLLM_CredentialsTable と LiteLLM_ProxyModelTable にある全プロバイダの API キー、DATABASE_URL のデータベース認証情報
なお、LiteLLM のプロキシで LITELLM_SALT_KEY を変更すると、データベースに暗号化して保存していた既存のキーが復号できなくなる点には注意が要る。変更の手順は LiteLLM のドキュメントで確認し、キーの再登録の手間を見込んでおくこと。
公式報告が特に挙げるローテーション対象は、インスタンス/タスクの IAM ロール、LiteLLM プロキシのデータベースに保存したすべてのプロバイダキーとマスターキー・仮想キー、DATABASE_URL のパスワード、そのホスト上のプロセスの環境変数にあったすべての秘密情報である。あわせて AWS では、CloudTrail で、インストール時刻の前後にインスタンスロールから GetCallerIdentity が呼ばれていないか、その後に自社のインフラの外からロールの認証情報が使われていないかを確認するよう勧めている。
再発防止 — 今日からできる固定と遅延
今回の件で重要なのは、ソースが正しくても配布物が汚染されうる、そして一度のインストールが全認証情報の漏えいにつながる、という2点である。組織側でできる対策を、効果の大きい順に並べる。
- バージョンとハッシュを固定する: litellm>=1.100 のような範囲指定をやめ、== で固定したうえで、ハッシュ付きでインストールする。pip-compile --generate-hashes で要件ファイルを作り、pip install --require-hashes -r requirements.txt で入れる。uv なら uv.lock をコミットして uv sync --frozen を使う。ハッシュ固定なら、同じバージョン番号で中身が差し替わっても検知できる(ただし、今回のように新しいバージョンが出た後に更新する時は、別途の確認が要る)
- Docker は、タグではなくダイジェストで固定する: latest や main-stable などの浮動タグは、今回のように中身が変わる。image: ghcr.io/berriai/litellm@sha256:<確認済みのダイジェスト> の形で参照し、更新は人が確認してから行う
- 新しいバージョンの採用を数日遅らせる: 今回、悪意ある版が公開されてから削除されるまでは約1時間45分だった。公開から3〜7日待つ運用にすれば、ほとんどの検知と削除が間に合う。uv の exclude-newer や、Renovate の minimumReleaseAge、Dependabot の cooldown が使える
- CI のエグレスを絞る: ビルドとテストのジョブから、許可リスト以外への外向き通信を遮断する。今回の窃取は www.litellm.dev への HTTP POST なので、許可リスト方式なら送信が失敗する。StepSecurity の Harden-Runner のようなツールで、通信のベースラインを取る方法もある
- CI の秘密情報を最小にする: テストやビルドの環境に、クラウドの本番鍵や公開用トークンを置かない。OIDC による短寿命のトークンに切り替え、シークレットは必要なジョブにだけ渡す
- 起動フックを監視する: 第三者の wheel に新しい .pth や sitecustomize.py が追加された時にアラートを出す。Chainguard は、ビルド設定([tool.maturin]、[tool.setuptools])の data 項目の追加も検知対象に挙げている
- 開発者の端末と本番を分ける: LiteLLM プロキシは、AI ツールのトークンや個人の SSH 鍵があるマシンと別の、最小権限の環境で動かす
- LLM ゲートウェイのログに秘密情報を残さない: 今回の入口は、社内の LiteLLM のログに記録された GitHub CLI のトークンだったと BerriAI は説明している。リクエストや応答の本文をログに残す設定は必要な時だけにし、残す場合は秘密情報をマスクする。管理画面や API をインターネットに直接公開せず、見覚えのないユーザーアカウントがないか定期的に点検する
- メンテナ側の対策(自分でパッケージを公開する場合): PyPI の Trusted Publishing(OIDC)で長期トークンをなくし、公開できるリポジトリとワークフローを固定する。リリース用ワークフローは、承認済みのリポジトリと、レビュー済みのコミットだけから動くよう制限する。公開物と、レビューしたソースの差分を照合する。ただし今回のように、署名と Trusted Publishing を入れていても、workflow そのものを乗っ取られると正規の署名付きで公開される。組織オーナーは最小人数にし、ブランチ保護の変更、force push、workflow の実行記録の削除を即時に通知する設定にする。権限を一時的に広げる場合は、期限を決めて記録を残す
依存関係の更新を遅らせる運用は、一方で脆弱性の修正が遅れるという代償を伴う。同じ時期の PHP の認証不要 RCE のように、すぐ当てるべき修正もある。重大な脆弱性の修正は例外として扱い、通常の更新は遅延、緊急の修正はすぐ適用という2系統のルールにしておくとよい。AI 関連の基盤をまとめて見直す視点としては、Stripe による OpenRouter 買収の解説が扱う、LLM ゲートウェイを巡る動向も参考になる。
3月の事件との比較
LiteLLM が2026年に侵害されるのは今回が2度目で、JFrog の記事の見出しは「Hijacked Again」である。3月には、バージョン 1.82.7 と 1.82.8 が改ざんされた。JFrog はこの2つを別のキャンペーンとして扱い、それぞれの IoC で調べるべきだとしている。BerriAI の公式報告は、3月の事件の後にリリース経路を強化したと述べるだけで、2つの事件の攻撃者の関連には触れていない。
| 項目 | 2026年3月(1.82.7 / 1.82.8) | 2026年10月(1.107.0) |
|---|---|---|
| 侵入経路 | CI で使われていた固定されていない悪意ある Trivy 0.69.4 から公開用の認証情報が漏れ、それで公開された(JFrog) | 社内の LiteLLM 環境のログから漏れた開発者3人分の GitHub CLI トークンでブランチ保護を外し、リリース workflow を偽リポジトリ Berri-AI/litellm に向けた(公式)。社内環境への侵入方法は未確定 |
| ソースコード | 検証できるソースが無かった(Chainguard) | GitHub のタグとソースは正常。悪意は wheel とイメージにのみ存在 |
| 起動の仕組み | 3月の件とは別(JFrog) | cook.pth と sitecustomize.py による Python 起動フック |
| 居座り | バックドア、Kubernetes ノードへの拡散(JFrog) | 1時間ごとのリモートコードローダー(ドロップ先は GitHub の domain.txt) |
| 送信先 | models.litellm.cloud | www.litellm.dev |
| 攻撃者 | TeamPCP とされる | 関連を示す証拠は現時点でなし(JFrog、Chainguard)。公式報告も関連に触れていない |
| 配布先 | PyPI | PyPI+Docker Hub+GHCR |
| 事件後の対策(公式) | 非公開リポジトリでのリリース、Trusted Publishers、書き換え不可のタグ、イメージ署名を導入 | 調査が終わるまでメンテナの権限を必要最小限に縮小 |
共通する教訓は、人気のあるオープンソースの AI 基盤が、攻撃者にとって認証情報の宝庫になっていることだ。3月の後にリリース経路を強化し、署名まで入れていても、今回は開発者のトークンを足がかりに正規の手順そのものを乗っ取られ、署名付きの不正な版が同じ配布先に届いた。利用者側は、配布元が安全だという前提を置かず、固定・遅延・隔離で被害を小さくする設計が必要になる。同様に、OS 側の重大な脆弱性への備えとしては、Linux カーネル Copy Fail の緩和策で整理した、パッチ適用と攻撃面の削減の考え方が参考になる。
よくある質問
litellm を使っていないサーバでも影響はありますか。
同じ Python 環境(仮想環境やコンテナ)に 1.107.0 がインストールされていれば、litellm を import していなくても、その環境で Python が起動するたびに悪意あるコードが動く。逆に、1.107.0 が一度も入っていない環境や、1.106.2 以前だけを使っている環境は、報告されている範囲では影響を受けない。依存関係として間接的に入っている場合もあるため、lockfile と site-packages の両方を確認すること。
根本原因は何だったのですか。
BerriAI の公式報告によると、攻撃者は開発者3人分の GitHub CLI の OAuth トークンを使い、GitHub 組織のオーナーの権限でブランチ保護を外して、リリース用の workflow を偽リポジトリ Berri-AI/litellm に向けた。トークンは、インターネットに公開されていた社内の LiteLLM 環境のログから取得されたとみられている。ただし、攻撃者がその社内環境にどう入ったかは、2026年10月23日時点で確認されていない。
1.107.0 を入れたが、すぐ削除しました。それでも安全ではないのですか。
安全とは言えない。インストール後に Python が1回でも起動していれば、その時点で窃取が実行された可能性がある。pip でのアンインストールそのものが Python の起動を伴うため、削除作業の中で発火したおそれもある。インストール期間の長さではなく、実行の有無で判断し、実行された可能性が否定できなければ認証情報をローテーションする。
マーカーファイルが無ければ、窃取は成功していないと考えてよいですか。
そうとは言えない。マーカーは、送信が HTTP 200 で成功した後にだけ作られる。送信が失敗した場合、コンテナを作り直した場合、Windows でパスが解決できない場合は、窃取が実行されていてもマーカーが残らない。無いことは感染していない証拠にならず、1.107.0 を実行した事実のほうを基準にする。
PyPI や Docker から削除されたので、もう新しく被害が出ることはありませんか。
新規にインストールされる危険は減ったが、すでに取得した環境は影響を受け続ける。社内のミラーやプロキシ、CI のキャッシュ、コンテナレジストリに 1.107.0 が残っていれば、そこから再び入る可能性がある。また、感染済みのホストでは窃取コードが動き続け、バックドアのローダーは1時間ごとに外部を参照する。現時点ではローダーの参照先が空と報告されているが、感染ホストを放置してよい理由にはならない。
sitecustomize.py が見つかりました。すぐ削除してよいですか。
すぐに削除してはいけない。sitecustomize.py は、ディストリビューションや社内の設定が正規に置いている環境もある。まず中身を確認するか、SHA-256 を公表されている悪意あるハッシュと照合する。site-packages の中で cook.pth と同時に見つかり、内容がハッシュと一致するなら悪意あるファイルである。一致しないものは正規の可能性が高い。
今後、同じ種類の攻撃を防ぐ最も効果的な方法は何ですか。
単独の特効薬はなく、組み合わせになる。優先度が高いのは、依存をバージョンとハッシュで固定すること、Docker をダイジェストで固定すること、新しい版の採用を数日遅らせること、CI の外向き通信を許可リスト方式にすることの4つ。これに加えて、本番の鍵を開発者端末や CI に置かない設計にすると、万一の侵害でも被害が限定される。
この記事に関連する無料ツール(登録不要・その場で結果)
お気軽にご相談ください
お問い合わせ