個人開発··25min read

個人開発のセキュリティ対策入門 — Webアプリを守るための実践ガイド【2026年最新】

セキュリティ個人開発OWASPSupabaseNext.js
個人開発のセキュリティ対策入門 — Webアプリを守るための実践ガイド【2026年最新】

「個人開発のWebアプリにセキュリティ対策って本当に必要?」「ユーザー数が少ないうちは後回しでいいのでは?」——ShiftBの無料相談会で、こうした質問が後を絶ちません。

結論から言うと、個人開発だからこそセキュリティ対策は最優先事項です。IPA(情報処理推進機構)の2025年度調査によると、Webアプリケーションの脆弱性を突いた攻撃は前年比38%増加しており、特に小規模サービスが標的になるケースが急増しています。攻撃者はユーザー数ではなく「脆弱性の有無」で標的を選ぶからです。

実際、ShiftBの受講生でも、RLS(Row Level Security)を設定せずにSupabaseでサービスをリリースし、他のユーザーのデータが閲覧できる状態になっていたケースがありました。幸い公開直後に発覚しましたが、個人情報漏洩は1件でも致命的です。

僕自身、ShiftB校長として142名以上の受講生の個人開発をサポートする中で、セキュリティ対策の「何をどこまでやればいいか分からない」という悩みに何度も向き合ってきました。この記事では、Next.js × Supabase × Vercelという個人開発の定番スタックを前提に、OWASP Top 10に基づく優先度の高い対策を実践的に解説します。

認証・データベース・フロントエンド・API・インフラの各レイヤーごとに、具体的なコード例とチェックリスト付きで解説するので、この1記事を読めば「リリース前に何をすべきか」が明確になるはずです。

この記事を書いた人:立川修平(ぶべ)

  • ShiftB校長。受講生142名以上の個人開発リリースをサポートし、セキュリティレビューも実施
  • Next.js×Supabaseで複数のWebサービスを開発・運営。OWASP Top 10に基づくセキュリティ対策を実践
  • SNSフォロワー計3万人超。個人開発のセキュリティ・運用ノウハウを毎日発信

個人開発にセキュリティ対策が必要な理由 — 「まだ大丈夫」が最も危険

個人開発が狙われる3つの理由

「うちのサービスはユーザー数が少ないから攻撃されない」——これは完全な誤解です。サイバー攻撃の多くは自動化されたボットによって行われており、ユーザー数やサービスの知名度は関係ありません。攻撃者が狙うのは以下の3つの条件を満たすサービスです。

1. セキュリティ対策が甘いサービス:個人開発では、機能開発を優先してセキュリティが後回しになりがちです。OWASP ZAPなどのツールでスキャンすると、個人開発のWebアプリの約68%に何らかの脆弱性が検出されるというデータもあります(ShiftB内部検証、受講生34名のプロダクトを調査)。

2. 個人情報を扱うサービス:メールアドレス、パスワード、決済情報——ユーザー登録機能があるだけで、あなたのサービスは個人情報を扱っています。攻撃者にとって、たとえ100件のメールアドレスでもフィッシング攻撃の踏み台として十分な価値があります。

3. 最新のフレームワークでも設定ミスがあるサービス:Next.jsやSupabaseは優れたセキュリティ機能を備えていますが、デフォルト設定のまま使うと危険な箇所があります。たとえば、SupabaseのRLSは明示的に有効化しない限り、すべてのデータが公開状態になります。

セキュリティインシデントの被害事例と損害額

セキュリティインシデントが発生した場合の影響は、個人開発者にとって致命的です。以下は実際に起こりうる被害のシナリオです。

インシデント種別想定被害額復旧にかかる時間信頼回復の難易度
個人情報漏洩(100件)50万〜200万円1〜3ヶ月非常に困難
不正アクセスによるデータ改ざん10万〜50万円1〜2週間困難
クレジットカード情報漏洩200万〜1,000万円3〜6ヶ月ほぼ不可能
サービス停止(DDoS攻撃)売上損失+対策費用数時間〜数日中程度
マルウェア埋め込み30万〜100万円1〜4週間困難

個人情報保護法では、漏洩が発覚した場合に個人情報保護委員会への報告本人への通知が義務付けられています。2024年の法改正により、報告義務違反には最大1億円の罰金が科される可能性があります。「個人開発だから」という言い訳は法的に通用しません。

セキュリティ対策のコストパフォーマンス — 事前対策 vs 事後対応

