security-operations-deployment について
security-operations-deploymentは、Webアプリのセキュリティ対策とデプロイに関する実践的なガイドを提供するスキルです。ミドルウェアの選択、シークレット管理、事前確認、稼働後の監視を扱います。
主な機能
- 01レート制限とCSRFミドルウェアの指針
- 02環境変数とシークレットの管理
- 03デプロイ前のセキュリティチェックリスト
- 04稼働後のメトリクスとログ監視
- 05セキュリティインシデントの対応優先度
活用シーン
- 01APIルートの保護策を選定
- 02本番環境のシークレットと設定を準備
- 03デプロイ前にアプリの安全性を確認
- 04認証、CSRF、レート制限のイベントを監視
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ harperaa/ secure-claude-skills --skill security-operations-deploymentインストール後、Claude Code で/security-operations-deploymentを入力して読み込まれたか確認します。
詳細
セキュリティ運用とデプロイ
各ミドルウェアを適用するタイミング — 判断ガイド
withRateLimit() を適用する対象:
✅ 必ず適用する対象:
- 悪用される可能性のあるルート(スパム、ブルートフォース攻撃)
- ログインに類似した操作(Clerk が認証を処理する場合も含む)
- データの作成・変更エンドポイント
- お問い合わせ/サポートフォームのエンドポイント
- Webhook(DoS を防止するため)
- ファイルアップロードのエンドポイント
- 検索エンドポイント
- データエクスポートのエンドポイント
- 高コストな AI/API 操作
- レポート生成
- 一括操作
❌ 通常は不要な対象:
- 静的アセットのリクエスト(CDN が処理)
- 公開データの読み取りのみを行う単純な GET エンドポイント
- ヘルスチェックのエンドポイント
- 認証のレート制限ですでに保護されているエンドポイント
withCsrf() を適用する対象:
✅ 必ず適用する対象:
- すべての POST/PUT/DELETE 操作
- 状態を変更するすべての操作
- フォーム送信
- アカウントの変更
- 決済操作
- データ削除操作
❌ 適用しない対象:
- GET リクエスト(読み取り専用操作)
- 公開読み取り専用エンドポイント
- Webhook(代わりに署名検証を使用)
両方のミドルウェアを組み合わせる
最大限の保護のために:
// Order matters: rate limit first, then CSRF
export const POST = withRateLimit(withCsrf(handler));
適用順序が重要な理由:
- レート制限を最初に実行し、過剰なリクエストを早い段階でブロックします
- レート制限を通過したリクエストに対して CSRF 検証を実行します
- レート制限されたリクエストに CSRF 検証を行わずに済むため、より効率的です
判断マトリックス:
| ルートの種類 | レート制限 | CSRF | 認証 |
|---|---|---|---|
| 公開フォームの送信 | ✅ はい | ✅ はい | ❌ いいえ |
| 保護されたデータ変更 | ✅ はい | ✅ はい | ✅ はい |
| 公開読み取り専用 API | ❌ いいえ | ❌ いいえ | ❌ いいえ |
| 保護された読み取り専用 API | ✅ 場合による | ❌ いいえ | ✅ はい |
| Webhook エンドポイント | ✅ はい | ❌ いいえ | ✅ 署名 |
| ファイルアップロード | ✅ はい | ✅ はい | ✅ はい |
環境変数とシークレット
このプロジェクトで必要な環境変数
開発環境(.env.local — 絶対にコミットしないでください):
# CSRF Protection
# Generate with: node -p "require('crypto').randomBytes(32).toString('base64url')"
CSRF_SECRET=<32-byte-base64url-string>
SESSION_SECRET=<32-byte-base64url-string>
# Clerk Authentication (from Clerk dashboard)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
NEXT_PUBLIC_CLERK_FRONTEND_API_URL=https://your-app.clerk.accounts.dev
# Convex Database (from Convex dashboard)
CONVEX_DEPLOYMENT=dev:...
NEXT_PUBLIC_CONVEX_URL=https://...convex.cloud
# Optional: Stripe (if using direct Stripe, not Clerk Billing)
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
# Optional: Clerk Webhook Secret
CLERK_WEBHOOK_SECRET=whsec_...
本番環境(Vercel/ホスティングプラットフォーム):
# CSRF Protection (different from dev!)
CSRF_SECRET=<different-32-byte-string>
SESSION_SECRET=<different-32-byte-string>
# Clerk Production Keys
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
NEXT_PUBLIC_CLERK_FRONTEND_API_URL=https://your-app.clerk.accounts.com
# Convex Production
CONVEX_DEPLOYMENT=prod:...
NEXT_PUBLIC_CONVEX_URL=https://...convex.cloud
# Optional: Stripe Production
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
# Optional: Clerk Webhook Secret (production)
CLERK_WEBHOOK_SECRET=whsec_...
シークレットの生成
# Generate CSRF_SECRET (32 bytes)
node -p "require('crypto').randomBytes(32).toString('base64url')"
# Generate SESSION_SECRET (32 bytes)
node -p "require('crypto').randomBytes(32).toString('base64url')"
環境変数に関するベストプラクティス
✅ 実施すること:
- 開発/ステージング/本番環境で異なるシークレットを使用する
- 強力なランダムシークレット(32 バイト以上)を生成する
.env.localを.gitignoreに追加する- 本番環境のシークレットをホスティングプラットフォームのシークレットマネージャーに保存する
- シークレットを四半期ごとにローテーションする
- 起動時に必須の環境変数を検証する
❌ 絶対にしないこと:
- API キー、トークン、シークレットをコードにハードコードする
.env.localをバージョン管理にコミットする- 環境変数をログに記録する
- クライアント側のコードでシークレットを公開する
.env.localの値をNEXT_PUBLIC_*変数で使用する(ブラウザーに公開されます!)- メール、Slack、安全でないチャネル経由でシークレットを共有する
起動時の設定検証
// lib/config.ts
const requiredEnvVars = [
'CSRF_SECRET',
'SESSION_SECRET',
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
'CLERK_SECRET_KEY',
'NEXT_PUBLIC_CONVEX_URL'
];
export function validateConfig() {
const missing = requiredEnvVars.filter(v => !process.env[v]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
// Validate secret lengths
if (process.env.CSRF_SECRET && process.env.CSRF_SECRET.length < 32) {
throw new Error('CSRF_SECRET must be at least 32 characters');
}
if (process.env.SESSION_SECRET && process.env.SESSION_SECRET.length < 32) {
throw new Error('SESSION_SECRET must be at least 32 characters');
}
}
// In your app startup (e.g., middleware.ts or layout.tsx)
validateConfig();
デプロイ前のセキュリティチェックリスト
すべての本番デプロイの前に、このチェックリストを確認してください。
環境と設定
- すべての環境変数が本番環境に設定されている
-
CSRF_SECRETが生成され、設定されている(32 バイト以上) -
SESSION_SECRETが生成され、設定されている(32 バイト以上) - Clerk の本番キーが設定されている(
pk_live_...、sk_live_...) - Convex の本番デプロイが設定されている
- Stripe のライブモードキーが設定されている(Stripe を直接使用する場合)
-
.env.localが git にコミットされていない(git statusで確認) - 開発環境と本番環境で異なるシークレットを使用している
依存関係
-
npm audit --productionを実行 — 脆弱性 0 件 -
npm outdatedを実行 — 重要なセキュリティ更新を確認 -
package-lock.jsonが git にコミットされている - Next.js が最新の安定版である(現在 15.5.4 以降)
- 重要なパッケージがすべて更新されている
セキュリティ機能
- CSRF 保護をテスト済み(
security-testingスキルを参照) - レート制限をテスト済み(
node scripts/test-rate-limit.js) - 悪意のある入力を使って入力検証をテスト済み
- セキュリティヘッダーを検証済み(
curl -I https://yourapp.com) - 本番環境で HSTS が有効になっている(ミドルウェアで自動設定)
- 本番環境でエラーメッセージが一般的な内容になっている(スタックトレースなし)
認証と認可
- 保護されたルートで認証を必須にする
- アクセス前にリソースの所有権を確認する
- プレミアム機能のサブスクリプション状況を確認する
- Webhook の署名を検証する(Clerk、Stripe)
- セッションの有効期限切れを適切に処理する
- コードに認証情報をハードコーディングしない
API セキュリティ
- すべての POST/PUT/DELETE ルートに CSRF 対策を適用する
- すべての公開エンドポイントにレート制限を適用する
- すべてのユーザー入力を Zod スキーマで検証する
- すべてのエラーをエラーハンドラーユーティリティで処理する
- ログに機密データ(パスワード、トークン、カード情報、個人情報)を含めない
- コードにシークレットをハードコーディングしない(下記の grep チェック)
決済セキュリティ(該当する場合)
- Clerk Billing と Stripe を使用する(カード情報を直接扱わない)
- Svix 署名で Webhook を検証する
- サーバーでサブスクリプション状況を確認する
- 本番環境でテストモードを無効にする
- カードデータをどこにも記録しない
テスト
- レート制限テストに合格する:
node scripts/test-rate-limit.js - CSRF 対策を手動でテストする
- XSS ペイロードを使って入力検証をテストする
- セキュリティヘッダーを確認する:
curl -I https://yourapp.com - 認証フローをテストする
- 本番モードでエラー処理をテストする
最終チェック
# Check for hardcoded secrets
grep -r "sk_live" . --exclude-dir=node_modules
grep -r "AKIA" . --exclude-dir=node_modules
grep -r "api_key.*=" . --exclude-dir=node_modules
# Verify .env.local not in git
git status | grep .env.local # Should return nothing
# Run full security audit
npm audit --production
bash scripts/security-check.sh
# Test production build
npm run build
NODE_ENV=production npm start
デプロイ後のセキュリティ監視
監視項目
サーバーログ(日次)
攻撃の可能性を示す次のパターンを監視します。
レート制限違反(HTTP 429):
- Repeated 429 errors from same IP → potential abuse/brute force
- High volume of 429s → possible distributed attack
- 429s on login endpoints → credential stuffing attempt
CSRF 失敗(HTTP 403):
- Repeated 403 with "CSRF token invalid" → potential CSRF attack
- Sudden spike in CSRF failures → possible automated attack
- 403s without prior token fetch → attack bypass attempt
認証失敗(HTTP 401/403):
- 401 spikes → potential brute force on protected endpoints
- 403 spikes → unauthorized access attempts
- Pattern of 401 followed by 403 → enumeration attack
異常なエラーパターン:
- Sudden increase in 500 errors → potential attack or system issue
- 400 errors with validation failures → input attack attempts
- Errors from unusual geographic locations
追跡するメトリクス(週次)
認証メトリクス:
- 1 時間あたりの認証失敗回数
- アカウントのロックアウト(実装されている場合)
- ログイン試行の地理的分布
- 通常と異なるログイン時間(午前 3 時に大量ログイン=ボット)
レート制限メトリクス:
- IP ごとのレート制限違反数
- レート制限に達した上位 IP
- レート制限が最も頻繁に適用されたエンドポイント
- 時間経過に伴うレート制限違反の傾向
CSRF 対策メトリクス:
- CSRF 検証の失敗数
- CSRF トークンの生成率
- トークンの再利用試行
- トークンの欠落試行
入力検証メトリクス:
- フィールドごとの検証失敗数
- XSS 試行パターン(入力内の script タグ)
- SQL インジェクション試行パターン
- 過剰な入力長の試行
エラー率メトリクス:
- エンドポイントごとのエラー率
- HTTP ステータスコードごとのエラー率
- 時間経過に伴うエラー率の傾向
- エラーの地理的分布
監視の設定
Vercel Logs(組み込み)
# View logs in Vercel dashboard
https://vercel.com/your-project/logs
# Filter by status code
Status: 429 # Rate limited
Status: 403 # CSRF/Forbidden
Status: 401 # Unauthorized
Clerk Dashboard(認証)
Clerk ダッシュボードで次の項目を監視します。
- サインイン失敗の試行
- アカウント作成率
- セッションのアクティビティ
- 不審な IP アドレス
カスタムログ
// lib/security-logger.ts
export function logSecurityEvent(event: {
type: 'RATE_LIMIT' | 'CSRF_FAILURE' | 'AUTH_FAILURE' | 'VALIDATION_FAILURE';
ip?: string;
userId?: string;
endpoint?: string;
details?: Record<string, any>;
}) {
const log = {
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV,
...event
};
// In production, send to logging service
if (process.env.NODE_ENV === 'production') {
console.log(JSON.stringify(log));
// Optional: Send to external service (Datadog, LogRocket, etc.)
} else {
console.log('Security Event:', log);
}
}
// Usage in middleware/routes
if (rateLimitExceeded) {
logSecurityEvent({
type: 'RATE_LIMIT',
ip: clientIp,
endpoint: request.nextUrl.pathname
});
}
対応手順
優先度の高いアラート(即時対応):
- 認証失敗の急増(100 件/分超)
- 多数の IP からの CSRF 失敗(組織的な攻撃)
- 500 エラー率の急上昇(通常の 10 倍超)
- 既知の脆弱性の悪用
優先度中(24 時間以内に対応):
- レート制限違反の緩やかな増加
- 1 つの IP からの継続的な認証失敗試行
- ログ内の新たなエラーパターン
- 新しい地理的地域からの通常と異なるトラフィック
優先度低(週次レビュー):
- 通常のバックグラウンドでの認証失敗試行
- 時折発生するレート制限への到達
- 一般的な入力検証の失敗
- 日常的なエラーパターン
自動アラート
ホスティングプラットフォームでアラートを設定します。
Vercel:
Alerts → New Alert Rule
- Error rate > 10% for 5 minutes → Email/Slack
- 429 responses > 100/min → Email/Slack
- 500 responses > 50/min → Email/Slack
カスタムアラート:
// Monitor and alert on patterns
if (rateLimitViolations > THRESHOLD) {
await sendAlert({
severity: 'HIGH',
message: `Rate limit violations: ${rateLimitViolations}/min`,
ip: attackerIp
});
}
リソースとドキュメント
プロジェクトのセキュリティドキュメント
実装ガイド:
.claude/skills/security/security-overview/SKILL.md- 全体アーキテクチャ.claude/skills/security/*/SKILL.md- 個別のセキュリティ機能docs/security/SECURITY_IMPLEMENTATION.md- 完全な実装ガイドREADME.md- セキュリティ設定セクション
認識と学習:
.claude/skills/security/security-awareness/- AI コードの脆弱性分析.claude/skills/security/security-awareness/awareness-overview/- セキュリティの全体像
テストと検証用スクリプト
セキュリティテスト:
scripts/test-rate-limit.js- レート制限の検証scripts/security-check.sh- 依存関係の監査scripts/security-test.sh- 包括的なセキュリティテストスイート(作成されている場合)
実装例:
app/api/example-protected/route.ts- セキュリティスタックの完全な例app/api/test-rate-limit/route.ts- レート制限テスト用エンドポイントapp/api/csrf/route.ts- CSRF トークンの生成
外部のセキュリティリソース
OWASP(セキュリティ標準):
- OWASP Top 10 2021: https://owasp.org/www-project-top-ten/
- OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org
- OWASP API Security Top 10: https://owasp.org/www-project-api-security/
フレームワークとサービスのドキュメント:
- Next.js Security: https://nextjs.org/docs/app/guides/security
- Clerk Security: https://clerk.com/docs/security
- Convex Security: https://docs.convex.dev/production/hosting/authentication
- Stripe Security: https://stripe.com/docs/security
テストツール:
- Security Headers Scanner: https://securityheaders.com/
- Mozilla Observatory: https://observatory.mozilla.org/
- SSL Labs Test: https://www.ssllabs.com/ssltest/
メンテナンススケジュール
毎日
- Vercel ダッシュボードでエラーログを確認します
- Clerk ダッシュボードで認証失敗の試行を監視します
- セキュリティアラートがあれば確認します
毎週
npm audit --productionを実行します- GitHub Dependabot アラートを確認します
- エラーログにパターンがないか確認します
- レート制限違反の傾向を確認します
毎月
- セキュリティ監査を全面的に実施します:
bash scripts/security-check.sh - 依存関係を更新します:
npm update+ テスト - 侵害されたシークレットがあれば確認し、ローテーションします
- セキュリティテストスイートを再実行します
- セキュリティヘッダーを確認します:https://securityheaders.com/
四半期ごと
- CSRF_SECRET と SESSION_SECRET をローテーションします
- フレームワークをメジャーアップデートします(Next.js、React)
- ペネトレーションテストを全面的に実施します(XSS、CSRF、認証バイパスの手動試行)
- セキュリティポリシーを確認し、更新します
- セキュリティ意識向上トレーニングを実施します(スキルを確認)
クイックリファレンスコマンド
# Generate secrets
node -p "require('crypto').randomBytes(32).toString('base64url')"
# Check for vulnerabilities
npm audit --production
# Check for outdated packages
npm outdated
# Run security test suite
node scripts/test-rate-limit.js
bash scripts/security-check.sh
# Check for hardcoded secrets
grep -r "sk_live" . --exclude-dir=node_modules
grep -r "AKIA" . --exclude-dir=node_modules
# Test security headers
curl -I https://yourapp.com
# Verify .env.local not committed
git status | grep .env.local
# Production build test
npm run build
NODE_ENV=production npm start
まとめ:セキュリティ運用の原則
🔒 デプロイ前:
- チェックリストを100%完了させる必要があります
- npm audit の脆弱性が0件であること
- すべてのテストに合格していること
- すべての環境変数が設定されていること
🔒 デプロイ後:
- ログを毎日監視します
- アラートには直ちに対応します
- メトリクスを毎週確認します
- 依存関係を毎月更新します
🔒 継続的な取り組み:
- セキュリティ対策に「完了」はありません
- 新たな脆弱性に関する情報を常に把握します
- 依存関係を最新の状態に保ちます
- セキュリティ機能を定期的にテストします
実装の詳細については、個々のセキュリティスキルを参照してください。 脆弱性の把握については、security-awareness スキルを参照してください。