Pythonの10月のセキュリティ:certifiの更新が必要な理由と、PyPIの配布物が差し替えられた件、pip環境の確認手順

Pythonの10月のセキュリティ:certifiの更新が必要な理由と、PyPIの配布物が差し替えられた件、pip環境の確認手順

2026年10月のPythonの世界では、性質のちがう2つの出来事がほぼ同じ週に重なりました。

  • ひとつは、インドの認証局eMudhra(イーマドラ)の中間証明書の秘密の値が計算で割り出せる状態だったと分かり、Pythonで広く使われている証明書の束「certifi(サーティファイ)」が10月18日から20日にかけて立て続けに更新されたことです。
  • もうひとつは、AIのアプリづくりで定番の部品LiteLLMの1.107.0という版に、情報を盗むプログラムが仕込まれてPyPI(Pythonの公式の部品配布所)から配られたことです。

前者は「古い部品を使い続けると危ない」話で、後者は「新しい部品にすぐ飛びつくと危ない」話です。一見すると正反対ですが、どちらも「自分のプログラムが何に依存していて、それがいつ、どこから、どんな中身で入ってきたのか」を把握できているかどうか、という同じ問いに行き着きます。

LiteLLMの事件の流れそのものは、関連記事の「”Black October”の10日間」で扱いました。この記事では事件の追いかけではなく、Pythonの仕組みの側から、証明書の束の扱い、Pythonが起動するたびに自動で読み込むファイル、PyPIの配布の仕組み、依存の固定のしかたを順に掘り下げます。

あわせて、10月に出たCPython本体やpip、Django、Pillowなどのセキュリティ更新もまとめます。

先に結論を書いておくと、次の5つです。

この記事の要点
  • certifiは2026.10.18でemSign系のルート証明書をすべて外し、2026.10.19で影響のないものを戻しました。requestsやhttpx、pipは基本的にこの束を使うため、certifiの版を古いまま固定している環境や、Dockerイメージに焼き込んだままの環境は、eMudhra問題への対応が入っていません。開発機を更新しても本番のイメージは作り直さないと直りません。
  • pip 26.2.2(10月20日)とuv 0.13.4(同日)も、それぞれ証明書の束を更新しました。CPythonは10月21日に3.15.1・3.14.9・3.13.17を前倒しで出し、同梱のpipを26.2.2に上げています。既存の仮想環境の中のpipやuvは自動では新しくならないので、自分で更新する必要があります。
  • LiteLLM 1.107.0の悪意あるプログラムは、Pythonが起動するたびに自動で実行される「.pthファイル」と「sitecustomize.py」を使っていました。部品を消しても残り、pipを動かすだけで再び動くため、対処はシェルからファイルを消すところから始める必要があります。
  • 公式のソースコードは無事で、配布物(wheel)にだけ悪意あるファイルが入っていました。GitHubのソースを読んで安心するやり方では見抜けず、「配布物そのもの」を確かめる手段(ハッシュの固定、出所の記録の確認、入れる前の中身の確認)が必要になります。
  • 守り方の基本は、ハッシュ付きのロックファイルで版と中身を固定する、新しい版は数日待ってから入れる、ただしセキュリティ更新は待たずに当てる、の3つの組み合わせです。固定と更新は対立するものではなく、「何を入れたかを記録し、意図して更新する」ための同じ作業です。

10月のPython周りで起きたことを一覧にする

夜の机に置かれたノートPCとマグカップ

まず、この記事で扱う出来事を日付の順に並べておきます。日付は、各プロジェクトの公式の発表やリリースの日付です。PyPIに版が載った日と、GitHubで変更が入った日が1日ずれているものがあるので、そうした場合は両方を書いています。

日付対象出来事この記事での扱い
10月1日前後CPython3.14.8・3.13.16・3.11.17・3.10.22(3.10系の最後の版)を公開。3.12.15は9月30日定例の保守・セキュリティ更新
10月6日Django6.1.2・6.0.9・5.2.18で4件のCVEを修正Webアプリを作る人向け
10月8日Djangoセキュリティの報告をHackerOneからメールでの受付に変更報告の窓口の変更
10月16日Pillow13.0.0で多数のセキュリティ修正(深刻度Criticalを含む)画像を扱う人向け
10月18日certifi2026.10.18でemSign系のルート証明書をすべて削除eMudhra問題への最初の対応
10月20日certifi2026.10.19で影響のないemSignのルートを戻す(PyPIへの公開は20日)推奨される最低の版
10月20日pip・uvpip 26.2.2が同梱のcertifiを更新。uv 0.13.4が同梱の証明書の束からemSignのルートを削除道具そのものの証明書
10月21日CPython3.15.1・3.14.9・3.13.17を前倒しで公開。同梱のpipを26.2.2にインストーラーごと更新
10月21日LiteLLM悪意あるプログラム入りの1.107.0がPyPIとDockerで配られるPythonの仕組みの側から解説
10月22日Brotli(PyPI版)Node.jsと同じBrotliの展開処理の欠陥が、PyPIのbrotli・brotlicffiにも及ぶと公表HTTPの圧縮を扱う人向け
10月23日certifi・LiteLLMcertifi 2026.10.23を公開。LiteLLMが攻撃の経路をまとめた正式な記録を公開最新の状況

表を眺めると、10月18日から23日までの1週間に、証明書の束、パッケージを入れる道具、Python本体、そして人気の部品の汚染が重なっていることが分かります。それぞれは別の出来事ですが、Pythonで何かを動かしている人にとっては、「部品を新しくする」「部品の中身を疑う」という2つの作業を同じ週にこなす必要があったことになります。

なお、eMudhraの事件そのもの(何が起き、ブラウザがどう守られたのか、GoやTailscale、OSの証明書ストアがどう対応したのか)は、関連記事の「eMudhra CA鍵の事件そのもの」で詳しく扱っています。また、Node.jsとJavaの対応もそれぞれ別の関連記事にまとめました。この記事ではPythonに関係する部分だけを取り上げます。

certifiとは何か:eMudhra問題でなぜ更新が必要だったのか

データセンターに並ぶサーバーラックと状態ランプ

Pythonのプログラムが「https://」で始まるサイトにつなぐとき、相手が本物かどうかを確かめるのに使うのが「証明書」です。サイトの証明書は、信頼できる発行元である「認証局(CA)」が署名しています。

プログラムの側は、どの認証局を信頼するかを記した一覧(ルート証明書の集まり)を持っていて、相手の証明書がその一覧のどれかにたどり着ければ「本物」と判断します。この一覧を「信頼ストア」や「証明書ストア」と呼びます。

certifiは、この信頼ストアをPythonの部品として配っているものです。中身は、Firefoxを作っているMozillaが管理しているルート証明書の一覧を、1つのファイル(cacert.pem)にまとめたものです。certifiを入れると、certifi.where()という関数でそのファイルの場所が分かり、通信の部品はそこにある証明書を使って相手を確かめます。

requestsやpipは、OSの証明書ではなくcertifiを使う