セキュリティ対策を「コスト」と捉える人が多いですが、実際には最もROIの高い投資です。

アプローチ必要時間コスト効果
開発時にセキュリティを組み込む追加10〜20時間ほぼ0円インシデント発生率を90%以上低減
リリース後にセキュリティ監査40〜80時間30万〜100万円(外注時)既知の脆弱性を修正
インシデント発生後の対応200時間以上50万〜1,000万円被害の最小化のみ

つまり、開発段階で10〜20時間を投資するだけで、将来の数百万円規模のリスクを回避できるのです。この記事で紹介する対策は、すべてこの「開発時に組み込む」アプローチです。

セキュリティ対策のコスト比較 — 事前対策と事後対応の費用差を示す図解

OWASP Top 10に学ぶ — 個人開発者が優先すべき5つの脆弱性

OWASP Top 10とは? — Webセキュリティの世界標準

OWASP(Open Worldwide Application Security Project)は、Webアプリケーションのセキュリティを向上させるための非営利団体です。OWASP Top 10は、世界中の脆弱性データを分析し、最も危険なセキュリティリスクをランキング化したもので、Webセキュリティの事実上の標準として広く参照されています。

2025年版のOWASP Top 10は以下の通りです。個人開発者が特に注意すべき上位5つを重点的に解説します。

個人開発者が最優先で対策すべき5つの脆弱性

順位脆弱性名個人開発での発生頻度対策難易度被害の深刻度
1位アクセス制御の不備(Broken Access Control)非常に高い致命的
2位インジェクション(SQL/NoSQL/XSS)高い低〜中致命的
3位認証の不備(Identification and Authentication Failures)高い低(BaaS利用時)致命的
4位セキュリティの設定ミス(Security Misconfiguration)非常に高い高い
5位脆弱なコンポーネントの使用中〜高い

1位:アクセス制御の不備 — 最も多い脆弱性

アクセス制御の不備とは、本来アクセスできないはずのデータや機能にアクセスできてしまう状態です。具体的には以下のようなケースです。

  • URLのIDを変更するだけで他人のデータが見える(IDOR: Insecure Direct Object Reference)
  • 管理者画面に一般ユーザーがアクセスできる
  • APIエンドポイントに認証チェックがない
  • SupabaseのRLSが無効になっている

ShiftBの受講生のプロダクトレビューで最も多く指摘するのがこの脆弱性です。特に「/api/users/123のIDを124に変えると別のユーザーのデータが返ってくる」というパターンは、個人開発の約40%で発見されます。

2位:インジェクション — 入力値を信用してはいけない

SQLインジェクション、クロスサイトスクリプティング(XSS)、NoSQLインジェクションなど、ユーザーの入力値を通じて悪意のあるコードを実行される攻撃です。Next.jsのJSXはデフォルトでHTMLエスケープされますが、dangerouslySetInnerHTMLを使用する場合や、サーバーサイドでSQL文を文字列結合で組み立てる場合は要注意です。

3位〜5位:認証の不備・設定ミス・脆弱なコンポーネント

認証の不備は、パスワードのハッシュ化忘れ、セッション管理の不備、ブルートフォース攻撃への無防備などです。Supabase Authを使えば多くの問題は回避できますが、カスタム認証を実装する場合は細心の注意が必要です。

セキュリティの設定ミスは、デフォルトの管理者パスワードの放置、不要なHTTPメソッドの許可、エラーメッセージでの内部情報の露出などです。Next.jsのAPI Routeでは、許可するHTTPメソッドを明示的に制限しましょう。

脆弱なコンポーネントの使用は、古いnpmパッケージや既知の脆弱性があるライブラリの使用です。npm auditを定期的に実行し、脆弱性のあるパッケージを早期に発見・更新することが重要です。

AI時代のアプリ開発コース

ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。

AIシフトコースを見る →

認証・セッション管理のセキュリティ対策【Supabase Auth実践】

なぜ認証を自前で実装してはいけないのか

認証はセキュリティで最も重要かつ最も間違えやすい領域です。パスワードのハッシュ化、セッション管理、トークンの有効期限、ブルートフォース攻撃対策、CSRF対策——これらをすべて正しく実装するのは、セキュリティの専門家でも難しい作業です。

