mcpField
S
security-audit

security-audit

github.com/mgd34msuセキュリティ659 回インストール10 週間前に更新
リポジトリ

security-audit について

security-auditは、開発者やセキュリティレビュー担当者向けのコードベース監査スキルです。認証、入力処理、データ保護、依存関係まで、段階的に脆弱性や設定上のリスクを確認します。

主な機能

  1. 01段階的な攻撃対象領域の確認
  2. 02認証とアクセス制御の点検
  3. 03インジェクションと安全でない入力の確認
  4. 04機密データとシークレット管理の点検
  5. 05依存関係とサプライチェーンリスクの確認

活用シーン

  1. 01デプロイ前のセキュリティレビュー
  2. 02インシデントや脆弱性報告への対応
  3. 03大規模な機能変更後の制御確認
  4. 04セキュリティコンプライアンス監査の準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill security-audit

インストール後、Claude Code で/security-auditを入力して読み込まれたか確認します。

同梱ファイルreferences/security-patterns.mdscripts/validate-security-audit.sh

詳細

リソース

scripts/
  validate-security-audit.sh
references/
  security-patterns.md

セキュリティ監査

このスキルでは、コードベースを包括的にセキュリティ監査し、脆弱性、安全でないパターン、設定上の問題を特定する方法を説明します。セキュリティレビューの実施、本番環境へのデプロイ準備、セキュリティインシデントへの対応時に使用してください。

このスキルを使用する場面

  • デプロイ前のセキュリティレビューを実施するとき
  • セキュリティインシデントや脆弱性の報告に対応するとき
  • 既存のコードベースに対して定期的なセキュリティ監査を実施するとき
  • 大規模な機能追加後にセキュリティ制御を検証するとき
  • セキュリティコンプライアンス監査(SOC 2、ISO 27001)に備えるとき
  • 新しいチームメンバーにセキュリティ標準を説明するとき

監査の手法

体系的なセキュリティ監査は、次のフェーズに沿って進めます。

フェーズ 1:偵察

目的: アプリケーションのアーキテクチャ、技術スタック、攻撃対象領域を把握します。

precision_grep を使ってコードベースを調査します:

precision_grep:
  queries:
    - id: auth_patterns
      pattern: "(session|token|auth|login|password|jwt)"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: api_endpoints
      pattern: "(router\\.(get|post|put|delete)|export.*GET|export.*POST)"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: database_queries
      pattern: "(prisma\\.|db\\.|query\\(|execute\\()"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: env_usage
      pattern: "process\\.env\\."
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: minimal

重要なコンポーネントを特定します:

  • 認証フロー(ログイン、登録、パスワードのリセット)
  • 認可ミドルウェアとガード
  • API エンドポイント(公開か認証必須か)
  • データベースへのアクセスパターン
  • ファイルのアップロード/ダウンロードハンドラー
  • 決済処理ロジック
  • 管理パネルや特権操作
フェーズ 2:認証監査

目的: 認証が安全に実装されていることを確認します。

脆弱なセッション管理を確認する

セッション設定の問題を検索します:

precision_grep:
  queries:
    - id: session_config
      pattern: "(session|cookie).*secure.*false|httpOnly.*false|sameSite.*(none|lax)"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: jwt_secrets
      pattern: "jwt\\.sign.*secret.*[\"'][^\"']{1,20}[\"']|new.*JwtStrategy"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: standard

よくある脆弱性:

  • httpOnly フラグのないセッション Cookie(XSS にさらされます)
  • secure フラグのないセッション Cookie(HTTP 経由での送信が可能になります)
  • 脆弱な JWT シークレット(エントロピーが 32 バイト未満)
  • 有効期限のないセッション(無期限のセッション)
  • ログアウト時にセッションが無効化されない

安全なセッションの例:

import { cookies } from 'next/headers';

export async function createSession(userId: string) {
  const sessionToken = await generateSecureToken();
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days

  await db.session.create({
    data: {
      token: sessionToken,
      userId,
      expiresAt,
    },
  });

  cookies().set('session', sessionToken, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    expires: expiresAt,
    path: '/',
  });
}

