「動いた」と「安全」は別物です
AIに「ログイン機能つけて」と頼んだら、数分で動くコードが返ってくる。ブラウザで触ってみたら、ちゃんとログインできる。
ここで「よし、できた」と本番に出してしまう人が、未経験者にはとても多いように感じます(自分が最初そうでした)。
でも、画面で動いているコードと、安全なコードは別物です。AIが書いたコードは「あなたが頼んだ通りに動く」ことは得意でも、「あなたが頼まなかった安全対策」は黙って省略していることがよくあります。動作確認では絶対に気づけない地雷が、平気で混ざっています。
AIで爆速にコードが書ける時代になって、エンジニアの価値は「速く書けること」から「危ない所を見抜けること」に移りました。コードを書くこと自体は、もうAIが肩代わりしてくれるからです。
しかも難しい話ではありません。AIに「これ、安全か確認して」と一言添えるだけで景色が変わります。この記事では、AI生成コードでありがちな「動くけど危険」な落とし穴を4つ、「なぜ危険か → 最小の直し方 → AIにこう頼めば気づける」の型で見ていきます。
未経験のうちに「動けばOK」のクセをつけると、後で必ず苦労します。逆に言えば、最初からこの目を持っておくだけで、ぐっと差がつきます。
落とし穴1: 秘密情報をコードに直接書いている
いちばん多くて、いちばん怖いのがこれです。APIキーやパスワードを、コードの中にそのまま書いてしまうパターン。
AIに「OpenAIのAPIを呼ぶコード書いて」と頼むと、説明のためにキーをベタ書きしたサンプルを返してくることがあります。それをそのままコピーして、Gitに push してしまう。
// ❌ NG: APIキーがコードに直書きされている
const client = new OpenAI({
apiKey: "sk-proj-abc123xxxxxxxxxxxxxxxxxxxx", // これが地雷
});
なぜ危険か: このコードを GitHub に上げた瞬間、世界中の誰でもあなたのキーを読めます。キーを使われて高額請求が来たり、データを抜かれたりします。一度 push したキーは、後から消しても Git の履歴に残るので「漏れた」とみなして作り直すしかありません。
最小の直し方: 秘密情報は .env.local という別ファイルに書き、コードからは環境変数として読み込みます。そして .env.local を .gitignore に入れて、Gitに上げないようにします。
// ✅ OK: .env.local に置いて、環境変数から読む
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
# .env.local(このファイルは Git に上げない)
OPENAI_API_KEY=sk-proj-abc123xxxxxxxxxxxxxxxxxxxx
# .gitignore に必ず追記する
.env.local
.env*.local
ポイントは「process.env.X で読む形」と「.gitignore に入れる」の両方をやること。片方だけだと意味がありません。
AIにこう頼めば気づける:
このコードに、APIキーやパスワードなどコードに直接書くべきでない秘密情報が含まれていないか確認して。あれば .env.local に分離する形に直して、.gitignore への追記も教えて。
落とし穴2: データベースが「誰でも読める」設定のまま
Supabase や Firebase のようなサービスを使うと、フロントエンドから直接データベースを触れて便利です。でもこの「便利さ」が、設定を忘れると「誰でもあなたのDBを読み書きできる」状態を作ります。
Supabase には RLS(Row Level Security/行レベルセキュリティ) という仕組みがあって、これを設定しないと、テーブルが事実上「全公開」になることがあります。AIが書いたコードは画面上は普通に動くので、まず気づけません。
なぜ危険か: 例えば「自分の家計簿だけ見える」つもりのアプリでも、RLSが無いと、他人のユーザーの家計簿データを誰でも引っ張れてしまいます。動作確認では自分のデータしか見ないので、絶対に気づけないタイプの事故です。
最小の直し方: テーブルごとに RLS を有効化して、「ログインしている本人の行だけ操作できる」ポリシーを書きます。
-- ✅ RLS を有効化する
alter table todos enable row level security;
-- ✅ 「自分が作った行だけ」読み書きできるポリシー
create policy "本人の行だけ操作可"
on todos
for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
auth.uid() は「いまログインしている人のID」で、それと行の user_id が一致する行だけ許可する、という意味です。これで他人のデータは触れなくなります。
AIにこう頼めば気づける:
このSupabaseのテーブルはRLS(行レベルセキュリティ)が有効になってる?有効でないなら、ログイン中の本人の行だけ読み書きできるポリシーのSQLを書いて。なぜそれが必要かも初心者向けに説明して。
落とし穴3: 入力チェックが無い(何でも受け入れてしまう)
フォームから来たデータを、そのまま信じて使ってしまうパターン。AIは「正しい値が来る前提」でコードを書きがちで、「変な値・悪意ある値が来たら?」までは、頼まないと面倒を見てくれないことが多いです。
// ❌ NG: 送られてきた値を一切チェックせず、そのまま保存
export async function POST(req: Request) {
const body = await req.json();
await db.insert(users).values({
email: body.email, // 空でもメール形式じゃなくても通る
age: body.age, // 文字列でもマイナスでも通る
});
return Response.json({ ok: true });
}
なぜ危険か: メール欄が空のまま登録できたり、年齢にマイナスや「あいうえお」が入ったり、想定外に巨大なデータを送り込まれてシステムが不安定になったりします。攻撃の入り口にもなります。「ユーザーは正しく入力してくれる」という前提は、本番では成り立ちません。
最小の直し方: 受け取った値を「使う前に」検証します。TypeScript なら Zod のような検証ライブラリを使うと、型と中身を同時にチェックできて楽です。
// ✅ OK: 使う前にスキーマで検証する
import { z } from "zod";
const schema = z.object({
email: z.string().email(), // メール形式かチェック
age: z.number().int().min(0), // 0以上の整数かチェック
});
export async function POST(req: Request) {
const parsed = schema.safeParse(await req.json());
if (!parsed.success) {
return Response.json({ error: "入力が不正です" }, { status: 400 });
}
await db.insert(users).values(parsed.data);
return Response.json({ ok: true });
}
「正しい形のデータだけが、この先に進める」という関所を1つ作るイメージです。
AIにこう頼めば気づける:
このAPIは外部から来た入力を検証せずに使ってる気がする。空・型違い・想定外の値が来た場合にどうなるか確認して、Zodで入力バリデーションを入れる形に直して。
落とし穴4: エラーを握りつぶしている
最後は地味だけど、後でじわじわ効いてくるやつです。try-catch でエラーを受け取っているのに、中で何もしていないパターン。
// ❌ NG: エラーを受け取って、何もせず握りつぶす
try {
await saveToDatabase(data);
} catch (e) {
// 握りつぶし。保存に失敗しても何も起きない
}
return Response.json({ ok: true }); // 失敗してるのに「成功」を返す
なぜ危険か: 保存に失敗しても、ユーザーには「成功しました」と表示される。本人は保存できたつもりなのにデータが消えている、という最悪のすれ違いが起きます。しかもエラーがどこにも記録されないので、後から原因を追うこともできません。「動いているように見えて、静かに壊れている」状態です。
最小の直し方: エラーは「記録する」か「ユーザーに正しく伝える」か、最低どちらかをやります。少なくとも黙って成功を返すのはやめます。
// ✅ OK: エラーを記録し、失敗を正しく伝える
try {
await saveToDatabase(data);
return Response.json({ ok: true });
} catch (e) {
console.error("保存に失敗:", e); // まず記録する
return Response.json(
{ error: "保存に失敗しました" },
{ status: 500 } // 失敗を正しく伝える
);
}
AIにこう頼めば気づける:
このコードに、エラーを catch しているのに何もせず握りつぶしている箇所がないか確認して。あればログ出力や、失敗をユーザー・呼び出し元に正しく返す形に直して。
共通の「魔法の一言」を覚えておく
4つの落とし穴を見てきましたが、毎回パターンを暗記する必要はありません。AIに渡せる「セルフレビューを頼む一言」を1つ持っておくと、未経験のうちから安全の目が育ちます。
いま書いてもらったコードを、セキュリティと堅牢性の観点でレビューして。
特に次の4点を確認して、問題があれば最小の修正案を出して:
1. 秘密情報(APIキー/パスワード)がコードに直書きされていないか
2. DB(Supabase等)のアクセス制御(RLS)が抜けていないか
3. 外部からの入力を検証せずに使っていないか
4. エラーを握りつぶして、失敗を成功として扱っていないか
修正の理由も、初心者にわかるように説明して。
大事なのは、AIが書いたコードを AI自身にもう一度チェックさせること。書く役とレビューする役を分けるだけで、見落としはぐっと減ります。
そして最終的に「その指摘、本当に直す必要ある?」を判断するのは人間です。ここがまさに、AI時代に価値が残る部分です。
まとめ: 「動いた」の先に一歩進む
- 画面で動くことと、安全なことは別物。動作確認では危険な落とし穴に気づけない
- 秘密情報の直書き/RLS無し/入力チェック無し/エラー握りつぶし は、AI生成コードの定番の地雷
- 直し方はどれも「最小の一手」で済む。難しくない
- AIに「安全か確認して」と頼むだけで、見落としは大きく減る
- 価値は「速く書ける」より「危ない所を見抜ける」へ。この目は未経験のうちから育てられる
「動けばOK」をいったん「動いた、じゃあ安全?」に置き換えるだけ。これを未経験のうちから習慣にしておくと、半年後・1年後に効いてきます。プログラミングの基礎と、AIとうまく協働するスキルが、こうやって同時に積み上がっていきます。
未経験者向けの講座を運営しています
中上級者の方には当たり前すぎる内容だったと思います。初心者の知り合いへの紹介や、社内研修・後輩育成の参考として役立てば嬉しいです。
未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を、全20セッションで体系化した教材を運営しています(この記事で触れた「安全の目」も学習の中で扱います)。
- 無料体験版(git clone してすぐ動く・最初の数セッション分・⭐ Star もよろしくお願いします)→ https://github.com/ayies128/next-ai-camp-trial
- 教材完全版+月5,500円のメンタリング(全20セッション+チャットで質問し放題)→ https://menta.work/plan/20251?ref=qiita
- YouTube『AIエンジニア情報局』(AI×開発ニュースを短くキャッチアップできる別運営チャンネル・無料)→ https://www.youtube.com/channel/UC1rXVD9WYsQPQEWZyd-A1KA/?ref=qiita