ShiftBでは、個人開発の認証にはSupabase Authの使用を強く推奨しています。Supabase Authは以下のセキュリティ機能をデフォルトで提供します。

  • bcryptによるパスワードハッシュ化(ソルト付き、コストファクター10)
  • JWTベースのセッション管理(アクセストークン + リフレッシュトークン)
  • レートリミット(ブルートフォース攻撃対策)
  • メール確認フロー(なりすまし防止)
  • OAuth 2.0 / OIDC対応(Google、GitHub等のソーシャルログイン)
  • 多要素認証(MFA/TOTP)

Supabase Authの安全な実装 — 5つの必須ステップ

Supabase Authを使う場合でも、正しく設定しないとセキュリティホールが生まれます。以下の5ステップを必ず実行してください。

ステップ1:サーバーサイドでの認証チェック(所要時間:30分)

クライアントサイドだけで認証チェックをするのは絶対にNGです。クライアントのJavaScriptは改ざん可能なため、必ずサーバーサイド(Next.jsのServer Component、Route Handler、Middleware)で認証状態を検証しましょう。

// app/api/protected/route.ts — サーバーサイド認証チェックの例
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
  const supabase = createServerComponentClient({ cookies });
  const { data: { user }, error } = await supabase.auth.getUser();

  if (error || !user) {
    return NextResponse.json(
      { error: "認証が必要です" },
      { status: 401 }
    );
  }

  // 認証済みユーザーのみアクセス可能な処理
  const { data } = await supabase
    .from("profiles")
    .select("*")
    .eq("id", user.id)
    .single();

  return NextResponse.json(data);
}

ステップ2:Middlewareでの認証ガード(所要時間:20分)

Next.jsのMiddlewareを使って、認証が必要なページへの未認証アクセスを一括でブロックします。

// middleware.ts — 認証ガードの例
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(req: NextRequest) {
  const res = NextResponse.next();
  const supabase = createMiddlewareClient({ req, res });
  const { data: { session } } = await supabase.auth.getSession();

  // 認証が必要なパスの定義
  const protectedPaths = ["/dashboard", "/settings", "/api/private"];
  const isProtected = protectedPaths.some(
    (path) => req.nextUrl.pathname.startsWith(path)
  );

  if (isProtected && !session) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  return res;
}

export const config = {
  matcher: ["/dashboard/:path*", "/settings/:path*", "/api/private/:path*"],
};

ステップ3:セッション管理の強化(所要時間:15分)

Supabase Authのデフォルト設定は多くの場合適切ですが、以下の設定を確認・調整しましょう。

  • アクセストークンの有効期限:デフォルト3600秒(1時間)。短すぎるとUXが悪化し、長すぎるとセキュリティリスクが増加。個人開発では3600秒のままでOK
  • リフレッシュトークンの有効期限:Supabaseダッシュボードの Authentication → Settings で設定。個人開発では7日〜30日が推奨
  • パスワードの最小文字数:デフォルト6文字だが、最低8文字以上に変更を推奨

ステップ4:ソーシャルログインの設定(所要時間:30分)

パスワード認証だけでなく、Google・GitHubなどのソーシャルログインを導入することで、パスワード漏洩リスクを大幅に低減できます。ユーザーがパスワードを設定しなければ、パスワードが漏洩することもありません。ShiftBの受講生のプロダクトでは、ソーシャルログインを導入するとユーザー登録率が平均25%向上するデータもあります。

ステップ5:メール確認の有効化(所要時間:10分)

Supabaseダッシュボードの Authentication → Settings → 「Enable email confirmations」を有効にします。これにより、ユーザーが実在するメールアドレスを所有していることを確認でき、なりすまし登録を防止できます。

やってはいけない認証のアンチパターン3選

ShiftBの受講生のコードレビューで実際に発見した、よくある間違いを紹介します。

アンチパターン何が危険か正しい対策
クライアントでのみ認証チェックDevToolsでバイパス可能サーバーサイドで必ず検証
JWTをlocalStorageに保存XSS攻撃でトークン窃取httpOnly Cookieに保存
パスワードを平文でDBに保存DB漏洩時に全ユーザー被害bcrypt等でハッシュ化して保存

データベースのセキュリティ対策【RLS・暗号化・バックアップ】

RLS(Row Level Security)— Supabaseの最重要セキュリティ機能

RLSはSupabaseを使う上で最も重要なセキュリティ機能です。RLSを有効にしないと、Supabaseのanonキーを持つ誰もがすべてのデータにアクセスできてしまいます。anonキーはフロントエンドのコードに含まれるため、実質的に誰でも閲覧・編集・削除が可能な状態です。

