【Next.js + Supabase】訓練校システムの堅牢なセキュリティ実装ガイド:生徒の個人情報をどう守るか?
こんにちは。今回は、私たちが開発している「訓練校 進捗管理システム」の裏側で動いているセキュリティ実装について、技術的な詳細を公開します。
このシステムでは、受講生の履歴書や職務経歴書、成績といった極めて重要な個人情報を扱います。「動けばいい」では済まされない、堅牢な守りが必要です。
今回は、アプリケーション層(Next.js)、データベース層(Supabase)、そして**インフラ層(Docker)**の3つの視点から、私たちが実践したセキュリティ対策を徹底解説します。
🛡️ 全体像:多層防御のアプローチ
セキュリティは「何か一つやればOK」というものではありません。攻撃者は常に弱いところを探して侵入しようとします。そのため、私たちは以下の図のような多層防御のアプローチを採用しています。
1. アプリケーション層の防御(Next.js)
まずは「玄関」であるアプリケーション側の対策です。
1.1 HTTPセキュリティヘッダーでブラウザを守る
next.config.ts にて、HTTPレスポンスヘッダーを厳格に設定しています。これは、ブラウザに対して「このサイトではこれをしてはいけない」と指示を出すようなものです。
特に重要なのが Content-Security-Policy (CSP) です。
// next.config.ts(一部抜粋)
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' ...; frame-ancestors 'none';"
}
];
- XSS(クロスサイトスクリプティング)対策: 許可されたドメイン(自分自身やGoogle Fontsなど)以外からのスクリプト実行をブロックします。
-
クリックジャッキング対策:
frame-ancestors 'none'により、他のサイトがこのサイトをiframeで埋め込むことを禁止します。透明なボタンを重ねてクリックさせる攻撃を防ぎます。
1.2 「入力はすべて疑え」:Zodによるバリデーション
ユーザーからの入力データは、サーバーに到達する前に必ず検証します。ここでは Zod というライブラリを使用し、型安全なスキーマ定義を行っています。
// schemas.ts
export const UpdateStudentProfileSchema = z.object({
fullName: z.string().min(1).max(100), // 文字数制限
courseId: z.string().uuid(), // UUID形式チェック
});
これにより、不正な形式のデータや、想定外に長い文字列(バッファオーバーフロー攻撃のきっかけになるもの)を入り口で遮断します。
1.3 CSRF(クロスサイトリクエストフォージェリ)対策
ログイン済みのユーザーのブラウザを悪用して、勝手に処理を実行させる攻撃を防ぐため、Server Actions 実行時に「リクエストの送信元」を検証しています。
// security.ts
export async function validateRequestOrigin(): Promise<boolean> {
const origin = headersList.get("origin");
const host = headersList.get("host");
// OriginとHostが一致しない場合は拒否
return originUrl.host === host;
}
2. データベース層の防御(Supabase)
次に、データの保管庫であるSupabase側の対策です。ここが「最後の砦」となります。
2.1 Row Level Security (RLS) の徹底
Supabase(PostgreSQL)の最強の機能が RLS(行レベルセキュリティ) です。
これは、「同じテーブルへのアクセスでも、誰がアクセスするかによって見える行(データ)を変える」機能です。
私たちは、すべてのテーブルでRLSを有効化しています。
具体例:application_attachments(応募書類)テーブル
| アクセスする人 | 権限 (Policy) | 結果 |
|---|---|---|
| 管理者 (Admin) | is_admin() |
全員の書類が見える・削除できる |
| 学生 (Student) | student_id = auth.uid() |
自分の書類だけが見える・アップロードできる |
| 部外者 | (ポリシーなし) | 何も見えない(エラーになる) |
SQLレベルでこの制御が行われているため、万が一アプリケーション側にバグがあっても、他人のデータが流出することはありません。
2.2 ファイルアップロードの厳格な管理
履歴書などのファイルアップロード(Storage)にもRLSを適用しています。さらに、ファイル名による攻撃を防ぐため、サーバー側でファイル名を再生成しています。
- 拡張子チェック: 許可されたファイル形式か?
- サイズ制限: 5MB以下か?
- 所有権確認: アップロードしようとしている場所は自分のフォルダか?
-
ファイル名無害化:
user-input.pdf→[timestamp]-[random].pdfに変換
3. インフラ・監査層の防御
3.1 Dockerコンテナの堅牢化(Rootレス)
今回、Dockerfile の記述を見直し、セキュリティを強化しました。
デフォルトではDockerコンテナは root 権限で動きますが、これはセキュリティリスクとなります。
改善点:非rootユーザーでの実行
# Dockerfile(改善版)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# ...ファイルの所有権変更...
USER nextjs # ← ここ重要!
これにより、万が一コンテナ内に侵入されても、攻撃者は権限が制限されているため、システム全体への被害を最小限に抑えることができます。
3.2 ログ・監査証跡(Audit Log)
「誰が」「いつ」「何をしたか」を記録することは、事後追跡のために不可欠です。
ただし、パスワードなどの機密情報をログに残してはいけません。
私たちはカスタムロガーを実装し、自動的に機密情報をマスク(隠蔽)しています。
// ログ出力イメージ
{
"action": "login_attempt",
"email": "student@example.com",
"password": "********", // 自動的にマスクされる
"ip_address": "192.168.1.1"
}
4. 今後の課題:パスワード漏洩検知
現在、Supabaseの "Leaked Password Protection"(漏洩パスワード保護)が無効になっています。これは、「過去に他のサイト等で流出したことが知られているパスワード」の使用をブロックする機能です。
次回のアップデートで、この機能を有効化し、辞書攻撃やパスワードリスト攻撃への耐性をさらに高める予定です。
📝 セキュリティ実装チェックリスト
最後に、今回の開発で使用したセキュリティチェックシートを公開します。皆さんの開発案件でもぜひ活用してください。
✅ アプリケーション(Next.js)
- HTTPヘッダー: CSP, HSTS, X-Frame-Options, X-Content-Type-Options が設定されているか
- バリデーション: すべての入力値に対して Zod 等で型・長さ・形式の検証を行っているか
- CSRF対策: Server Actions で Origin/Host ヘッダーの検証を行っているか
- ファイルアップロード: ファイルサイズ、拡張子、MIMEタイプをサーバー側で検証しているか
✅ データベース(Supabase)
-
RLS有効化: すべてのテーブルでRLSが有効になっているか(
alter table ... enable row level security) - ポリシー定義: 「誰が」「何を」して良いか、最小権限の原則に基づき定義されているか
- Storage RLS: ファイルストレージ(バケット)にも適切なポリシーが適用されているか
-
関数セキュリティ:
SECURITY DEFINERを使用する際、search_pathを設定しているか
✅ インフラ(Docker / AWS)
-
非root実行: コンテナは非rootユーザー(例:
nextjs)で実行されているか -
機密情報の除外:
.dockerignoreに.envや.gitが含まれているか - 環境変数: APIキーなどの機密情報はコードに埋め込まず、環境変数で注入しているか
- ヘルスチェック: コンテナの死活監視が設定されているか
✅ 運用・監視
- ログのマスキング: パスワードやトークンがログに出力されていないか
- 監査ログ: 重要な操作(削除、権限変更など)が記録されているか
まとめ
セキュリティは「機能」ではなく「品質」です。
受講生の皆さんが安心して学習に取り組み、素晴らしいキャリアを築くための第一歩として、私たちはこのシステムを安全に守り続けます。
もし、このセキュリティ実装について詳しく知りたい点があれば、お気軽に質問してください!