記事一覧に戻る
guide2026年9月8日27分で読めます

Aikido Libraries が必要な場合 — 「最新版へ上げればいい」が現実的ではないとき

間接依存 starlette の CVE に AutoFix が出した PR は CI 緑でもインストールできなかった。原因分析の誤りも実測で訂正しつつ、Aikido Libraries で変更範囲を抑えて本番まで反映した実録。AutoTriage が重要度を上下させた例も併記。

Aikido LibrariesAutoFixstarletteFastAPICVE-2026-48710間接依存pipyankedAutoTriageSAST

Aikido Libraries が必要な場合 — 「最新版へ上げればいい」が現実的ではないとき

1. Critical が出た

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 が緑になり、マージして終わる話だ。

そう思っていた。

issue 一覧。Critical 2 件、High 1 件が並ぶ画面

Reachability 分析を開くと、starlette は requirements.txt に直接書かれていなかった。fastapi==0.109.0 が連れてくる間接依存である。この一点が、後で全部の分岐点になる。

2. starlette は何をしているのか

FastAPI を使っていても starlette を意識することは少ない。役割分担を整理しておく。

  • uvicorn はサーバー。ソケットを開き、HTTP を受けて ASGI の形でアプリに渡す
  • starlette は Web フレームワークの土台。ルーティング、Request / Response、ミドルウェア(CORS、TrustedHost など)、WebSocket、バックグラウンドタスク
  • fastapi はその上に載る層。pydantic による入力バリデーション、依存性注入、OpenAPI の自動生成

つまり @app.get("/execute") で受けた HTTP の URL やヘッダーを解釈しているのは starlette であって、FastAPI ではない。Host ヘッダーの穴が starlette 側にあるのはそのためだ。

そして FastAPI は、Request クラスや例外処理の内部 API に直接依存しているため、starlette のバージョンを狭くピン留めしている。fastapi 0.109.0 の要求は starlette>=0.35.0,<0.36.0。エンジンと計器盤の関係で、計器盤は特定のエンジンにしか付かない。

3. AutoFix の PR は緑だった

AutoFix の差分は、requirements.txt の末尾に starlette==1.0.1 を 1 行追記するものだった。Create PR を押すと PR ができ、CI が走り、"All checks have passed" と表示された。

PR 画面。All checks have passed、Merge pull request ボタン

AutoFix の PR は緑だった。 でも、その修正はインストールできなかった。

この CI の validate ジョブは 7 秒で終わる。構文チェックと軽い検証だけで、pip install を実行していない。依存関係の解決に成功したかどうかは、この緑には含まれていない。

AutoFix が出した修正案だけでは依存関係を解決できなかった。そして CI もそれを検出しなかった。ここで重要なのは、どちらかを責めることではなく、PR が緑であることと、実際にインストールして動くことは別だという点である。緑のボタンを押す前に、そのジョブが何をしているかを一度見る。今回はそれが命綱になった。

4. DEV で試したら通らない — そして私の読みも間違っていた

マージすると 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

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 ...

ピンを外した dry-run。fastapi-0.141.1 / 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 本のサービスでそれを検証する時間は、そのリリース計画の中には無かった。

原因分析の一段目(ピン衝突)は正しく、二段目(上げ先が無い)は誤りで、判断(今は親を大きく上げない)は結果として同じところに落ちた。誤認を実測で訂正する過程も、そのまま書いておく。

間接依存の脆弱性は、当該ライブラリの行を書き換えれば直る、とは限らない。親のピンが上げ先を決めていて、親を動かすと変更範囲が一気に広がる。

5. 選択肢を整理する

ここで取れる手を並べる。

  • (a) fastapi のピンを残したまま starlette だけ 1.0.1 に上げる — pip が拒否する。不可能
  • (b) fastapi を 0.141.1 まで上げ、starlette 1.0.1 を正規版で入れる — 正攻法。ただし 2 年分の変更をまたぎ、その間にある複数の Breaking Change を含めて 10 本のサービスで影響を検証することになる。今のリリース計画には収まらない
  • (c) fastapi を互換範囲内で上げ、その範囲に入る starlette の版で CVE 修正だけを当てた版を使う

(c) が Aikido Libraries である。

Libraries の狙いは「大きなアップグレードを避け、CVE 修正をバックポートした版を使う」ことにある。今回は同じ版のパッチは無かったが、その考え方を使って、変更範囲を FastAPI 0.116.1 / Starlette 0.47.3 までに抑えた。破壊的変更を受け入れれば正規版で直る。だが、その検証コストを今は払えない。「最新版へ上げれば直る」は正しいが、現実的ではない。タイトルの答えはここにある。