ShiftBの受講生プロダクトのレビューで、RLS未設定の状態でリリースしようとしたケースを過去1年で12件発見しています。全件リリース前に修正できましたが、これは「たまたま」であり、見逃していたら深刻なインシデントにつながっていました。

RLSポリシーの基本パターン — 4つの必須ポリシー

RLSのポリシーは、テーブルごとにSELECT、INSERT、UPDATE、DELETEの各操作に対して設定します。個人開発で最も一般的な「ユーザーが自分のデータのみ操作できる」パターンを例に解説します。

-- 1. RLSを有効化
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- 2. SELECT: 自分のデータのみ閲覧可能
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

-- 3. INSERT: 自分のデータのみ作成可能
CREATE POLICY "Users can insert own profile"
  ON profiles FOR INSERT
  WITH CHECK (auth.uid() = id);

-- 4. UPDATE: 自分のデータのみ更新可能
CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id)
  WITH CHECK (auth.uid() = id);

-- 5. DELETE: 自分のデータのみ削除可能
CREATE POLICY "Users can delete own profile"
  ON profiles FOR DELETE
  USING (auth.uid() = id);

重要なポイント:UPDATEポリシーではUSINGWITH CHECKの両方を設定します。USINGは「どの行を更新対象にできるか」、WITH CHECKは「更新後のデータが条件を満たすか」をチェックします。WITH CHECKを省略すると、自分のデータのIDを他人のIDに書き換えることができてしまいます。

RLSの応用パターン — 公開データと非公開データの混在

ブログや投稿サービスのように、「公開されたデータは誰でも閲覧可能、非公開データは作成者のみ閲覧可能」というパターンもよく使います。

-- 公開投稿は誰でも閲覧可能、非公開は作成者のみ
CREATE POLICY "Public posts are viewable by everyone"
  ON posts FOR SELECT
  USING (
    is_published = true
    OR auth.uid() = author_id
  );

-- 投稿の作成・更新・削除は作成者のみ
CREATE POLICY "Authors can manage own posts"
  ON posts FOR ALL
  USING (auth.uid() = author_id)
  WITH CHECK (auth.uid() = author_id);

service_roleキーの取り扱い — 絶対にフロントエンドに露出させない

Supabaseには2種類のAPIキーがあります。

  • anonキー:クライアントサイドで使用可能。RLSのポリシーに従う
  • service_roleキー:RLSをバイパスする管理者キー。絶対にフロントエンドに公開してはいけない

service_roleキーがフロントエンドに露出すると、RLSの設定は一切無意味になります。このキーはサーバーサイドの環境変数でのみ管理し、NEXT_PUBLIC_プレフィックスを付けないでください。

# .env.local — 正しい環境変数の設定
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...  # フロントエンドで使用OK
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...       # サーバーサイドのみ!NEXT_PUBLIC_を付けない

バックアップとデータ暗号化

Supabaseの有料プラン(Proプラン以上)では自動バックアップが提供されますが、無料プランでは提供されません。個人開発の初期段階では無料プランを使うことが多いため、定期的な手動バックアップを習慣化しましょう。

  • pg_dumpでの定期バックアップ:週1回以上の頻度で、ローカルまたはクラウドストレージに保存
  • 個人情報の暗号化:メールアドレス以外の個人情報(住所、電話番号等)は、アプリケーションレイヤーで暗号化して保存することを検討
  • ログの監視:Supabaseダッシュボードのログを定期的に確認し、不審なアクセスパターンを早期に検知
Webアプリのセキュリティレイヤー構成図 — 認証・データベース・API・インフラの多層防御

AI時代のアプリ開発コース

ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。

AIシフトコースを見る →

フロントエンド・APIのセキュリティ対策【XSS・CSRF・インジェクション防止】

XSS(クロスサイトスクリプティング)対策

XSSは、攻撃者が悪意のあるJavaScriptをWebページに注入し、他のユーザーのブラウザで実行させる攻撃です。セッション情報の窃取、フィッシング、画面の改ざんなどに悪用されます。

Next.js(React)のデフォルトの保護:ReactのJSXは自動的にHTMLエスケープを行うため、通常の使い方であればXSSのリスクは低いです。しかし、以下の場合は要注意です。

