ChatGPTやClaude、Gemini、AIコーディングエージェントを使うと、API、SQL、認証処理、ファイル操作まで短時間で実装できるようになりました。
AIが生成したコードを実行すると、正常に動くことも珍しくありません。
- ビルドが通る
- テストデータでは動く
- 期待したJSONが返る
という状態と、
- 本番環境へ安全に公開できる
という状態は別です。
たとえばユーザーIDをURLから受け取ってデータベースを検索するAPIは、正常なユーザーでテストすれば問題なく動きます。
しかし別ユーザーのIDへ変更するだけで他人のデータを取得できるなら、重大なアクセス制御の不備です。
SQLへ文字列を直接連結するコードも、通常の検索ワードなら正常に動きます。問題が発覚するのは、攻撃用の文字列を入力されたときです。
2026年8月時点の最新OWASP Top 10:2025でも、Broken Access Controlが最上位のリスクとなっており、Software Supply Chain Failures、Injection、Authentication Failures、Security Logging and Alerting Failuresなどが主要リスクとして挙げられています。
AIが生成したコードをレビューするときは、「このコードは動くか」だけでなく、攻撃者が入力値、URL、権限、実行順序を自由に変更した場合でも安全かという視点が必要です。
この記事ではTypeScriptとNode.jsを中心に、AIが生成したコードを本番採用する前に確認したいポイントを解説します。
- 最初に「正常系」ではなく「攻撃者なら何を変えるか」を考える
- 認証があるだけで安全だと思わない
- 管理者判定をフロントエンドだけで行っていないか確認する
- 外部入力を型だけで信用していないか確認する
- SQLを文字列結合していないか確認する
- ORMを使っているからSQL Injectionはないと思わない
- child_processへユーザー入力を渡していないか確認する
- ファイルパスをそのまま結合していないか確認する
- URLをそのままfetchしていないか確認する
- APIキーやパスワードがコードへ埋め込まれていないか確認する
- NEXT_PUBLICなどの公開Environment Variableへ秘密情報を入れていないか確認する
- request body全体をログへ出していないか確認する
- エラー内容をそのままユーザーへ返していないか確認する
- catchした結果、安全側ではなく成功扱いになっていないか確認する
- 外部APIにタイムアウトが設定されているか確認する
- Retryを追加したことで二重処理にならないか確認する
- 同時実行された場合を確認する
- Unique ConstraintをApplicationのif文だけで代用していないか確認する
- limitやpageSizeを無制限に受け取っていないか確認する
- AI APIを呼ぶコードでは料金上限も確認する
- 暗号用途でMath.random()を使っていないか確認する
- パスワードを独自方式で暗号化していないか確認する
- dangerouslySetInnerHTMLなどの危険な逃げ道を確認する
- CORSをとりあえず全許可にしていないか確認する
- エラーを直すためにSecurity機能を無効化していないか確認する
- AIが追加した依存パッケージをそのままインストールしない
- package-lock.jsonを勝手に削除していないか確認する
- 「最新版へ全部更新」で直していないか確認する
- デバッグ用Endpointが残っていないか確認する
- コメントアウトした認証処理が残っていないか確認する
- レスポンスへ必要以上のデータを返していないか確認する
- Mass Assignmentになっていないか確認する
- 削除処理がGETになっていないか確認する
- 削除や送信にHuman in the Loopが必要ではないか確認する
- AIのコメントを仕様だと思わない
- TODOがSecurity処理を先送りしていないか確認する
- Unit Testが成功するだけでは十分ではない
- AI自身にレビューさせるだけで完了しない
- Diffが大きすぎる場合はいったん分割する
- 最後にコードを上から読むのではなくデータの流れを追う
- AI生成コードをレビューする実践的な順番
- 関連記事
- AI生成コードのレビューに関するよくある質問
- まとめ
最初に「正常系」ではなく「攻撃者なら何を変えるか」を考える
AI生成コードをレビューするときに最初に変更したいのが、テストする視点です。
たとえばAIが次のAPIを作ったとします。
app.get(
"/api/orders/:id",
async (req, res) => {
const order =
await db.order.findUnique({
where: {
id: req.params.id,
},
});
res.json(order);
},
);
存在する注文IDを入力すれば正常にJSONが返るため、機能テストだけなら合格してしまいます。
しかし確認すべきなのは、
- 自分とは別のユーザーの注文IDを指定したらどうなるか
です。
注文IDだけで検索しているため、認証済みユーザーと注文の所有者を照合していません。
このようなアクセス制御の問題はOWASP Top 10:2025でも最重要カテゴリとして扱われています。
AI生成コードのレビューでは、正常な入力を1回通して終わりにせず、入力値、ユーザーID、URL、HTTPメソッド、権限、処理順序を変更した場合を確認します。
認証があるだけで安全だと思わない
認証と認可は別です。
ログインしているかどうかを確認していても、そのユーザーが対象データを操作できるとは限りません。
先ほどの注文APIを修正します。
app.get(
"/api/orders/:id",
requireLogin,
async (req, res) => {
const order =
await db.order.findFirst({
where: {
id: req.params.id,
userId:
req.user.id,
},
});
if (!order) {
return res
.status(404)
.json({
error:
"Order not found",
});
}
return res.json(order);
},
);
重要なのは、
id: req.params.id
だけではなく、
userId: req.user.id
も検索条件へ入れている点です。
ユーザーから受け取った、
req.body.userId
を所有者判定へ利用するのも避けます。
攻撃者が自分で値を変更できるからです。
認可判定には、セッションや検証済みアクセストークンなど、サーバー側で信頼できる情報を使用します。
管理者判定をフロントエンドだけで行っていないか確認する
AIが管理画面を生成すると、React側だけで、
if (
user.role !== "admin"
) {
return null;
}
として管理ボタンを隠すコードを作ることがあります。
これはUI制御としては使えますが、認可にはなりません。
攻撃者はブラウザのボタンを押さず、APIへ直接リクエストできます。
- DELETE /api/users/123
バックエンドでも権限を確認します。
app.delete(
"/api/users/:id",
requireLogin,
async (req, res) => {
if (
req.user.role !==
"admin"
) {
return res
.status(403)
.json({
error:
"Forbidden",
});
}
await deleteUser(
req.params.id,
);
return res.sendStatus(204);
},
);
画面上から機能を隠す処理と、APIでアクセスを拒否する処理を混同しないことが重要です。
外部入力を型だけで信用していないか確認する
TypeScriptを使っていると、
type CreateUserInput = {
email: string;
age: number;
};
と定義しただけで安全になったように見えます。
しかしTypeScriptの型は、インターネットから届いたJSONを実行時に検証してくれるわけではありません。
次のようなコードは危険です。
const input =
req.body as
CreateUserInput;
await createUser(
input,
);
攻撃者は任意のJSONを送信できます。
Runtime Validationを行います。
import { z } from "zod";
const CreateUserSchema =
z.object({
email:
z.string()
.email()
.max(254),
age:
z.number()
.int()
.min(0)
.max(120),
});
app.post(
"/api/users",
async (req, res) => {
const parsed =
CreateUserSchema
.safeParse(
req.body,
);
if (!parsed.success) {
return res
.status(400)
.json({
error:
"Invalid input",
});
}
const user =
await createUser(
parsed.data,
);
return res.json(user);
},
);
CWE Top 25でも不適切なInput Validationは重要なSoftware Weaknessとして扱われています。入力値を後工程が期待する形式・範囲に検証することは、SQL、ファイル操作、外部APIなどへデータを渡す前の基本です。
SQLを文字列結合していないか確認する
AI生成コードで特に見つけやすい危険パターンがSQLの文字列連結です。
const sql = `
SELECT *
FROM users
WHERE email =
'${email}'
`;
const result =
await pool.query(sql);
通常の、
- user@example.com
なら正常に動きます。
しかしSQLとして意味を持つ入力を渡されたときに、Queryそのものが変更される可能性があります。
OWASP Top 10:2025ではInjectionがA05に位置付けられ、SQL Injectionなどが含まれています。2025 CWE Top 25にもSQL InjectionとOS Command Injectionが含まれています(SQL InjectionはCWE-89で2位、OS Command InjectionはCWE-78で9位)。
Node.jsからPostgreSQLを利用するなら、値をParameterとして渡します。
const result =
await pool.query(
`
SELECT
id,
email,
name
FROM users
WHERE email = $1
`,
[
email,
],
);
レビューするときは、
`SELECT ... ${value}`
のように外部入力がSQL文字列へ直接埋め込まれていないか検索します。
ORMを使っているからSQL Injectionはないと思わない
ORMを利用していても、Raw SQL機能を使えば同じ問題が発生します。
AIは複雑なQueryを実装するときに、ORMの通常APIではなくRaw Queryへ切り替えることがあります。
レビューでは、
- raw
- unsafe
- queryRaw
- executeRaw
などのAPIを重点的に確認します。
ORMそのものが安全かどうかではなく、ユーザー入力とSQL構文が同じ文字列へ結合されていないかを見ることが重要です。
child_processへユーザー入力を渡していないか確認する
AIコーディングでは、ファイル変換やGit操作を簡単に実装するためにchild_process.exec()が生成されることがあります。
たとえば次のコードです。
import {
exec,
} from "node:child_process";
function convertFile(
fileName: string,
) {
exec(
`convert ${fileName} output.png`,
);
}
fileNameをユーザーが操作できるなら、Shell Commandとして解釈される特殊文字が混ざる危険があります。
Node.jsのexec()はShellを起動してCommandを実行するAPIです。OS Command Injectionは2025 CWE Top 25でも9位に位置する危険なSoftware Weaknessです。
可能ならShell文字列を組み立てず、実行ファイルとArgumentsを分離します。
import {
execFile,
} from "node:child_process";
execFile(
"convert",
[
inputPath,
outputPath,
],
(error) => {
if (error) {
console.error(
"convert failed",
);
}
},
);
ただし、実行対象、OS、ファイル形式によって追加の注意点があります。
特にWindowsのBatch File処理では、spawn/spawnSyncのArgument処理からCommand InjectionにつながるNode.js脆弱性(CVE-2024-27980、CVE-2024-36138)が実際に公開されています。外部入力から実行ファイル名やBatch Fileを自由に選ばせる設計自体を避けるほうが安全です。
ファイルパスをそのまま結合していないか確認する
AIにファイル取得APIを作らせると、次のようなコードが生成されることがあります。
const filePath =
path.join(
"/app/uploads",
req.params.file,
);
return res.sendFile(
filePath,
);
問題は、
- ../../
のようなパスを指定された場合です。
Path Traversalは2025 CWE Top 25でもCWE-22として6位に位置しています。
アプリケーションが許可したディレクトリから外へ出ていないことを検証します。
import path from "node:path";
const BASE_DIR =
path.resolve(
"/app/uploads",
);
function resolveSafePath(
input: string,
): string {
const target =
path.resolve(
BASE_DIR,
input,
);
const relative =
path.relative(
BASE_DIR,
target,
);
if (
relative.startsWith("..") ||
path.isAbsolute(relative)
) {
throw new Error(
"Invalid path",
);
}
return target;
}
さらに、攻撃者がSymlinkなどを作成できる環境では文字列上のPath Validationだけでは不十分な場合があります。
重要ファイルへアクセスできないOS権限、Container、Sandboxなどと組み合わせます。
URLをそのままfetchしていないか確認する
URL取得機能も注意が必要です。
app.post(
"/api/fetch",
async (req, res) => {
const response =
await fetch(
req.body.url,
);
res.send(
await response.text(),
);
},
);
この実装では、ユーザーが指定したURLへサーバー自身がアクセスします。
外部Webサイトだけでなく、
- localhost
- 社内ネットワーク
- クラウドのMetadata Service
などへアクセスできる構成ならSSRFにつながります。
OWASP Top 10:2025ではSSRFがBroken Access Controlカテゴリに統合されています。
アクセス先が決まっているサービスならAllowlist方式にします。
const ALLOWED_HOSTS =
new Set([
"api.example.com",
"images.example.com",
]);
function validateUrl(
rawUrl: string,
): URL {
const url =
new URL(rawUrl);
if (
url.protocol !==
"https:"
) {
throw new Error(
"HTTPS only",
);
}
if (
!ALLOWED_HOSTS.has(
url.hostname,
)
) {
throw new Error(
"Host not allowed",
);
}
return url;
}
任意URLへのアクセスが必要なサービスでは、単純なlocalhost文字列チェックだけでは十分ではありません。
DNS解決後のIP、Private Network、Redirect先、IPv6なども含めた対策と、Network LevelのEgress Controlを検討します。
APIキーやパスワードがコードへ埋め込まれていないか確認する
AIへ、
- OpenAI APIを呼ぶサンプルを書いて
と依頼した際、説明用のPlaceholderではなく実際のSecretをそのままコードへ貼り付けてしまう運用は避けます。
危険なコードは次のようなものです。
const apiKey = "sk-real-secret-value";
CWEではHard-coded Credentials(CWE-798)が独立したSoftware Weaknessとして定義されています。GitHubのSecret ScanningもGit History内のAPI Key、Password、TokenなどのHard-coded Credentialを検出対象としており、Push ProtectionではRepositoryへ入る前にSecretを含むPushをBlockできます。ただしPush Protectionは新規にプッシュされる内容が対象で、既にリポジトリ履歴に含まれてしまった過去のコミットまでは遡ってブロックしない点には注意してください。
サーバー側のSecret StoreやEnvironment Variableから取得します。
const apiKey =
process.env.API_KEY;
if (!apiKey) {
throw new Error(
"API_KEY is required",
);
}
ただしEnvironment Variableへ移しただけで安全対策が完了するわけではありません。
ログ、例外、デバッグ画面、クライアント向けBundleへSecretが流れていないかも確認します。
NEXT_PUBLICなどの公開Environment Variableへ秘密情報を入れていないか確認する
フロントエンドFrameworkでは、Environment Variableの一部がBrowser Bundleへ公開される仕組みがあります。
AIが、
const apiKey =
process.env
.NEXT_PUBLIC_API_KEY;
のようなコードを生成した場合、名前の意味を理解せず採用しないようにします。
ブラウザ側で使用できる値は、ユーザーにも取得できる値だと考えます。
秘密のAPIキーで外部サービスを呼び出すなら、
- Browser
- ↓
- 自社Backend
- ↓
- External API
という構成にし、秘密情報はBackend側だけで保持します。
request body全体をログへ出していないか確認する
AIはDebugしやすさを優先して、
console.log( req.body, );
を追加することがあります。
問い合わせフォームなら、
- 氏名
- メールアドレス
- 電話番号
- 住所
- パスワード
- アクセストークン
などがログへ残る可能性があります。
OWASP Top 10:2025のSecurity Logging and Alerting Failuresでは、重要イベントが適切に記録されない問題だけでなく、Sensitive InformationをLogへ挿入するCWEも関連付けられています。
ログへは必要なMetadataだけを出します。
console.info({
event:
"user_update",
userId:
currentUser.id,
requestId,
status:
"success",
});
前の記事で解説したPIIマスキングと同じく、AI APIのPromptだけではなくApplication Logもデータフローの一部として確認します。
エラー内容をそのままユーザーへ返していないか確認する
次のコードも開発中には便利です。
catch (error) {
return res
.status(500)
.json({
error:
String(error),
});
}
しかしErrorには、
- SQL
- 内部Path
- Host Name
- ライブラリ名
- Stack Trace
- 外部API Response
などが含まれる場合があります。
OWASP Top 10:2025のSecurity Misconfigurationでは、Stack Traceなど過剰に詳細なError Messageの公開もリスクとして挙げられています。
ユーザー向けには一般化します。
catch (error) {
console.error({
requestId,
error:
normalizeError(
error,
),
});
return res
.status(500)
.json({
error:
"Internal server error",
requestId,
});
}
詳細はサーバー側で追跡します。
catchした結果、安全側ではなく成功扱いになっていないか確認する
AI生成コードでは、Errorを消すためだけにcatchが追加されることがあります。
特に危険なのがFail Openです。
async function canAccess(
userId: string,
): Promise<boolean> {
try {
return await checkPermission(
userId,
);
} catch {
return true;
}
}
Permission Serverが停止すると、
return true;
によって全員が許可されます。
OWASP Top 10:2025ではA10としてMishandling of Exceptional Conditionsが新たに設けられ、Error Handling不足、Logical Error、Fail Openなどが対象になっています。
認可確認に失敗したなら、基本的には拒否側へ倒します。
catch {
return false;
}
さらに監視用ログを残します。
正常系だけを見ていると、この違いは発見できません。
外部APIにタイムアウトが設定されているか確認する
AIが生成するfetch()は非常に簡潔です。
const response = await fetch(url);
しかし外部サービスが応答しなければ、Application側のRequestも長時間占有される可能性があります。
時間上限を明示します。
const response =
await fetch(
url,
{
signal:
AbortSignal.timeout(
10_000,
),
},
);
AI生成コードをレビューするときは、
- 外部HTTP
- LLM API
- DB Query
- Shell Command
- MCP Tool
など、Process外へ出る処理にTimeoutがあるかを確認します。
Timeout後に再試行する場合は、前の記事で解説したように副作用と冪等性も確認します。
Retryを追加したことで二重処理にならないか確認する
AIへ、
- 失敗したら3回再試行するよう修正して
と依頼すると、簡単なRetry Loopを生成できます。
for (
let i = 0;
i < 3;
i += 1
) {
try {
return await createOrder();
} catch {
await sleep(1000);
}
}
しかしcreateOrder()がServer側では成功し、Responseだけ失われたケースでは、二重注文になる可能性があります。
読み取り処理と書き込み処理を同じRetry Policyにしないことが重要です。
注文、決済、メール送信などはoperationIdやIdempotency Keyを導入します。
await createOrder({
operationId,
productId,
});
「Retryが実装されているから堅牢」ではなく、「Retryされても安全か」を確認します。
同時実行された場合を確認する
AI生成コードは1Requestずつ実行すると正常でも、同時アクセスで壊れる場合があります。
たとえば在庫を確認してから減らす処理です。
const product =
await getProduct(
productId,
);
if (
product.stock <= 0
) {
throw new Error(
"Out of stock",
);
}
await updateStock(
productId,
product.stock - 1,
);
同じ瞬間に2Requestが実行されると、両方が、
- stock = 1
を読み取る可能性があります。
その後どちらも購入成功になるかもしれません。
このような処理ではDatabase Transaction、Atomic Update、Unique Constraint、Lockなど、データベース側の整合性機構を利用します。
AI生成コードのレビューでは、
- 同じAPIを同時に10回呼んだらどうなるか
という視点を入れると、単体テストでは見えない問題を発見しやすくなります。
Unique ConstraintをApplicationのif文だけで代用していないか確認する
次のコードも正常系では動きます。
const existing =
await db.user.findUnique({
where: {
email,
},
});
if (!existing) {
await db.user.create({
data: {
email,
},
});
}
しかし2Requestが同時にexisting === nullを確認する可能性があります。
「重複禁止」が本当に必要なら、Application側の確認だけでなくDatabase側にもUnique Constraintを設定します。
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
Application Codeは制約違反を適切に処理します。
AIが生成したBusiness Logicを、データベースが保証すべきInvariantと分けて考えることが重要です。
limitやpageSizeを無制限に受け取っていないか確認する
検索APIでは、
const limit =
Number(
req.query.limit,
);
const users =
await db.user.findMany({
take: limit,
});
のようなコードが生成されることがあります。
ユーザーが、
- limit=100000000
を指定したらどうなるでしょうか。
OWASP API Security Top 10ではUnrestricted Resource Consumption(API4:2023)が主要リスクとして挙げられ、CPU、Memory、Storage、Network Bandwidthだけでなく、API連携による課金Resourceの消費も問題として扱われています。
上限をサーバー側で決めます。
const requested =
Number(
req.query.limit ??
20,
);
const limit =
Math.min(
Math.max(
requested,
1,
),
100,
);
Upload Size、生成AIのmax_tokens、Batch Size、同時実行数にも同じ考え方を適用します。
AI APIを呼ぶコードでは料金上限も確認する
生成AIを組み込んだApplicationでは、セキュリティだけでなくCost Abuseも確認します。
たとえば、
app.post(
"/api/generate",
async (req, res) => {
return callLlm(
req.body.prompt,
);
},
);
というEndpointを認証なし、Rate Limitなしで公開すれば、第三者に大量利用される可能性があります。
AI APIでは、
- 認証
- ユーザー別Rate Limit
- 最大入力サイズ
- 最大出力量
- 1Request料金上限
- 日次予算
をApplication側で制御します。
ユーザーが指定したモデル名やmax_output_tokensを無条件でAPIへ渡していないかも確認します。
暗号用途でMath.random()を使っていないか確認する
AIにToken生成処理を書かせると、
const token =
Math.random()
.toString(36)
.slice(2);
のようなコードが生成されることがあります。
UI用のランダム表現と、Authentication TokenやPassword Reset Tokenは別です。
Security-sensitiveなRandom ValueにはNode.jsのCryptographic APIを使用します。
import {
randomBytes,
} from "node:crypto";
const token =
randomBytes(32)
.toString("hex");
または用途に応じて、
import {
randomUUID,
} from "node:crypto";
const operationId =
randomUUID();
を使います。
AI生成コードに、
- token
- nonce
- reset code
- session id
- API key
のような値があれば、生成方法まで確認します。
パスワードを独自方式で暗号化していないか確認する
AIへ認証処理を一式作らせると、独自のPassword Hashing処理が出てくることがあります。
Security-sensitiveな処理では、
- 暗号アルゴリズムを自作
- Password Hashを独自実装
- Encryption Keyをコードへ埋め込み
- 固定IVを使用
といった実装を避けます。
OWASP Top 10:2025にもCryptographic Failuresが含まれています。
成熟したLibraryやPlatform標準機能を使い、AlgorithmやParameterは現在の公式Security Guidanceに合わせます。
「AIが実装できるから自作する」のではなく、Security Primitiveは可能な限り自作しないことが重要です。
dangerouslySetInnerHTMLなどの危険な逃げ道を確認する
Reactでは通常の、
<p>
{userText}
</p>
ならTextとしてEscapeされます。
しかしAIはHTMLを表示する要件に対し、
<div
dangerouslySetInnerHTML={{
__html:
userContent,
}}
/>
を生成することがあります。
userContentが外部入力ならXSSにつながる可能性があります。
InjectionはOWASP Top 10:2025の主要カテゴリで、Cross-site Scriptingもその中に含まれています。
HTML表示が本当に必要なのかを最初に確認し、必要なら用途に合ったSanitizationを行います。
「画面にHTMLとして表示できた」ことを成功条件にしないことが重要です。
CORSをとりあえず全許可にしていないか確認する
開発中にCORS Errorが出ると、AIは素早い解決策として広い許可設定を提案することがあります。
たとえば、
app.use(
cors({
origin: "*",
}),
);
です。
公開APIなら意図した設定の場合もあります。
しかしCookieや管理APIを扱うサービスで、「エラーを消すため」に広げた設定をそのまま本番へ入れるのは避けます。
OWASP Top 10:2025ではSecurity MisconfigurationがA02に位置付けられており、不要な機能、過剰な権限、安全でない設定などがリスクとして扱われています。
AIがConfigを変更したときは、
- なぜその設定が必要なのか
を説明できる状態にしてから採用します。
エラーを直すためにSecurity機能を無効化していないか確認する
AIへ、
- このエラーを直して
とだけ依頼すると、原因を修正するのではなく安全機能を無効化してエラーを消す回答になることがあります。
たとえば、
- TLS証明書検証を無効化
- CSRF保護を無効化
- CORSを全許可
- 認証Middlewareを削除
- SSL検証を無効化
- Permission Checkを削除
といった変更です。
コードレビューでは、
- エラーが消えた理由
を確認します。
正しい設定へ修正した結果なのか、安全装置を取り外しただけなのかを区別します。
Security MisconfigurationはOWASP Top 10:2025でも主要リスクです。
AIが追加した依存パッケージをそのままインストールしない
AIに実装を依頼すると、新しいnpm Packageを追加することがあります。
コードだけを見ると、
import {
someFunction,
} from "some-package";
が自然に見えても、そのPackageを採用してよいかは別問題です。
Package名が正しいか、公式・想定したPackageなのか、Maintainer、更新状況、依存関係、Install Script、Licenseなどを確認します。
OWASP Top 10:2025ではSoftware Supply Chain FailuresがA03へ位置付けられており、Third-party Code、Tools、Dependenciesなどに起因する問題が対象です。
npm Projectでは Dependencyの既知脆弱性を、
npm audit
で確認できます。
npm公式ドキュメントでも、npm auditはProjectのDependenciesについて既知のSecurity Vulnerabilityを調査するための機能とされています。
ただしnpm auditが0件だからPackageの安全性が保証されるわけではありません。
既知のVulnerability確認と、Packageそのものを信頼してよいかの確認は別です。
package-lock.jsonを勝手に削除していないか確認する
依存関係Errorを解消するため、
- package-lock.jsonを削除してnpm installしてください
という修正を安易に採用すると、Dependency Treeが大きく変化する場合があります。
npm公式ドキュメントではpackage-lock.jsonは生成されたDependency Treeを正確に記述し、後のInstallでも同じTreeを再現するためのファイルとして説明されています。
CIでは、
npm ci
を使う方法もあります。
npm ciはLockfileとpackage.jsonが一致しなければErrorにし、Lockfileを勝手に更新しない動作です。
AIがDependency問題を解決するためにLockfileやVersion Constraintを大幅変更していないか、Diffを確認します。
「最新版へ全部更新」で直していないか確認する
AIへDependency Errorを直させると、
- すべて最新版へ更新
という解決策になることがあります。
Security Patchを適用すること自体は重要ですが、大量のMajor Updateを一度に行うとBehavior Changeの原因を追跡しにくくなります。
Dependency変更もCode Reviewの対象にします。
- どのPackageが変わったか
- なぜ必要か
- Major Versionが変わったか
- Transitive Dependencyはどう変化したか
- Lockfile Diffが異常に大きくないか
を確認し、必要な変更だけを採用します。
デバッグ用Endpointが残っていないか確認する
AIに障害調査を依頼すると、一時的に、
app.get(
"/debug/config",
(_req, res) => {
res.json(
process.env,
);
},
);
のようなDebug Endpointを追加する場合があります。
開発中には便利でも、本番へ残せばSecretや内部構成が露出します。
また、
app.get(
"/debug/users",
async (_req, res) => {
res.json(
await db.user.findMany(),
);
},
);
のような認証なしEndpointも危険です。
OWASPのSecurity Misconfigurationでは、不必要な機能、Test Framework、Account、Serviceなどを有効なままにすることもリスクとして挙げられています。
AIが追加したTemporary Codeは、Merge前に必ず確認します。
コメントアウトした認証処理が残っていないか確認する
エラー切り分け中に、
// app.use(requireAuth);
と認証を一時停止することがあります。
AIエージェントに複数ファイルを編集させた場合、このような一時変更が別Fileへ残ることもあります。
レビューでは追加されたコードだけでなく、削除されたSecurity Codeも確認します。
Diffを見るときは、
- 認証Middleware
- Permission Check
- Validation
- Rate Limit
- Timeout
- Security Header
などが消えていないか注意します。
レスポンスへ必要以上のデータを返していないか確認する
AIに、
- ユーザー情報を取得するAPIを作って
と依頼すると、
const user =
await db.user.findUnique({
where: {
id,
},
});
return res.json(user);
とDatabase Recordを丸ごと返すコードが生成されることがあります。
User Tableに、
- passwordHash
- resetToken
- internalNote
- billingId
などが追加されると、その値までAPI Responseへ出る可能性があります。
Response用の型を明示します。
return res.json({
id:
user.id,
name:
user.name,
email:
user.email,
});
Database ModelとPublic API Responseを同じ型として扱わないほうが安全です。
Mass Assignmentになっていないか確認する
更新APIにも似た問題があります。
await db.user.update({
where: {
id:
req.user.id,
},
data:
req.body,
});
一見すると非常に簡潔です。
しかしUser Modelに、
- role
- isAdmin
- plan
- creditBalance
などが存在すれば、ユーザーが本来変更できないFieldまで送信する可能性があります。
更新可能なFieldを明示します。
const schema =
z.object({
name:
z.string()
.min(1)
.max(100),
bio:
z.string()
.max(1000),
});
const input =
schema.parse(
req.body,
);
await db.user.update({
where: {
id:
req.user.id,
},
data: input,
});
AIが生成した、
data: req.body
や
Object.assign( user, req.body, )
のような処理は重点的に確認します。
削除処理がGETになっていないか確認する
AIが簡単な管理ツールを作る際、
app.get(
"/users/:id/delete",
async (req, res) => {
await deleteUser(
req.params.id,
);
res.redirect(
"/users",
);
},
);
のような実装を作ることがあります。
状態を変更する処理をGETへ置くのは避けます。
Crawler、Prefetch、リンク確認ツールなどがURLへアクセスしただけで副作用が発生する設計にしないことが重要です。
HTTP MethodだけでSecurityが保証されるわけではありませんが、APIの意味と副作用を一致させることは安全な設計の基本です。
削除や送信にHuman in the Loopが必要ではないか確認する
AI Agentがコードを直接実行するシステムでは、Code ReviewだけでなくTool Execution Boundaryも確認します。
たとえば、
- delete_file
- send_email
- publish_post
- create_order
をAIが自動実行できる場合です。
前の記事で解説したように、外部へ不可逆な副作用を与える処理は、実行直前にHuman Approvalを要求する設計を検討します。
「AIが正しいコードを生成したか」と「AIにその操作を自動実行させてよいか」は別のレビュー項目です。
AIのコメントを仕様だと思わない
AIはもっともらしいCommentも生成します。
// 管理者のみ実行可能
async function deleteAllUsers() {
return db.user.deleteMany();
}
しかしFunction内にはPermission Checkがありません。
コメントではなく実際のControl Flowを確認します。
同様に、
- // SQL Injection対策済み
- // Secure token
- // Validate user input
と書かれていても、それだけで安全とは判断しません。
AI生成コードでは「説明と実装が一致しているか」を必ず確認します。
TODOがSecurity処理を先送りしていないか確認する
生成コードに、
- // TODO: add authentication
- // TODO: validate permissions
- // TODO: rate limit
が残っている場合があります。
Prototypeでは意図的でも、そのままProductionへDeployされれば未実装のSecurity Boundaryになります。
レビュー時には、
- TODO
- FIXME
- temporary
- skip
- disable
- bypass
などを検索すると、暫定処理を発見しやすくなります。
Unit Testが成功するだけでは十分ではない
通常のAI生成Testは、
- 正しい入力で200になる
- Databaseへ保存できる
- 期待したJSONになる
といった正常系へ偏りやすくなります。
Security Reviewでは失敗させるTestを追加します。
たとえば注文APIなら、
it(
"他人の注文は取得できない",
async () => {
const response =
await request(app)
.get(
"/api/orders/other-user-order",
)
.set(
"Authorization",
userToken,
);
expect(
response.status,
).toBe(404);
},
);
入力検証なら、
it(
"limitの上限を超えられない",
async () => {
const response =
await request(app)
.get(
"/api/users?limit=999999",
);
expect(
response.body.items
.length,
).toBeLessThanOrEqual(
100,
);
},
);
を追加します。
NISTのSecure Software Development Frameworkでも、Human-readable CodeのReview/Analysisと、Executable CodeのTestingによって脆弱性を発見することがSecure Development Practiceとして挙げられています(SP 800-218のPW.7・PW.8)。
AI自身にレビューさせるだけで完了しない
生成したAIへ、
- このコードにセキュリティ上の問題はありますか?
と再質問する方法は有効です。
別モデルにSecond Reviewさせる方法もあります。
しかし、AIレビューをHuman Review、Static Analysis、Dependency Audit、Testの代わりにはしません。
NIST SSDFも単一の手法ではなく、Code Review、Analysis、Executable TestingなどをSecure Development Processへ組み込む考え方を採っています。
AIはレビューを補助する道具として利用し、最終的なSecurity Boundaryはコード、設定、テスト、運用から確認します。
Diffが大きすぎる場合はいったん分割する
AIコーディングエージェントへ、
- 認証を追加して
- DBを変更して
- APIも修正して
- テストも直して
とまとめて依頼すると、大量のDiffが生成されます。
変更量が増えるほど、
- 必要な変更
- ついでに変更された部分
- Security Checkの削除
- Dependency変更
を見落としやすくなります。
Security-sensitiveな変更は小さく分割します。
たとえば、
- Schema変更
- 認証Middleware
- Endpoint変更
- Test追加
を別々のCommitまたはReview単位にすると、意図しない変更を見つけやすくなります。
「AIなら一度に大量修正できる」ことと、「一度に大量修正したほうが安全」は同じではありません。
最後にコードを上から読むのではなくデータの流れを追う
AI生成コードをレビューするときは、Fileを1行目から最後まで読むだけでは不十分です。
攻撃者が入力した値がどこへ流れるかを追います。
たとえば、
HTTP Request | Validation | Authorization | Business Logic | SQL | External API | File System | Response | Log
という流れです。
req.body.urlが最終的にfetch()へ入るならSSRFを確認します。
req.params.fileがreadFile()へ入るならPath Traversalを確認します。
req.body.queryがSQL文字列へ入るならInjectionを確認します。
req.body.commandがexec()へ入るならCommand Injectionを確認します。
このように外部入力から危険なSinkまでを追うと、AI生成コードの見た目に惑わされにくくなります。
AI生成コードをレビューする実践的な順番
まずAuthenticationとAuthorizationを確認します。
次に外部入力がSQL、Shell、File Path、URL、HTMLへどう流れるかを追います。
その後、API KeyやPasswordなどSecretの扱い、Database Transaction、Concurrent Request、Retry、Idempotency、Timeout、Resource Limitを確認します。
さらにDependencyとLockfileの変更を確認し、最後にLog、Error Handling、Debug Code、Testを確認します。
この順番にすると、StyleやNamingを細かく直したあとで重大なSecurity Designの問題を発見し、全面的に書き直す状況を減らせます。
関連記事
- AI APIへ個人情報を送る前に確認すること|PIIマスキングをTypeScriptで実装
- AIエージェントの無限ループを防ぐ方法|最大ステップ数・予算・終了条件
- MCPツールがタイムアウトする原因|再試行・キャンセル・冪等性の設計
- AIエージェント完全実装ガイド【TypeScript】|ReAct・Plan-and-Execute・Reflection・マルチエージェントまで実装コード付きで解説
AI生成コードのレビューに関するよくある質問
npm auditをDependencyの既知Security Vulnerabilityを確認する仕組みとして説明しています。Packageそのものの信頼性、Maintainer、Install Script、更新状況、不要なDependencyが増えていないかも別途確認します。まとめ
AIが生成したコードをレビューするときに、最も危険なのは、
- 動いたから大丈夫
と判断することです。
SQL Injection、Broken Access Control、Path Traversal、Command Injection、SSRF、Hard-coded Credentialなどの脆弱性は、普通の入力で機能テストをしているだけでは発見できないことがあります。
2026年8月時点のOWASP Top 10:2025でも、Broken Access Control、Security Misconfiguration、Software Supply Chain Failures、Injection、Authentication Failures、Logging and Alerting Failures、Exceptional Conditionsの不適切な処理などが主要なApplication Security Riskとして扱われています。
AI生成コードでは、最初にAuthenticationとAuthorizationを確認します。
その後、外部入力がSQL、Shell Command、File Path、URL、HTMLなどの危険な処理へ到達していないかを追います。
さらにSecret、Dependency、Resource Limit、Timeout、Retry、Transaction、Concurrency、Idempotency、Logging、Error Handlingまで確認します。
そして正常系だけではなく、
- 別ユーザーのIDに変更したらどうなるか
- 巨大な値を入力したらどうなるか
- 同じRequestを同時に送ったらどうなるか
- 途中で外部APIが失敗したらどうなるか
- 同じ処理がRetryされたらどうなるか
という条件でTestします。
NIST SSDFでも、Secure Software DevelopmentではCode Review/AnalysisとExecutable CodeのTestingによって脆弱性を発見・確認することが推奨されています。
AIコーディングではコードを書く時間を大幅に短縮できます。
その分、開発者が見るべき場所は「どう書くか」から「このコードをどこまで信用してよいか」へ変わります。
AI生成コードの最終レビューでは、コードが期待どおり動くことではなく、攻撃者が期待どおりに使ってくれなくても安全に動くことを確認するのが重要です。
