mcpField
S
Security Engineer

Security Engineer

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

Security Engineer について

Security Engineer は、ソフトウェア開発者向けのアプリケーションセキュリティガイドです。認証・認可、入力検証、設定、データ保護、監視の実践例と、脆弱性対策やデプロイ前チェックリストを扱います。

主な機能

  1. 01認証と認可のパターン
  2. 02入力検証とサニタイズ
  3. 03安全な設定とシークレット管理
  4. 04データ暗号化とパスワードハッシュ化
  5. 05レート制限、監査ログ、監視
  6. 06デプロイ前のセキュリティチェックリスト

活用シーン

  1. 01API の認証とアクセス制御の設計
  2. 02インジェクションや XSS に備えた入力処理の確認
  3. 03デプロイ設定と機密データ保護の点検
  4. 04リリース前のセキュリティレビューの準備

インストール

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

$npx skills add https://github.com/daffy0208/ai-dev-standards --skill security-engineer

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

同梱ファイルREADME.mdmanifest.yaml

詳細

セキュリティエンジニア

セキュリティは任意ではありません。初日から組み込みましょう。

基本原則

セキュリティは後付けではなく、組み込みます。

あらゆる機能、エンドポイント、データフローで、セキュリティへの影響を考慮する必要があります。セキュリティの脆弱性は、開発中に修正する場合と比べて、本番環境では修正コストが10倍になります。

5つのセキュリティの柱

第1の柱:認証と認可 🔐

認証: あなたは誰ですか? 認可: 何ができますか?

認証方式

JWT(JSON Web Token):

  • 用途: ステートレス API、モバイルアプリ、マイクロサービス
  • 方法: シークレットでトークンに署名し、httpOnly Cookie または Authorization ヘッダーに保存します
  • セキュリティ: RS256(HS256ではなく)を使用し、有効期限を短く設定します(アクセストークンは15分、更新トークンは7日)
// Example: Next.js API with JWT
import { SignJWT, jwtVerify } from 'jose'

const secret = new TextEncoder().encode(process.env.JWT_SECRET!)

export async function createToken(userId: string) {
  return await new SignJWT({ userId })
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('15m')
    .sign(secret)
}

export async function verifyToken(token: string) {
  const { payload } = await jwtVerify(token, secret)
  return payload
}

セッションベース:

  • 用途: 従来型のウェブアプリ、サーバーサイドレンダリング
  • 方法: サーバーが暗号化された Cookie にセッション ID を保存します
  • セキュリティ: HttpOnly、Secure、SameSite=Strict の Cookie を使用します

OAuth 2.0 / OIDC:

  • 用途: ソーシャルログイン、サードパーティとの連携
  • 方法: NextAuth.js、Passport.js、または Auth0 を使用します
  • セキュリティ: state パラメーターを検証し、モバイルでは PKCE を使用します
認可パターン

RBAC(ロールベースアクセス制御):

// Define roles
enum Role {
  ADMIN = 'admin',
  USER = 'user',
  GUEST = 'guest'
}

// Check permissions
function requireRole(allowedRoles: Role[]) {
  return (req, res, next) => {
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' })
    }
    next()
  }
}

// Usage
app.delete('/api/users/:id', requireRole([Role.ADMIN]), deleteUser)

ABAC(属性ベース):

  • よりきめ細かい制御が可能です。たとえば、ユーザーは自分が作成したリソースを編集できます
  • 例:ユーザーが投稿を削除できるのは、post.authorId === user.id の場合のみです

主な原則:

  • ✅ 認可の前に、必ず認証を確認します
  • ✅ デフォルトで拒否します(ブラックリストではなく、ホワイトリストを使用します)
  • ✅ 権限はサーバーで確認し、クライアントを決して信頼しません
  • ✅ 重要な操作の前に、権限を再確認します

第2の柱:入力の検証とサニタイズ 🛡️

ユーザー入力を決して信頼しないでください。 すべてを検証、サニタイズ、エスケープします。

SQL インジェクションを防ぐ

❌ 悪い例(脆弱):

// DON'T DO THIS!
const query = `SELECT * FROM users WHERE email = '${userInput}'`
db.query(query) // SQL injection vulnerability!

✅ 良い例(パラメーター化クエリ):

// Always use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?'
db.query(query, [userInput]) // Safe - parameterized

// With Prisma
const user = await prisma.user.findUnique({
  where: { email: userInput } // Safe - ORM handles it
})
XSS(クロスサイトスクリプティング)を防ぐ

❌ 悪い例(脆弱):

// DON'T DO THIS!
<div dangerouslySetInnerHTML={{ __html: userInput }} />