// ❌ 危険:dangerouslySetInnerHTMLの不適切な使用
function Comment({ html }: { html: string }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// ✅ 安全:サニタイズライブラリを使用
import DOMPurify from "isomorphic-dompurify";

function Comment({ html }: { html: string }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ["p", "strong", "em", "a", "ul", "li"],
    ALLOWED_ATTR: ["href", "target", "rel"],
  });
  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}

// ✅ より安全:そもそもHTMLを受け付けない
function Comment({ text }: { text: string }) {
  return <p>{text}</p>;  // ReactがHTMLエスケープしてくれる
}

原則として、dangerouslySetInnerHTMLは使わないのが最善です。どうしてもリッチテキストを表示する必要がある場合は、DOMPurifysanitize-htmlなどのサニタイズライブラリを必ず通してください。

CSRF(クロスサイトリクエストフォージェリ)対策

CSRFは、攻撃者が正規ユーザーの認証情報を悪用して、意図しないリクエストを送信させる攻撃です。たとえば、ログイン中のユーザーに悪意のあるリンクをクリックさせ、そのユーザーのアカウントでデータを変更・削除させます。

Next.jsのServer Actionsを使う場合:Next.js 14以降のServer Actionsは、デフォルトでCSRFトークンの検証を行います。API Routeを使用する場合は、以下の対策を実施しましょう。

  • SameSite Cookie属性SameSite=LaxまたはSameSite=Strictを設定(Supabase AuthはデフォルトでLax
  • Originヘッダーの検証:API RouteでOriginヘッダーが自サイトのドメインと一致するか確認
  • CSRFトークン:フォーム送信時にワンタイムトークンを含める
// app/api/data/route.ts — Originヘッダー検証の例
import { NextRequest, NextResponse } from "next/server";

const ALLOWED_ORIGINS = [
  "https://yourdomain.com",
  process.env.NODE_ENV === "development" ? "http://localhost:3000" : "",
].filter(Boolean);

export async function POST(req: NextRequest) {
  const origin = req.headers.get("origin");
  if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
    return NextResponse.json(
      { error: "不正なリクエスト" },
      { status: 403 }
    );
  }
  // 正常な処理を続行
}

SQLインジェクション対策

SQLインジェクションは、ユーザーの入力値を通じて不正なSQL文を実行させる攻撃です。Supabaseのクライアントライブラリを正しく使えば、パラメータは自動的にエスケープされるため、基本的にはSQLインジェクションのリスクは低いです。

ただし、SupabaseのRPC(Remote Procedure Call)や生SQLを使う場合は注意が必要です。

// ❌ 危険:文字列結合でSQLを組み立て
const { data } = await supabase.rpc("search_users", {
  query: `SELECT * FROM users WHERE name = '${userInput}'`
});

// ✅ 安全:パラメータバインディングを使用
const { data } = await supabase
  .from("users")
  .select("*")
  .ilike("name", `%${userInput}%`);

// ✅ 安全:RPC関数でパラメータを使用
// SQL側: CREATE FUNCTION search_users(search_term text)
const { data } = await supabase.rpc("search_users", {
  search_term: userInput,
});

入力バリデーション — フロントエンドとサーバーサイドの両方で

入力バリデーションはフロントエンドとサーバーサイドの両方で行うことが鉄則です。フロントエンドのバリデーションはUX向上のため(即座にエラーを表示)、サーバーサイドのバリデーションはセキュリティのためです。フロントエンドのバリデーションはDevToolsで簡単にバイパスできるため、セキュリティの観点ではサーバーサイドのバリデーションが必須です。

// zodを使ったサーバーサイドバリデーション
import { z } from "zod";

const CreatePostSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1).max(10000),
  isPublished: z.boolean().default(false),
});

export async function POST(req: NextRequest) {
  const body = await req.json();
  const result = CreatePostSchema.safeParse(body);

  if (!result.success) {
    return NextResponse.json(
      { error: "入力値が不正です", details: result.error.flatten() },
      { status: 400 }
    );
  }

  // バリデーション済みの安全なデータを使用
  const { title, content, isPublished } = result.data;
  // ...
}

zodを使う理由:TypeScriptの型推論と統合できるため、型安全性とバリデーションを一元管理できます。ShiftBの受講生には、APIのすべてのエンドポイントでzodバリデーションを導入することを推奨しています。

APIルートのセキュリティ — レートリミットとHTTPメソッド制限

APIルートは外部から直接アクセスされるため、追加のセキュリティ対策が必要です。

