間接依存 starlette の CVE に AutoFix が出した PR は CI 緑でもインストールできなかった。原因分析の誤りも実測で訂正しつつ、Aikido Libraries で変更範囲を抑えて本番まで反映した実録。AutoTriage が重要度を上下させた例も併記。
Python で書いた MCP サーバー群(10 本、いずれも FastAPI + uvicorn の薄い構成)を Aikido でスキャンしたところ、依存ライブラリ starlette に Aikido 上で Critical と評価された指摘が 1 件出た。CVE-2026-48710、Host ヘッダーの検証不備で request.url.path が実際に要求されたパスと食い違い、request.url.path を使ったパスベースの認可やミドルウェアを迂回されうる、という内容である。GitHub Advisory 上の CVSS は 6.5(Moderate)だが、Aikido 上では Critical と表示されていた。Aikido は到達性やコードコンテキストも重要度評価に使うが、この指摘が Critical になった評価内訳までは確認していない。
Aikido の推奨は「starlette を 1.0.1 に更新」。AutoFix があり、Create PR ボタンもある。普通ならここで PR を作り、CI が緑になり、マージして終わる話だ。
そう思っていた。

Reachability 分析を開くと、starlette は requirements.txt に直接書かれていなかった。fastapi==0.109.0 が連れてくる間接依存である。この一点が、後で全部の分岐点になる。
FastAPI を使っていても starlette を意識することは少ない。役割分担を整理しておく。
Request / Response、ミドルウェア(CORS、TrustedHost など)、WebSocket、バックグラウンドタスクつまり @app.get("/execute") で受けた HTTP の URL やヘッダーを解釈しているのは starlette であって、FastAPI ではない。Host ヘッダーの穴が starlette 側にあるのはそのためだ。
そして FastAPI は、Request クラスや例外処理の内部 API に直接依存しているため、starlette のバージョンを狭くピン留めしている。fastapi 0.109.0 の要求は starlette>=0.35.0,<0.36.0。エンジンと計器盤の関係で、計器盤は特定のエンジンにしか付かない。
AutoFix の差分は、requirements.txt の末尾に starlette==1.0.1 を 1 行追記するものだった。Create PR を押すと PR ができ、CI が走り、"All checks have passed" と表示された。

AutoFix の PR は緑だった。 でも、その修正はインストールできなかった。
この CI の validate ジョブは 7 秒で終わる。構文チェックと軽い検証だけで、pip install を実行していない。依存関係の解決に成功したかどうかは、この緑には含まれていない。
AutoFix が出した修正案だけでは依存関係を解決できなかった。そして CI もそれを検出しなかった。ここで重要なのは、どちらかを責めることではなく、PR が緑であることと、実際にインストールして動くことは別だという点である。緑のボタンを押す前に、そのジョブが何をしているかを一度見る。今回はそれが命綱になった。
マージすると UAT へ自動デプロイされる構成だったので、その前に DEV で依存解決だけ確かめた。空の Python コンテナで、AutoFix が作った requirements のまま pip install --dry-run を回す。
The conflict is caused by:
The user requested starlette==1.0.1
fastapi 0.109.0 depends on starlette<0.36.0 and >=0.35.0
ERROR: ResolutionImpossible

fastapi==0.109.0 のピンが starlette<0.36.0 を要求している以上、末尾に starlette==1.0.1 を足しても pip は止まる。AutoFix の 1 行追記は、親のピンと衝突する。マージしていれば UAT の自動ビルドで落ちていた。ここまでは事実で、撤回するものはない。
問題はその次だ。私はこの時点で「fastapi を最新まで上げても starlette 1.0.1 は受け付けないのではないか」と考え、そう結論した。手元の dry-run の途中経過を見て、そう読んだ。これは間違っていた。
この記事を書く段になって確かめ直した。Python 3.11 の空コンテナで、ピンを外して fastapi と starlette==1.0.1 だけを指定すると、こう解決する。
Would install ... fastapi-0.141.1 ... pydantic-2.13.5 ... starlette-1.0.1 ...

