# GitHub AI Scan は危険な Shell コードを2か所検出した。それでもチェックが成功した理由

> Canonical: https://vxi.me/ja/posts/github-ai-scan-pr-security-checks/
> Locale: ja-jp
> Published: 2026-10-16
> Updated: 2026-10-16
> Markdown: https://vxi.me/ja/posts/github-ai-scan-pr-security-checks.md

# GitHub AI Scan は危険な Shell コードを2か所検出した。それでもチェックが成功した理由

今日は公開テスト用リポジトリに [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) を作り、意図的に危険な Bash コードを2か所入れた。約81秒後、`github-advanced-security[bot]` が Files changed に2件のインラインコメントを投稿し、どちらの問題も指摘した。

ところが [GitHub Actions の実行記録 #38835686338](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338) では、`github-advanced-security` ジョブの結果は `Success`。脆弱性への指摘も、緑色のチェックも、どちらも実際の記録だ。

テストの前日、GitHub は [AI Scan for pull requests の一般提供開始（GA）](https://github.blog/changelog/2026-10-15-ai-scan-for-pull-requests-generally-available/) を発表していた。気になったのは「AI スキャナーは賢いか」ではない。**危険なコードを見つけたのに、なぜ PR のセキュリティチェックが成功するのか**という点だ。

結論は、**AI Scan のジョブが成功してもコードの安全性が認証されたわけではない**、ということ。現在の検出結果はアドバイスであり、それ自体がマージを禁止するゲートではない。

## 2つの Bash パターンで何が検出されたか

テストファイルは一時 PR の `ai-scan-test/` ディレクトリだけに置いた。本番アプリケーションのコードを変えたり、実際の認証情報やサーバーを使ったりはしていない。全文は [固定コミット `29e24722d3`](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/blob/29e24722d3f8868322da3aa28ec01c9648f90726/ai-scan-test/vulnerable-deploy.sh) で確認できる。以下は**危険性を示すために意図的に書いたコード**であり、本番では使わないでほしい。

```bash
eval "systemctl restart ${service_name}"

cmd="curl -fsSL http://${host}/${path} -o /tmp/svc.conf"
$cmd
```

1つ目はサービス再起動の処理。外部から渡された `service_name` を `eval` に組み込んでおり、組み立てられた文字列がシェルによって再びコマンドとして解釈される。名前に悪意のある構文が混ざれば、単なる「サービス名」では済まない。[17行目へのボットコメント](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992720) は CWE-78 と分類し、`eval` の削除、引数の引用符による保護、許可するサービスの制限を勧めている。

2つ目は仕組みが異なる。`curl` のコマンド全体を `cmd` に保存したあと、引用符なしの `$cmd` を実行している。変数展開時の単語分割によって、制御可能な `host` や `path` が `curl` に渡る引数やオプションを変え得る。[27行目へのコメント](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992738) は、平文の `http://` で設定ファイルを取得すると通信途中で改ざんされる危険性も指摘した。

| 場所 | AI Scan が指摘した内容 | 元のコメント |
| --- | --- | --- |
| 17行目、`eval` | 入力をシェル構文として再解釈する危険（CWE-78） | [コメント](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992720) |
| 27行目、`$cmd` | 引数・オプション注入、および平文 HTTP の危険 | [コメント](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1#discussion_r4289992738) |

ここで2件を同一の脆弱性として扱うのは正しくない。前者は入力がシェル構文として再評価される。後者で確認できる主な仕組みは**引数・オプション注入**であり、引用符がないという事実だけで「任意の独立したシェルコマンドを実行できる」とは断言できない。

もう1つ重要な制約がある。このテスト用コードは**脆弱性を隠していなかった**。[README](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/blob/29e24722d3f8868322da3aa28ec01c9648f90726/ai-scan-test/README.md) とコメントには `INTENTIONAL VULNERABILITY (CWE-78)` と明記され、`String injection` という想定カテゴリも示されている。ボットが挙げた具体的な悪用例そのものはコードに書かれていないが、こうしたヒントが検出に影響した可能性はある。今回の2/2は、ラベルのない実際の脆弱性に対する検出率ではない。

時刻は公開記録から追える。[PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) は **2026-10-16 01:32:31 UTC** に作成され、2件のコメントは **01:33:52 UTC**、差は **81秒**。これは1件の PR における「作成からコメントまで」の観測値であり、複数回の平均スキャン時間ではない。

意図的に仕込んだ2件はいずれも指摘されたが、正常なコードだけの PR を別に作る陰性対照は実施していない。したがって誤検出率も測定していない。

## 脆弱性コメントがあるのに Actions が緑色になる理由

[動的スキャンの実行 #38835686338](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338) のワークフロー名は `Code scanning AI findings on PR #1`。記録では `github-advanced-security` ジョブが **01:33:34 UTC** に `success` で終了している。ボットのコメントは **01:33:52 UTC**、つまり **18秒後** だ。

緑色のチェックが先で、脆弱性コメントが後。この順序を見るだけでも `Success` を「安全上の指摘なし」とは読めない。

![GitHub AI Scan の PR #1 を説明する図。ラベル付き Bash の危険箇所2件に助言コメントが付き、01:33:34 にジョブが成功、01:33:52 にコメントが届いた。マージ制御は別途必要](/images/posts/github-ai-scan-pr/ai-scan-advisory.ja-jp.svg)

*図は公開 [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) と [Actions の実行記録](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/actions/runs/38835686338) に基づく説明用の図であり、**GitHub 画面のスクリーンショットではない**。81秒は PR 作成からコメントまでの時間で、スキャン処理そのものの実行時間ではない。*

スキャナーが故障したわけではない。GitHub の [AI Scan 公式ドキュメント](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) は検出結果を **advisory** と明記している。これらは code scanning check を失敗させず、AI Scan の指摘だけを Ruleset の強制マージ禁止条件にすることもできない。レビューの着眼点を知らせる役割であって、マージ可否を決定する役割ではない。

| GitHub で見えるもの | 本当に示していること |
| --- | --- |
| AI Scan の行単位コメント | リスクの可能性。作者・レビュアーが評価するもので、直接はマージを止めない |
| AI Scan 動的ジョブの `Success` | スキャン処理は完了した。警告コメントがあっても緑になる |
| CodeQL などの対応スキャン＋Ruleset | 実際の設定によってはマージ条件を強制できる。設定が動くか要検証 |
| Required checks と人間によるレビュー | 失敗・未実行のチェックや未承認時にマージを拒否するかはリポジトリの設定次第 |

GitHub は別途 [Code scanning merge protection の設定方法](https://docs.github.com/en/code-security/how-tos/find-and-fix-code-vulnerabilities/manage-your-configuration/set-merge-protection) を説明している。「高リスクな指摘が残っていたらマージしない」ことが目的なら、AI Scan の助言を required check と勘違いせず、強制力のあるルールを構成する必要がある。

今回の危険な PR で、Ruleset によるマージ拒否を実際に試したわけではない。したがって `Success` から「GitHub はあらゆる脆弱性を含む PR をマージさせる」と結論づけることはできない。確認できたのは**2件の助言コメントと成功した AI Scan ジョブが共存した**という事実だ。

## 有効化する前に：CodeQL、利用条件、課金

使用したのは個人アカウントの**公開リポジトリ**。[2026年10月の GA 公式説明](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) によれば、個人の公開リポジトリでは使えるが、個人の非公開リポジトリには対応しない。組織の公開リポジトリは対象で、組織の private/internal リポジトリには GitHub Code Security または GitHub Advanced Security の該当ライセンスが必要だ。AI Scan 自体のために別途 Copilot ライセンスを購入する必要はない。

CodeQL default setup も必須ではない。テスト時、状態は `not-configured` と記録されたまま、AI Scan は2件のコメントを投稿した。[公式説明](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) でも、CodeQL default setup や CodeQL 解析の成功は前提条件になっていない。

また、これは過去を含むリポジトリ全体のスキャンではなく、対象となる PR の差分を見る仕組みだ。Shell/Bash、Terraform HCL、Dockerfile など、CodeQL が扱わない一部のケースにも対応する。一方、Fork や Dependabot が作成した PR は、この GA の対象範囲に含まれない。そうした PR を多く使うチームは別の検査・レビュー手段も必要になる。

有効化するときはリポジトリの Code scanning 設定で AI Scan のスイッチを確認する。組織の場合は組織ポリシーとセキュリティ機能の許可も確認しておく。

GA 後の利用量は **Code Security AI Credits** で計量される。負担するアカウント、含まれる枠、予算超過時の制限は [GitHub の課金ポリシー](https://docs.github.com/en/code-security/concepts/code-scanning/ai-powered-security-detections) に従う。今回の最終請求額は確認できていないので、`$0` だったとは書かない。PR の多いリポジトリほど Billing でクレジットと予算上限を先に見ておきたい。

## 危険な変更を本当にマージさせないために

[2件のコメント](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) が示す修正方針は基本に忠実だ。引数を `eval` に渡し直さず、シェルコマンド全体を文字列として組み立てて展開しない。呼び出しの形なら、例えば次のようにする。

```bash
# 调用形式示意：service_name 和 url 仍需单独校验
systemctl restart "$service_name"
curl -fsSL "$url" -o /tmp/svc.conf
```

これは**修正の方向を示した例で、再検証済みの安全なパッチではない**。`service_name` には許可リストが必要で、ダウンロード URL には HTTPS の強制、許可するホストやリダイレクトの制限、設定ファイルの重要度に応じた整合性確認が必要だ。

リポジトリでは、CodeQL や実際にマージを止められるスキャナーを適切な Ruleset／required checks に結びつけ、保護対象ブランチでチェックの失敗や欠落が本当に拒否されるか試しておく。AI Scan の助言コメントは人が確認し、動的ジョブが緑色という理由だけで読み飛ばさない。

テスト後は [PR #1](https://github.com/kuangyongjie/cliproxyapi-codex-deploy/pull/1) をマージせずに閉じ、テストブランチも削除した。公開リポジトリの `main` は元の `87624c26a5e6` のままだった。所有者の REST 応答では AI Scan の設定が `disabled` に戻ったことも確認している。PR を閉じても過去のコメントや固定コミットは残り、リンクから確認できる。

このラベル付きの小さな陽性テストでは、AI Scan は行単位で有用なリスクを示した。ただし**スキャンジョブの成功と安全なマージの強制は別の問題**だ。前者は問題の発見を助け、後者はリポジトリ側で設定し、動作を検証する必要がある。

関連する別の境界として、Git push の前に AI レビューが失敗した場合の挙動は [OpenQodex のローカル pre-push Hook 実測](/posts/openqodex-prepush-review-fail-open/) にまとめている。こちらは実行位置もゲートのリスクも異なる。

*実測日：2026年10月16日（UTC）。*