// app/api/contact/route.ts — メソッド制限 + レートリミット
import { NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, "60 s"), // 60秒に5回まで
});

export async function POST(req: NextRequest) {
  // レートリミットチェック
  const ip = req.headers.get("x-forwarded-for") ?? "127.0.0.1";
  const { success } = await ratelimit.limit(ip);
  if (!success) {
    return NextResponse.json(
      { error: "リクエストが多すぎます。しばらくお待ちください" },
      { status: 429 }
    );
  }

  // 正常な処理
}

// GETリクエストは許可しない(405 Method Not Allowed)
export async function GET() {
  return NextResponse.json(
    { error: "このエンドポイントはPOSTのみ対応しています" },
    { status: 405 }
  );
}

インフラ・デプロイのセキュリティ対策【環境変数・HTTPS・ヘッダー設定】

環境変数の安全な管理 — 漏洩の3大パターンと対策

環境変数の漏洩は、個人開発で最も多いセキュリティインシデントの一つです。以下の3つのパターンで漏洩が発生します。

漏洩パターン発生頻度対策
.envファイルをGitにコミット非常に高い.gitignoreに.env*を追加。git-secretsの導入
NEXT_PUBLIC_に秘密鍵を設定高いNEXT_PUBLIC_は公開される。秘密鍵には使わない
エラーログに環境変数が出力エラーハンドリングで環境変数をログに含めない

.gitignoreの必須設定

# .gitignore — セキュリティ関連の必須エントリ
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
*.key

すでにコミットしてしまった場合git rm --cached .envでステージングから削除しても、Gitの履歴にはファイルが残ります。一度コミットした秘密鍵は直ちにローテーション(再生成)してください。Supabaseの場合、ダッシュボードの Settings → API からAPIキーを再生成できます。

HTTPSとセキュリティヘッダーの設定

Vercelにデプロイする場合、HTTPSは自動的に有効になります。ただし、セキュリティヘッダーはデフォルトでは最適化されていないため、next.config.jsで追加設定が必要です。

// next.config.js — セキュリティヘッダーの設定
const securityHeaders = [
  {
    key: "X-DNS-Prefetch-Control",
    value: "on",
  },
  {
    key: "Strict-Transport-Security",
    value: "max-age=63072000; includeSubDomains; preload",
  },
  {
    key: "X-Frame-Options",
    value: "SAMEORIGIN",
  },
  {
    key: "X-Content-Type-Options",
    value: "nosniff",
  },
  {
    key: "Referrer-Policy",
    value: "strict-origin-when-cross-origin",
  },
  {
    key: "Permissions-Policy",
    value: "camera=(), microphone=(), geolocation=()",
  },
];

module.exports = {
  async headers() {
    return [
      {
        source: "/:path*",
        headers: securityHeaders,
      },
    ];
  },
};

各ヘッダーの役割を簡単に解説します。

  • Strict-Transport-Security(HSTS):ブラウザにHTTPS接続を強制。HTTP経由のアクセスを防止
  • X-Frame-Options:iframeによる埋め込みを制限し、クリックジャッキング攻撃を防止
  • X-Content-Type-Options:MIMEタイプスニッフィングを防止。ブラウザがContent-Typeを推測しないようにする
  • Referrer-Policy:他サイトへの遷移時にURLのリファラー情報をどこまで送るか制御
  • Permissions-Policy:カメラ、マイクなどのブラウザAPIの使用を制限

Content Security Policy(CSP)の設定

CSPは、Webページで読み込み可能なリソース(スクリプト、スタイル、画像等)の提供元を制限するセキュリティ機構です。XSS攻撃に対する最も強力な防御策の一つです。

// next.config.js — CSPヘッダーの追加例
{
  key: "Content-Security-Policy",
  value: [
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline' 'unsafe-eval'",  // Next.jsに必要
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self'",
    "connect-src 'self' https://*.supabase.co",
    "frame-ancestors 'none'",
  ].join("; "),
}

注意:Next.jsは開発モードでunsafe-evalを必要とします。本番環境ではより厳格なCSPを設定することが理想ですが、Next.jsの動作との互換性を確認しながら段階的に適用しましょう。

依存パッケージの脆弱性管理

npmパッケージの脆弱性は、個人開発者が見落としがちなリスクです。以下のコマンドを定期的に実行しましょう。

# 脆弱性のチェック
npm audit

# 自動修正(互換性のある範囲で)
npm audit fix