6. Aikido Libraries

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 の実際の価値だと思う。

7. 実装

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 ...

8. yanked の警告

ビルド中にこの警告が出た。

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 がそう意図しているかは確認中である。確認が取れたら追記する。

9. DEV → UAT → PROD

DEV で 10 本すべてをビルドし、起動ログとサービス間の疎通(/execute の応答)を確認した。starlette の版が変わっているので、SAP Community 検索や OData メタデータの読み込みなど、実際にリクエストが流れる機能も叩いた。破壊的変更一覧に挙がった機能を使っていないことは grep で確かめてあるが、動かして確認するのは別の話である。

問題がないので、AutoFix の PR ブランチに追加コミットして push、PR をマージ。UAT へは main へのマージで自動デプロイされ、9 本が新版で起動した。PROD は手動実行のワークフローで反映した。

再スキャンの結果、Critical 0、High 0。

10. 余談 — AI 評価は両方向に動く

Libraries の話とは別に、同じ日に同じスキャン基盤の別リポジトリ(管理コンソールとマッピングツール)で見た挙動を書いておく。Aikido の SAST では、検出後に AutoTriage が入り、到達性やコードコンテキストを使って false positive の除外や重要度の再評価を行う。その評価が、同じ日に逆方向へ二度動いた。

下げた例。 管理コンソールの JavaScript に「innerHTML にユーザー由来の値を入れている」という High が出た。AutoFix が escapeHtml を足すパッチを作り、DEV で確認してマージした。ところが再スキャンしても High は消えなかった。少なくとも今回観測した範囲では、ルールは innerHTML というシンクへの代入というコード上の形を拾っており、自作のエスケープ関数をサニタイザとしては扱っていない。AutoFix が出した修正を、同じ製品のルールが消せない状態である。

もう 1 サイクル待つと、AI 評価が修正後のコードを読んで false positive と判定し、自動で Ignore に落とした。手動で Ignore する必要はなかった。

AI が false positive と判定した行

ただし同じ日、別のファイルで同じ型の指摘を手で直したものは High → Medium への格下げに留まり、解決にはならなかった。同じ対処でも扱いが揃わないことがある。

上げた例。 マッピングツールの同期用ミドルウェアで、共有トークンを === で比較している行に High が出た。タイミング攻撃の指摘である。AI 評価はこれを "easily exploitable" に格上げした。

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 が消えた。

リポジトリ一覧(名前はマスク)。上から 4 行のうち 3 行が Critical 0 / High 0

なお「1 サイクル遅れて AI 評価が走る」は今日の観測であって、仕様として確認したものではない。

11. まとめ — Aikido Libraries が必要な場合

Libraries が必要になるのは、次のような場合だ。

  • 脆弱なライブラリが間接依存で、正規の修正版へ行くには親フレームワークまで大きく上げる必要がある
  • その親の更新に破壊的変更が何段も挟まり、検証コストを今のリリース計画では払えない
  • 親側の追従や修正版そのものを待つ時間がなく、Critical を抱えたままにできない

そして今回の事例から持ち帰れる教訓は、Libraries の機能とは別のところにある。

  • AutoFix の PR は完成品ではなく、修正案である
  • CI が緑でも、その CI が依存解決を試していなければ、インストール可能とは限らない
  • 間接依存の脆弱性では、まず DEV で依存グラフと pip install --dry-run を確認し、修正後は実際に動かして確認する
  • 自分の原因分析も実測で確かめ直す。「上げ先が無い」という読みは、確かめ直したら間違っていた
  • 自分の実装も公式ドキュメントに当てる。ビルド引数でトークンを渡す形は、推奨に反していた
  • SAST の AI 評価は下げる側にも上げる側にも動く。格上げされた指摘は鵜呑みにせず、直すコストが小さいなら判定に付き合う方が早い

AutoFix を疑え、ではない。CI を疑え、でもない。AI を疑え、でもない。どの段階の結果も、次の検証に進むための材料として扱う。

「最新版へ上げれば直る」は正しい。ただし、上げる距離が現実的なときだけ実行できる。

キーワード

  • Aikido Libraries
  • AutoFix
  • starlette
  • FastAPI
  • CVE-2026-48710
  • 間接依存
  • pip
  • yanked
  • AutoTriage
  • SAST
A

Aikido Community Japan

公開日: 2026/9/8

最終更新: 2026/9/8

シェア:

ニュースを購読

セキュリティの最新情報をお届けします。

Aikido Community Japan からのお知らせの受信に同意します。詳しくはプライバシーポリシーをご確認ください。