security-hardening
SKILL.md
/security-hardening - Threat -> Mitigation -> Tests -> Gate
Goal
単一の脅威シナリオを入力として受け取り、 (1) 脅威モデル化 → (2) 緩和設計 → (3) 実装方針 → (4) テスト/ログ → (5) release-gate化 までを一貫して出す。百科事典は作らない。
Input (required)
| フィールド | 説明 | 例 |
|---|---|---|
target |
対象(endpoint/function/module) | src/app/api/files/[id]/download/route.ts |
threat |
再現可能な脅威シナリオ(1文) | "他ユーザーのfileIdを指定するとファイルがダウンロードできる" |
environment |
ランタイム | Next.js 16 + Supabase |
constraints |
互換性/性能/運用制約(あれば) | "admin-dashboardからも呼ぶ" |
入力は「1つの脅威」に絞る。"IDOR全般" のような曖昧な入力は禁止。
Output (must deliver all 5)
出せないならスキル失敗。
| # | 成果物 | 内容 |
|---|---|---|
| 1 | Threat Model | 攻撃者/影響/悪用難易度/既存防御層 |
| 2 | Mitigation Design | fail-closed/最小権限/境界チェックの方針 |
| 3 | Implementation Plan | diff-oriented: どのファイルのどこをどう変えるか |
| 4 | Tests + Logging | 再現テスト(修正前fail, 修正後pass) + SecurityMonitorログ |
| 5 | Release Gate | 再発防止ゲートの提案(自動検出可能なもの) |
Workflow
Step 0: Evidence (再現確認)
攻撃が実際に成立するか、コードを読んで確認する。
# 1. 対象ファイルを読む
Read <target>
# 2. 認証/認可チェックの有無を確認
Grep "performSecurityChecks\|getRequestScopedAuth\|auth\.uid()" <target>
# 3. RLS policyの確認(DB関連の場合)
Grep "CREATE POLICY\|ALTER POLICY" supabase/migrations/ --glob "*.sql"
出力: 攻撃が成立する根拠(コード引用付き) + 期待する失敗条件(403/401/400)
Step 1: Threat Model
以下のテンプレートを埋める:
### Threat Model
- **攻撃者**: anonymous / authenticated / insider
- **影響**: data exfiltration / privilege escalation / DoS / billing fraud
- **悪用難易度**: low(ツール不要) / medium(カスタムリクエスト) / high(特殊条件)
- **既存の防御層**: [RLS / JWT / performSecurityChecks / rate-limit / CSP]
- **欠落している防御**: [具体的に何が足りないか]
Step 2: Mitigation Design
原則(この順に適用):
- Fail-closed: エラー時はアクセス拒否(fail-open禁止)
- Least privilege: identityはトークンから導出、入力から取らない
- Input validation: schema + bounds + deny-list
- Defense in depth: RLS + アプリ層の二重チェック
- Observability: SecurityMonitor.logEvent(PIIなし)
Step 3: Implementation Plan (パターン参照)
対象の脅威に最も近いパターンを選び、diff方針を書く。
Pattern A: IDOR (認可バイパス)
// ❌ BEFORE: userIdを入力から取得
const { userId } = await request.json();
const { data } = await supabase.from("files").select().eq("user_id", userId);
// ✅ AFTER: auth.uid()に固定 + RLS二重防御
const auth = await getRequestScopedAuth(request);
if (!auth.userId) return createErrorResponse("認証が必要です", 401);
const { data } = await supabase.from("files").select().eq("user_id", auth.userId);
RLS側: USING (user_id = auth.uid())
Pattern B: Token Replay (JTIリプレイ)
// jti-tracker.ts のパターン
const result = await isJtiUsed(jti);
if (result.used && !result.withinGrace) {
await invalidateUserSessions(userId); // 全セッション無効化
return; // fail-closed
}
await markJtiAsUsed(jti, userId, expiresAt);
grace window: 並行リクエスト許容(60秒デフォルト)
Pattern C: Input Injection (パス/URL/SQL)
// Path traversal防御
const allowedDir = path.resolve(process.cwd(), "uploads");
const resolvedPath = path.resolve(allowedDir, filePath);
if (!resolvedPath.startsWith(allowedDir + path.sep)) {
// reject
}
// Open redirect防御
import { sanitizeNextPath } from "@/lib/security/url-sanitizer";
const safePath = sanitizeNextPath(next, appUrl);
// null byte, backslash, //, 外部origin → 全てDEFAULT_PATHにフォールバック
Pattern D: Rate Limiting (新規エンドポイント)
// performSecurityChecks に rateLimit を渡す
const securityResult = await performSecurityChecks(request, {
rateLimit: { windowMs: 3600_000, max: 50, keyPrefix: "translate" },
csrf: true,
origin: true,
});
if (!securityResult.success) {
return createErrorResponse(securityResult.error!, securityResult.status!, ...);
}
E2E対応: DISABLE_RATE_LIMIT_IN_E2E=true + X-Bypass-Rate-Limit ヘッダー
Pattern E: Webhook Verification
// Stripe webhook: 署名検証は fail-closed
const sig = request.headers.get("stripe-signature");
if (!sig) return createErrorResponse("Missing signature", 400);
const event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
// 失敗 → 400, 成功 → 処理続行
Step 4: Tests + Logging
テスト構造: 修正前に攻撃が成功することを確認 → 修正後に失敗することを確認
// テストテンプレート
describe("Security: [脅威名]", () => {
// 正常系: 正当なリクエストは通る
it("allows legitimate request", async () => {
const response = await handler(validRequest);
expect(response.status).toBe(200);
});
// 攻撃系: 脅威シナリオが拒否される
it("rejects [threat scenario]", async () => {
const response = await handler(maliciousRequest);
expect(response.status).toBe(403); // or 401, 400, 429
});
// ログ系: SecurityMonitorにイベントが記録される
it("logs security event", async () => {
await handler(maliciousRequest);
expect(mockLogEvent).toHaveBeenCalledWith(
expect.objectContaining({
type: expect.any(String),
severity: expect.stringMatching(/high|critical/),
})
);
});
});
Logging必須チェック:
SecurityMonitor.logEvent()が脅威パスで呼ばれるか- ログにPII(email, token等)が含まれないか
requestIdがログに含まれるか(追跡用)
Step 5: Release Gate (mandatory)
修正だけでは再発する。自動検出可能なゲートを1つ提案する。
| ゲート種別 | 検出方法 | 例 |
|---|---|---|
| API Route呼び出し漏れ | grep -rL "performSecurityChecks" src/app/api/ |
全API Routeで統合セキュリティチェック必須 |
| RLS policy不在 | SQLマイグレーションでCREATE TABLEがあるのにCREATE POLICYがない | 新テーブルにはRLS必須 |
| SecurityMonitor未導入 | 脅威ハンドリングコードでlogEventがない | 脅威パスにはログ必須 |
| 認証チェック漏れ | getRequestScopedAuth を呼ばずにuserIdを使用 |
Server Actionsで認証必須 |
# ゲート検証コマンド例
# 1. performSecurityChecks 呼び出し漏れ
grep -rL "performSecurityChecks" src/app/api/*/route.ts \
| grep -v "webhook\|health\|__test"
# 2. RLSなしテーブル検出
grep -l "CREATE TABLE" supabase/migrations/*.sql | while read f; do
table=$(grep -oP 'CREATE TABLE (?:IF NOT EXISTS )?\K\w+' "$f")
if ! grep -q "CREATE POLICY.*ON $table" "$f"; then
echo "MISSING RLS: $table in $f"
fi
done
Allowlist (例外管理)
例外は必ずここに明記する。暗黙の例外は禁止。
| 対象 | 例外内容 | 理由 |
|---|---|---|
/api/stripe/webhook |
CSRF検証スキップ | 外部Webhookは自前署名検証 |
/api/health |
認証スキップ | ヘルスチェック(機密情報なし) |
/api/test/* |
E2E環境でのみアクセス可 | validateE2eAccess() で保護 |
| JTI tracker | fail-open (DBエラー時) | 可用性優先(ログは記録) |
例外を追加する場合: このテーブルに行を追加し、PRで明示的にレビューを受ける。
Non-Goals
- 複数脅威を1回で処理しない(1脅威1実行)
- パターン辞典を増やすだけの作業はしない(必ずテスト+ゲートを伴う)
- 既存の安全なコードを「念のため」で修正しない
AI Assistant Instructions
このスキルが有効化された時:
MUST
- Step 0 (Evidence) を必ず最初に実行。再現できない脅威は扱わない
- 5つの成果物すべてを出力する。1つでも欠けたらスキル失敗
- テストは「修正前fail → 修正後pass」の構造にする
- Release gateはgrepやASTで自動検出可能なものに限定する
- Allowlistに載っていない例外を作る場合は、ユーザーに確認する
NEVER
- 「セキュリティ全般を強化」のような曖昧なゴールで動かない
- SecurityMonitor.logEventにPII(email, token, password)を渡さない
- fail-openを暗黙に導入しない(やむを得ない場合はAllowlistに明記)
- テストなしで緩和策を実装しない
Weekly Installs
20
Repository
yusuketsunoda/ppt-transFirst Seen
14 days ago
Security Audits
Installed on
opencode20
gemini-cli20
codebuddy20
github-copilot20
codex20
kimi-cli20