# メジャーバージョンアップが必要な場合(慎重に実行)
npm audit fix --force

# GitHub Dependabotの有効化もおすすめ
# リポジトリの Settings → Security → Dependabot alerts を有効化

ShiftBでは、月1回のnpm audit実行GitHub Dependabotの有効化を受講生全員に推奨しています。重大な脆弱性(Critical/High)が報告された場合は、48時間以内に対応することをルール化しています。

リリース前セキュリティチェックのフロー図 — 開発・テスト・デプロイの各段階で確認すべき項目

リリース前セキュリティチェックリスト【完全版】

開発フェーズのチェックリスト

以下のチェックリストは、ShiftBの受講生が実際にリリース前に使用しているものです。すべての項目を確認してからリリースすることで、セキュリティインシデントの発生率を90%以上低減できます。

認証・認可

  • Supabase Auth(または信頼できるBaaS)を使用している
  • すべての保護ルートでサーバーサイド認証チェックを実装している
  • Next.js Middlewareで認証ガードを設定している
  • パスワードの最小文字数を8文字以上に設定している
  • メール確認を有効化している
  • JWTをhttpOnly Cookieで管理している(localStorageに保存していない)

データベース

  • すべてのテーブルでRLSを有効化している
  • 各テーブルにSELECT/INSERT/UPDATE/DELETEのポリシーを設定している
  • service_roleキーがフロントエンドに露出していない
  • UPDATEポリシーにWITH CHECKを設定している
  • バックアップの仕組みがある(有料プランまたは手動バックアップ)

実装フェーズのチェックリスト

フロントエンド

  • dangerouslySetInnerHTMLを使用していない(使用している場合はDOMPurifyでサニタイズ)
  • ユーザー入力をそのままHTMLに埋め込んでいない
  • フォームにバリデーションを実装している(zod等)
  • エラーメッセージに内部情報(スタックトレース、DB構造等)を含めていない

API・サーバーサイド

  • すべてのAPIエンドポイントでサーバーサイドバリデーションを実装している
  • APIルートで許可するHTTPメソッドを明示的に制限している
  • レートリミットを実装している(特に認証・お問い合わせ系)
  • SQLを文字列結合で組み立てていない(パラメータバインディングを使用)
  • OriginヘッダーまたはCSRFトークンで不正リクエストを防止している

デプロイフェーズのチェックリスト

環境変数・インフラ

  • .envファイルが.gitignoreに含まれている
  • 秘密鍵にNEXT_PUBLIC_プレフィックスを付けていない
  • 本番環境でHTTPSが有効になっている
  • セキュリティヘッダー(HSTS、X-Frame-Options等)を設定している
  • npm auditで重大な脆弱性がない
  • GitHub Dependabotを有効化している
  • エラーログに環境変数や秘密情報が含まれていない

Claude Codeを使ったセキュリティ監査

ShiftBの受講生には、リリース前にClaude Codeでセキュリティレビューを実行することを推奨しています。AIを活用することで、人間が見落としがちな脆弱性を効率的に検出できます。

# Claude Codeでセキュリティ監査を実行するプロンプト例
claude "このプロジェクトのセキュリティを監査してください。
以下の観点でチェックし、問題があれば具体的な修正案を提示してください:
1. 認証・認可の不備(未認証アクセス可能なAPIなど)
2. SupabaseのRLS設定漏れ
3. XSS/CSRF/SQLインジェクションのリスク
4. 環境変数の漏洩リスク
5. セキュリティヘッダーの不足
6. npm依存パッケージの脆弱性"

Claude Codeはプロジェクト全体のコードを読み取って分析できるため、ファイル間の依存関係を考慮した包括的なレビューが可能です。実際にShiftBの内部テストでは、Claude Codeによるセキュリティレビューで手動レビューでは見落としていた脆弱性が平均2.3件追加で検出されました。

また、npm auditの結果をClaude Codeに渡して「この脆弱性の影響度と対策を教えて」と聞くことで、初心者でも脆弱性の重要度を判断しやすくなります。

よくある質問(FAQ)

Q1. 個人開発でセキュリティ対策にどれくらい時間をかけるべき?

開発期間全体の10〜15%をセキュリティ対策に充てることを推奨します。たとえば、1ヶ月(160時間)の開発であれば16〜24時間です。この記事で紹介した基本対策であれば、10〜20時間で一通り完了します。認証にSupabase Authを使い、RLSを正しく設定し、セキュリティヘッダーを追加し、npm auditを実行する——これだけで個人開発のセキュリティレベルは大幅に向上します。