export async function invalidateSession(sessionToken: string) {
  await db.session.delete({ where: { token: sessionToken } });
  cookies().delete('session');
}
パスワードのセキュリティ問題を確認する

脆弱なパスワード処理を検索します:

precision_grep:
  queries:
    - id: password_storage
      pattern: "password.*=.*(req\\.body|params|query)|password.*toString|password.*text"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: password_hashing
      pattern: "(bcrypt|argon2|scrypt|pbkdf2)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: verbose

よくある脆弱性:

  • パスワードが平文で保存されている
  • 脆弱なハッシュアルゴリズム(MD5、SHA1、ソルトなしの SHA256)
  • パスワードの複雑さに関する要件がない
  • パスワードのヒントや秘密の質問
  • パスワードがログに記録されたり、エラーメッセージに露出したりする

安全なパスワードハッシュ化:

import { hash, verify } from '@node-rs/argon2';

export async function hashPassword(password: string): Promise<string> {
  return await hash(password, {
    memoryCost: 19456, // 19 MB
    timeCost: 2,
    outputLen: 32,
    parallelism: 1,
  });
}

export async function verifyPassword(
  hash: string,
  password: string
): Promise<boolean> {
  try {
    return await verify(hash, password);
  } catch {
    return false;
  }
}
MFA の実装を確認する

MFA のパターンを検索します:

precision_grep:
  queries:
    - id: mfa_usage
      pattern: "(totp|authenticator|2fa|mfa|otp)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: minimal

MFA のベストプラクティス:

  • 認証アプリを使用した TOTP(時間ベースのワンタイムパスワード)
  • アカウント復旧用のバックアップコード
  • フォールバックとしての SMS OTP(主要な手段にはしない)
  • ハードウェアキー向けの WebAuthn/FIDO2
  • OTP 検証に対するレート制限
フェーズ 3:認可監査

目的: 適切なアクセス制御と権限チェックを徹底します。

認可チェックの欠落を確認する

API エンドポイントを特定します:

precision_grep:
  queries:
    - id: api_routes
      pattern: "export async function (GET|POST|PUT|DELETE|PATCH)"
      glob: "**/api/**/*.{ts,tsx,js,jsx}"
    - id: auth_middleware
      pattern: "(requireAuth|withAuth|authorize|checkPermission)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: locations

手動レビューのチェックリスト:

  • 認証必須のすべてのエンドポイントでユーザーの身元を検証していますか?
  • エンドポイントでリソースの所有者を確認していますか(ユーザーは自分のデータにのみアクセスできますか)?
  • 管理者向けルートはロールチェックで保護されていますか?
  • 認可はサーバー側でチェックされていますか(クライアント側だけではありませんか)?

よくある脆弱性:

  • 安全でない直接オブジェクト参照(IDOR):/api/users/123 が任意のユーザーを返す
  • 権限昇格:一般ユーザーが管理者機能にアクセスできる
  • 認可の欠落:エンドポイントがクライアント側のチェックだけに依存している

安全な認可パターン:

import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { NextResponse } from 'next/server';

export async function GET(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await auth();
  
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const post = await db.post.findUnique({
    where: { id: params.id },
    select: { id: true, title: true, content: true, authorId: true },
  });

  if (!post) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  // Check ownership
  if (post.authorId !== session.user.id) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  return NextResponse.json(post);
}
ロールベースアクセス制御(RBAC)を確認する

ロール定義を検索します:

precision_grep:
  queries:
    - id: role_checks
      pattern: "(role.*===|role.*includes|hasRole|checkRole|permissions)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: matches

RBAC の実装:

type Role = 'user' | 'admin' | 'moderator';

type Permission = 
  | 'posts:read'
  | 'posts:write'
  | 'posts:delete'
  | 'users:manage'
  | 'settings:admin';