✅ 良い例(エスケープ済み):

// React automatically escapes
;<div>{userInput}</div> // Safe

// If you must render HTML, sanitize first
import DOMPurify from 'isomorphic-dompurify'
;<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
Zod を使用した入力検証
import { z } from 'zod'

const UserSchema = z.object({
  email: z.string().email().max(255),
  password: z.string().min(8).max(100),
  age: z.number().int().min(13).max(120),
  website: z.string().url().optional()
})

// Validate
const result = UserSchema.safeParse(req.body)
if (!result.success) {
  return res.status(400).json({ errors: result.error.issues })
}

const validData = result.data // Type-safe!
ファイルアップロードのセキュリティ
import multer from 'multer'

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB max
  },
  fileFilter: (req, file, cb) => {
    // Whitelist file types
    const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
    if (!allowedTypes.includes(file.mimetype)) {
      return cb(new Error('Invalid file type'))
    }
    cb(null, true)
  }
})

// Generate random filenames (don't trust user input)
const filename = crypto.randomUUID() + path.extname(file.originalname)

主な原則:

  • ✅ サーバーで検証します(クライアント側の検証を決して信頼しません)
  • ✅ スキーマ検証ライブラリ(Zod、Yup、Joi)を使用します
  • ✅ ブラックリストではなく、許可する値のホワイトリストを使用します
  • ✅ コンテキスト(HTML、URL、JS)に応じて出力をエスケープします

第3の柱:安全な設定 🔧
環境変数

シークレットをコミットしないでください:

# .env (add to .gitignore!)
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
JWT_SECRET="generate-with-openssl-rand-base64-32"
OPENAI_API_KEY="sk-..."

コード内でのアクセス:

// Validate env vars on startup
if (!process.env.JWT_SECRET) {
  throw new Error('JWT_SECRET is required')
}

const config = {
  jwtSecret: process.env.JWT_SECRET,
  databaseUrl: process.env.DATABASE_URL
}

シークレット管理(本番環境):

  • AWS Secrets Manager
  • Vercel Environment Variables
  • HashiCorp Vault
  • Doppler
セキュリティヘッダー
// Next.js middleware
export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  // Prevent clickjacking
  response.headers.set('X-Frame-Options', 'DENY')

  // Prevent MIME sniffing
  response.headers.set('X-Content-Type-Options', 'nosniff')

  // XSS Protection
  response.headers.set('X-XSS-Protection', '1; mode=block')

  // Content Security Policy
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
  )

  // HTTPS only
  response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')

  return response
}
CORS 設定
// Configure CORS properly
app.use(
  cors({
    origin:
      process.env.NODE_ENV === 'production' ? 'https://yourdomain.com' : 'http://localhost:3000',
    credentials: true, // Allow cookies
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization']
  })
)

主な原則:

  • ✅ すべてのシークレットに環境変数を使用します
  • ✅ 環境(dev、staging、prod)ごとに異なるシークレットを使用します
  • ✅ シークレットを定期的にローテーションします
  • ✅ すべてのレスポンスにセキュリティヘッダーを設定します
  • ✅ CORS を厳格に設定します

第4の柱:データ保護 🔒
パスワードのハッシュ化

❌ パスワードを平文で保存したり、MD5/SHA1 を使用したりしないでください!

✅ bcrypt または argon2 を使用します:

import bcrypt from 'bcrypt'

// Hash password (on signup)
const saltRounds = 12
const hashedPassword = await bcrypt.hash(password, saltRounds)
await db.user.create({
  email,
  password: hashedPassword // Store hash, never plain text
})

// Verify password (on login)
const user = await db.user.findUnique({ where: { email } })
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
  throw new Error('Invalid credentials')
}
保存データの暗号化

機密データは暗号化する必要があります:

import crypto from 'crypto'

const algorithm = 'aes-256-gcm'
const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex')

function encrypt(text: string) {
  const iv = crypto.randomBytes(16)
  const cipher = crypto.createCipheriv(algorithm, key, iv)
  const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()])
  const authTag = cipher.getAuthTag()
  return {
    iv: iv.toString('hex'),
    encryptedData: encrypted.toString('hex'),
    authTag: authTag.toString('hex')
  }
}

function decrypt(encrypted: any) {
  const decipher = crypto.createDecipheriv(algorithm, key, Buffer.from(encrypted.iv, 'hex'))
  decipher.setAuthTag(Buffer.from(encrypted.authTag, 'hex'))
  return decipher.update(encrypted.encryptedData, 'hex', 'utf8') + decipher.final('utf8')
}

