Security Engineer について
Security Engineer は、ソフトウェア開発者向けのアプリケーションセキュリティガイドです。認証・認可、入力検証、設定、データ保護、監視の実践例と、脆弱性対策やデプロイ前チェックリストを扱います。
主な機能
- 01認証と認可のパターン
- 02入力検証とサニタイズ
- 03安全な設定とシークレット管理
- 04データ暗号化とパスワードハッシュ化
- 05レート制限、監査ログ、監視
- 06デプロイ前のセキュリティチェックリスト
活用シーン
- 01API の認証とアクセス制御の設計
- 02インジェクションや XSS に備えた入力処理の確認
- 03デプロイ設定と機密データ保護の点検
- 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 | ログ記録と監視の失敗 | セキュリティイベントを記録し、アラートを設定します |
| 10 | SSRF | URL を検証し、許可するドメインをホワイトリストに登録します |
セキュリティチェックリスト(デプロイ前)
認証
- パスワードは 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 でのセキュリティ監査
手動テスト
- SQLインジェクションを試す:
' OR '1'='1 - XSSを試す:
<script>alert('XSS')</script> - CSRFを試す: 別のオリジンからフォームを送信する
- パストラバーサルを試す:
../../etc/passwd - 認証のバイパスを試す: トークンなしで管理者用ルートにアクセスする
このスキルを使用する場面
次の場合は、security-engineer スキルを使用してください。
- ✅ 認証/認可を実装する場合
- ✅ API エンドポイントを構築する場合
- ✅ 機密データ(PII、決済、健康情報)を扱う場合
- ✅ 脆弱性がないかコードをレビューする場合
- ✅ 本番環境にデプロイする場合
- ✅ セキュリティ監査を実施する場合
関連リソース
スキル:
api-designer- API 設計パターン(セキュリティと併用)testing-strategist- セキュリティテスト戦略deployment-advisor- 本番環境のセキュリティ設定
パターン:
/STANDARDS/architecture-patterns/authentication-patterns.md/STANDARDS/best-practices/security-best-practices.md
外部:
セキュリティは全員の責任です。初日から組み込みましょう。 🔒