const rolePermissions: Record<Role, Permission[]> = {
  user: ['posts:read', 'posts:write'],
  moderator: ['posts:read', 'posts:write', 'posts:delete'],
  admin: ['posts:read', 'posts:write', 'posts:delete', 'users:manage', 'settings:admin'],
};

export function hasPermission(role: Role, permission: Permission): boolean {
  return rolePermissions[role].includes(permission);
}

export function requirePermission(permission: Permission) {
  return async (req: Request) => {
    const session = await auth();
    
    if (!session || !hasPermission(session.user.role, permission)) {
      throw new Error('Insufficient permissions');
    }
  };
}
フェーズ 4:入力検証監査

目的: インジェクション攻撃や悪意のある入力を防ぎます。

SQL インジェクションを確認する

安全でないデータベースクエリを検索します:

precision_grep:
  queries:
    - id: raw_sql
      pattern: "(\\$executeRaw|\\$queryRaw|db\\.query|connection\\.query).*\\$\\{|.*`.*\\$\\{"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: string_concatenation
      pattern: "(SELECT|INSERT|UPDATE|DELETE).*\\+.*req\\.(body|query|params)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: verbose

よくある脆弱性:

  • SQL クエリでの文字列連結
  • 生 SQL での未サニタイズのユーザー入力
  • ユーザー入力に基づく動的なテーブル名/カラム名

安全なデータベースクエリ:

// UNSAFE - SQL injection vulnerable
export async function getUserByEmail(email: string) {
  const query = `SELECT * FROM users WHERE email = '${email}'`;
  return await db.$queryRawUnsafe(query);
}

// SAFE - Parameterized query
export async function getUserByEmail(email: string) {
  return await db.user.findUnique({
    where: { email },
  });
}

// SAFE - Raw query with parameters
export async function searchUsers(query: string) {
  return await db.$queryRaw`
    SELECT id, name, email 
    FROM users 
    WHERE name ILIKE ${'%' + query + '%'}
    LIMIT 20
  `;
}
XSS(クロスサイトスクリプティング)を確認する

安全でないレンダリングを検索します:

precision_grep:
  queries:
    - id: dangerous_html
      pattern: "(dangerouslySetInnerHTML|innerHTML|outerHTML)"
      glob: "**/*.{tsx,jsx}"
    - id: unescaped_output
      pattern: "(v-html|\\[innerHTML\\])"
      glob: "**/*.{vue,html}"
  output:
    format: locations

よくある脆弱性:

  • dangerouslySetInnerHTML を使って未サニタイズのユーザー入力をレンダリングする
  • innerHTML を使ってユーザー提供のコンテンツを挿入する
  • テンプレートエンジンで自動エスケープを無効にする

安全なレンダリング:

import DOMPurify from 'isomorphic-dompurify';

// UNSAFE - XSS vulnerable
export function UnsafeComment({ content }: { content: string }) {
  return <div dangerouslySetInnerHTML={{ __html: content }} />;
}

// SAFE - Auto-escaped by React
export function SafeComment({ content }: { content: string }) {
  return <div>{content}</div>;
}

// SAFE - Sanitized HTML if needed
export function SafeRichComment({ html }: { html: string }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
    ALLOWED_ATTR: ['href'],
  });
  return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
コマンドインジェクションの確認

シェルコマンドの実行箇所を検索します:

precision_grep:
  queries:
    - id: shell_exec
      pattern: "(exec|spawn|execSync|spawnSync|execFile).*req\\.(body|query|params)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: verbose

よくある脆弱性:

  • ユーザー入力をシェルコマンドに直接渡す
  • ファイル操作でサニタイズされていないファイルパスを使用する

安全なコマンド実行:

import { spawn } from 'child_process';
import { z } from 'zod';

const allowedCommands = ['convert', 'resize', 'compress'] as const;
const commandSchema = z.enum(allowedCommands);

// UNSAFE - Command injection vulnerable
export async function processImage(filename: string) {
  exec(`convert ${filename} output.png`);
}