FastAPI は 2026 年 2 月の 0.133.0 で Starlette 1.0 系を正式にサポートしている。CVE の公表より前だ。正攻法は最初から存在していた。 fastapi を 0.109.0 から 0.133 以降、実際には最新の 0.141.1 まで上げればよい。
では、正攻法を取らなかった判断は間違いだったのか。距離を見ると、そうは思わない。0.109.0 は 2024 年初のリリースで、0.141.1 との間には 2 年以上の変更が挟まる。yield を使う依存性の例外処理や終了タイミングの変更、Python 3.9 のサポート終了、JSON リクエストの Content-Type チェックの厳格化、router 内部構造の変更など、実際に Breaking Change として記録された変更も複数ある。今回のコードがそのすべてに影響されるわけではないが、間接依存 1 件の CVE を直すために確認すべき範囲が一気に広がる。10 本のサービスでそれを検証する時間は、そのリリース計画の中には無かった。
原因分析の一段目(ピン衝突)は正しく、二段目(上げ先が無い)は誤りで、判断(今は親を大きく上げない)は結果として同じところに落ちた。誤認を実測で訂正する過程も、そのまま書いておく。
間接依存の脆弱性は、当該ライブラリの行を書き換えれば直る、とは限らない。親のピンが上げ先を決めていて、親を動かすと変更範囲が一気に広がる。
ここで取れる手を並べる。
(c) が Aikido Libraries である。
Libraries の狙いは「大きなアップグレードを避け、CVE 修正をバックポートした版を使う」ことにある。今回は同じ版のパッチは無かったが、その考え方を使って、変更範囲を FastAPI 0.116.1 / Starlette 0.47.3 までに抑えた。破壊的変更を受け入れれば正規版で直る。だが、その検証コストを今は払えない。「最新版へ上げれば直る」は正しいが、現実的ではない。タイトルの答えはここにある。
Aikido Libraries は、既存バージョンのライブラリに CVE の修正をバックポートしたビルドを提供する仕組みだ。パッケージ名は同じで、バージョンに +aikido.N が付く。どの版が利用できるかは Library Catalog で確認できる。Python なら pip のインデックス URL を Aikido のレジストリに向け、requirements に starlette==0.47.3+aikido.5 のように書く。
カタログを見ると、starlette は 0.9 台から 1.3 まで 50 近い版が並び、それぞれにどの CVE の修正が入っているかが表示される。
今回使っていた 0.35.1 にはパッチ版が無かった。そこで、fastapi の互換範囲内で利用でき、かつ CVE-2026-48710 の修正を含む 0.47.3+aikido.5 まで上げた。fastapi は、当時 DEV で検証した 0.116.1(starlette>=0.40,<0.48)まで上げた。fastapi の方は PyPI の通常版であり、Libraries ではない。
つまり今回は「同じ版にパッチを当てただけ」ではなく、0.35.1 から 0.47.3 まで動いている。0.x 系はマイナー更新でも互換性が保証されない。Aikido が PR に添えてくれる「この範囲の破壊的変更一覧」を確認したところ、Jinja2 テンプレート、multipart のサイズ制限、TestClient の引数、WebSocket の定数、Python 3.9 のサポート終了などが並んだ。今回の MCP サーバー群は Python 3.11、これらの機能はいずれも使っていない。それを grep で確かめたうえで、影響がないことは DEV で実際に動かして確認した(後述)。
Libraries を使えば検証不要、ではない。2 年分を跳ぶより、必要な修正を含む互換範囲内の版を選び、差分を人間が検証できる大きさに抑えられる。それが Libraries の実際の価値だと思う。
AutoFix が作った PR のブランチをそのまま流用した。DEV に fetch して、中身を書き換える。
requirements.txt(10 本すべて統一)
fastapi==0.116.1
starlette==0.47.3+aikido.5
1 本だけ fastapi>=0.104.0 と緩く書かれたサービスがあり、他と入る版が違っていた。この機会に == で揃えた。
なお、Libraries 版を選んだ AutoFix は requirements.txt の 1 行目に --extra-index-url https://pypi.aikido.io/<組織トークン> を直書きしてくる。Aikido のインデックス URL には組織トークンが含まれ、これをコミットすると Aikido 自身のシークレットスキャンに引っかかる。この行は削除し、トークンはリポジトリの外に置くことにした。AutoFix の差分は完成品ではなく、リポジトリの方針に合わせて手を入れる前提で読む。
Dockerfile(pip install の直前)
ARG PIP_INDEX_URL
ARG PIP_EXTRA_INDEX_URL=https://pypi.org/simple
RUN pip install --no-cache-dir -r requirements.txt
pip は環境変数 PIP_INDEX_URL / PIP_EXTRA_INDEX_URL を pip.conf と同等に読むので、pip install の引数は触らなくてよい。
docker-compose.yml(各サービスの build)
build:
context: ./mcp-common
dockerfile: Dockerfile
args:
PIP_INDEX_URL: ${AIKIDO_PIP_INDEX_URL}
PIP_EXTRA_INDEX_URL: https://pypi.org/simple
.env(gitignore 済み)
AIKIDO_PIP_INDEX_URL=https://pypi.aikido.io/<組織トークン>
トークンは .env(DEV / UAT / PROD 各サーバーに置く)に置き、ビルド引数で渡した。この構成で DEV / UAT / PROD まで反映している。
この実装の改善点。 記事を整理する過程で Docker の公式ドキュメントを確認すると、トークンなどのシークレットの受け渡しに ARG / ENV を使うことは推奨されていない。ビルド引数の値は実行時のコンテナ環境には残らないが、docker history や provenance attestation のメタデータに露出しうる。推奨は BuildKit のシークレットマウント(RUN --mount=type=secret)で、Compose も build.secrets を正式にサポートしている。ここは別途切り替える。切り替えた時点で追記する。
AutoFix にも穴があり、CI にも穴があり、自分が書いた修正にも改善点があった。どれも確認して次へ進むしかない。
ビルドログには、starlette だけが Aikido のレジストリから取得されている様子が出る。
Collecting fastapi==0.116.1
Downloading fastapi-0.116.1-py3-none-any.whl.metadata
Collecting starlette==0.47.3+aikido.5
Downloading https://pypi.aikido.io/<token>/packages/starlette/starlette-0.47.3+aikido.5-py3-none-any.whl.metadata
...
Successfully installed ... fastapi-0.116.1 ... starlette-0.47.3+aikido.5 ...
ビルド中にこの警告が出た。
WARNING: The candidate selected for download or install is a yanked version:
'starlette' candidate (version 0.47.3+aikido.5 ...)
yanked は PEP 592 で定義された仕組みで、「公開は取り下げたが、ダウンロードはできる」状態を指す。pip は通常の範囲指定では yanked 版を候補から外し、== で厳密に指定された場合には警告を出しながら選択できる。starlette>=0.40,<0.48 のような通常の範囲指定では候補から外れ、starlette==0.47.3+aikido.5 のように exact pin した場合には警告付きで選択される。
本来は壊れたリリースを引っ込めつつ、既に固定している人のビルドを壊さないための機能だ。Aikido は Libraries のパッチ版をすべて yanked で公開しているように見える。効果は「明示的に選んだ人だけが Libraries 版を取る。範囲指定で勝手に Aikido 版に置き換わる事故が起きない」。設計として筋は通っている。
ただし、これは観測からの読みであり、Aikido がそう意図しているかは確認中である。確認が取れたら追記する。
DEV で 10 本すべてをビルドし、起動ログとサービス間の疎通(/execute の応答)を確認した。starlette の版が変わっているので、SAP Community 検索や OData メタデータの読み込みなど、実際にリクエストが流れる機能も叩いた。破壊的変更一覧に挙がった機能を使っていないことは grep で確かめてあるが、動かして確認するのは別の話である。
問題がないので、AutoFix の PR ブランチに追加コミットして push、PR をマージ。UAT へは main へのマージで自動デプロイされ、9 本が新版で起動した。PROD は手動実行のワークフローで反映した。
再スキャンの結果、Critical 0、High 0。
Libraries の話とは別に、同じ日に同じスキャン基盤の別リポジトリ(管理コンソールとマッピングツール)で見た挙動を書いておく。Aikido の SAST では、検出後に AutoTriage が入り、到達性やコードコンテキストを使って false positive の除外や重要度の再評価を行う。その評価が、同じ日に逆方向へ二度動いた。
下げた例。 管理コンソールの JavaScript に「innerHTML にユーザー由来の値を入れている」という High が出た。AutoFix が escapeHtml を足すパッチを作り、DEV で確認してマージした。ところが再スキャンしても High は消えなかった。少なくとも今回観測した範囲では、ルールは innerHTML というシンクへの代入というコード上の形を拾っており、自作のエスケープ関数をサニタイザとしては扱っていない。AutoFix が出した修正を、同じ製品のルールが消せない状態である。
もう 1 サイクル待つと、AI 評価が修正後のコードを読んで false positive と判定し、自動で Ignore に落とした。手動で Ignore する必要はなかった。

