Chuyển đến nội dung chính
友田 陽大
Vibe coding: shipping AI-generated code safely
バイブコーディング
AI駆動開発
セキュリティ
TypeScript

AI生成コードのレビュー術 — 自分が書いていないコードを自分の責任で出荷する

AIが生成したコードを『上から順に読む』レビューは効きません。diffを開いて最初に疑う4つの境界——入力・認可・秘密・依存——を先に見るレビュー手順を、一人×生成AIの実務から解説。grepで機械化する箇所と、人間にしか下せない認可判断の切り分け、npx @aegiskit/cli scan での自動化を示します。

Published
Reading time
14 min read
Author
友田 陽大
Chia sẻ

AI生成コードのレビューは、上から順に読んではいけない。最初に見るのは「境界」——入力・認可・秘密・依存——であって、ロジックの正しさはその後だ。 なぜなら、AIが書いたコードが本番で漏れる原因は、ほぼ全てこの4つの境界に集中しているからだ。

私は一人 × 生成AI(Claude Code)で、経済産業大臣賞を受賞したB2B SaaS、本番二重課金0件の決済基盤、放送局向けのAIプラットフォームを作ってきました。生成の速度は手放せません。しかし——mergeボタンを押した瞬間、そのコードの作者は私になります。 git blame に残るのはAIの名前ではなく、私の名前だ。だから「AIが書いたから」は本番障害の言い訳になりません。書いていないコードを、自分の責任で出荷する。そのための技術が、レビューです。

本記事は、AI生成コードのdiffを開いて「最初に疑う場所」を先に見るレビュー手順を、実務から体系化します。「なぜAIが脆弱性を量産するのか」の原理と診断ツールの全体像はAI生成コードの脆弱性診断(4層診断)に、安全なコードの書き方のカタログはセキュアコーディングの実践(NIST SSDF / OWASP ASVS)に譲り、ここでは**「読む順番」と「疑う場所」**に集中します。


1. 「自分が書いていないコード」をレビューするのは、書くのとは別の技能だ

コードを書くとき、人は書きながら設計モデルを頭の中に積み上げます。「この値はここで検証済み」「この分岐はここで潰した」という前提が、手を動かすたびに更新される。だからバグに気づく。

ところがAIにコードを書かせると、その設計モデルが自分の中に無いまま、流暢な完成品だけが降ってきます。 ここに罠があります。AIの出力は——変数名は綺麗、コメントは丁寧、構造は整っている。読みやすい。 そして人間の脳は、読みやすさを「正しさ」や「安全さ」と取り違えます。これは研究でも裏付けられていて、Veracodeの2025年の調査では、AI生成コードの45%が既知のセキュリティ欠陥を含み、モデルが賢くなってもこのセキュリティ成績は横ばいでした。流暢さと安全さは相関しません。

だからレビュアーの問いは「これは正しそうに見えるか」ではありません。問うべきは常にこれです——

不正な入力・同時アクセス・他人のIDを指す正規リクエストが来たとき、このコードはどう壊れるか。

この問いは、コードを上から下へ読んでも答えが出ません。ハッピーパスの美しいロジックを追っている間に、集中力は尽き、肝心の境界に着く前にレビューが終わります。読む順番を、境界から始める必要があります。


2. 読む前に疑う — 4つの境界

AIが本番で作り込む穴は、驚くほど同じ場所に出ます。私はdiffを開いたら、ロジックより先に次の4境界をこの順で探します。順番は「壊れたときの被害半径(blast radius)」の大きい順です。

境界AIがやりがちなミスdiffで最初に探すもの
① 入力境界外部入力を検証せずDB・シェル・HTMLへ直挿入文字列連結のクエリ、dangerouslySetInnerHTML、Zodなしのハンドラ
② 認可境界認証はするが認可しない(他人のIDでも通る)所有者で絞らないfindUniqueUSING (true)のRLS
③ 秘密境界鍵のハードコード、クライアントへの秘密の漏出sk_liveAKIAprocess.envの直参照、NEXT_PUBLIC_の誤用
④ 依存境界存在しない/不要なパッケージの追加package.jsonの差分に増えた見覚えのない依存

「4境界を先に見る」——これがこの記事の全てです。以下、境界ごとにdiffで何をgrepし、何を良/悪と判断するかを示します。


3. 入力境界 — 「この値はどこから来たか」を最初に問う

AIは「入力は正しい形で来る」という楽観のもとにコードを書きます。だから境界で検証せず、外部入力をそのまま危険なシンク(DB・シェル・HTML・URL)へ流します。