// SAFE - Validated input and array arguments
export async function processImage(command: string, filename: string) {
  const validCommand = commandSchema.parse(command);
  const sanitizedFilename = filename.replace(/[^a-zA-Z0-9._-]/g, '');
  
  return new Promise((resolve, reject) => {
    const child = spawn('imagemagick', [validCommand, sanitizedFilename, 'output.png']);
    child.on('exit', (code) => {
      if (code === 0) resolve(undefined);
      else reject(new Error('Processing failed'));
    });
  });
}
パストラバーサルの確認

ファイル操作を検索します:

precision_grep:
  queries:
    - id: file_operations
      pattern: "(readFile|writeFile|unlink|stat|createReadStream).*req\\.(body|query|params)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: locations

よくある脆弱性:

  • 検証せずにユーザー制御のファイルパスを使用する
  • パスの正規化が不足している(../ の並びを許可する)

安全なファイル処理:

import path from 'path';
import fs from 'fs/promises';

const UPLOADS_DIR = path.join(process.cwd(), 'uploads');

// UNSAFE - Path traversal vulnerable
export async function getFile(filename: string) {
  return await fs.readFile(`./uploads/${filename}`);
}

// SAFE - Path validation with defense against encoded traversals
export async function getFile(filename: string) {
  // Decode URL-encoded sequences (e.g., %2e%2e%2f -> ../)
  const decoded = decodeURIComponent(filename);
  const safePath = path.normalize(decoded).replace(/^(\.\.\/)+/, '');
  const fullPath = path.resolve(path.join(UPLOADS_DIR, safePath));
  
  // Ensure resolved path is within UPLOADS_DIR (prevents prefix collision)
  if (!fullPath.startsWith(UPLOADS_DIR + path.sep) && fullPath !== UPLOADS_DIR) {
    throw new Error('Invalid file path');
  }
  
  return await fs.readFile(fullPath);
}
フェーズ 5:データ保護の監査

目的: 機密データが暗号化され、適切に扱われていることを確認します。

保存データの暗号化を確認する

機密データの保存箇所を検索します:

precision_grep:
  queries:
    - id: sensitive_fields
      pattern: "(ssn|credit.*card|bank.*account|passport|drivers.*license)"
      glob: "**/*.{ts,tsx,js,jsx,prisma}"
    - id: encryption_usage
      pattern: "(encrypt|decrypt|cipher|crypto)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: minimal

暗号化のベストプラクティス:

  • 共通鍵暗号には AES-256-GCM を使用します
  • 暗号化キーは安全なキー管理サービス(AWS KMS、HashiCorp Vault)に保管します
  • 暗号化キーをバージョン管理にコミットしないでください
  • 暗号化キーを定期的にローテーションします

安全な暗号化の実装:

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY;
if (!ENCRYPTION_KEY) throw new Error('ENCRYPTION_KEY environment variable is required');
const KEY = Buffer.from(ENCRYPTION_KEY, 'hex'); // 32 bytes

export function encrypt(plaintext: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv(ALGORITHM, KEY, iv);
  
  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag();
  
  // Return: iv:authTag:ciphertext
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

export function decrypt(ciphertext: string): string {
  const [ivHex, authTagHex, encrypted] = ciphertext.split(':');
  
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  
  const decipher = createDecipheriv(ALGORITHM, KEY, iv);
  decipher.setAuthTag(authTag);
  
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}
PII の取り扱いを確認する

個人を特定できる情報を検索します:

precision_grep:
  queries:
    - id: pii_fields
      pattern: "(email|phone|address|name|dob|birth.*date)"
      glob: "**/*.prisma"
    - id: logging_pii
      pattern: "(console\\.log|logger\\.(info|debug|warn)).*\\.(email|phone|ssn)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: locations

PII 保護チェックリスト:

  • PII の収集を最小限に抑えます(必要なものだけを収集します)
  • ログやエラーメッセージ内の PII をマスキングします
  • データ保持ポリシーを実装します(古いデータを自動削除します)
  • ユーザーデータのエクスポート機能を提供します(GDPR/CCPA に準拠)
  • ユーザーデータの削除機能を提供します(忘れられる権利)
シークレット管理を確認する

ハードコードされたシークレットを検索します:

precision_grep:
  queries:
    - id: hardcoded_secrets
      pattern: "(api.*key.*=.*[\"'][a-zA-Z0-9]{20,}|secret.*=.*[\"'][a-zA-Z0-9]{20,}|password.*=.*[\"'][^\"']{8,})"
      glob: "**/*.{ts,tsx,js,jsx}"
    - id: committed_env
      pattern: ".*"
      glob: ".env"
  output:
    format: verbose

よくある脆弱性:

  • API キーがソースコードにハードコードされている
  • .env ファイルがバージョン管理にコミットされている
  • シークレットがクライアント側のコードに露出している
  • デフォルトの認証情報が変更されていない

安全なシークレット管理:

import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
  OPENAI_API_KEY: z.string().startsWith('sk-'),
});