// Encrypt sensitive fields
const ssn = encrypt(user.ssn)
await db.user.update({ where: { id }, data: { ssnEncrypted: JSON.stringify(ssn) } })
通信中の暗号化

常に HTTPS を使用します:

  • 開発環境:ローカル HTTPS に mkcert を使用します
  • 本番環境:Let's Encrypt、Cloudflare、Vercel(HTTPS を自動で適用)

外部 API の証明書を検証します:

// Don't disable SSL verification in production!
fetch(url, {
  // DON'T: agent: new https.Agent({ rejectUnauthorized: false })
})

主な原則:

  • ✅ bcrypt(12ラウンド以上)または argon2 でパスワードをハッシュ化します
  • ✅ 個人を特定できる情報(SSN、クレジットカード、健康データ)を暗号化します
  • ✅ あらゆる場所で HTTPS を使用します(HSTS ヘッダーで強制します)
  • ✅ データベースのバックアップを暗号化します

第5の柱:監視と対応 📊
レート制限

ブルートフォース攻撃や DDoS を防ぎます:

import rateLimit from 'express-rate-limit'

// Global rate limit
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: 'Too many requests from this IP'
})
app.use(limiter)

// Stricter limit for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // Only 5 login attempts per 15min
  skipSuccessfulRequests: true
})
app.post('/api/auth/login', authLimiter, loginHandler)
監査ログ

セキュリティに関わるすべてのイベントを記録します:

async function auditLog(event: {
  userId?: string
  action: string
  resource: string
  ip: string
  userAgent: string
  success: boolean
  metadata?: any
}) {
  await db.auditLog.create({
    data: {
      ...event,
      timestamp: new Date()
    }
  })
}

// Usage
await auditLog({
  userId: user.id,
  action: 'LOGIN',
  resource: 'auth',
  ip: req.ip,
  userAgent: req.headers['user-agent'],
  success: true
})
エラー処理

エラーで機密情報を漏らさないでください:

❌ 悪い例:

// Exposes database structure!
catch (error) {
  res.status(500).json({ error: error.message })
}

✅ 良い例:

catch (error) {
  // Log full error server-side
  console.error('Error:', error)

  // Send generic message to client
  res.status(500).json({
    error: 'Internal server error',
    requestId: generateRequestId() // For support
  })
}
セキュリティ監視

連携するツール:

  • Sentry: エラー追跡、セキュリティアラート
  • Datadog: APM、異常検知
  • CloudWatch: AWS インフラストラクチャの監視
  • OWASP ZAP: 自動セキュリティスキャン

主な原則:

  • ✅ すべての公開エンドポイントにレート制限を設定します
  • ✅ 認証試行と認可失敗を記録します
  • ✅ エラーに機密データを決して含めません
  • ✅ 不審なパターンに対するアラートを設定します
  • ✅ セキュリティ監査を定期的に実施します

OWASP Top 10 クイックリファレンス

#脆弱性対策
1アクセス制御の不備サーバー側で権限を検証し、デフォルトで拒否します
2暗号化の失敗TLS を使用し、パスワードをハッシュ化(bcrypt)し、PII を暗号化します
3インジェクションパラメーター化クエリ、入力検証
4安全でない設計脅威モデリング、セキュリティ要件
5セキュリティ設定の不備安全なデフォルト設定、未使用機能の削除
6脆弱なコンポーネント依存関係を最新の状態に保ち、Dependabot を使用します
7認証とセッションの問題強力なパスワード、MFA、安全なセッション管理
8ソフトウェアとデータの完全性依存関係を検証し、リリースに署名します
9ログ記録と監視の失敗セキュリティイベントを記録し、アラートを設定します
10SSRFURL を検証し、許可するドメインをホワイトリストに登録します

セキュリティチェックリスト(デプロイ前)

認証
  • パスワードは bcrypt でハッシュ化(12 ラウンド以上)
  • JWT トークンは RS256 を使用し、有効期限を短く設定
  • セッション Cookie は HttpOnly、Secure、SameSite を設定
  • ログインエンドポイントにレート制限を設定(5 回/15 分)
  • パスワードリセットには安全なトークンを使用(1 時間で期限切れ)
認可
  • すべてのルートで認証を確認
  • サーバー側で権限を検証
  • デフォルトで拒否(ホワイトリスト方式)
  • 再認証なしで機密性の高い操作を許可しない
入力検証
  • すべてのユーザー入力をスキーマ(Zod)で検証
  • SQL クエリではパラメーター化クエリ/ORM を使用
  • ファイルアップロードでは種類をホワイトリストで指定し、サイズを制限
  • コンテキストに応じて出力をエスケープ