ただし同じ日、別のファイルで同じ型の指摘を手で直したものは High → Medium への格下げに留まり、解決にはならなかった。同じ対処でも扱いが揃わないことがある。
上げた例。 マッピングツールの同期用ミドルウェアで、共有トークンを === で比較している行に High が出た。タイミング攻撃の指摘である。AI 評価はこれを "easily exploitable" に格上げした。

当該ポートは Security Group で内部と 2 環境の固定 IP に絞ってあり、この構成で現実的な悪用可能性は低いと判断した。ただ、直すコストが 5 分で(AutoFix が SHA-256 ダイジェスト同士を crypto.timingSafeEqual で比べるパッチを出す)、議論するコストの方が高い。DEV でトークン有無の応答を確認してマージした。
同じ日に、管理コンソールの環境間同期で「target パラメータが宛先 URL に影響する」という SSRF の指摘も "easily exploitable" に上がった。実際には target は環境変数に登録された数個の名前にしか解決されず、任意 URL を指定できる構造ではない。これも allowlist を明示するパッチが 5 分で入るので、議論せず入れた。
今回観測した限りでは、ルールがコード上の形を拾い、AI 評価がその文脈を後から読んでいるように見える。ルールだけなら escapeHtml 入りのコードは High のまま残り、AI だけなら見落としを検証する手段がない。どちらかが偉いのではなく、両方あるから回る。そして AI の判定は下げる側にも上げる側にも動くので、格上げされた指摘を鵜呑みにはせず、しかし直すコストが小さいなら判定に付き合う。その日のうちに、4 リポジトリ中 3 つで Critical と High が消えた。

なお「1 サイクル遅れて AI 評価が走る」は今日の観測であって、仕様として確認したものではない。
Libraries が必要になるのは、次のような場合だ。
そして今回の事例から持ち帰れる教訓は、Libraries の機能とは別のところにある。
pip install --dry-run を確認し、修正後は実際に動かして確認するAutoFix を疑え、ではない。CI を疑え、でもない。AI を疑え、でもない。どの段階の結果も、次の検証に進むための材料として扱う。
「最新版へ上げれば直る」は正しい。ただし、上げる距離が現実的なときだけ実行できる。
Aikido Community Japan
公開日: 2026/9/8
最終更新: 2026/9/8
セキュリティの最新情報をお届けします。