export const env = envSchema.parse({
  DATABASE_URL: process.env.DATABASE_URL,
  NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
  STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
  OPENAI_API_KEY: process.env.OPENAI_API_KEY,
});

// Ensure .env is in .gitignore
// Use .env.example (without values) to document required variables
// Use secrets management in production (Vercel Env Vars, AWS Secrets Manager)
フェーズ 6:依存関係の監査

目的: 脆弱な依存関係を特定し、修正します。

脆弱性スキャナーを自動実行する

npm audit を使用します:

precision_exec:
  commands:
    - cmd: "npm audit --json"
      timeout_ms: 30000
  verbosity: standard

古いパッケージを確認します:

precision_exec:
  commands:
    - cmd: "npm outdated --json"
      timeout_ms: 10000
  verbosity: minimal

修正の優先順位:

  • 重大/高: デプロイ前に直ちに修正します
  • 中: 30 日以内に修正します
  • 低: 定期メンテナンス時に修正します
サプライチェーン攻撃を確認する

ロックファイルの整合性を検証します:

precision_exec:
  commands:
    - cmd: "npm audit signatures"
      timeout_ms: 30000
  verbosity: standard

チェックリスト:

  • ロックファイル(package-lock.json、yarn.lock、pnpm-lock.yaml)をコミットします
  • PR で依存関係の変更を確認します
  • CI/CD では npm install ではなく npm ci を使用します
  • 重要なプロジェクトでは依存関係のバージョンを固定します
  • 依存関係の分析には Socket.dev などのツールを使用します
フェーズ 7:API セキュリティの監査

目的: 一般的な攻撃から API エンドポイントを保護します。

レート制限を確認する

レート制限の実装箇所を検索します:

precision_grep:
  queries:
    - id: rate_limit_usage
      pattern: "(rateLimit|rate.*limiter|Ratelimit|upstash.*ratelimit)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: minimal

レート制限を実装します:

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
import { NextResponse } from 'next/server';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
  analytics: true,
});

export async function POST(req: Request) {
  const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
  const { success, limit, reset, remaining } = await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { 
        status: 429,
        headers: {
          'X-RateLimit-Limit': limit.toString(),
          'X-RateLimit-Remaining': remaining.toString(),
          'X-RateLimit-Reset': new Date(reset).toISOString(),
        },
      }
    );
  }

  // Process request
  return NextResponse.json({ success: true });
}
CORS 設定を確認する

CORS の設定箇所を検索します:

precision_grep:
  queries:
    - id: cors_config
      pattern: "(Access-Control-Allow-Origin|cors\\(|corsOptions)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: matches

よくある脆弱性:

  • 認証情報を使用している状態でのワイルドカード CORS(Access-Control-Allow-Origin: *)
  • 許可するオリジンのホワイトリストが広すぎる
  • プリフライトリクエストの処理が欠けている

安全な CORS 設定:

import { NextResponse } from 'next/server';

const ALLOWED_ORIGINS = [
  'https://example.com',
  'https://app.example.com',
];

export async function GET(req: Request) {
  const origin = req.headers.get('origin');
  
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
  };
  
  if (origin && ALLOWED_ORIGINS.includes(origin)) {
    headers['Access-Control-Allow-Origin'] = origin;
    headers['Access-Control-Allow-Credentials'] = 'true';
  }
  
  return NextResponse.json({ data: 'response' }, { headers });
}
Webhook 署名の検証

ベストプラクティス: タイミング攻撃を防ぐため、Webhook 署名の比較には crypto.timingSafeEqual() を使用します:

import crypto from 'crypto';

export function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
  const expectedSignature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
  const signatureBuffer = Buffer.from(signature);
  const expectedBuffer = Buffer.from(expectedSignature);
  
  // Prevent timing attacks - constant-time comparison
  if (signatureBuffer.length !== expectedBuffer.length) return false;
  return crypto.timingSafeEqual(signatureBuffer, expectedBuffer);
}
CORS プリフライトの処理
export async function OPTIONS(req: Request) {
  const origin = req.headers.get('origin');
  
  const headers: Record<string, string> = {
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    'Access-Control-Max-Age': '86400', // 24 hours
  };
  
  if (origin && ALLOWED_ORIGINS.includes(origin)) {
    headers['Access-Control-Allow-Origin'] = origin;
    headers['Access-Control-Allow-Credentials'] = 'true';
  }
  
  return new NextResponse(null, { status: 204, headers });
}
CSRF 保護を確認する

CSRF の実装箇所を検索します:

precision_grep:
  queries:
    - id: csrf_tokens
      pattern: "(csrf|csrfToken|xsrf)"
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: minimal

CSRF 保護の方法:

  • SameSite Cookie(sameSite: 'strict' または 'lax')を使用します
  • 状態を変更する操作には CSRF トークンを実装します
  • API リクエストにカスタムヘッダーを必須とします(例:X-Requested-With)
  • Origin ヘッダーと Referer ヘッダーを検証します
コンテンツセキュリティポリシー(CSP)を確認する

CSP ヘッダーを検索します:

precision_grep:
  queries:
    - id: csp_headers
      pattern: "Content-Security-Policy"
      glob: "**/*.{ts,tsx,js,jsx,json}"
  output:
    format: locations

CSP を実装します:

// next.config.js
const ContentSecurityPolicy = `
  default-src 'self';
  // WARNING: 'unsafe-eval' and 'unsafe-inline' significantly weaken CSP. Use nonces or hashes instead.
  script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.vercel-insights.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
`;

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: ContentSecurityPolicy.replace(/\n/g, ''),
          },
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=()',
          },
        ],
      },
    ];
  },
};
フェーズ 8:インフラストラクチャセキュリティの監査

目的: デプロイインフラストラクチャを強化します。

Docker セキュリティを確認する

Dockerfile を検索する:

precision_read:
  files:
    - path: "Dockerfile"
      extract: content
  verbosity: minimal

Docker セキュリティチェックリスト:

  • 最小限のベースイメージ(alpine、distroless)を使用する
  • 非 root ユーザーとして実行する
  • マルチステージビルドを使用して攻撃対象領域を縮小する
  • イメージレイヤーにシークレットを含めない(ビルド引数を使用する)
  • ベースイメージのバージョンを固定する(latest タグは避ける)
  • イメージの脆弱性をスキャンする(docker scan、Trivy)

安全な Dockerfile:

FROM node:20-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

# Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs

EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"

CMD ["node", "server.js"]
環境変数のセキュリティを確認する

環境変数ファイルの使用状況を検索する:

precision_glob:
  patterns:
    - ".env*"
    - "*.env"
  verbosity: count_only

.gitignore を確認する:

precision_read:
  files:
    - path: ".gitignore"
      extract: content
  verbosity: minimal