diffを開いたら、まず外部入力がどこから入り、検証を通らずにどこへ着くかを追います。

# 入力境界: 検証を通らず危険なシンクへ着く経路を洗う
rg -n "dangerouslySetInnerHTML|child_process|new Function\(" --type ts
rg -n "`SELECT .*\$\{|`.*\+ *req\.|query\(.*\+" --type ts   # 文字列連結のSQL

ルートハンドラのdiffでは、入口にスキーマ検証があるかを最初に見ます。無ければ、その下のロジックがどれだけ綺麗でも「未検証の値が全体に流れている」ということです。

 export async function POST(req: Request) {
-  const body = await req.json();               // 何でも通る
-  const user = await db.user.create({ data: body });
+  const body = ParsedInput.parse(await req.json()); // 境界で型と形を固定
+  const user = await db.user.create({ data: body });
   return Response.json(user);
 }

ポイントは、検証を「境界」に集約することです。ハンドラの奥で個別にifチェックを散らすのではなく、入口でZodスキーマに通し、以降は型で保証された値だけが流れる状態にする。個々の注入クラス(SQLi / XSS / SSRF / パストラバーサル)の潰し方そのものはセキュアコーディングの実践記事に譲ります。レビューで見るべきは一点——「この値、境界で検証されているか?」 です。


4. 認可境界 — 「認証はするが認可しない」を狙い撃つ

ここが最重要かつ最頻出です。AIは**認証(ログイン済みか)は書きます。しかし認可(その人がそのデータの持ち主か)**を平気で省きます。結果、ログインさえしていれば他人のリソースにアクセスできる——IDOR(オブジェクト単位の認可欠陥 / BOLA)が生まれます。これはOWASP API Security Top 10で2019年以来ずっと第1位の最頻出リスクです。

OSSのAegisの言葉を借りれば、セキュリティには**アプリ横断で一律に効く「水平の統制」と、「誰が何を所有するか」に依存する「縦のリスク」**があります。入力検証やヘッダーは水平——ライブラリが肩代わりできる。しかし認可は縦——あなたの事業のデータモデルに依存するので、ライブラリには直せません。 だからレビュアーが最も鋭く見るべき境界がここです。

diffで探すのは「idで引いているのに、所有者で絞っていない」パターンです。

# 認可境界: id で引いて所有者スコープが無い箇所を洗う
rg -n "findUnique\(|findFirst\(|\.eq\('id'" --type ts -A2 \
  | rg -v "user_?id|owner|auth\.uid|session"
 export async function GET(req: Request, { params }: { params: { id: string } }) {
-  // ❌ 認証はするが認可しない:ログイン済みなら「他人の文書」も返る
-  const doc = await db.document.findUnique({ where: { id: params.id } });
+  const { userId } = await requireSession(req);        // 認証
+  const doc = await db.document.findFirst({
+    where: { id: params.id, ownerId: userId },          // 認可=所有者で絞る(縦の統制)
+  });
   return Response.json(doc);
 }

Supabaseなら、RLSポリシのdiffを必ずSQLで読む