ここがPythonの少しややこしいところです。WindowsやmacOS、Linuxには、OSが管理している証明書ストアがそれぞれあり、OSのアップデートで中身が更新されます。ブラウザやOSの機能はこちらを使います。

一方で、Pythonで最もよく使われているHTTPの部品であるrequestsは、標準ではOSのストアではなくcertifiの束を使います。httpxも同じです。つまり、OSを最新にしていても、Pythonの環境に入っているcertifiが古ければ、requestsはその古い一覧で相手を判断し続けます。

pipについては、少し事情が違います。pipの公式ドキュメントによると、pip 24.2からはtruststore(OSの証明書ストアをPythonから使えるようにする部品)を使って、OSの証明書を「certifiに加えて」使うのが標準になりました。

ただし、pipの中には自分専用のcertifiも同梱されていて、こちらも同時に使われます。どちらかに問題のある証明書が残っていれば、それをたどった接続は通ってしまう可能性があるため、pipそのものの更新も必要になります。

図:どの証明書を使うかの違い
OSの証明書ストア
  • ブラウザやOSの機能
  • urllib.requestやssl(OSやOpenSSLの設定に従う)
certifiの束
  • requests(標準)
  • httpx
pip
  • OSの証明書に加えて使う
  • 自分専用のcertifiも同梱

Python標準のurllib.requestやsslモジュールを直接使う場合は、基本的にOSや、Pythonが組み込んでいるOpenSSLの設定に従います。次の2点が例外や注意点です。

  • python.orgのmacOS向けインストーラーで入れたPythonは、インストール後に実行する「Install Certificates.command」でcertifiを入れてそれを使う仕組みです。
  • certifiの勧告でも、影響を受けるものとしてrequests、urllib3、pip、そしてPython.orgのmacOS向けインストーラーが名指しされています。

何が起き、certifiはどう変わったのか

eMudhraの件を短くまとめると、同社の傘下にある「emSign」というブランドの中間証明書8枚について、秘密の値がほぼ乱数を使わずに作られていたため、公開されている証明書から逆算できてしまう状態だったと10月17日に報告されました。

中間証明書は、ルート証明書とサイトの証明書の間に入る「中継ぎ」の証明書です。その秘密の値を手に入れた人は、どのサイトの証明書でも偽物を作れてしまいます。

通信の途中に割り込める立場の攻撃者がそれを使えば、「本物のサイト」になりすまして通信を盗み見たり書き換えたりできます。10月24日の時点で、実際に悪用された例は確認されていません。

図:中間証明書の秘密の値が悪用されうる流れ
STEP 1秘密の値を逆算公開されている証明書から割り出せる
STEP 2偽の証明書を作るどのサイトの偽物も作れる
STEP 3なりすまし通信を盗み見・書き換え