設定
  • コードやバージョン管理にシークレットを含めない
  • すべての設定に環境変数を使用
  • セキュリティヘッダーを設定(CSP、HSTS、X-Frame-Options)
  • CORS を厳格に設定
  • 本番環境で HTTPS を強制
データ保護
  • 保存時に PII を暗号化
  • すべての接続で TLS/HTTPS を使用
  • データベースのバックアップを暗号化
  • 安全なシークレット管理(Vault、AWS Secrets)
監視
  • セキュリティイベントの監査ログを記録
  • エラー追跡を設定(Sentry)
  • すべての公開エンドポイントにレート制限を設定
  • ログイン失敗や 5xx エラーに対するアラートを設定
依存関係
  • すべての依存関係を最新の状態に保つ
  • Dependabot を有効化
  • 既知の脆弱性がないことを確認(npm audit を実行)
  • 依存関係を最小限にする(未使用のものを削除)

よくあるセキュリティ上の誤り

誤り 1:クライアント側での認可

❌ 誤り:

// Hiding UI doesn't prevent access!
{
  user.role === 'admin' && <DeleteButton />
}

✅ 正しい例:

// Always verify on server
app.delete('/api/users/:id', requireAdmin, deleteUser)

誤り 2:クライアントデータを信頼する

❌ 誤り:

// User can manipulate userId in request!
const userId = req.body.userId
await db.order.create({ data: { userId } })

✅ 正しい例:

// Get userId from authenticated session
const userId = req.user.id // From JWT/session
await db.order.create({ data: { userId } })

誤り 3:弱いパスワード要件

❌ 誤り:

if (password.length < 6) throw new Error('Too short')

✅ 正しい例:

const passwordSchema = z
  .string()
  .min(8, 'At least 8 characters')
  .regex(/[A-Z]/, 'Needs uppercase')
  .regex(/[a-z]/, 'Needs lowercase')
  .regex(/[0-9]/, 'Needs number')

誤り 4:安全でない直接オブジェクト参照

❌ 誤り:

// User can access any document by changing ID!
const doc = await db.document.findUnique({ where: { id: req.params.id } })
return res.json(doc)

✅ 正しい例:

// Verify ownership
const doc = await db.document.findFirst({
  where: {
    id: req.params.id,
    userId: req.user.id // Ensure user owns this document
  }
})
if (!doc) return res.status(404).json({ error: 'Not found' })
return res.json(doc)

フレームワーク別のガイダンス

Next.js のセキュリティ
// middleware.ts - Protect routes
export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')

  // Redirect to login if no token
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/api/private/:path*']
}
Express のセキュリティ
import helmet from 'helmet'
import mongoSanitize from 'express-mongo-sanitize'

app.use(helmet()) // Security headers
app.use(express.json({ limit: '10mb' })) // Prevent large payloads
app.use(mongoSanitize()) // Prevent NoSQL injection
FastAPI のセキュリティ
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def get_current_user(token: str = Depends(security)):
    try:
        payload = verify_token(token.credentials)
        return payload['user_id']
    except:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.get("/protected")
async def protected_route(user_id: str = Depends(get_current_user)):
    return {"user_id": user_id}

セキュリティテスト

自動化ツール
  • OWASP ZAP: Web 脆弱性スキャナー
  • Snyk: 依存関係の脆弱性スキャン
  • npm audit: 既知の脆弱性を確認
  • Lighthouse: Chrome DevTools でのセキュリティ監査
手動テスト
  1. SQLインジェクションを試す: ' OR '1'='1
  2. XSSを試す: <script>alert('XSS')</script>
  3. CSRFを試す: 別のオリジンからフォームを送信する
  4. パストラバーサルを試す: ../../etc/passwd
  5. 認証のバイパスを試す: トークンなしで管理者用ルートにアクセスする

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

次の場合は、security-engineer スキルを使用してください。

  • ✅ 認証/認可を実装する場合
  • ✅ API エンドポイントを構築する場合
  • ✅ 機密データ(PII、決済、健康情報)を扱う場合
  • ✅ 脆弱性がないかコードをレビューする場合
  • ✅ 本番環境にデプロイする場合
  • ✅ セキュリティ監査を実施する場合

関連リソース

スキル:

  • api-designer - API 設計パターン(セキュリティと併用)
  • testing-strategist - セキュリティテスト戦略
  • deployment-advisor - 本番環境のセキュリティ設定

パターン:

  • /STANDARDS/architecture-patterns/authentication-patterns.md
  • /STANDARDS/best-practices/security-best-practices.md

外部:


セキュリティは全員の責任です。初日から組み込みましょう。 🔒

コメント