datamodel-code-generator 脆弱性アドバイザリ12件が一括で公開された。OpenAPI/JSON SchemaからPydantic v2のモデルやdataclassを自動生成するPythonツールで、うち6件は「悪意あるスキーマを読ませると、生成されたPythonコードの中に攻撃者の文が書き込まれ、そのファイルをimportした側で実行される」という型だ。走るのはジェネレータを動かした瞬間ではない。生成物を読み込んだ、別のプロセス・別のマシン・別の人間の手元で走る。本記事では12件を4系統に整理し、公開されているソース配布物の実差分から修正内容を確認したうえで、自環境とすでに生成済みのコードの点検手順までまとめる。

datamodel-code-generatorの時系列。2026年6月8日から15日にかけて修正版0.60.1から0.64.0が順次リリースされ、約6週間後の7月29日にアドバイザリ12件が一括公開された
時系列(すべてJST換算)。修正が先行し、アドバイザリ公開は約6週間後。この間、依存関係スキャナには何も出なかった(出典: PyPIリリース履歴GitHub Security Advisories
この記事のポイント(30秒でわかる)
  • 何が公開されたか:datamodel-code-generatorの脆弱性アドバイザリ12件(CVE付き)が、2026-07-29 06:26〜06:50 JSTにまとめて公開された。11件がhigh、1件がlow。最高CVSSは8.8。
  • いちばん効くのはどれか:6件がコード注入系。スキーマの1フィールドが生成コードのimport文を破り、モジュール直下に攻撃者の文を残す。実行されるのは生成物をimportした側で、ジェネレータのホストではない。
  • 残り6件:SSRF 2件、DNSリバインディングによるSSRF保護バイパス1件、ローカルファイル読み出し2件、認証ヘッダ漏洩1件。こちらはジェネレータを動かしたホストが被害を受ける。
  • 修正版:全12件を塞ぐ最小バージョンは 0.64.0。0.63.0は11件までで、CVE-2026-55415が残る。2026-07-29時点の最新は0.71.0。
  • 上げるだけでは終わらない:すでに生成・コミット済みの .py は、ジェネレータを更新しても書き換わらない。生成物側の点検が別途必要。

まず自分の環境を確認する。コード生成ツールは、アプリケーションの依存関係に入っていないことが多い——pre-commitフックやCIワークフローの中だけに書かれている、あるいは uvx で都度取得している、というパターンが普通にある。pip show だけでは取りこぼす。

# 1) 導入バージョン(0.64.0未満なら12件のいずれかに該当)
pip show datamodel-code-generator 2>/dev/null | grep -iE "^(Name|Version|Location)"
uv pip show datamodel-code-generator 2>/dev/null | grep -i version

# 2) pip show に出ない経路を洗う(ここが本命)
grep -rnE "datamodel-code-generator|datamodel-codegen" \
  .pre-commit-config.yaml .github/workflows/ Makefile* pyproject.toml tox.ini 2>/dev/null

# 3) lockfileに固定された解決済みバージョンを確認
grep -B1 -A3 -iE '"?datamodel-code-generator"?' uv.lock poetry.lock requirements*.txt 2>/dev/null

# 4) 依存関係スキャナで確認(アドバイザリは2026-07-29 JST公開なので、今日から検出できる)
pipx run pip-audit

開発ツールとレジストリを狙う攻撃全体の見取り図は サプライチェーンセキュリティ2026|攻撃手法・防御ツール・実践チェックリスト にまとめています。

datamodel-code-generator 脆弱性12件の全体像——何が公表されたのか

datamodel-code-generatorは、OpenAPI・JSON Schema・GraphQL・Avro・Protobuf・生のJSON/YAML/CSVといった入力から、Pydantic v2モデル・dataclass・TypedDict・msgspec.Struct を生成するCLIツールだ。リポジトリの実測値は、スター 3,982、fork 449、ライセンスは MIT、対応Pythonは 3.10以上、公開リリース数は 268(いずれも2026年7月29日時点のGitHub API/PyPI JSON APIの実測値)。2019年5月に公開され、Pythonでスキーマ駆動の開発をする現場では定番の位置にある。

このツールに、CVE付きのアドバイザリが12件、同じ朝にまとめて公開された。公開時刻はGitHub Advisory Databaseの published_at2026-07-28 21:26:41Z 〜 21:50:09Z、JSTに直すと 2026-07-29 06:26〜06:50 である。OSV.dev の published フィールドも秒単位で同じ値を返し、NVDへの登録(CVE-2026-55415)も 2026-07-28 22:17:48Z と直後に続いている。

「6月に公開されていた」ように見える理由

ここで一点、混乱しやすい事実がある。リポジトリのアドバイザリ管理APIを叩くと、12件の published_at2026年6月8日〜12日 を返す。公開日が2つ存在するように見えるが、そうではない。

判定は簡単だった。OSV.dev はGitHub Advisory Databaseの公開日を取り込む。そこで、同時期(2026年6月8日〜12日)に公開された無関係のPyPI向けアドバイザリを6件選び、GitHub側の published_at とOSV側の published を突き合わせたところ、6件すべてが秒単位で完全一致した。つまりOSVの取り込み遅延は通常ゼロに近い。7週間の遅れが偶然この12件だけに起きたと考えるより、6月の日時が非公開ドラフト時代のタイムスタンプだと考えるほうが自然だ。

決定的な裏づけは、アドバイザリ本文そのものにある。CVE-2026-55415のアドバイザリには、公開を保留する旨が maintainer status として明記されている——非公開の修正PRがマージされ、修正版がリリースされるまでこのアドバイザリは未公開のままにすべきだ、という趣旨の記述だ。公開前提で書かれた文章ではない

つまり実際の順序は「修正が静かに先行し、アドバイザリは約6週間後に一括公開」。調整された脆弱性開示としては筋の通った手順だが、利用者から見ると、その間 pip-audit にもDependabotにも何も出なかったという意味になる。

この時間差には実務上の帰結がある。2026年6月にリリースノートを見ていた人は、0.60.1から0.64.0まで短期間に6回のリリースが続くのを見ていたはずだが、そこにセキュリティ上の緊急性を示すシグナルは公開されていなかった。バージョンを固定していたプロジェクトは、約44日間(0.64.0リリースからアドバイザリ公開まで)、自動検出のきっかけを持たなかったことになる。

datamodel-code-generatorのアドバイザリ12件の主要数値。CVE 12件、high 11件、最高CVSS 8.8、全件を塞ぐ最小バージョン0.64.0、パッチ先行44日
数字で押さえる12件。CVSSはいずれもGitHub Advisory Database掲載値(NVDは登録直後で独自評価が付いていない)
項目 実測値(2026-07-29時点)
対象パッケージ PyPI datamodel-code-generator
アドバイザリ件数 12件(すべてCVE採番済み)
深刻度の内訳 high 11件 / low 1件
CVSSの範囲 3.7 〜 8.8(GitHub Advisory Database掲載値)
公開日時 2026-07-29 06:26〜06:50 JST(2026-07-28 21:26〜21:50 UTC)
NVDの状態 Received(登録直後・NVD独自の分析値は未付与)
修正版リリース 2026-06-08 〜 2026-06-15 JST(0.60.1/0.60.2/0.61.0/0.62.0/0.63.0/0.64.0)
全件を塞ぐ最小版 0.64.0
最新版 0.71.0(2026-07-25 00:32 JST)
リポジトリ スター 3,982 / fork 449 / MIT / Python 3.10+

仕組み:スキーマの拡張フィールドが生成コードのimport文を破る

12件のうち中核をなすのがコード注入系の6件だ。仕組みは一貫している。スキーマの中の文字列が、検証されないまま生成コードの構文の一部として書き出される

改行1つで from X import Y の外に出る

最も分かりやすいのがCVE-2026-55415(GHSA-5578-w22f-pfx9)の経路で、アドバイザリは該当コードを行番号つきで示している。関係するのは2つの関数だ。

1つ目は src/datamodel_code_generator/imports.pyImport.from_full_path()。完全修飾パス(typing.Optional のような文字列)を受け取り、ドットだけで分割して「どのモジュールから何をimportするか」に変換する。実装はドット以外の文字を一切見ない——つまり改行文字もそのまま通す

2つ目は同ファイルの Imports.create_line()。分割結果を from {モジュール} import {名前} という文字列に組み立てて返す。ここにも検証はない。

結果として、スキーマ側で指定された「名前」の中に改行が含まれていれば、生成される .py ファイルの中で from ... import ... の行がそこで終わり、改行以降は独立した文としてモジュール直下に着地する。Pythonのモジュール直下に書かれた文は、そのモジュールがimportされた瞬間に実行される。制約はドットを含められないことだけで、これは属性アクセスを使わない組み込み関数だけで容易に回避できる、とアドバイザリは述べている。

実行されるのは「生成した人」ではなく「生成物を読み込んだ人」
この点が本件をふつうのRCEと分けている。ジェネレータを動かした時点では、攻撃者のコードはまだ実行されていない。単に .py ファイルに書き込まれただけだ。実行されるのは、その生成物を import したときである。生成コードをリポジトリにコミットする運用なら、実行するのはCI、同僚の開発機、そして本番環境ということになる。攻撃の発生地点と着弾地点が、時間的にも空間的にも離れる。

スキーマ側の入口は2つ

この改行が入り込む経路として、アドバイザリは既定設定のまま到達できる2つの拡張フィールドを挙げている。

  • x-python-import — JSON Schemaの拡張キー。参照先の型を「このモジュールのこの名前からimportする」と指定するために用意されたもので、modulename を連結して先ほどの関数に渡される
  • customTypePath — 同じく拡張キーで、独自型のパスを指定する。こちらも同じimport生成の経路に合流する

どちらも追加のCLIフラグを必要としない。既定の呼び出しでスキーマを読ませるだけで通る。

flowchart TD A["外部由来のスキーマ
OpenAPI / JSON Schema"] --> B["datamodel-codegen で生成"] B --> C{"拡張フィールドの値を
検証しているか"} C -- "0.63.0 以前: 検証なし" --> D["Import.from_full_path
ドットのみで分割・改行は素通り"] D --> E["Imports.create_line
from M import N をそのまま組み立て"] E --> F["生成された .py に
モジュール直下の文が残る"] F --> G["生成物を import した側で実行
CI・同僚の開発機・本番"] C -- "0.64.0 以降: 識別子として検証" --> H["Error を送出して生成を中断"]

残る5件のコード注入も、着地点は同じ「生成コードの中身」だ。CVE-2026-54621はGraphQLのUnion定義のdescriptionに含まれる復帰文字、CVE-2026-54653は default_factory に指定された値、CVE-2026-54654は --extra-template-datacomment に含まれる復帰文字、CVE-2026-54655は x-python-type 拡張、CVE-2026-54656は --extra-template-datavalidators エントリを経由する。入口は違うが、いずれもスキーマ/設定由来の文字列が生成コードの構文を破るという同じ形をしている

このパターンは、テンプレートエンジンが評価時にホストのオブジェクトグラフへ到達してしまう Prompty脆弱性CVSS10.0|プロンプトインジェクションとは違うSSTI→RCEの仕組みと確認手順 とは層が違う。Promptyの場合はレンダリングしたその場で実行が成立する。本件は生成の場では何も起きず、成果物という形で実行を先送りする。依存関係スキャナが見ているのはパッケージであって生成された .py ではないため、後者のほうが検出のかかりが遅い。

datamodel-code-generator 脆弱性を4系統に分けて読む——コード実行・SSRF・ファイル読み出し・ヘッダ漏洩

12件を並べると、系統ごとに「誰が被害を受けるか」が違う。ここを混ぜると対策の優先順位を誤る。

12件のCVEを4系統に分類した図。コード注入6件は生成物のimport側、SSRF系3件とファイル読み出し2件とヘッダ漏洩1件はジェネレータのホストが被害を受ける
系統ごとに着弾地点が違う。上段は生成物を読み込んだ側、下段はジェネレータを走らせたホスト
CVE 系統 入口 CVSS 影響バージョン 修正版
CVE-2026-54621 コード注入 GraphQL Unionのdescription内の復帰文字 7.8 >= 0.25.0, < 0.60.1 0.60.1
CVE-2026-54653 コード注入 スキーマの default_factory 8.8 >= 0.17.0, <= 0.60.1 0.60.2
CVE-2026-54654 コード注入 --extra-template-datacomment 7.8 >= 0.14.1, <= 0.60.1 0.60.2
CVE-2026-54655 コード注入 x-python-type 拡張 7.8 >= 0.51.0, <= 0.60.1 0.60.2
CVE-2026-54656 コード注入 --extra-template-datavalidators 7.8 >= 0.52.1, <= 0.60.1 0.60.2
CVE-2026-54690 SSRF JSON Schemaの $ref がHTTP URLを指す 8.2 >= 0.9.1, <= 0.60.2 0.61.0
CVE-2026-54691 SSRF --url にホスト/IP検証がない 8.2 >= 0.9.1, <= 0.60.2 0.61.0
CVE-2026-55389 ファイル読み出し $reffile:// とパス traversal 7.5 <= 0.61.0 0.62.0
CVE-2026-55390 ファイル読み出し XSDの schemaLocation 7.5 >= 0.59.0, <= 0.61.0 0.62.0
CVE-2026-55391 SSRF保護バイパス DNSリバインディング 7.5 <= 0.62.0 0.63.0
CVE-2026-55403 ヘッダ漏洩 クロスオリジンのリダイレクト先 3.7 <= 0.62.0 0.63.0
CVE-2026-55415 コード注入 x-python-importcustomTypePath 7.5 >= 0.11.6, <= 0.63.0 0.64.0

読み方の要点を4つ挙げる。

第一に、影響バージョンの下限がバラバラである。 >= 0.9.1 のものもあれば >= 0.59.0 のものもある。これは各機能が導入された時期の差で、たとえばXSD対応(CVE-2026-55390)は0.59.0で入ったばかりの新機能だ。古いバージョンほど該当件数が多いとは限らない——0.59.0以降だけが該当するものもある。逆に言えば「うちは古いから関係ない」は成り立たない。

第二に、修正が6段階に分かれている。 0.60.1 → 0.60.2 → 0.61.0 → 0.62.0 → 0.63.0 → 0.64.0 と、6月8日から15日にかけて順次塞がれた。途中のバージョンで止まっていると、そこから先の分は未修正のまま残る

第三に、系統によって被害を受けるホストが違う。 コード注入6件は生成物をimportした側。SSRF系・ファイル読み出し・ヘッダ漏洩の6件は、ジェネレータを実行したホストだ。CI上でスキーマからモデルを生成している構成では、後者はCIランナーが持つネットワーク到達性(内部APIやクラウドのメタデータエンドポイントなど)と、チェックアウトされたファイルが射程に入る。この観点は AsyncAPIサプライチェーン攻撃の全手口|CI設定不備からnpm 4パッケージ汚染・自環境の確認まで で扱ったCIの信頼境界の話と地続きで、実際 datamodel-code-generator はAsyncAPIのパーサも同梱している。

第四に、SSRF系は「スキーマの中身が悪意あるものでなくても」成立しうる。 $ref が外部URLを指しているだけで、ジェネレータがそのURLを取りに行く。CVE-2026-54690のタイトルは、この挙動が既定で無言で起きることを指摘している。

CVSSの出どころに注意
本記事のCVSS値はすべてGitHub Advisory Database掲載値である。NVD側は登録直後で状態が Received——独自の分析・スコア付与がまだ行われていない。社内の脆弱性管理台帳がNVDのスコアを参照する運用の場合、現時点ではスコア欄が空で入ってくる可能性がある。

CVE-2026-55415の深掘り:0.63.0→0.64.0の実差分が示す「修正漏れ」

12件のうちCVE-2026-55415だけが、他の11件と成り立ちが違う。これは最初の修正が届かなかった場所として、後から報告された1件だ。

0.61.0までの修正では、スキーマ由来の文字列が生成コードに流れ込む複数の口が塞がれた。x-python-type には型注釈として妥当かを検査する関数が、validators の設定には「ドット区切りのPython識別子か」を検査する関数が、それぞれ追加された。ところが、同じimport生成コードに合流する x-python-importcustomTypePath の2経路は、この時点で手つかずのまま残った

これを机上ではなく実物で確かめられる。PyPIから0.63.0と0.64.0のソース配布物を取得して差分を取ると、次のことが分かる。

  • 0.63.0parser/jsonschema.py には customTypePath がフィールド定義として1箇所現れるだけで、検証関数は存在しない。_validate_schema_python_import_path という名前はファイル内のどこにも無い
  • 0.64.0 では parser/jsonschema.py_validate_schema_python_import_path() が新設され、validators.py_validate_dotted_python_identifier_path() を呼び出す。適用箇所は3つ——x-python-import(1箇所)と customTypePath(2箇所)

検証の中身は素朴で、それゆえ堅い。validators.py の実装は、値をドットで分割し、各要素が str.isidentifier() を満たし、かつPythonの予約語でないことを要求する。要素数の下限は2。改行を含む文字列は isidentifier() を通らないので、この時点で弾かれる。危険な文字列をブラックリストで探すのではなく、許される形を定義して外れたものを落とす方式だ。

0.61.0の修正が兄弟の経路だけを塞ぎ、x-python-importとcustomTypePathが同じimport生成コードに到達したまま残っていた構造の図
0.61.0の修正範囲(緑)と、同じ合流点に届いたまま残った2経路(赤)。修正漏れは「直し方」ではなく「直す範囲」の問題だった

修正の置き場所が、アドバイザリの提案とは違う

もう1つ、差分を読まないと分からない事実がある。アドバイザリの「Suggested fix」は、検証を Import.from_full_path の中に集約する案を挙げていた。入口が何本あっても、最後の合流点で一度検査すれば漏れないという発想だ。

しかし実際にリリースされた0.64.0では、imports.py に識別子の検証は入っていない。同ファイルの差分はキャッシュ上限の明示(lru_cache(maxsize=4096))と、保存キー生成を _storage_key() に切り出すリファクタリングが中心で、セキュリティ上の検査は追加されていない。検証はパーサ側の3つの入口に個別に置かれた。

この設計判断の是非を断じるつもりはないが、構造上の性質は指摘できる。入口ごとに検査を置く方式は、新しい入口が増えたときに検査を付け忘れると同じ穴が再発する。実際、その後のバージョンでも入口は増えている——最新の0.71.0を確認すると、_validate_schema_python_import_path() の呼び出し箇所は5つに増えており、新たに customBasePath という別の設定項目にも同じ検証が適用されている。つまり同種の入口が後から追加され、そのつど検証を足す形で運用されている。0.64.0で入った検証が最新版でも維持されていることの確認にもなる。

観点 0.61.0までの修正 0.64.0の修正
対象 x-python-type / validators / default_factory / GraphQL union description x-python-import / customTypePath
検証の置き場所 各機能の設定・型解決の側 パーサの入口3箇所
検証方法 型注釈として妥当か/ドット区切り識別子か ドット区切り識別子か(要素数2以上)
imports.py への変更 なし なし(リファクタリングのみ)
合流点での一括検査 未採用 未採用
その後の拡張 0.71.0で customBasePath にも適用(呼び出し5箇所)

対策:0.64.0以上へ上げる、そして生成済みコードを点検する

対策は2つある。片方だけでは終わらないというのが本件の要点だ。

1. ジェネレータを0.64.0以上へ

全12件を塞ぐ最小のバージョンは 0.64.0 である。2026-07-29時点の最新は0.71.0で、修正は維持されている(前節で確認したとおり、むしろ適用範囲は広がっている)。

# pip の場合
pip install --upgrade 'datamodel-code-generator>=0.64.0'

# uv の場合
uv pip install --upgrade 'datamodel-code-generator>=0.64.0'

# pre-commit で使っている場合は rev の固定値を上げる
#   .pre-commit-config.yaml の該当 repo の rev を 0.64.0 以上のタグへ
pre-commit autoupdate --repo https://github.com/koxudaxi/datamodel-code-generator

# 反映を確認
datamodel-codegen --version
0.63.0で止めないこと
0.63.0は12件中11件を塞いだ状態で、コード実行に直結するCVE-2026-55415だけが残る。バージョン番号の並びを見ていると「0.63.0まで上げたから最新の修正は入っている」と誤読しやすいが、この1件は0.63.0の次のリリースで塞がれた。>= 0.64.0 と指定する。

2. すでに生成済みのコードを点検する

ここが見落とされやすい。ジェネレータを更新しても、過去に生成してリポジトリにコミットした .py ファイルは書き換わらない。コード注入系の攻撃は、生成物のモジュール直下に文を残す形で成立する。ファイルが残っている限り、importのたびに実行される。

確実な方法は、信頼できるスキーマから生成し直して差分を取ることだ。それが難しい場合は、生成物に「スキーマから生成されたはずのないトップレベル文」が混ざっていないかを機械的に調べられる。datamodel-code-generatorの正常な出力は、import文・クラス定義・型エイリアスの代入・関数定義でほぼ構成される。モジュール直下で関数を呼び出す式は、通常は現れない

# 生成物ディレクトリを指定して実行する(例: python check_generated.py src/generated)
cat > check_generated.py <<'PY'
import ast, pathlib, sys

root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
ALLOWED = (ast.Import, ast.ImportFrom, ast.ClassDef,
           ast.FunctionDef, ast.AsyncFunctionDef, ast.If, ast.Try)
hits = 0
for path in sorted(root.rglob("*.py")):
    try:
        body = ast.parse(path.read_text(encoding="utf-8")).body
    except SyntaxError as exc:
        print(f"[parse-error] {path}: {exc}")
        continue
    for node in body:
        if isinstance(node, ast.Expr):
            # docstring 以外の「裸の式」= import 時に評価される
            if not isinstance(node.value, ast.Constant):
                print(f"[suspect] {path}:{node.lineno} モジュール直下に評価される式がある")
                hits += 1
        elif isinstance(node, (ast.Assign, ast.AnnAssign)):
            # 代入の右辺に関数呼び出しがあれば、それも import 時に走る
            if node.value is not None and any(isinstance(n, ast.Call) for n in ast.walk(node.value)):
                print(f"[suspect] {path}:{node.lineno} モジュール直下の代入に関数呼び出しがある")
                hits += 1
        elif not isinstance(node, ALLOWED):
            print(f"[suspect] {path}:{node.lineno} {type(node).__name__}")
            hits += 1
print(f"--- 要確認: {hits} 件")
PY
python check_generated.py src/generated

このスクリプトは構文だけを見ており、対象ファイルをimportしない。疑わしい生成物を調べるときに、調べる行為そのものが実行のきっかけになっては意味がないためだ。

実際に0.71.0で無害なJSON Schemaからモデルを生成して試すと、出力は from __future__ import annotationsfrom pydantic import BaseModel ・2つのクラス定義だけで構成され、検出は0件になる。datamodel-code-generatorの正常な出力にはモジュール直下の実行文が現れない、という前提はこうして確認できる。[suspect] が出た箇所は、元のスキーマに対応する記述があるかを人間が確認する。

なお、この検査が見ているのは「モジュール直下で評価される式」と「関数呼び出しを含むモジュール直下の代入」の2つだ。0件であることはこの2つが無いことの確認であって、生成物全体の安全性を保証するものではない。確実さを求めるなら、信頼できるスキーマから生成し直して差分を取るほうが強い。

依存パッケージがインストール時に任意のコードを走らせる問題(npmが既定でpostinstallを実行しなくなった経緯は npm v12でpostinstallがデフォルト無効化へ|自システム確認コマンドと段階的な移行手順 にまとめた)と本件は、「コードが走るタイミングをどこまで削れるか」という同じ問いの別の面にある。npmの場合はパッケージマネージャ側で実行フックを止められた。生成コードの場合、importは止めようがない——だからこそ、生成物に何が書かれているかを見るしかない。

3. スキーマの出どころを棚卸しする

そのうえで、そもそもどこから来たスキーマを食わせているかを確認しておく価値がある。前提が成り立たなければ、これらの脆弱性は発火しない。

スキーマの出どころ コード注入系 SSRF・ファイル読み出し系
自社リポジトリ内の自前スキーマのみ 前提が成立しない $ref が外部を指していなければ成立しない
取引先・ベンダー提供のOpenAPI 該当しうる 該当しうる
公開レジストリ・URLから取得(--url 該当しうる 該当する
スキーマ自体を機械生成している 該当しうる 該当しうる
CI上で外部仕様を取得して生成 該当しうる 該当する(CIの到達性が射程)

CI/CDでのコード生成をどう組み立てるかという観点は AI自動化ツール|ノーコードからコードまで2026年版の比較と選び方 の自動化パイプラインの整理も参考になる。生成ステップを持つパイプラインでは、入力のスキーマを取得元ごとに固定し、生成物をレビュー対象に含めるかどうかが判断の分かれ目になる。

Pydanticモデルは構造化出力の土台——スキーマが攻撃面になる条件

最後に、AI開発の文脈でこの脆弱性群がどう効くかを整理しておく。ここは過剰に一般化せず、成立条件を明示して書く。

datamodel-code-generatorが生成するもののうち最も使われるのが Pydantic モデルであり、これは現在、LLMの構造化出力を定義する事実上の標準的な書き方になっている。ツール定義のスキーマ、レスポンス形式の指定、エージェントが受け取る引数の型——いずれもPydanticのモデルクラスを起点に組み立てるのが一般的だ。

スキーマからPydanticモデルを生成し、それがLLMの構造化出力の型定義として使われる4層の流れと、どこに信頼境界が必要かを示した図
スキーマ→生成→import→構造化出力の型定義、という流れ。信頼境界は最初の「スキーマの出どころ」に引く必要がある

ここで注意深く言うべきことがある。「AIを使っているから危ない」のではない。危ないのは、外部から受け取ったスキーマをコード生成に通す経路がある場合だけだ。そのうえで、AI開発の現場ではその経路が生まれやすい構造がある、というのが正確な言い方になる。

  • 外部APIをエージェントのツールとして統合するとき、相手方のOpenAPI仕様からモデルを生成する流れは自然に発生する
  • ツール定義のスキーマを、連携先のサーバから取得して扱う構成がある
  • スキーマ自体をモデルに書かせる、あるいは既存のドキュメントから機械的に起こす、という運用がある

これらはいずれも「スキーマの中身を人間が1行ずつ読んだわけではない」状態を作る。そしてスキーマの拡張フィールド(x- で始まるキーなど)は、そもそも人間がレビューで注視する場所ではない。型名や必須フィールドは見ても、見慣れない拡張キーの値までは追いにくい。

構図としては、人間がレビューしていると思っている対象と、実際に効力を持つ対象がずれているということだ。本件でレビューされるのはスキーマの「型定義としての意味」であって、拡張フィールドの値が生成コードの構文に与える影響ではない。前者を丁寧に読んでも、後者は視野に入らない。

現実的な線引きとしては、次のようになる。自前のスキーマだけを自リポジトリから読んでいる構成なら、今回の12件で慌てる必要は薄い(それでも0.64.0以上には上げておく)。外部由来のスキーマがコード生成に入る経路が1本でもあるなら、バージョン更新と生成済みコードの点検の両方を実施する価値がある。

参照ソース