TypeScriptだけ見ても認可境界は分かりません。認可がSQLで書かれているからです。supabase/migrations/**.sql のdiffを開き、ポリシのUSING句を読みます。

-- ❌ 認証はするが認可しない:ログイン済みなら全員の行が見える
create policy "read" on documents for select
  using ( auth.role() = 'authenticated' );

-- ✅ 行を所有者に絞る(縦の統制)
create policy "read own" on documents for select
  using ( auth.uid() = owner_id );

レビューの勘所を3つ、Aegisの検証観点に沿って挙げます。

  1. USING (true) は「そのテーブルが本当に公開でよい」場合のみ正しい。 公開プロフィールや公開記事ならtrueは正解です。しかし請求書やチャットにtrueが付いていたら赤信号。「認証はするが認可しない」の典型なので、認証と認可の違い(所有者スコープ)を実コードで解説した記事で全パターンを確認してください。
  2. service_role 経路には所有権チェックが要る。 Supabase公式ドキュメントの通り、service_roleキーはPostgreSQLのBYPASSRLSで動き、RLSを完全に無視します。守りの境界が「鍵の置き場所」と「サーバー側の明示的な所有権チェック」に移るので、service_roleを使うルートはownerId検証がコード側にあるかを必ず見ます。
  3. SECURITY DEFINER 関数は search_path を固定しているか。 固定しないと権限昇格の温床になります。

これは机上の話ではありません。AIビルダー製アプリのRLS未設定により、未認証で他人のデータを読み書きできた事例がCVE-2025-48757(CVSS 9.3 CRITICAL)として登録されています。そして規模感として——公開Supabaseアプリ1,000件の一次調査で、RLSを持つアプリのうち約9.2%が『認証はするが行を所有者に絞らない』ポリシを持っていた(公開リポジトリ標本ゆえ下限値。所有者非スコープ=即脆弱ではなく要確認)という結果があります。手法と全数はSupabase RLSの実地調査にまとめました。10件に1件近く、この境界に穴が開いている前提でレビューすべきです。


5. 秘密境界 — 鍵とenvの衛生

AIは、動かすことを優先して鍵を平気でハードコードします。また、Next.jsのように「サーバー/クライアントの境界」があるフレームワークでは、秘密をクライアントバンドルに漏らすミスをします。

# 秘密境界: 混入した鍵・トークンと、env の直参照を洗う
rg -n "sk_live|AKIA[0-9A-Z]{16}|-----BEGIN|ghp_[0-9A-Za-z]{36}" --type ts
rg -n "process\.env\.[A-Z_]+" --type ts        # 型なしの env 直参照

レビューで見る観点は2つです。

  • 鍵はコードに存在してよいか。 答えは常にNo。envか秘密管理へ。ハードコードされた鍵がdiffにあれば無条件でブロックし、しかもgit履歴から失効させる必要があります(コミットに残った鍵は消しても履歴に生きています)。
  • NEXT_PUBLIC_ の付け間違いはないか。 NEXT_PUBLIC_ を付けた値はクライアントバンドルに焼き込まれます。ここに秘密鍵が入っていたら世界公開です。envは型付きの境界モジュールを1つ通すprocess.envの直参照を禁止する)と、この事故を構造的に防げます。

6. 依存境界 — package.json の差分を必ず読む

AIはハルシネーション(幻覚)で存在しないパッケージ名を提案します。攻撃者がその幻覚名を先回り登録すると、AIの幻覚がそのままサプライチェーン攻撃になる——これが「slopsquatting」です(仕組みと防御は4層診断記事で詳説したので、ここでは繰り返しません)。

レビュー手順としては単純です。package.json の差分を、機能の差分と同じ真剣さで読むこと。

git diff package.json package-lock.json
npm view <新しく増えたパッケージ名>   # 実在するか・極端に新しくないか・DL数

見覚えのない依存が1つでも増えていたら、「それは本当に実在し、まともか」を人間が一度確認する。lockfileを信頼の唯一の源にし、CIではnpm ciで完全一致を強制する。依存の増加は、機能ではなく攻撃面の増加として見る——これがレビュアーの構えです。


7. レビューを機械に肩代わりさせる — grep と npx @aegiskit/cli scan

ここまでのgrepを毎回手で叩くのは続きません。機械にできる疑いは、機械に肩代わりさせるべきです。

入力境界・秘密境界・水平の統制(ヘッダー/CSP・レート制限・CSRF・型付きenv)は、静的解析で自動化できます。私はAI生成コードのレビューに、無料のOSS Aegis を1コマンドで挟みます。

npx @aegiskit/cli scan   # インストール・設定不要。いまのdiffを静的解析する

正直に、これが何をして何をしないかを明記します。scan は——

  • 水平の統制の欠落を自動で洗い出す(未検証の入力、混入した秘密、注入経路のtaint解析)。
  • 縦のリスク(認可/IDOR・Supabase RLS設計)は「検出して警告」するだけ。あるテーブルが所有者非スコープでよいか=意図的な公開かは、事業ルールの意味の問題であり、ツールには判断できません。だからAegisは「完全に守る」とは決して言いません——そう謳うツールはむしろ危険です。

つまりscan私のgrepチェックリストを機械化し、疑う場所を漏れなく提示するものであって、レビューを置き換えるものではありません。そしてこれをCIに固定すれば、同じ穴の再発を機械が止め続けます。CIでの品質ゲート化(型・テスト・静的解析・セキュリティの4層をどう強制するか)はAI駆動開発の品質ゲート設計にまとめました。


8. 人間にしか下せない判断 — 認可の「意味」と業務ロジック

機械が疑いを出したあと、最後に残るのは意味の判断です。ツールはポリシやコードの「形」を読みますが、あなたの事業ルールの「意味」は読めません。

  • このUSING (true)は意図的な公開か、認可漏れか。 テーブルの目的を知る人間にしか答えられません。
  • この状態遷移は二重に走っても安全か。 私が決済基盤で本番二重課金0件を達成できたのは、スキャナではなく、状態遷移と冪等性を人間の目で設計・レビューし切ったからです。「同じ支払いリクエストが2回来たらどうなるか」は、コードの形ではなく業務の意味の問題で、どんな静的解析も代わりに判断してくれません。
  • この数量・価格・上限は悪用できないか。 業務ロジックの穴も同じく、仕様を握る人間の領域です。

だからレビューが機能する前提は、「正しい」の定義=受け入れ条件が言語化されていることです。仕様を先に固め、受け入れ条件をテストにするワークフローはSpec駆動開発の本番ワークフローに書きました。仕様が曖昧なら、レビュアーは「正しそう」以上の判断ができません。AIに実装を、人間に仕様決定と検証を——この役割分担が、レビューを可能にします。


9. diffを開いたら最初に見る順(チェックリスト)

最後に、私が実際にdiffを開いてから緑を出すまでの順序を、そのまま置きます。上から順に、被害半径の大きい境界からです。

① 入力境界   外部入力は境界で検証されているか(Zod等)。危険なシンクへの直挿入は無いか
② 認可境界   idで引いて所有者で絞っているか。RLSのUSING(true)は意図的公開か。
             service_role経路に所有権チェックはあるか
③ 秘密境界   鍵のハードコードは無いか。NEXT_PUBLIC_で秘密を漏らしていないか
④ 依存境界   package.jsonに見覚えのない依存が増えていないか。実在するか
⑤ 機械化     npx @aegiskit/cli scan を通し、疑いを機械にも洗わせる(CIに固定)
⑥ 意味の判断 認可の意図・状態遷移の冪等性・業務ロジックの悪用可否を人間が確認
⑦ ロジック   ①〜⑥が緑になって初めて、ハッピーパスの正しさを読む

順番が逆——つまりロジックを先に読み、境界を後回しにする——と、AI生成コードのレビューはほぼ確実に境界を見落とします。綺麗なロジックは最後でいい。先に、壊れる場所を疑う。


生成AIは、私の出荷速度を何倍にもしてくれます。しかし速度は、検証で相殺して初めて安全になります。そして検証の最後の一枚は、いつも人間のレビューです。自分が書いていないコードを、自分の責任で出荷する。 そのために、diffを開いたら——ロジックではなく、境界から疑う。git blame に残るのは、私の名前だからです。

まずは手元のプロジェクトで npx @aegiskit/cli scan を1回。機械が洗える境界を洗い、人間は意味の判断に集中する。そこがAI時代のレビューの出発点です。

Câu hỏi thường gặp

AI生成コードのレビューは、普通のコードレビューと何が違いますか?
最大の違いは『自分の頭の中に設計モデルがない』ことです。人間が書くコードは書きながら意図が積み上がりますが、AIの出力は流暢な完成品として降ってくるため、読みやすさが正しさや安全性に見えてしまいます。だからロジックの美しさに引っ張られず、入力・認可・秘密・依存の4境界を先に疑うのが実務的です。
なぜ『上から順に読む』レビューは効かないのですか?
AIが本番で漏らす原因が、ほぼ全て境界に集中しているからです。ハッピーパスのロジックはAIが得意で綺麗に書けますが、不正入力・同時アクセス・他人のIDを指す正規リクエストへの振る舞いは抜けます。上から読むと綺麗なロジックに時間を使い、肝心の境界へ到達する前に集中力が切れます。境界を先に見るべきです。
レビューをツールに任せれば、人間のレビューは不要になりますか?
いいえ。npx @aegiskit/cli scan のような静的解析は、入力検証やヘッダーといった水平の統制を自動化し、認可/IDOR・RLS設計といった縦のリスクは『検出して警告』するだけです。あるテーブルが所有者非スコープでよいか(=意図的な公開か)は事業ルールの意味であり、人間にしか判断できません。ツールは疑いを機械化し、人間は意味を握ります。
認可の問題は、diffのどこを見れば見つかりますか?
『idで引いているが所有者で絞っていない』箇所を最初に洗います。findUnique/findFirst や .eq('id') の周辺に user_id・owner・auth.uid() が無ければ疑いです。Supabaseなら RLS の USING(true) や auth.role()='authenticated' 止まりのポリシ、service_role 経路の所有権チェック漏れが定番の穴です。

Tài liệu tham khảo

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

That AI-generated code — safe to deploy as-is?

After “it works” comes “it doesn't leak” — scan your project free with one command

Aegis, an MIT-licensed OSS, automates the controls AI tends to skip (headers/CSP, rate limiting, input validation, secrets) and detects-and-warns on vertical risks in authorization and RLS design. The fix plan from `aegis fix --format json` pastes straight into Claude Code or Cursor.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc