Safari で確認用のページを開くと、証明書のエラーが出て「守られている」と分かります。ところが同じ Mac のターミナルで curl を打つと、侵害された中間CAの証明書を使うテスト用のホストに HTTP 200 で接続できました。ブラウザが無事でも、開発で使う curl・Python・Node.js は、それぞれ別の証明書の束を見ているからです。
2026年10月22日の午前、私たち(グリームハブの開発チーム)は、開発に使う Mac 1台でこの点検をしました。本記事はその記録です。どの道具がどの束を見ているか、どう確かめたか、更新の途中でどこにつまずいたかを、実際のコマンドと結果で残します。
本記事は、10月21〜22日に私たちが行ったセキュリティ対応を3回に分けて記録する特集の第2回です。第1回はPHPの緊急リリースへの対応、第3回はNode.js 22.23.4 への社内の一括更新を扱います。
ブラウザの外は、それぞれの束を更新するまで残る
問題をまとめたページ(emudhra.we-have-a-problem.com、10月20日公開)によると、eMudhra の中間CA 8件の秘密鍵が侵害されました。CAが鍵を誤った手順で作ったため、証明書から誰でも鍵を復元できる状態だったとしています。報告は10月17日で、遮断は Firefox と Chrome が17日、Apple が19日、Microsoft が20日です。
この鍵があれば、どのサイトにもなりすませます。ブラウザと OS は、失効の情報を配る仕組み(Firefox の OneCRL や CRLite、Chrome の CRLSets など)で一斉に止めました。一方で同じページは、多くのクライアントは失効の情報を読まないため、ソフトウェアの更新が必要だと書いています。Cloudflare のブログも「ブラウザのテストが通っても、裏で動くサービスが守られている証明にはならない」「アプリが別の検証の仕組みを使うなら curl のテストも同じ」と注意しています。
テスト用のホストは、侵害された中間CAの証明書を使っています。証明書の連なりは2通りあり、dv-g3b-emudhra は emSign ECC Root CA - G3 に、dv-g3b-aaa-emudhra は Sectigo(旧 Comodo)の AAA Certificate Services につながっていました。束で対応する場合は、この2つのルートを外すのが基本です。Python の certifi の束で何が消えたかは、certifiのemSign削除の記事で整理しています。
テスト用のホストに、道具ごとに接続する
判定は単純で、接続できたら危険、証明書のエラーになれば保護されています。ブラウザは確認用のページの「Run check」で調べられます。ターミナルの道具は、2つのホストにそれぞれ接続します。
# curl(パスを指定して、どの curl かをはっきりさせる)
/usr/bin/curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-emudhra.we-have-a-problem.com/
/opt/homebrew/opt/curl/bin/curl -sS -o /dev/null -w '%{http_code}\n' https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
# Python(どの束を読むかも表示する)
python3 -c "import ssl; print(ssl.get_default_verify_paths().openssl_cafile)"
python3 -c "import urllib.request as u; print(u.urlopen('https://dv-g3b-emudhra.we-have-a-problem.com/').status)"
# Node.js
node -e "require('https').get('https://dv-g3b-emudhra.we-have-a-problem.com/', r => console.log(r.statusCode)).on('error', e => console.log(e.code))"
python3 や node は、PATH の順番で別の版が動くことがあります。macOS 標準の /usr/bin/python3、Homebrew の Python、pyenv の Python のように、入っているものをすべてパスで指定して試しました。
落とし穴1:nscurl は失敗しても終了コードが0
macOS 標準の通信の仕組み(URLSession)は nscurl で確かめられます。ただし、終了コードでは判定できません。証明書のエラーでも、期限切れの証明書を返すテストサイト(expired.badssl.com)でも、終了コードは0でした。-v を付けて出力を読みます。
nscurl -v https://dv-g3b-emudhra.we-have-a-problem.com/ 2>&1 | grep -E 'successfully connected|Code=-1202'
拒否されると NSURLErrorDomain Code=-1202 が出ます。接続できた場合は、テスト用のホストが返す「Your client successfully connected to the site. That’s bad!」という本文が表示されます。
もう1つ、同じ日の昼前に再確認したところ、12あるテスト用のホストのうち11は -1202 で拒否され、dv-g3b-emudhra の1つだけ nscurl で接続できました。同じ証明書の連なりを security verify-cert で調べると、結果は失効(CSSMERR_TP_CERT_REVOKED)です。原因は分かっていません。1つのホストだけで判断せず、複数のホストで確かめることを勧めます。
対応前と対応後の結果
対応前と、対応後の10月22日の再確認の結果です。macOS は 26 系、Mac 1台での結果です。
| 道具 | 参照する証明書 | 対応前 | 対応後(10月22日の再確認) |
|---|---|---|---|
| Safari | macOS の信頼の仕組み | 拒否 | 変更なし |
| macOS 標準の通信(nscurl) | macOS の信頼の仕組み | 拒否(-1202) | 12ホスト中11で拒否、1で接続 |
標準の curl(LibreSSL)と /usr/bin/python3 | /etc/ssl/cert.pem | 接続(200) | 接続(200)のまま |
| Homebrew の curl(OpenSSL) | Homebrew の ca-certificates | 接続(200) | 拒否 |
| Homebrew と pyenv の Python | Homebrew の OpenSSL の cert.pem(実体は ca-certificates) | 接続(200) | 拒否 |
| Node.js(Volta) | Node.js に組み込みの束 | 22.19.0 で接続(200) | 22.23.4 で拒否(CERT_REVOKED) |
Chrome は、各社の発表では遮断済みですが、この端末では確かめていません。拒否されたときの表示は、Homebrew の curl が unable to get local issuer certificate、Python が CERTIFICATE_VERIFY_FAILED でした。どちらも、通常のサイト(example.com)には接続できています。
Homebrew の ca-certificates を上げる
Homebrew の curl と Python は、Homebrew の ca-certificates が作る束を読みます。この端末の版は2026-08-13版でした。確認用のページは Homebrew の修正版を「2026-09-25_1」としています。その版の定義(Formula)には、AAA Certificate Services と emSign G3 のルートを公開鍵で除くという記述があります。
brew upgrade ca-certificates
2026-10-20版に上がり、Homebrew の curl と Python 2種はテスト用のホストを拒否するようになりました。更新後の束を openssl で読むと、emSign ECC Root CA - G3 と AAA Certificate Services は入っていません。
落とし穴2:約78件が一緒に更新され、Python の venv が6つ止まった
このコマンドは、ca-certificates だけでなく依存するパッケージもまとめて更新し、約78件が同時に上がりました。その中で python@3.14 がなくなって python@3.15 に置き換わり、/opt/homebrew/bin/python3 は 3.15.0 になりました。
困ったのは、Homebrew の Python 3.14 で作った venv(Python の仮想環境)です。6つが起動しなくなりました。venv の pyvenv.cfg は、作ったときの Python の場所を記録しています。
grep '^home' <venv のフォルダ>/pyvenv.cfg
# home = /opt/homebrew/opt/python@3.14/bin
この場所がなくなったため、venv の Python が動かなくなりました。復旧は、入るものが1つだけかを先に確かめてから、python@3.14 を入れ直しました。
brew install --dry-run python@3.14 # 入るのが python@3.14 の1件だけかを確かめる
brew install python@3.14 # 3.14.9 が入った
<venv のフォルダ>/bin/python -V
<venv のフォルダ>/bin/python -m pip check # uv で作った環境は uv pip check
6つとも起動し、依存関係の整合も取れていました。uv がビルドに使った一時フォルダ539個も同じ場所を指していましたが、使い終わった残りなので実害はありません。
なぜ大量に更新されたのかは、man brew に説明があります。brew upgrade は、HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK を設定していない限り、更新したパッケージに依存していて古くなったもの(dependents)も更新します。この環境変数を設定すると、更新される dependents は減ります。ただしマニュアルは、その分 brew upgrade で壊れることが増えるかもしれないとも書いています。
python@3.14 が消えた理由は、ログが残っておらず確かめられていません。私たちの推測では、python@3.14 は他のパッケージの依存として入っていて、依存元が python@3.15 に切り替わったため、不要になった依存として自動で削除されました。いまの端末では、python@3.14 に依存するパッケージは0件、python@3.15 に依存するパッケージは1件です。マニュアルによると、brew upgrade の後には brew cleanup が走ります。不要になった依存の自動削除は、HOMEBREW_NO_AUTOREMOVE で止められます。
次からは、更新の前に何が動くかを見ます。
brew upgrade --dry-run ca-certificates
--dry-run は、更新されるものを表示するだけで、実際には何も更新しません。ただし、今回のように python@3.14 が消えることまで予告されるかは、確かめていません。venv をたくさん持っている端末では、更新の前に pyvenv.cfg の home を一覧しておくと、影響する範囲が先に分かります。
Volta の Node.js を 22.23.4 に上げる
Node.js は、組み込みの証明書の束を使います。修正版の 22.23.4(10月21日公開)のリリースノートには、「root certificates を NSS 3.131.1 に更新」とあります。私たちは Volta で Node.js を管理しているため、既定の版を上げました。
volta install node@22.23.4
テスト用のホストは CERT_REVOKED で拒否されました。ただし、古い 22.19.0 は今も接続できます(volta run --node 22.19.0 で確認)。古い Node.js を消したわけではないので、その版で動くものは危険なままです。
落とし穴3:CLI ツールは入れたときの Node.js で動き続ける
Volta で入れた CLI ツールは、インストールしたときの Node.js の版に固定されます。今回は 22.17.0 や 20.19.0 に固定されたものがありました。既定の版を上げても、これらは古い Node.js で動き続けます。固定先は次のコマンドで確かめられます。
volta list --format plain | grep '^package'
# package <名前>@<版> / <コマンド> / node@22.17.0 npm@built-in (default)
ツールの版は変えずに入れ直すと、固定先が今の既定(22.23.4)に変わります。Volta で入れた CLI ツール11個をこの方法で入れ直しました。
volta install <パッケージ>@<今の版>
もう1つ、同じ名前のコマンドが、PATH の前の方にある別のインストール(公式のインストーラーで入れたものなど)に隠れている例がありました。この場合、Volta 側を入れ直しても、実際に動くコマンドは変わりません。type -a <コマンド名> で、同じ名前のコマンドがどこに何個あるかを確かめます。
なお、コマンドを持たないため入れ直さなかったパッケージが2個、古い Node.js に固定されたまま残っています。
残った課題
/etc/ssl/cert.pem は更新していません。macOS 標準の curl や python3 はこのファイルで検証し、失効は確かめません。確認用のページによると、このファイルは更新されていません。この端末のファイルにも、emSign ECC Root CA - G3 と AAA Certificate Services が入ったままでした。
確認用のページには、2つのルートを sudo でこのファイルから除く手順があります。私たちは、OS が管理するファイルを書き換えることになるため、まだ実施していません。macOS 標準の pip3 や、標準の python3 から作った venv の pip は、さらに別の古い束を使うと同じページは書いています(この点は確かめていません)。
記事をまとめる段階で、git も確かめました。macOS 標準の git だけでなく、Homebrew の git もテスト用のホストに接続できました。Homebrew の git の git-remote-https は、macOS の /usr/lib/libcurl.4.dylib を使っています。Homebrew で入れた道具でも、Homebrew の束を使うとは限りません。
otool -L "$(git --exec-path)/git-remote-https" | grep curl
# /usr/lib/libcurl.4.dylib
もう1つ残っているのが、各プロジェクトの package.json などで固定した Node.js の版です。既定の Node.js を上げても、プロジェクトで古い版を指定していれば、その版で動きます。社内のリポジトリとビルド環境をまとめて 22.23.4 に上げた記録は、第3回で扱います。Python 3.15 が既定になったときに uv で起きる変化は、uv 0.13の変更点で整理しています。
確認した範囲
2026年10月22日の午前、私たちの開発チームの Mac 1台(macOS 26.5.1)で、テスト用のホストへの接続、Homebrew の ca-certificates の更新、python@3.14 の入れ直しと venv 6つの起動・依存関係の確認、Volta の Node.js の更新と CLI ツール11個の入れ直しを行いました(自社の実務経験・実機作業)。同じ日の昼前に、12のテスト用のホストへの nscurl・curl 2種の接続、Python 4種と Node.js 22.19.0・22.23.4 の接続、
security verify-cert、git 2種、両方の束に入っているルートを再確認しました。資料は、問題をまとめたページ、Cloudflare のブログ、Homebrew の ca-certificates の公開情報と Formula、Node.js 22.23.4 のリリースノート、man brewを確認しました(資料調査)。Mozilla のブログは開けませんでした。Chrome での遮断、/etc/ssl/cert.pemを書き換える手順、macOS 標準の pip3、Intel の Mac、Windows と Linux の端末は確認していません。
開発端末やビルド環境の点検・更新の進め方は、開発・AI・自動化のご相談からお問い合わせください。
参考資料
- eMudhra Intermediate CA Compromise(emudhra.we-have-a-problem.com)
- 8 eMudhra certificates are compromised. Here’s what you need to know(The Cloudflare Blog)
- ca-certificates — Homebrew Formulae
- Homebrew/homebrew-core — Formula/c/ca-certificates.rb(2026-09-25_1 の時点)
- CA certificates extracted from Mozilla(curl)
- Node.js v22.23.4 のリリース(nodejs/node)
- Homebrew のマニュアル(brew(1))