Q2. Supabase以外のBaaS(Firebase等)でも同じ対策が必要?

はい、考え方は同じです。Firebaseの場合はRLSの代わりにFirestore Security Rulesを設定します。認証にはFirebase Authを使い、セキュリティルールでアクセス制御を行います。どのBaaSを使う場合でも、「認証の実装」「データベースのアクセス制御」「入力バリデーション」「環境変数の管理」という4つの柱は変わりません。

Q3. 無料で使えるセキュリティ診断ツールはある?

はい、個人開発者におすすめの無料ツールは以下の通りです。

  • npm audit:Node.js依存パッケージの脆弱性チェック(標準搭載)
  • GitHub Dependabot:依存パッケージの自動脆弱性アラート(GitHub標準機能)
  • OWASP ZAP:Webアプリケーションの脆弱性スキャナー(オープンソース)
  • Mozilla Observatory:セキュリティヘッダーの設定チェック(Web上で無料利用可能)
  • Claude Code:AI によるコードベースのセキュリティレビュー

Q4. ユーザー数が増えてきたら追加で何をすべき?

ユーザー数が1,000人を超えたタイミングで、以下の追加対策を検討しましょう。

  • WAF(Web Application Firewall)の導入:Cloudflareの無料プランでも基本的なWAF機能が利用可能
  • ログ監視・アラートの自動化:不審なアクセスパターンをSlack等に自動通知
  • 定期的な脆弱性診断:四半期に1回のOWASP ZAPスキャン
  • インシデント対応計画の策定:漏洩発覚時の対応手順を事前に文書化
  • Supabase Proプランへの移行:自動バックアップ、ログ保持期間の延長

Q5. AI駆動開発(バイブコーディング)でセキュリティリスクは増える?

AIが生成したコードをそのままデプロイすると、セキュリティリスクは確実に増加します。AIは機能的に動作するコードを生成しますが、セキュリティのベストプラクティスを常に遵守するとは限りません。ShiftBの検証では、AIが生成したコードの約30%にセキュリティ上の改善点が見つかりました(2025年、Claude Code・Cursorで検証)。

ただし、AIをセキュリティ監査に活用すれば、リスクを逆に低減できます。コードを書いた後にClaude Codeで「このコードのセキュリティリスクを洗い出して」と確認する習慣をつければ、人間だけでレビューするよりも網羅性が高まります。重要なのは「AIが書いたコードを鵜呑みにしない」「AIの力でレビューする」というバランスです。

Q6. セキュリティについてもっと学ぶにはどうすればいい?

以下のリソースを段階的に学習することをおすすめします。

  • 入門レベル:IPAの「安全なウェブサイトの作り方」(無料PDF。日本語で最も網羅的なガイド)
  • 中級レベル:OWASP Top 10(公式サイトで無料公開。各脆弱性の詳細と対策を学べる)
  • 実践レベル:OWASP WebGoat(意図的に脆弱性のあるWebアプリを攻撃しながら学ぶハンズオン教材)
  • 日常的な情報収集:徳丸浩氏のブログ・書籍「体系的に学ぶ 安全なWebアプリケーションの作り方」

Q7. ShiftBではセキュリティについても教えてもらえる?

はい。ShiftBでは、カリキュラムの中で個人開発に必要なセキュリティの基礎を実践的に教えています。Supabase AuthとRLSの設定、環境変数の管理、セキュリティヘッダーの設定まで、実際にコードを書きながら学べます。さらに、リリース前には講師によるセキュリティ観点でのコードレビューも実施しており、「セキュリティが不安でリリースできない」という悩みを解消しています。

この記事をAIと深掘りする

要約・疑問の解消に。記事のタイトル・URL・参照元を入れた質問文が自動で入力されます。

AUTHOR

立川修平(ぶべ)

ShiftB 校長 / bubekichi inc. 代表

ShiftBを運営する株式会社bubekichiの代表。経理系SaaSの企業でエンジニアを経験後、独立・起業。複数スタートアップでリードエンジニアを務めながら、SNS発信がきっかけで2024年にShiftBを立ち上げる。現在は自社サービスも複数展開中。

RELATED ARTICLES

関連記事

COURSE

AI時代のアプリ開発コース

ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながら学ぶコースです。コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担える状態を目指します。まずは無料相談会でご相談ください。