certifiのGitHubに残っている記録によると、対応は次のように進みました。

  1. 10月18日、「Remove emsign」という変更で、emSign系のルート証明書をまとめて削除し、2026.10.18として公開しました。
  2. 削除の直後から、GitHubの課題(issue #439)で「なぜemSignを丸ごと外したのか」という問い合わせが相次ぎました。米国の研究・教育機関向けの認証サービスがemSignのルートを使っていて、丸ごと外すと多くのPythonのソフトが使えなくなる、という指摘です。実際に、大学の観測データのサイトにつながらなくなったという報告も寄せられています。
  3. メンテナー(管理者)は、中間証明書だけを信頼しないようにする手段がcertifiの仕組みにはないため、ルートを外すほかなかったと説明しています。そのうえで、影響を受けたのは特定のルート(G3と呼ばれる世代)に連なるものだと整理し、10月20日に「Restore unaffected emSign roots」という変更で影響のないルートを戻しました。これが2026.10.19です。
  4. 10月23日には2026.10.23が公開されました。自動で作られる定期の更新で、どの証明書が変わったのかは、10月24日の時点でリリースの記録から確かめられませんでした。

GitHubのセキュリティ勧告(GHSA-6q48-6c7w-9gw2)では、2019年3月の版から2026.10.19より前までのすべての版が影響を受けるとされ、深刻度はCVSS v4で9.1(Critical)です。CVSSは脆弱性の深刻さを0〜10の点数で表す指標で、9以上は最も重い区分です。

勧告は、「emSign ECC Root CA – G3」というルートが含まれていることを問題とし、2026.10.19以降への更新と、certifiを中に抱え込んだコンテナイメージの作り直しを求めています。

ポイント

ここで覚えておきたいのは、certifiのような「信頼の一覧」の更新は、ふだんの機能追加とは違って、遅れると直接危険につながる種類の更新だということです。その一方で、2026.10.18のように「念のため広く外した」版は、正規のサイトにつながらなくなる副作用も出ました。急いで当てつつ、つながらなくなったサイトがないかを確かめる、という両面の対応が必要だったわけです。

自分の環境はどの証明書を使っているのか:確かめ方と更新のしかた

ターミナルを開いたノートPCと背後のネットワーク機器

では、自分のPythonの環境がどの版のcertifiを使っているのかを確かめてみましょう。Pythonは「仮想環境(venv)」という仕組みで、プロジェクトごとに部品を分けて入れるのが一般的です。

そのため、パソコンの中にcertifiが何個も入っていることは珍しくありません。確かめるときは、実際にプログラムを動かす環境ごとに見ていく必要があります。

手元のパソコンで確かめる

まず、確かめたい仮想環境を有効にしてから、python -m pip show certifiを実行します。「Version:」の行に版が表示されます。2026.10.19以降であれば、eMudhraへの対応が入っています。

あわせて、python -c "import certifi; print(certifi.where())"を実行すると、実際に使われている証明書のファイルの場所が分かります。思っていた仮想環境とは違う場所が表示されたら、別のPythonを動かしていることになります。

確かめるコマンドと目安の版を表にまとめます。

対象確かめるコマンド目安の版
certifipython -m pip show certifi2026.10.19以降
pippython -m pip --version26.2.2以降
uvuv --version0.13.4以降

uvは10月21日の0.13.5で、問題の中間証明書を使った証明書の連なりそのものを拒否する変更(rustls-webpki 0.103.16への更新)も入れているので、uvを使っている人はできるだけ新しい版にしておくと安心です。

更新は、仮想環境の中でpython -m pip install -U certifi pipを実行します。

uvで管理しているプロジェクトなら、uv lock --upgrade-package certifiでロックファイルの中のcertifiだけを上げ、uv syncで環境に反映するのが分かりやすい方法です。uv本体は、公式のインストーラーで入れた場合はuv self updateで更新できます。

certifiを固定している場合

requirements.txtやロックファイルでcertifi==2024.8.30のように版を固定していると、上の更新をしても、次に環境を作り直したときに古い版に戻ってしまいます。版の固定はこの記事の後半で勧める大事な習慣ですが、固定したものは自分で上げない限り永遠に古いままです。

リポジトリの中をcertifiで検索して、固定している場所を洗い出しておきましょう。DependabotやRenovateのような、部品の更新を自動で提案してくれる道具を入れている場合は、certifiの更新の提案が来ていないか確かめてください。

ほかの部品が、certifiを中に抱え込んでいる(ベンダリングしている)こともあります。pipがその代表で、pipの中のcertifiはpipを更新しない限り変わりません。

AWSのPython向けSDKの一部であるbotocoreも、certifiが入っていない環境向けに独自の証明書の束を持っており、修正版が出ているとする資料があります。自分の使っている部品が独自の束を持っていないか、気になる場合は部品の更新履歴を確かめてください。

Dockerイメージとサーバーレスは「作り直し」が必要

注意

Pythonのアプリをコンテナで動かしている場合、certifiはイメージを作った時点の版で焼き込まれています。開発機でcertifiを更新しても、本番で動いているイメージの中身は変わりません。イメージを作り直して配り直すまで、本番は古いcertifiのままです。

確かめるには、動いているコンテナの中でpython -m pip show certifiを実行するか、docker run --rm イメージ名 python -m pip show certifiのようにイメージから直接確かめます。作り直すときは、Dockerのキャッシュで古い層が使い回されないよう、docker build --pull --no-cacheのように、元のイメージを取り直してキャッシュを使わない指定をすると確実です。

コンテナの中には、certifiとは別に、OSの証明書ストア(DebianやUbuntuならca-certificatesというパッケージ)もあります。Python標準のurllibやOSのコマンド(curlなど)はこちらを使うので、元のイメージ(python:3.14-slimなど)の更新も取り込む必要があります。

DebianやUbuntu、Alpineなどは10月中にca-certificatesの修正版を出したとする資料がありますが、配布元によっては10月24日の時点で修正版が出ていないものもあるとされています。使っている元のイメージの配布元の告知を確かめてください。

AWS Lambdaのような「サーバーレス」の仕組みで、自分の部品を「レイヤー」や配布用のzipにまとめて上げている場合も同じです。クラウドの事業者は、利用者が自分で同梱した部品までは更新してくれません。certifiを含む部品をまとめ直して、上げ直す必要があります。

企業の中継サーバーを使っている場合の注意

会社のネットワークでは、通信を検査する装置が独自の証明書を使っていて、Pythonからの通信のためにREQUESTS_CA_BUNDLEやSSL_CERT_FILEという環境変数で独自の証明書の束を指定していることがあります。

この場合、certifiを更新してもPythonはその指定のファイルを使い続けます。その束が昔のcertifiを写して作ったものなら、eMudhraの問題の証明書が残ったままかもしれません。社内の情報システムの担当者に、束がいつ作られたものなのかを確かめてもらうとよいでしょう。

Pythonが起動するたびに読み込むファイル:.pthとsitecustomize

夜の机でターミナルを開いたノートPCと置き時計

ここからはLiteLLMの事件に移ります。この事件で最も注目したいのは、悪意あるプログラムが「LiteLLMを使わなくても動いた」点です。

Datadog Security Labsの分析によると、1.107.0を入れると、Pythonの部品の置き場所(site-packagesというフォルダ)に、cook.pth、cook.py、sitecustomize.pyという3つのファイルが置かれました。このうちcook.pthとsitecustomize.pyは、Pythonの仕組み上、Pythonが起動するたびに自動で読み込まれるファイルです。

.pthファイルは本来、部品の置き場所を足すためのもの

Pythonの公式ドキュメントのsiteモジュールの説明によると、Pythonは起動するときにsiteというモジュールを自動で読み込み、site-packagesのフォルダの中にある拡張子が.pthのファイルを、名前のアルファベット順にすべて処理します。

.pthファイルの各行には、本来は「部品を探しに行くフォルダの場所」を書きます。Pythonはそれを、部品を探す場所の一覧(sys.path)に付け足します。

問題は、その例外のルールです。ドキュメントには、互換性のために、import(とそれに続く空白かタブ)で始まる行はexec()で実行される、と書かれています。exec()は、文字列をPythonのプログラムとして実行する関数です。

つまり、site-packagesに.pthファイルを1つ置くだけで、そのPythonが起動するたびに好きなプログラムを動かせてしまいます。Aikidoの解説によると、cook.pthはまさにこの仕組みで、起動のたびに情報を盗む本体のcook.pyを読み込ませていました。

図:Pythonの起動時に読み込まれるものの流れ
STEP 1Pythonが起動siteモジュールを自動で読み込む
STEP 2.pthを処理アルファベット順にすべて処理
STEP 3importで始まる行exec()で実行される
STEP 4sitecustomize読み込もうとし、あれば動く
cook.pthはこの仕組みで、起動のたびにcook.pyを読み込ませていました

この仕組みは、もともと悪いものではありません。setuptoolsが入れるdistutils-precedence.pthや、開発中の部品を「編集可能な状態」で入れたときに作られる__editable__で始まる.pthファイルなど、正規の部品も使っています。だからこそ、知らない.pthファイルが1つ増えていても気づきにくいのです。

sitecustomize.pyは「管理者のための起動時の設定」

もうひとつのsitecustomize.pyも、siteモジュールの仕組みのひとつです。ドキュメントによると、siteは部品の置き場所の処理を終えたあとで、sitecustomizeという名前のモジュールを読み込もうとします。

見つからなければ何もしません。本来は、システムの管理者が、そのPythonを使う全員に共通の設定を入れるための仕組みです。似たものとして、利用者ごとのusercustomizeもあります。

LiteLLMの件では、このsitecustomize.pyが遠隔操作のためのプログラムでした。Datadogによると、1時間おきにGitHub上のファイルを見に行き、そこに書かれた場所からPythonのプログラムを取ってきて実行する仕組みで、調査の時点ではそのファイルにテスト用の値しか入っておらず、実際に2段目のプログラムが配られた様子は観測されていません。

部品を消しても残り、pipを動かすと再び動く

注意

この2つのファイルは、LiteLLMの部品のフォルダの外、site-packagesの直下に置かれていました。そのため、pip uninstall litellmでLiteLLMを消しても残ります。しかも、pipもPythonで書かれたプログラムなので、pipを動かした瞬間にPythonが起動し、.pthとsitecustomize.pyがまた実行されます。

LiteLLMの公式の告知が、対処の手順の最初に「影響を受けた環境で、まずpipを動かさないこと」を挙げているのはこのためです。告知が求めている対処は、次の順です。

  1. シェル(コマンドの画面)からcook.pth、cook.py、sitecustomize.pyと、それに対応する__pycache__の中のcook*.pyc、sitecustomize*.pycを消す。
  2. /tmp/remote_code_loader.lockを持っている常駐のPythonのプロセスを止める。
  3. 1.106.2に固定して入れ直す。

仮想環境やイメージは、できれば一から作り直すのが望ましいともしています。

告知に載っている確かめ方の例は、Linuxならfind / -xdev \( -name "litellm-1.107.0.dist-info" -o -name "cook.pth" -o -name "cook.py" -o -name "sitecustomize.py" \) 2>/dev/nullのようなものです。Windowsなら、PowerShellでGet-ChildItem -Path C:\ -Recurse -Include cook.pth,cook.py,sitecustomize.py -ErrorAction SilentlyContinueのように探せます。

ただし告知は、ファイルが見つからなくても無事だとは言い切れない、と念を押しています。コンテナを作り直したり一時ファイルが片付けられたりすると、痕跡が消えるためです。

1.107.0を入れた、または問題のイメージを動かしたことがはっきりしているなら、その環境は侵害されたものとして扱い、そこから届く範囲の認証情報をすべて交換する必要があります。

ふだんから.pthを眺めておく

今回の件に関係がなくても、自分の環境にどんな.pthファイルがあるのかを一度見ておくのは良い習慣です。python -c "import site; print(site.getsitepackages())"でsite-packagesの場所が分かるので、そのフォルダで*.pthを一覧にし、中身にimportで始まる行があるものを確かめます。

知っている部品のものなら問題ありません。知らない名前のものがあれば、どの部品が入れたのかを調べてみてください。

調べる作業そのものでPythonを動かしたくない場合は、Pythonの起動オプションも使えます。-Sを付けるとsiteモジュールの自動の読み込みが止まり、.pthの実行行やsitecustomizeは動きません。-I(隔離モード)は、利用者ごとのsite-packagesや環境変数の影響を受けないようにする指定です。

ただし、すでに怪しいと分かっている環境では、Pythonを動かさずにシェルのコマンドだけで調べるのが安全です。

Python 3.15で始まった「起動時の実行」の見直し

この.pthの仕組みには、Python本体の側でも見直しが進んでいます。Python 3.15のドキュメントによると、.pthファイルのimportで始まる行は3.15から「非推奨」になりました。

当面はこれまでどおり実行されます。予定されている扱いを表にまとめます。

版importで始まる行の扱い
3.15非推奨になった(当面はこれまでどおり実行される)
3.18と3.19黙って無視されるようになる
3.20警告が出る予定

代わりに、起動時に呼び出したい関数をパッケージ名.モジュール名:関数名の形で書く「.startファイル」という新しい仕組みが3.15で入りました。これはPEP 829(Pythonの仕様の提案書の829番、「Structured Startup Configuration Files」)で議論されてきたものです。

.startファイルに変わっても、「site-packagesに書き込める人は、起動時に何かを実行させられる」という性質そのものがなくなるわけではありません。ただ、起動時に何が呼ばれるのかが決まった形で書かれるようになれば、道具で一覧にしたり、見慣れない登録を見つけたりしやすくなります。いずれにしても、site-packagesは「部品を置くだけの場所」ではなく、「Pythonが起動のたびに中身を読む場所」だと意識しておくことが大切です。

ソースは無事でも配布物が違った:wheelとビルドの仕組み

倉庫の棚に積まれた段ボール箱と作業台の上の開いた箱

LiteLLMの事件のもうひとつの特徴は、GitHubにある公式のソースコードには悪意あるプログラムが入っていなかったことです。LiteLLMの公式の告知によると、GitHubの公式のv1.107.0のタグ(その版のソースコードに付けた目印)には悪意あるファイルは含まれておらず、問題の配布物は、ハイフンがひとつ多いだけの偽のリポジトリ「Berri-AI/litellm」から作られていました。

図:公式のソースと問題の配布物の違い
GitHubの公式のソース
  • v1.107.0のタグに悪意あるファイルは含まれていない
問題の配布物(wheel)
  • ハイフンがひとつ多い偽のリポジトリ「Berri-AI/litellm」から作られた
  • 悪意あるファイルが入っていた

sdistとwheelの違い

ここで、PyPIで配られている部品の形を説明しておきます。PyPIの部品には、大きく分けて2つの形があります。

  • sdist(ソース配布物):ソースコードをそのまま固めたものです。入れるときに、利用者のパソコンの上で組み立て(ビルド)が行われます。
  • wheel(ホイール):組み立て済みの配布物で、拡張子は.whlです。中身は決まった形のzipファイルで、入れるときはほぼ展開してコピーするだけなので速く、今のpipはwheelがあればそちらを優先して使います。

LiteLLMの1.107.0では、7つのwheelがPyPIに上げられました。Datadogが挙げた危険の目印の一覧にも、macOS、Linux(manylinux・musllinux)、Windows向けのwheelのハッシュ値が並んでいます。ハッシュ値は、ファイルの中身から計算する「指紋」のような値で、中身が1バイトでも違えば別の値になります。

Aikidoの解説によると、悪意あるファイルは元のソースコードではなく、組み立ての途中で入り込んでいました。pyproject.toml(Pythonの部品の作り方を書く設定ファイル)の中の、データとして同梱するファイルを指定する部分にcookを加え、wheelを作るときに3つのファイルがsite-packagesの直下に置かれるようにしていた、という説明です。

wheelには、部品のフォルダの外にファイルを置く仕組みがあり、それが使われた形です。

ポイント

このことから分かるのは、「GitHubでソースを読んで問題がなかったから、PyPIの版も安全だ」とは言えない、ということです。利用者が実際に入れるのは、ソースコードではなく配布物です。配布物は、誰が、どのソースから、どの手順で作ったのかによって中身が変わりえます。

攻撃者が越えた「何重もの守り」

LiteLLMは10月23日に、攻撃の経路をまとめた正式な記録を公開しました。それまでの告知に含まれていた推測の一部が不正確だったとして、こちらを正式な記録と位置づけています。

関連記事では「3人のエンジニアのGitHubの合鍵(トークン)が使われた」ところまでを書きましたが、正式な記録では、その前後の流れがより詳しく分かります。Pythonの部品を配る側にとって参考になる点を、表にまとめます。

本来の守り何が起きていたか(LiteLLMの記録より)
漏れた秘密の無効化5月27日に、社内の別サービスの管理用の値を含む設定ファイルが公開のリポジトリに上げられた。履歴は上書きしたが、値そのものは無効にしていなかった
権限の絞り込みそのサービスは、専用のアカウントではなく、組織のオーナーの個人アカウントに結びついた、範囲を絞っていないトークンを使っていた
CIの秘密の分離10月10日、作業用のブランチで動かしたCI(自動のビルドとテスト)から、リポジトリの秘密の値が外部のサイトに送られた
ブランチの保護10月21日、リリースの手順への変更は保護設定で一度拒否されたが、オーナーのトークンで保護を上書きされた
2人による承認本番の公開の承認が、特定の端末に結びついた専用アカウントから「任意のエンジニア2人」に緩められていて、2つの盗まれたトークンで起動と承認が行われた
記録の保全公開の直後に、3人目のトークンで実行の記録が消された

表を見ると、どれかひとつの守りが破られたのではなく、いくつもの守りが少しずつ緩んでいたところを、順にすり抜けられたことが分かります。LiteLLM自身も、攻撃者は「複数の独立した守りを破る必要があった」と書いています。

また記録では、GitHub CLIのトークンは二段階認証のあとで発行され、重要な操作のときの再確認も素通りする性質があることも指摘されています。

公開されたのは10月21日の協定世界時11時39分51秒から40分13秒にかけてで、12時6分には外部の研究者がGitHubに問題を報告しました。PyPIで公開が止められた時刻は、LiteLLMの記録では13時23分ごろ、Datadogのまとめでは13時20分とされており、数分の差があります。

PyPIの隔離、Trusted Publishing、出所の記録

フェンスで囲まれた保管場所の木箱

次に、配布所であるPyPIの側の仕組みを見ていきます。PyPIは、この1年ほどで、汚染された部品が広がるのを防ぐための仕組みをいくつも入れてきました。どれも万能ではありませんが、仕組みを知っておくと、利用者として何を確かめればいいのかが見えてきます。

隔離(quarantine):消すのではなく、入れられないようにする

PyPIの公式ブログによると、2024年に「プロジェクトの隔離」という仕組みが入りました。それまで、悪意のある部品が見つかったときに管理者が取れる手段は、プロジェクトを丸ごと消すことだけでした。消すと元に戻せず、正規の作者の部品だった場合の影響も大きくなります。

隔離は、プロジェクトを「害がある可能性がある」と印を付けて、pipなどから簡単には入れられないようにする仕組みです。調べた結果、問題がなければ元に戻せます。

隔離のきっかけになるのは、利用者からの報告です。PyPIの各プロジェクトのページには「Report project as malware(マルウェアとして報告)」という窓口があります。

PyPIが4月に公開した、3月のLiteLLMとtelnyxの事件の報告書によると、信頼できる報告者として登録された人たちからの報告には重みが付けられ、条件がそろうと自動で隔離される仕組みも動いています。

その報告書には、3月の事件のときの数字も載っています。

  • LiteLLMの悪意ある版は、公開から最初の報告まで1時間19分、報告から隔離まで1時間12分、合計2時間32分公開されていました。
  • その間に11万9,000回以上ダウンロードされました。
  • 報告書は、LiteLLMが1分間に約1,700回入れられていること、そのうち40〜50%が版を固定せずに最新を取りに行っていたと見積もっていることも書いています。

隔離がどれだけ早くても、2時間あれば十分に広がってしまう、ということです。10月の事件でも、公開は約1時間40分(PyPIの場合)でした。

Trusted Publishing:長く使える合鍵をなくす

Trusted Publishing(トラステッド・パブリッシング、信頼された公開者)は、PyPIに部品を上げるときに、長く使えるAPIトークンを使わずに済ませる仕組みです。あらかじめPyPIに「このGitHubのリポジトリの、このワークフロー(自動の手順)からの公開だけを認める」と登録しておくと、公開のたびにGitHub ActionsなどからPyPIに短い時間だけ使える許可が発行されます。

PyPIの報告書は、APIトークンは長く使えるので盗まれるとずっと後まで悪用されうるのに対し、Trusted Publishingは短い時間しか使えない許可なので、侵害のあとに手で交換する必要もない、と説明しています。

3月の事件のあと、LiteLLMはTrusted Publishingに切り替えたとPyPIの報告書に書かれています。それでも10月の事件は防げませんでした。

ポイント

今回の攻撃者はPyPIのトークンを盗んだのではなく、LiteLLMの正規の公開の手順そのものを、盗んだGitHubのトークンで動かしたからです。Trusted Publishingは「誰が公開できるか」を正規の手順に絞る仕組みなので、その正規の手順が乗っ取られれば、公開は正規のものとして通ります。

ここから、配る側への教訓が見えてきます。PyPIの報告書は、Trusted Publishingと合わせて、次のようなことを勧めています。

  • GitHubの「Environments(環境)」という機能を使って、公開の前に人の確認を必須にすること。
  • GitHub Actionsの危ない設定(pull_request_targetという起動のしかたなど)を避けること。
  • 手順の中で使う部品をタグではなくコミットの値で固定すること。
  • zizmorという道具で手順の設定を点検すること。

LiteLLMの件では、その承認が緩められていたことが表の中にありました。

出所の記録(attestation)は、見なければ役に立たない

PyPIには、配布物ごとに「どこで、どの手順で作られたか」を暗号の署名付きで記録する「デジタル証明(attestation)」という仕組みもあります。PyPIのドキュメントによると、Trusted Publishingを通じて公開されたことを示す「公開の証明」と、元のソースの場所を示す「SLSA Provenance(出所の記録)」の2種類があり、それぞれの配布物の中身の指紋と結びつけられています。

Datadogによると、LiteLLMの1.107.0の配布物に付いていた出所の記録は、偽のリポジトリを元にしたことを「忠実に」記録していました。記録がうそをついていたのではなく、記録は正しく、元にしたソースが間違っていたのです。

言いかえると、利用者の側で「この部品はBerriAI/litellmから作られたはずだ」と照らし合わせていれば、違いに気づけた可能性があります。ただ、現状ではpipは入れるときに出所の記録を自動では照らし合わせないので、記録があるだけでは守りにはなりません。大事な部品については、記録が付いているか、その元のリポジトリが本物かを確かめる手順を、自分たちの仕組みに組み込む必要があります。

古い版への「後からの追加」を断る14日ルール

PyPIは7月から、公開から14日を過ぎた版には新しいファイルを追加できないようにしました。PyPIのブログによると、これは公開用の認証情報が盗まれたときの被害を抑えるための変更です。

たとえば、人気のある古い版に、悪意あるwheelを1つだけ後から足す、といった手口が使えなくなります。PyPIではもともと、一度上げたファイル名と同じ名前で中身を差し替えることはできません。

この2つを合わせると、「一度確かめた版の、確かめたファイル」は、後から中身が変わらないと考えてよいことになります。次の節で説明する「ハッシュでの固定」は、この性質の上に成り立っています。

依存を固定する:ハッシュ、ロックファイル、クールダウン

机の上にきれいに積まれた木のブロック

ここからは、利用者の側でできる守り方を、具体的な手順として書いていきます。中心になるのは、「どの版の、どのファイルを入れるのか」を前もって決めて記録しておくことです。

版を固定するだけでは足りない理由

requirements.txtにlitellm==1.106.2のように書いて版を固定するのは、最初の一歩としてとても効果があります。LiteLLMの3月の事件でも、40〜50%の利用者が版を固定していなかったために最新の悪意ある版を取りに行ったと見積もられています。

ただし、版の固定には2つの穴があります。ひとつは、自分が直接書いた部品の版を固定しても、その部品が頼っている部品(推移的な依存)は固定されないことです。

もうひとつは、同じ版の番号のまま中身の違うファイルが配られた場合に気づけないことです。PyPIでは同じファイル名の差し替えはできませんが、社内の中継サーバーやミラーを経由している場合や、PyPI以外の配布元を使っている場合は、その保証が成り立たないこともあります。

PyPIの報告書も、pip freezeの出力では足りないと書いています。pip freezeは今入っている部品の版を一覧にするだけで、ファイルの中身の指紋(ハッシュ)を含まないからです。

pipのハッシュ確認モード(–require-hashes)

pipには「ハッシュ確認モード」があります。requirements.txtの各行に、版と一緒に--hash=sha256:…という形でファイルの指紋を書いておくと、pipはダウンロードしたファイルの指紋を計算し、書かれたものと一致しなければ入れるのを止めます。

pipの公式ドキュメントによると、ひとつでも--hashがあればこのモードになり、--require-hashesを付けると、すべての部品に指紋が書かれていることを強制できます。指紋がない部品や、版を固定していない部品があると、エラーになります。

pipのドキュメントは、安全に入れるための組み合わせとして、ハッシュ確認モードと--only-binary :all:(sdistを使わず、wheelだけを使う指定)を挙げています。sdistは入れるときに利用者のパソコンの上で組み立てのプログラムが動くため、その時点で任意の処理が実行されうるからです。自分のプロジェクトそのものを入れるときは、python -m pip install --no-deps .のように--no-depsを付けて、確かめていない部品が勝手に入らないようにすることも勧められています。

なお、PyPIの部品のページのURLに付いている#sha256=の値は、ダウンロード中の破損を防ぐためのもので、利用者が手元に持っている指紋の代わりにはならない、ともドキュメントに書かれています。指紋は、自分で確かめたときの値を、自分のリポジトリに記録しておく必要があります。

ロックファイルを作る道具:pip-tools、uv、pip lock

すべての部品の指紋を手で書くのは現実的ではないので、普通は道具に作らせます。こうして作る「すべての部品の版と指紋を記録したファイル」をロックファイルと呼びます。PyPIの報告書が名前を挙げている道具は次のとおりです。

  • pip-tools:requirements.inに直接使う部品だけを書き、pip-compile --generate-hashes requirements.inを実行すると、推移的な依存まで含めた版と指紋入りのrequirements.txtが作られます。入れるときはpip install --require-hashes -r requirements.txt、またはpip-syncを使います。
  • uv:プロジェクトのpyproject.tomlをもとにuv lockでuv.lockを作ります。uv.lockには各ファイルの指紋が記録され、uv sync --lockedとするとロックファイルが最新でない場合にエラーになるので、CIや本番の組み立てで使うと安心です。pip向けの形式が必要なら、uv pip compile --generate-hashesも使えます。
  • pipenv:Pipfile.lockに指紋を記録します。
  • pip lock:pipにも、Pythonの標準のロックファイルの形式pylock.tomlを作る試験的な機能が入っています。
注意

ここで大事な注意があります。ハッシュでの固定が守ってくれるのは、「ロックファイルを作った時点で入っていた中身」から変わらないことです。ロックファイルを作り直したタイミングがちょうど悪意ある版の公開中だった場合、その悪意ある版の指紋が正しく記録されてしまいます。

LiteLLMの10月の事件でいえば、21日の協定世界時11時39分から13時20分ごろまでの間にuv lock --upgradeを実行した人のロックファイルには、1.107.0が記録された可能性があります。ロックファイルの変更をレビューするときは、どの部品の版がいつ上がったのかに目を通す習慣をつけておくと、こうした事態に気づきやすくなります。

新しい版を数日待つ「クールダウン」

そこで、もうひとつの守りとして勧められているのが「クールダウン」です。公開されたばかりの版はすぐには使わず、数日待ってから取り込む、という考え方です。LiteLLMの3月と10月の悪意ある版は、どちらも数時間で隔離されました。3日待つ設定にしていれば、そもそも候補に上がらなかったことになります。

PyPIの報告書には、具体的な設定例が載っています。

  • uvなら、pyproject.tomlの[tool.uv]の部分、または利用者全体の設定ファイルuv.tomlにexclude-newer = "P3D"と書くと、3日以内に公開された版を無視します。「P3D」は「3日間」を表す書き方です。
  • pipでは、pip 26.0以降で--uploaded-prior-toという指定が使え、26.1からは設定ファイルにuploaded-prior-to = P3Dのように相対の期間で書けるようになりました。

ただし報告書は、クールダウンは「どこにでも当てればいい万能の策ではない」とも書いています。セキュリティの修正まで3日待っていては、今回のcertifiのような更新が遅れてしまうからです。

部品の更新を提案してくれるDependabotやRenovateは、標準ではセキュリティ更新についてはクールダウンを飛ばす設定になっていると報告書は説明しています。「ふだんの更新はゆっくり、セキュリティの更新は早く」という使い分けが現実的です。

入れる前に中身を見る:pip downloadとwheelの確認

新しく使い始める部品や、大きく版を上げる部品については、入れる前に配布物の中身を確かめることもできます。python -m pip download litellm==1.106.2 --no-deps -d ./checkのようにすると、部品を入れずに配布物のファイルだけをcheckフォルダに取ってきます。

pipのドキュメントによると、ハッシュ確認モードはpip downloadでも使えます。

wheelは中身がzipなので、python -m zipfile -l ファイル名.whlや、普通の展開の道具でファイルの一覧を見られます。注目するのは、次のような点です。

  • 拡張子が.pthのファイルが入っていないか。入っている場合、importで始まる行がないか。
  • sitecustomize.pyやusercustomize.pyという名前のファイルがないか。普通の部品が配るものではありません。
  • 部品の名前のフォルダの外に、見慣れないファイルが置かれる指定になっていないか。wheelの中の.dataで終わるフォルダは、部品のフォルダの外にファイルを置くための場所です。
  • wheelの中のRECORDというファイルには、入れられるすべてのファイルと指紋の一覧が書かれています。前の版と比べて、増えたファイルがないかを見るのも手がかりになります。

このとき、取ってきたファイルを確かめる作業でも、そのフォルダの中でPythonを動かさないように注意してください。Pythonは、実行したスクリプトのあるフォルダや今いるフォルダから部品を読み込むことがあるため、悪意あるファイルが置かれたフォルダの中でpythonを動かすと、意図せずそれを読み込む恐れがあります。

確かめるための作業は別のフォルダから行い、Pythonを使う場合は-Iを付けて隔離モードで動かすのが安全です。

毎回の更新ですべての部品をここまで確かめるのは大変なので、重要な部品、新しく加える部品、公開の担当者が変わった部品など、気になるものに絞って行えば十分です。最近は、部品の配布物の変化を自動で調べてくれる商用・無償のサービスも増えています。

10月に出た、そのほかのPython関連のセキュリティ更新

棚に並ぶ小型サーバーとNASの点滅するランプ

certifiとLiteLLM以外にも、10月にはPythonに関係するセキュリティ更新がいくつも出ています。自分が使っているものがないか確かめてみてください。

CPython本体:10月21日の前倒しのリリース

Pythonそのもの(CPythonと呼ばれる、python.orgで配られている標準の実装)は、10月1日前後に定例の更新として3.14.8、3.13.16、3.11.17、3.10.22を出しました(3.12.15は9月30日)。3.10.22は3.10系の最後の版で、3.10はこれで保守が終わりました。3.10を使い続けている環境は、今後見つかる欠陥の修正を受けられないので、新しい版への移行を計画する必要があります。

そして10月21日、eMudhraの件を受けて、3.15.1、3.14.9、3.13.17が前倒しで公開されました。python.orgの3.14.9の説明では「前倒しのセキュリティリリース」とされ、同梱のpipを26.2.2に上げ、その中のcertifiが2026.10.19になったことが、eMudhra / emSignの証明書の件への対応だと書かれています。

3.13.17の説明では、3.13はセキュリティ修正だけを受ける時期に入ったばかりで、ふだんはそうした版でpipを更新しないものの、今回はインストーラー付きの特別な版を出すのが賢明と判断した、と説明しています。

どちらの説明にも、既存の仮想環境のpipとuvを更新するよう書かれています。理由と、特別な版が出ていない版の対応は次のとおりです。

  • Pythonを入れ直しても、すでに作ってある仮想環境の中のpipは古いままです。新しいPythonに含まれる新しいpipが使われるのは、そのPythonで新しく作る仮想環境だけです。
  • 3.12と3.11は、ソースコードでの配布だけを行う時期に入っているため、今回の特別な版は出ていません。これらを使っている場合は、仮想環境の中でpython -m pip install -U pip certifiを実行して対応します。

pip 26.2.2とuv 0.13.4・0.13.5

pipの更新履歴によると、10月20日の26.2.2の変更点は、同梱のcertifiを2026.10.19に上げたことだけです。10月に出たpipの版はこれだけで、CVEの付いた修正は含まれていません。なお、7月の26.2では、リンクのファイル名を決めるときにURLのパスを2回解読してしまう欠陥(CVE-2026-13346)が直されています。

uvは、10月20日の0.13.4で、同梱の証明書の束からemSignのルートを外しました。GitHubの勧告(GHSA-hq85-5cpj-mr62)では、深刻度はCVSS v4で9.1(Critical)です。

翌21日の0.13.5では、問題の中間証明書を使った証明書の連なりを拒否する変更に加えて、uvが管理するPython(3.11以降)を、10月21日のCPythonのセキュリティ修正を含むものに作り直しています。uvでPythonそのものを入れている人は、uv python upgradeなどで新しい版に入れ替えておくとよいでしょう。

Django:10月6日に4件

Webアプリの枠組み(フレームワーク)として広く使われているDjangoは、10月6日に6.1.2、6.0.9、5.2.18を公開し、4件のCVEを直しました。Djangoの公式ブログによると内容は次のとおりです。

CVE深刻度内容
CVE-2026-77050Low言語の判定で、非常に長い言語コードを大量に送るとメモリを使いすぎる恐れ(サービス妨害)
CVE-2026-84429ModerateHTTPヘッダーの解析が、特定の形の入力で極端に遅くなる恐れ。ログインなしでAcceptなどのヘッダーから起こせる
CVE-2026-87890Moderate地図データを扱う機能(GeoDjango)で、細工したデータによりサーバーから外部へ通信させられる恐れ
CVE-2026-87975Moderate主キーをフォームから設定できる特定の作りのモデルで、偽のPOSTにより範囲外のデータを消されたり作られたりする恐れ

これに加えて、以前の版で直したはずの地図データの入れ子によるサービス妨害(CVE-2026-15830)の対策が不十分だった点も補強されています。CVE-2026-87890の修正には、以前の書き方が使えなくなる変更が含まれているので、GeoDjangoを使っている場合は更新の前に公式ブログの説明を読んでください。

また、Djangoは10月8日、新しいセキュリティの報告をHackerOneでは受け付けず、メール(security@djangoproject.com)で受け付けるように変えると発表しています。

FastAPI:10月の告知は見当たらず。Starletteの5月の欠陥に注意

FastAPIについては、10月24日の時点で、10月に出たFastAPI本体のセキュリティ告知は見つけられませんでした。ただし、FastAPIの土台になっているStarletteには、5月に公表されたHostヘッダーの扱いの欠陥(CVE-2026-48710、通称BadHost)があり、1.0.1で直されています。

複数の資料が、この欠陥が9月2日にCISA(米国のサイバーセキュリティ・インフラ安全保障庁)の「悪用が確認された脆弱性の一覧(KEV)」に加えられたと伝えています。FastAPIを使っている人は、python -m pip show starletteで版を確かめておくとよいでしょう。

Pillow 13.0.0:画像を開くだけで危ない欠陥を含む大量の修正

画像を扱う部品Pillowは、10月16日に13.0.0を公開しました。リリースノートによると、深刻度Criticalが2項目、Highが12項目、Mediumが約20項目、Lowが3項目と、非常に多くのセキュリティ修正を含んでいます。

このうち、GitHubの勧告GHSA-5x8w-8jj5-q52h(CVE-2026-112398)は、FlashPix(FPX)という画像形式の中のJPEGの処理でメモリのあふれが起き、任意のプログラムを実行されうるというもので、CVSS v4で9.2です。

勧告によると、この欠陥はolefileという追加の部品が入っていてFPXを読める状態のときに、攻撃者が用意したファイルをImage.open()で開いて読み込むと起きます。Pillowはファイルの拡張子ではなく中身で形式を判断するので、拡張子を「.jpg」に変えたファイルでも条件は同じです。

利用者の画像を受け取って縮小画像を作るようなWebサービスは、人の操作なしにこの処理が動くため、特に早めの更新が勧められます。13.0.0は12.3.0以前の版が対象で、Python 3.10への対応を打ち切り、いくつか以前の書き方が使えなくなる変更も含んでいます。すぐに上げられない場合は、FPXを使わないならolefileを外すことも回避策として挙げられています。

Brotli:Node.jsと同じ欠陥がPyPIの部品にも

Brotli(ブロトリ)は、Googleが作ったデータの圧縮の方式で、Webの通信の圧縮によく使われます。Node.jsの10月のセキュリティリリースで直された、Brotliの展開処理の欠陥(CVE-2026-111497)は、Google自身のBrotliの実装の問題で、Pythonのbrotli(1.0.3から1.2.1より前)とbrotlicffi(1.0.3から1.2.1.0より前)にも及ぶと、10月22日にGitHubの勧告(GHSA-9v8h-qqgm-4m35)で公表されました。

勧告の題名には「Node2Shell」という通称が使われており、深刻度はCVSS v4で9.2です。

Pythonで気をつけたいのは、requestsやurllib3などのHTTPの部品が、brotliの部品が入っていると、サーバーにBrotliでの圧縮を受け付けると伝え、届いた応答を自動で展開することです。つまり、自分のプログラムが悪意あるサーバーや乗っ取られたサーバーにつないだときに、細工された圧縮データを受け取って展開してしまう可能性があります。

python -m pip show brotli brotlicffiで入っているかを確かめ、入っていれば1.2.1(brotlicffiは1.2.1.0)以降に上げてください。自分では入れた覚えがなくても、ほかの部品の依存として入っていることがあります。Node.js側の詳しい内容は、関連記事の「Node.js:10月21日のセキュリティリリース」で扱っています。

AIまわりの部品にもCriticalが相次ぐ

GitHubの勧告データベースで、10月1日から24日に公開されたPythonの部品の勧告を深刻度Criticalで絞り込むと、AIのアプリづくりに使われる部品が目立ちます。例として、次のものがあります。

  • ノーコードでAIの処理を組めるLangflowのコマンド実行の欠陥(GHSA-w794-rj3p-xv45など)
  • AIの推論を速くするキャッシュの部品LMCacheの安全でない読み込みによる遠隔実行(GHSA-w5f2-9q9c-ff4h)
  • AIエージェントの枠組みPraisonAIの複数の欠陥

また10月23日には、仮想環境を作る道具virtualenvや、ファイルの排他制御に使われるfilelockについても、まとまった数の勧告が公開されました。AIの分野の部品は更新の頻度が高く、勧告も多いので、使っているものは定期的に確かめておくのが安全です。

立場別にやること

窓際の机に並ぶ2台のノートPCとルーター

ここまでの内容を、立場ごとに「まずやること」と「続けてやること」に分けて整理します。自分に当てはまる行から手をつけてみてください。

立場まずやること続けてやること
趣味でPythonを使っている人よく使う仮想環境でpipとcertifiを更新する。Pythonを入れ直したなら、既存の仮想環境のpipも更新する使わなくなった仮想環境を消して、管理する環境の数を減らす
AIのアプリを作っている人LiteLLM 1.107.0を入れていないか、cook.pthがないかを確かめる。入れていたら、pipを動かす前にファイルを消し、鍵をすべて交換するuvのexclude-newerなどでクールダウンを設定し、AI関連の部品の勧告を定期的に見る
Webアプリを運用している人Django、Starlette、Pillow、brotliの版を確かめ、該当すれば更新するロックファイルをハッシュ付きにし、本番の組み立てでは--require-hashesやuv sync --lockedを使う
コンテナで動かしている人本番のイメージの中のcertifiとca-certificatesの版を確かめ、キャッシュを使わずに作り直す元のイメージの更新を定期的に取り込む仕組みを作る
部品を公開している人PyPIへの公開をTrusted Publishingにし、古いAPIトークンを無効にする公開の前に人の承認を必須にし、GitHubのトークンの範囲と保管場所を見直す
会社の管理者社内で独自に指定している証明書の束(REQUESTS_CA_BUNDLEなど)の作成日を確かめる社内の部品の中継サーバーで、新しい版の取り込みを遅らせる設定と、隔離された版を消す手順を用意する

表の中で、特に誤解されやすい点を2つ補足しておきます。

ひとつ目は、「LiteLLMを使っていないから関係ない」とは限らないことです。LiteLLMは、AIのエージェントの枠組みや評価の道具など、ほかの部品の依存として入っていることがあります。

python -m pip show litellmで何も出なければその環境には入っていませんが、確かめるのは自分で直接入れた部品だけでなく、環境全体です。uvを使っているなら、uv tree --invert --package litellmのようにすると、どの部品がLiteLLMを必要としているのかが分かります。

ふたつ目は、「固定」と「更新」は対立しないことです。certifiの件は「固定したまま放置すると危ない」話で、LiteLLMの件は「固定しないと危ない」話でした。両方を満たすやり方は、ロックファイルで版と中身を固定したうえで、その固定を意図して、記録を残しながら更新していくことです。

図:固定と更新の考え方
固定したまま放置
  • certifiの件
  • 古いままで危ない
固定しない
  • LiteLLMの件
  • 最新の悪意ある版を取りに行く
固定して、意図して更新
  • ロックファイルで版と中身を固定
  • 記録を残しながら更新

ロックファイルの変更は、ほかのコードの変更と同じようにレビューの対象にします。どの部品が、どの版から、どの版に上がったのかが差分で見えるだけでも、思いがけない更新に気づく手がかりになります。

手元に置いておきたい一冊

ロックファイルや仮想環境、uvの使い方は、断片的な知識で済ませていると、いざというときに「どこに何が入っているのか」が分からなくなりがちです。基本を体系的に押さえておきたい人には、技術評論社の『改訂新版 Pythonエンジニア育成推進協会監修 Python実践レシピ』があります。鈴木たかのり氏ら6人の著者による本で、2026年3月に発売されました。

  • 出版社の紹介によると、Python 3.14に対応するよう全体が更新されており、今回の記事で扱ったuvの章が新たに加えられています。
  • 第1章が「パッケージ管理と環境構築」で、pip、venvによる仮想環境、パッケージマネージャーのuvを順に扱っており、この記事の手順の土台になる部分をまとめて確かめられます。
  • HTTP通信の章でRequestsとHTTPXを、暗号関連の章でsecrets・hashlib・cryptographyを扱っており、証明書やハッシュといった、この記事に出てきた道具を実際に使う場面も載っています。
  • 困ったときに目的別に引ける「レシピ」形式で、テストの章(unittest、pytestなど)もあり、仕事でPythonを使う人の手元の参照用に向いています。

改訂新版 Pythonエンジニア育成推進協会監修 Python実践レシピをAmazonで見る

まとめ

2026年10月のPythonの世界では、eMudhraの中間証明書の問題を受けたcertifiの更新(2026.10.18で削除、2026.10.19で影響のないものを復元)、それに続くpip 26.2.2とuv 0.13.4・0.13.5、CPython 3.15.1・3.14.9・3.13.17の前倒しのリリースがあり、同じ週にLiteLLM 1.107.0の汚染が起きました。さらに、Django、Pillow、Brotliの部品にもセキュリティ更新が出ています。

certifiの件は、requestsやpipがOSではなく自分の証明書の束を使うというPythonの事情から、OSを更新しても直らず、仮想環境、ロックファイル、コンテナイメージのそれぞれで更新が必要になるという話でした。

LiteLLMの件は、Pythonが起動のたびに読み込む.pthファイルとsitecustomize.pyの仕組みが使われ、公式のソースは無事なまま配布物にだけ悪意あるファイルが入ったという話でした。

PyPIの隔離やTrusted Publishing、出所の記録は被害を小さくする仕組みですが、正規の公開の手順そのものが乗っ取られた場合や、記録を誰も照らし合わせない場合には、それだけでは防げません。

  • 動かしている環境ごとにcertifiとpipの版を確かめ、コンテナはキャッシュを使わずに作り直す
  • site-packagesの.pthファイルとsitecustomize.pyを一度眺め、知らないものがないか確かめる
  • ハッシュ付きのロックファイルで版と中身を固定し、その変更をレビューする
  • ふだんの更新には数日のクールダウンを置き、セキュリティの更新は待たずに当てる
  • 部品を公開する側は、Trusted Publishingに加えて、公開の承認と開発者のトークンを固める

部品を組み合わせてすばやく作れるのがPythonの良いところですが、その分、自分のプログラムの中身の大半は他人の書いたものです。どの部品を、どの版で、どの中身で入れたのかを記録しておくことは、今回のような週を落ち着いて乗り切るための、いちばん地道で確かな備えになります。

出典

※この記事の内容は、2026年10月24日時点で公表されている情報をもとにしています。