GitHub CodeQLの2026年4月更新で最も重要なのは、models-as-dataでサニタイザとバリデータを定義できるようになったことです。これにより、社内フレームワークや独自ライブラリの「この関数を通れば安全」「この検証に通った値だけ使える」といった知識を、カスタムQLを書かずにYAMLでCodeQL分析へ反映しやすくなりました。GitHubは2026年4月21日のChangelogで、C/C++、C#、Go、Java/Kotlin、JavaScript/TypeScript、Python、Ruby、Rust向けに、この機能をCodeQL 2.25.2以降で利用できると発表しています。(The GitHub Blog)
CodeQLのアラートに対して「実際には自社のサニタイズ関数を通しているのに警告される」「バリデーション済みの分岐なのにtaint flowが止まらない」と感じていた開発者、DevOpsエンジニア、platform teamにとって、今回の更新は実務上かなり大きい変更です。単なる検出ルール追加ではなく、組織固有の安全性判断をCodeQLのデータフロー解析に載せやすくなったと考えると分かりやすいでしょう。
GitHub CodeQLの最新動向: models-as-dataで何が変わったか
今回の「CodeQL adds sanitizers and validators support in models-as-data」は、CodeQLのtaint trackingをカスタマイズする方法を広げる更新です。
これまでCodeQLで独自のサニタイザやバリデータを正確に扱うには、カスタムCodeQLコードを書く必要がありました。2026年4月の更新後は、YAMLのdata extensionファイルにモデルを追加することで、barrierModelとbarrierGuardModelを宣言できます。GitHubの説明では、サニタイザはbarrier、バリデータはbarrier guardとして扱われます。(The GitHub Blog)
| 項目 | これまで | 2026年4月更新後 |
|---|---|---|
| 独自サニタイザの扱い | カスタムQLが必要になりやすい | barrierModelでYAML定義できる |
| 独自バリデータの扱い | 分岐条件を表現するにはQL実装が必要になりやすい | barrierGuardModelで条件付きの安全判定を表現できる |
| 導入しやすさ | CodeQLに詳しい担当者が必要 | セキュリティチームと開発チームがモデルを共同管理しやすい |
| platform teamでの展開 | リポジトリごとの個別対応になりがち | model packとして組織横断で共有しやすい |
| 主な効果 | 検出精度の調整に手間がかかる | false positive削減と社内ライブラリ対応を進めやすい |
重要なのは、これは「CodeQLが自動的にすべての独自関数を安全と判断する」機能ではない点です。安全とみなす関数・メソッド・条件を、開発組織側が明示的にモデル化します。つまり、便利になる一方で、誤ったモデルを入れると本来出るべきアラートまで消える可能性があります。
models-as-dataとは何か
models-as-dataは、CodeQLの分析に必要なライブラリモデルを、CodeQLコードではなくデータとして表現する考え方です。実体としては、YAML形式のdata extensionsを使い、ソース、シンク、フローサマリ、今回追加されたbarrierやbarrier guardなどを定義します。
GitHub Docsでは、model packsは標準ライブラリで認識されていないカスタムライブラリやフレームワークをCodeQL分析に認識させるために使えると説明されています。model packはdata extensionsを含み、指定された分析に追加モデルを自動的に反映できます。(GitHub Docs)
たとえば、社内に次のような共通関数があるケースを考えます。
const safeHtml = escapeForHtml(userInput);
document.body.innerHTML = safeHtml;
実際にはescapeForHtmlがHTMLエスケープを適切に行っているとしても、CodeQLがその関数を知らなければ、ユーザー入力がinnerHTMLへ流れているように見える場合があります。models-as-dataを使うと、「escapeForHtmlの戻り値は特定のXSS系クエリではtaint flowを止めてよい」といったモデルを追加できます。
sanitizerとvalidatorの違いを理解する
今回の更新を正しく使うには、sanitizerとvalidatorを分けて考える必要があります。
sanitizerは値そのものを安全な形に変換する
sanitizerは、危険な入力を安全に使える形へ変換する関数です。CodeQLではbarrierとして表現され、barrierModelで定義します。
典型例は次のようなものです。
| 脆弱性の種類 | sanitizerの例 | 期待する扱い |
|---|---|---|
| XSS | HTMLエスケープ関数 | エスケープ後の戻り値はHTML出力で安全とみなす |
| URL redirect | 許可済みURLへ正規化する関数 | 正規化後の値だけを安全とみなす |
| Command injection | 引数を安全な形式へエンコードする関数 | 対象コマンドの文脈でのみ安全とみなす |
ただし、サニタイズは文脈依存です。HTML本文に安全な値が、JavaScript文字列、HTML属性、URLパラメータでも安全とは限りません。barrierModelを広く定義しすぎると、実際の脆弱性を見落とすリスクがあります。
validatorは条件を満たした場合だけ安全と判断する
validatorは、入力値を変換するのではなく「この値は条件を満たしているか」を判定する関数です。CodeQLではbarrier guardとして表現され、barrierGuardModelで定義します。
たとえば次のようなコードです。
if (isValid(userInput)) {
db.query(userInput);
}
この場合、isValid(userInput)がtrueを返す分岐内だけ、userInputを安全とみなしたい場面があります。barrierGuardModelでは、どの引数が検証対象か、どの戻り値なら受け入れ条件か、どのquery kindに適用するかを指定します。GitHubのJavaScript向けガイドでも、barrierGuardModel(type, path, acceptingValue, kind)として定義する例が示されています。(codeql.github.com)
| モデル | 使う場面 | 例 | 注意点 |
|---|---|---|---|
barrierModel | 関数の戻り値などが安全化される | escape(input)の戻り値 | 文脈ごとの安全性を確認する |
barrierGuardModel | 条件分岐で安全性が保証される | if (isValid(input))の中 | trueとfalseを取り違えない |
sourceModel | 新しいtaint sourceを追加する | 独自HTTP request wrapper | 今回の主役ではないが併用されやすい |
sinkModel | 新しい危険な利用先を追加する | 独自SQL実行関数 | barrierだけ入れてsinkを忘れない |
summaryModel | 関数を通るデータフローを表現する | wrapper関数の引数から戻り値 | sanitizerとは逆にflowをつなぐ |
実務で効くユースケース
社内共通ライブラリのfalse positiveを減らす
最も分かりやすい活用シーンは、社内共通のサニタイズ関数です。
たとえば、Webアプリケーション基盤チームがescapeHtml()、sanitizeUrl()、validateRedirectUrl()のような関数を提供している場合、各リポジトリで同じCodeQLアラートが繰り返し発生することがあります。これを個別にdismissするのではなく、model packとして定義すれば、分析側に「この関数はこの文脈では安全性を担保する」と伝えられます。
ただし、まずは本当にfalse positiveかを確認してください。単に「アラートが多いから消したい」という目的でbarrierを追加すると、CodeQLの価値を下げてしまいます。
独自フレームワークのセキュリティ仕様をCodeQLに教える
大規模組織では、Express、Django、Springなどの標準フレームワークだけでなく、社内独自のAPIゲートウェイ、認可ミドルウェア、入力検証レイヤーを使うことがあります。
CodeQLがそれらを標準で把握していない場合、taint sourceやsinkの追加だけでなく、今回追加されたsanitizerとvalidatorのモデル化が重要になります。
たとえば、次のような設計です。
| 社内仕様 | CodeQLで表現したい内容 |
|---|---|
HtmlSafeString.from(input)はHTMLエスケープ済み文字列を返す | barrierModelで戻り値をbarrierにする |
UrlPolicy.isAllowed(url)がtrueならリダイレクト可能 | barrierGuardModelで許可分岐を表現する |
db.safeQuery(sqlTemplate, params)はプレースホルダ付きで実行する | 必要に応じてsinkやsummaryと組み合わせて確認する |
RequestContext.currentUserInput()が外部入力を返す | sourceModelと組み合わせる |
platform teamが組織横断で展開する
platform teamやAppSecチームにとっては、model packを標準化できる点が大きなメリットです。GitHub Docsでは、default setupのリポジトリ単位では.github/codeql/extensions配下にmodel packを置く方法、組織全体では公開済みmodel packを指定して展開する方法が説明されています。(GitHub Docs)
個別リポジトリで場当たり的にアラートを閉じるより、モデルをバージョン管理し、レビューし、共通展開するほうが再現性があります。特にグローバル開発組織では、地域やチームごとに判断がぶれにくくなります。
具体例: barrierModelでサニタイザを定義する
JavaScript/TypeScriptの例では、encodeURIComponentの戻り値をHTML injection向けのbarrierとして扱う定義が示されています。形式は次のようなYAMLです。(codeql.github.com)
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: barrierModel
data:
- ["global", "Member[encodeURIComponent].ReturnValue", "html-injection"]
この定義では、3つの要素を指定しています。
| 要素 | 意味 |
|---|---|
global | グローバルオブジェクトから対象を探す |
Member[encodeURIComponent].ReturnValue | encodeURIComponentの戻り値を対象にする |
html-injection | 適用するbarrierの種類を指定する |
実務では、encodeURIComponentをそのまま真似するのではなく、自社のサニタイズ関数がどの脆弱性文脈で安全なのかを確認してから定義します。
たとえば、社内関数escapeForHtmlがHTML本文への出力に限って安全なら、考え方としては次のようになります。
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: barrierModel
data:
- ["@my-org/security-utils", "Member[escapeForHtml].ReturnValue", "html-injection"]
このようなモデルを入れる前に、escapeForHtmlがHTML属性、URL、JavaScriptコンテキストでも安全なのかを確認してください。安全でない文脈までbarrierにすると、XSSの見落としにつながります。
具体例: barrierGuardModelでバリデータを定義する
validatorは、条件分岐とセットで意味を持ちます。JavaScriptの例では、isValid(userInput)がtrueを返す場合に、SQL injection向けのtaint flowを止めるモデルが示されています。(codeql.github.com)
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: barrierGuardModel
data:
- ["my-package", "Member[isValid].Argument[0]", "true", "sql-injection"]
この定義で特に重要なのは、3列目の"true"です。これは「この戻り値の場合に安全とみなす」という受け入れ条件を表します。
Pythonの例では、Djangoのurl_has_allowed_host_and_schemeがtrueを返す場合に、URL redirection向けのbarrier guardとして扱うモデルが示されています。(codeql.github.com)
extensions:
- addsTo:
pack: codeql/python-all
extensible: barrierGuardModel
data:
- [
"django",
"Member[utils].Member[http].Member[url_has_allowed_host_and_scheme].Argument[0,url:]",
"true",
"url-redirection",
]
実務でよくある失敗は、「名前がvalidateだから安全」と判断してしまうことです。たとえば、validateLength(input)が文字数だけを見ている場合、SQL injectionやXSSの安全性は保証しません。そのような関数をbarrierGuardModelに登録してはいけません。
導入前に確認すべき判断基準
models-as-dataでsanitizerやvalidatorを追加する前に、次の観点で棚卸ししてください。
| 判断ポイント | 確認すること | NG例 |
|---|---|---|
| 安全性の根拠 | セキュリティレビュー済みの関数か | 実装を見ずに関数名だけで判断する |
| 適用文脈 | XSS、SQL injection、URL redirectなど、どの種類に効くか | すべての脆弱性に効くと広く定義する |
| 戻り値と引数 | 安全なのは戻り値か、引数か、条件分岐内の値か | Argument[0]とReturnValueを取り違える |
| 条件値 | validatorでtrueとfalseのどちらが安全側か | 否定条件を見落とす |
| 影響範囲 | どのリポジトリ、言語、query kindに反映されるか | 組織全体に未検証モデルを配布する |
| 検証方法 | 導入前後でアラート差分を確認できるか | いきなり本番のcode scanningに反映する |
特に、platform teamが全社展開する場合は、最初から組織全体へ適用するのではなく、代表的な数リポジトリで差分を確認するのが安全です。false positiveが減るだけでなく、本来残すべきアラートが消えていないかを確認してください。
導入手順: 開発チームとplatform team向け
実務では、次の順序で進めると失敗しにくくなります。
| 手順 | 作業 | 担当の目安 |
|---|---|---|
| 1 | CodeQLアラートから、同じ独自関数に起因するfalse positiveを抽出する | 開発チーム、AppSec |
| 2 | 対象関数が本当にサニタイザまたはバリデータかレビューする | AppSec、ライブラリオーナー |
| 3 | barrierModelまたはbarrierGuardModelで最小限のモデルを書く | 開発チーム、platform team |
| 4 | 小さな検証用リポジトリまたは代表リポジトリで差分を見る | DevOps、platform team |
| 5 | model packとしてバージョン管理する | platform team |
| 6 | リポジトリ単位、または組織単位で展開する | platform team |
| 7 | CodeQLやquery packの更新時にモデルを再評価する | AppSec、platform team |
CodeQL CLIでpublished model packを使う場合は、codeql database analyzeに--model-packsを指定できます。GitHub Docsでは、標準query packと組み合わせてmodel packの依存情報を分析に利用する例が示されています。(GitHub Docs)
codeql database analyze /codeql-dbs/my-company \
--format=sarif-latest \
--model-packs my-org/my-model-pack \
--output=/temp/results.sarif \
codeql/javascript-queries
default setupを使っている場合は、個別リポジトリでは.github/codeql/extensions配下にmodel packを置く方法があります。組織全体で使う場合は、GitHub Container registryに公開し、各リポジトリからアクセスできる状態にしてから設定する必要があります。(GitHub Docs)
導入時に失敗しやすいポイント
barrierを広く定義しすぎる
最も危険なのは、便利だからといってbarrierを広く定義することです。
たとえば、sanitize(input)という名前の関数があったとしても、それがHTML用なのか、SQL用なのか、URL用なのかは実装を見なければ分かりません。HTMLエスケープ関数をSQL injectionのbarrierにしてしまうと、明らかに誤ったモデルになります。
validatorの条件を逆にする
barrierGuardModelでは、受け入れ値を正しく指定する必要があります。isUnsafe(input)のように、危険な場合にtrueを返す関数を、trueで安全と定義してしまうと分析結果が壊れます。
関数名だけで判断せず、次のような条件分岐をコードで確認してください。
if (!isUnsafe(input)) {
use(input);
}
このようなケースでは、単純に"true"を指定すればよいとは限りません。
query kindを確認しない
html-injection、sql-injection、url-redirectionなどのkindは、どのクエリにモデルを適用するかを決める重要な値です。対象言語のカスタマイズガイドでサポートされるkindを確認し、実際に減らしたいアラートと対応しているかを見てください。
model packのバージョン範囲を放置する
model packはextensionTargetsで対象query packとバージョン範囲を指定します。CodeQLやquery packを更新したときに、モデルが想定どおり注入されているか確認しないと、「モデルを書いたのに効かない」という状態になり得ます。GitHub Docsでも、model packは指定したextensionTargetsの条件に合うquery packへdata extensionsを注入すると説明されています。(GitHub Docs)
今回の更新をどう評価すべきか
今回のGitHub CodeQL更新は、単なる機能追加というより、CodeQLを組織の実コードに合わせて育てるための変更です。
特に効果が出やすいのは、次のような組織です。
| 組織・プロジェクトの状態 | 期待できる効果 |
|---|---|
| 社内共通のセキュリティライブラリを使っている | 独自サニタイザやバリデータをCodeQLへ反映しやすい |
| CodeQLアラートの一部が繰り返しfalse positiveになる | レビュー済みモデルによりトリアージ負荷を下げられる |
| 複数リポジトリで同じフレームワークを使っている | model packで横展開しやすい |
| AppSecチームとplatform teamがある | セキュリティ知識をYAMLモデルとして管理しやすい |
| グローバルに開発拠点がある | 判断基準をコード化し、地域差を減らしやすい |
一方で、サニタイズやバリデーションの仕様が曖昧なプロジェクトでは、先にライブラリ仕様を整理するべきです。models-as-dataは、曖昧な安全性を自動で判断してくれる機能ではありません。正しいセキュリティ仕様を、CodeQLが理解できる形に変換する仕組みです。
まず取るべきアクション
GitHub CodeQLをすでに使っているチームは、最初に過去のcode scanningアラートを見直し、「同じ独自サニタイズ関数を通っているのに繰り返し検出される」パターンを探してください。次に、その関数がどの脆弱性文脈で本当に安全なのかをAppSecやライブラリオーナーと確認し、小さなmodel packでbarrierModelまたはbarrierGuardModelを試すのが現実的です。
platform teamは、いきなり全社展開するのではなく、代表リポジトリで導入前後のSARIFやGitHub code scanning alertsを比較し、消えるべきアラートだけが消えているかを確認しましょう。問題なければ、model packをバージョン管理し、default setupやCLIの--model-packsで展開範囲を広げます。
2026年4月のCodeQL更新は、独自ライブラリが多い開発組織ほど価値があります。アラートを単に減らすためではなく、組織のセキュリティ設計をCodeQL分析へ正しく伝えるための仕組みとして活用することが重要です。

コメント