環境変数のセキュリティチェックリスト:

  • .env ファイルを .gitignore に含める
  • 変数を文書化するために、値を含まない .env.example を使用する
  • 本番環境ではシークレット管理を使用する(.env ファイルは使用しない)
  • 環境を分離する(開発、ステージング、本番)
  • シークレットを定期的にローテーションする
TLS 設定を確認する

HTTPS の強制を確認する:

precision_grep:
  queries:
    - id: https_redirect
      pattern: "(https|ssl|tls|hsts|Strict-Transport-Security)"
      glob: "**/*.{ts,tsx,js,jsx,json}"
  output:
    format: minimal

TLS のベストプラクティス:

  • HTTPS を強制する(HTTP を HTTPS にリダイレクトする)
  • HSTS(Strict-Transport-Security)を有効にする
  • TLS 1.2 以降を使用する(TLS 1.0、1.1 は無効にする)
  • 強力な暗号スイートを使用する
  • モバイルアプリには証明書ピンニングを実装する

監査レポート

監査結果は次の形式でまとめてください。

エグゼクティブサマリー
## Security Audit Report

**Application:** [Name]
**Audit Date:** [Date]
**Auditor:** [Name/Team]
**Scope:** [Components audited]

### Summary
- Total findings: X
- Critical: Y
- High: Z
- Medium: A
- Low: B

### Risk Assessment
[Overall risk level: Critical/High/Medium/Low]
詳細な調査結果

脆弱性ごとに、次の項目を記載します。

### [SEVERITY] Finding #X: [Title]

**Category:** [Authentication/Authorization/Input Validation/etc.]
**Severity:** [Critical/High/Medium/Low]
**CWE:** [CWE-XXX if applicable]

**Description:**
[Clear description of the vulnerability]

**Location:**
- File: `path/to/file.ts`
- Lines: 42-58

**Impact:**
[What can an attacker do? What data is at risk?]

**Proof of Concept:**
```typescript
// Example exploit code or reproduction steps

修正方法:

// Secure code example

参考資料:

  • [OWASP リンク]
  • [CWE リンク]

### Severity Classification

| Severity | Criteria |
|----------|----------|
| **Critical** | Remote code execution, authentication bypass, sensitive data exposure |
| **High** | Privilege escalation, SQL injection, XSS in critical flows |
| **Medium** | Information disclosure, CSRF, weak authentication |
| **Low** | Minor information leaks, missing security headers |

## Precision Tool Workflows

### Full Security Scan Workflow

Run a comprehensive security scan using precision_grep:

```yaml
precision_grep:
  queries:
    # Authentication issues
    - id: weak_auth
      pattern: "(password.*plain|password.*clear|md5|sha1)\\("
      glob: "**/*.{ts,tsx,js,jsx}"
    
    # SQL injection
    - id: sql_injection
      pattern: "(\\$queryRaw|\\$executeRaw).*\\$\\{|query.*\\+.*params"
      glob: "**/*.{ts,tsx,js,jsx}"
    
    # XSS
    - id: xss
      pattern: "(dangerouslySetInnerHTML|innerHTML|v-html)"
      glob: "**/*.{tsx,jsx,vue}"
    
    # Command injection
    - id: command_injection
      pattern: "(exec|spawn).*\\(.*req\\.(body|query)"
      glob: "**/*.{ts,tsx,js,jsx}"
    
    # Path traversal
    - id: path_traversal
      pattern: "(readFile|writeFile).*req\\.(body|query|params)"
      glob: "**/*.{ts,tsx,js,jsx}"
    
    # Hardcoded secrets
    - id: hardcoded_secrets
      pattern: "(api.*key.*=.*[\"'][a-zA-Z0-9]{20,}|sk_live)"
      glob: "**/*.{ts,tsx,js,jsx}"
    
    # Insecure cookies
    - id: insecure_cookies
      pattern: "(httpOnly.*false|secure.*false|sameSite.*none)"
      glob: "**/*.{ts,tsx,js,jsx}"
  
  output:
    format: locations
一括セキュリティ監査

効率的に監査するには、discover + batch を使用します。

