2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIが書いたコード、「動いたからOK」で本番に出してませんか

2
Posted at

「動いた」と「安全」は別物です

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セッションで体系化した教材を運営しています(この記事で触れた「安全の目」も学習の中で扱います)。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?