AI

「動いた」は安全の証明ではない — AI 生成コードを静的スキャンで検証する運用

AI が書いたコードは「動いた」時点が一番危ない。鍵の直書きやコマンドインジェクションが、動くという事実に隠れるからです。自作スキャナの知見をきっかけに、AI 起稿コードを人間レビューと静的解析の二重ゲートに通す運用を、運用者目線でまとめます。

SoSoraEndo2026年6月15日 12:057 min2,115

動画で読む

まず結論 — 「動いた」は安全の証明ではない

結論から書きます。AI が生成したコードは、「動いた」時点こそ一番危ない。動くという事実が、中に刺さった鍵やコマンドインジェクション(外部入力がそのままコマンドとして実行される穴)を覆い隠してしまうからです。運用者がやるべきは出力を信じることではなく、人間レビューと静的スキャン(コードを実行せず構造だけから問題を探す解析)の二重ゲートに必ず通すことです。

きっかけは、AI 生成コードのリスクを自作スキャナで点検した Zenn の「なぜAI生成コードは危ないのか」 という記事でした。著者は LLM ではなく決定的な静的解析を選び、14 カテゴリ・93 ルールで検査して、サンプルの「ほぼ全部に鍵が刺さっている」と書いています。私はこれを、Cursor や Claude にコードを書かせる日々の自分への警告として読みました。速く大量に出てくる出力ほど、確認の密度は下げてはいけない。

AI 生成コードに何が刺さっているのか — 速くて、大量で、もっともらしい

AI 生成コードの危うさは、珍しいバグではなく、ありふれた基本の抜けが大量に混ざる点にあります。難解な脆弱性ではなく、レビューなら 10 秒で気づくはずの初歩が、大量生成のスピードに紛れて本番へ流れる。これが一番こわい。

先の記事や、私自身が AI 起稿コードのレビューで実際に踏んだものを並べると、頻出パターンはだいたい 4 つに収まります。

  1. API キーやトークンのコード直書き(環境変数にすべき値がそのまま文字列で埋まる)
  2. ユーザー入力を検証せずシェルコマンドや SQL に渡す(インジェクションの王道)
  3. SSL 証明書の検証を無効化する(verify=False のような「とりあえず通す」設定)
  4. エラーハンドリングの欠落、または例外を握りつぶす空の catch

どれも単体では地味です。問題は密度で、AI は人間より速く、大量に、しかも「もっともらしく」これらを生産します。読んで違和感がないコードほど、レビュアーの目が滑る。もっともらしい出力ほど中身の検証が要るという話は、フロンティア LLM は事実判定の 67% で割れるで書いたのと地続きです。

なぜ LLM ではなく決定的な静的解析で検証するのか

検証する側の道具は、確率的ではなく決定的(同じ入力に必ず同じ結果を返す)であるべきです。生成を AI に任せたなら、せめてチェックは毎回同じ判定を返すものに固定する。ここがブレると、ゲートそのものが信用できなくなります。

理由は単純で、検証ツールに LLM を使うと、同じコードを 2 回見ても結果が変わりうるからです。今日は鍵を見つけ、明日は見逃すスキャナは、ゲートとして機能しません。だから秘密情報の検出には gitleaks、構造的なパターン検出には Semgrep のような、ルールベースで結果が再現するツールを土台に置きます。AI 起稿の脆弱性探索を別途やるとしても(Anthropic の脆弱性発見ハーネスのような取り組みは有望です)、合否を決める最終ゲートは決定的なルールに任せるのが私の方針です。生成は確率的でよい。判定は確定的でなければ困る。

運用に AI コードゲートを組み込む — 私が回している順番

AI 起稿コードを本番に通すゲートは、特別な仕組みではありません。既存の CI に 3 段足すだけです。私は「秘密情報スキャン → 静的解析 → 人間の差分レビュー」の順で、必ずこの順番で回しています。

順番には理由があります。秘密情報は混入した瞬間に履歴へ残り、後から消すのが一番面倒なので最初に弾く。次に Semgrep でインジェクションや証明書無効化などの構造的な穴を拾う。最後に人間が差分(diff)を読む。ここで大事なのは、人間レビューを最後に置くこと、そして AI が書いた量に対して時間をケチらないことです。

正直に失敗談を書きます。少し前、Cursor に書かせた API クライアントを「テストも通っているし」とほぼ流し読みでマージしたら、外部 API のトークンが定数として直書きされていました。動いていたので気づきにくい。幸い公開リポジトリではなかったものの、ヒヤリとして、その日のうちに gitleaks を pre-commit フックに入れました。以来、秘密情報の検出だけは人間の集中力に頼らず、コミットの前で機械に止めさせています。順番を「機械を先、人間を後」に変えただけで、見落としが目に見えて減りました。ちなみに、半年前の自分が書いたコードを読み返すと AI のせいにできない雑さも普通に出てくるので、これは AI 限定の話ではないのかもしれません。

線引き — AI を責める話ではなく、レビューを仕組みにする話

最後に線を引きます。これは「AI にコードを書かせるな」という話ではありません。問題は生成そのものではなく、生成物を検証せずに通すことにある。その一点だけです。

AI による生成は、もう日々の開発から外せない速度を与えてくれます。だからこそ、速さに見合うだけの検証を仕組み側に持たせる。人間の注意力は波があるので、秘密情報スキャンと静的解析という機械の網を先に張り、人間は機械が拾えない設計や意図のズレに集中する。役割を分けると、速さと安全は両立できます。

ちなみにこの AetherEchoes も、AI が記事の下書きを書き、私が公開前に確認・編集して出しています。文章もコードも、AI が起稿して人間が検証して通す、という構図は同じです。生成を任せるほど、検証を仕組みにする責任は重くなる。Zenn の記事が運用者に残す教訓は、たぶんそこに尽きます。

よくある質問

AI が生成したコードのどこが一番危ないのですか?
珍しい脆弱性ではなく、APIキーの直書きや入力未検証のコマンド実行、証明書検証の無効化といった初歩的な抜けが大量に紛れる点です。コードが「動く」と問題が隠れて見落としやすく、生成のスピードに対して検証の密度が下がることが最大のリスクになります。
なぜ検証に LLM ではなく静的解析を使うのですか?
検証ツールは同じコードに同じ判定を返す決定的さが必要だからです。LLM は実行ごとに結果が揺れるため、合否を決めるゲートには向きません。生成は確率的でよくても、判定は gitleaks や Semgrep のような再現するルールベースに任せるのが安全です。
AI 起稿コードを通すゲートはどう組めばよいですか?
既存の CI に「秘密情報スキャン → 静的解析 → 人間の差分レビュー」を足すだけで十分です。秘密情報は混入した瞬間に履歴へ残るので最初に弾き、構造的な穴を静的解析で拾い、機械が拾えない設計や意図のズレを最後に人間が見ます。

参考文献

  1. なぜAI生成コードは危ないのか — スキャナを作って見えた現実(Zenn / numarn)
  2. gitleaks — Find secrets in code and git history
  3. Semgrep — Static analysis for finding bugs and security issues

Reaction

Share

X (Twitter)