discover:
  queries:
    - id: auth_files
      type: glob
      patterns: ["**/auth/**/*.{ts,tsx,js,jsx}", "**/api/auth/**/*.{ts,tsx,js,jsx}"]
    - id: api_routes
      type: glob
      patterns: ["**/api/**/*.{ts,tsx,js,jsx}"]
    - id: db_files
      type: grep
      pattern: "(prisma|db|database)"
      glob: "**/*.{ts,tsx,js,jsx}"
  verbosity: files_only

次に、batch で読み取って分析します。

precision_read:
  files: [/* Use discovered files */]
  extract: symbols
  symbol_filter: ["function", "class"]
  verbosity: standard

自動セキュリティテスト

セキュリティチェックを CI/CD に統合します。

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run npm audit
        run: npm audit --audit-level=high
      
      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          severity: 'CRITICAL,HIGH'
      
      - name: Run OWASP ZAP scan
        uses: zaproxy/[email protected]
        with:
          target: 'http://localhost:3000'

よくあるセキュリティ上の間違い

1. クライアント側の検証を信頼する

問題: セキュリティをクライアント側のチェックに依存しています。

解決策: 必ずサーバー側で検証します。

// UNSAFE - Client-side only
export function ClientForm() {
  const [email, setEmail] = useState('');
  
  const isValid = email.includes('@');
  
  return (
    <form onSubmit={() => fetch('/api/subscribe', {
      method: 'POST',
      body: JSON.stringify({ email }),
    })}>
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button disabled={!isValid}>Subscribe</button>
    </form>
  );
}

// SAFE - Server-side validation
export async function POST(req: Request) {
  const body = await req.json();
  
  const schema = z.object({
    email: z.string().email(),
  });
  
  const result = schema.safeParse(body);
  
  if (!result.success) {
    return NextResponse.json(
      { error: result.error.flatten() },
      { status: 400 }
    );
  }
  
  // Process valid email
  await subscribeUser(result.data.email);
  return NextResponse.json({ success: true });
}
2. API レスポンスで機密データを公開する

問題: 必要以上のデータを返しています。

解決策: 明示的に select を使用してフィールドを制限します。

// UNSAFE - Returns password hash
export async function GET(req: Request) {
  const user = await db.user.findUnique({ where: { id: userId } });
  return NextResponse.json(user);
}

// SAFE - Excludes sensitive fields
export async function GET(req: Request) {
  const user = await db.user.findUnique({
    where: { id: userId },
    select: {
      id: true,
      name: true,
      email: true,
      avatar: true,
      // passwordHash excluded
    },
  });
  return NextResponse.json(user);
}
3. 機密データをログに記録する

問題: 個人識別情報(PII)やシークレットをログに書き込んでいます。

解決策: ログに記録する前に機密データをマスキングします。

// UNSAFE - Logs password
export async function login(email: string, password: string) {
  console.log('Login attempt:', { email, password });
  // ...
}

// SAFE - Redacts password
export async function login(email: string, password: string) {
  console.log('Login attempt:', { email, password: '[REDACTED]' });
  // ...
}

監査後の対応

監査の完了後、次の作業を行います。

  1. 重大度と悪用されやすさに基づいて調査結果に優先順位を付ける
  2. 調査結果ごとにチケットを作成する(監査レポートへのリンクを含める)
  3. 重大度がクリティカルまたは高の問題を直ちに修正する
  4. 検証スクリプトを実行して修正を確認する
  5. 修正後にフォローアップ監査を予定する
  6. 得られた教訓を反映してセキュリティドキュメントを更新する
  7. 発見されたよくある脆弱性についてチームをトレーニングする

検証

監査での修正後に検証スクリプトを実行します。

./scripts/validate-security-audit.sh /path/to/project

スクリプトでは、次の項目を確認します。

  • よくある脆弱性のパターン
  • 安全な認証の実装
  • 入力検証の適用範囲
  • シークレット管理
  • セキュリティヘッダーの設定
  • 依存関係の脆弱性

さらに読む

コメント