security engineer
npx skills add https://github.com/daffy0208/ai-dev-standards --skill Security Engineer
Skill 文档
Security Engineer
Security is not optional – build it in from day one.
Core Principle
Security is built-in, not bolted-on.
Every feature, every endpoint, every data flow must consider security implications. Security vulnerabilities cost 10x more to fix in production than during development.
5 Security Pillars
Pillar 1: Authentication & Authorization ð
Authentication: Who are you? Authorization: What can you do?
Authentication Strategies
JWT (JSON Web Tokens):
- When: Stateless APIs, mobile apps, microservices
- How: Sign tokens with secret, store in httpOnly cookies or Authorization header
- Security: Use RS256 (not HS256), short expiry (15min access, 7d refresh)
// 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
}
Session-Based:
- When: Traditional web apps, server-side rendering
- How: Server stores session ID in encrypted cookie
- Security: HttpOnly, Secure, SameSite=Strict cookies
OAuth 2.0 / OIDC:
- When: Social login, third-party integrations
- How: Use NextAuth.js, Passport.js, or Auth0
- Security: Validate state parameter, use PKCE for mobile
Authorization Patterns
RBAC (Role-Based Access Control):
// 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 (Attribute-Based):
- More granular: user can edit resource if they created it
- Example: User can delete post only if post.authorId === user.id
Key Principles:
- â Always verify authentication before authorization
- â Default deny (whitelist, not blacklist)
- â Check permissions on server, never trust client
- â Re-verify permissions before critical actions
Pillar 2: Input Validation & Sanitization ð¡ï¸
Never trust user input – validate, sanitize, escape everything.
Prevent SQL Injection
â Bad (Vulnerable):
// DON'T DO THIS!
const query = `SELECT * FROM users WHERE email = '${userInput}'`
db.query(query) // SQL injection vulnerability!
â Good (Parameterized Queries):
// 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
})
Prevent XSS (Cross-Site Scripting)
â Bad (Vulnerable):
// DON'T DO THIS!
<div dangerouslySetInnerHTML={{ __html: userInput }} />
â Good (Escaped):
// 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) }} />
Input Validation with 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!
File Upload Security
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)
Key Principles:
- â Validate on server (never trust client validation)
- â Use schema validation libraries (Zod, Yup, Joi)
- â Whitelist allowed values, don’t blacklist
- â Escape output based on context (HTML, URL, JS)
Pillar 3: Secure Configuration ð§
Environment Variables
Never commit secrets:
# .env (add to .gitignore!)
DATABASE_URL="postgresql://user:pass@localhost:5432/db"
JWT_SECRET="generate-with-openssl-rand-base64-32"
OPENAI_API_KEY="sk-..."
Access in code:
// 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
}
Secret Management (Production):
- AWS Secrets Manager
- Vercel Environment Variables
- HashiCorp Vault
- Doppler
Security Headers
// 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 Configuration
// 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']
})
)
Key Principles:
- â Use environment variables for all secrets
- â Different secrets per environment (dev, staging, prod)
- â Rotate secrets regularly
- â Set security headers on all responses
- â Configure CORS restrictively
Pillar 4: Data Protection ð
Password Hashing
â Never store passwords in plain text or use MD5/SHA1!
â Use bcrypt or 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')
}
Encryption at Rest
Sensitive data should be encrypted:
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) } })
Encryption in Transit
Always use HTTPS:
- Development:
mkcertfor local HTTPS - Production: Let’s Encrypt, Cloudflare, Vercel (auto HTTPS)
Verify external API certificates:
// Don't disable SSL verification in production!
fetch(url, {
// DON'T: agent: new https.Agent({ rejectUnauthorized: false })
})
Key Principles:
- â Hash passwords with bcrypt (12+ rounds) or argon2
- â Encrypt PII (SSN, credit cards, health data)
- â Use HTTPS everywhere (enforce with HSTS header)
- â Encrypt database backups
Pillar 5: Monitoring & Response ð
Rate Limiting
Prevent brute force and 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)
Audit Logging
Log all security-relevant events:
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
})
Error Handling
Don’t leak sensitive info in errors:
â Bad:
// Exposes database structure!
catch (error) {
res.status(500).json({ error: error.message })
}
â Good:
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
})
}
Security Monitoring
Tools to integrate:
- Sentry: Error tracking, security alerts
- Datadog: APM, anomaly detection
- CloudWatch: AWS infrastructure monitoring
- OWASP ZAP: Automated security scans
Key Principles:
- â Rate limit all public endpoints
- â Log authentication attempts, authorization failures
- â Never expose sensitive data in errors
- â Set up alerts for suspicious patterns
- â Regular security audits
OWASP Top 10 Quick Reference
| # | Vulnerability | Prevention |
|---|---|---|
| 1 | Broken Access Control | Verify permissions server-side, default deny |
| 2 | Cryptographic Failures | Use TLS, hash passwords (bcrypt), encrypt PII |
| 3 | Injection | Parameterized queries, input validation |
| 4 | Insecure Design | Threat modeling, security requirements |
| 5 | Security Misconfiguration | Secure defaults, remove unused features |
| 6 | Vulnerable Components | Keep dependencies updated, use Dependabot |
| 7 | Auth & Session Issues | Strong passwords, MFA, secure session mgmt |
| 8 | Software & Data Integrity | Verify dependencies, sign releases |
| 9 | Logging & Monitoring Failures | Log security events, set up alerts |
| 10 | SSRF | Validate URLs, whitelist allowed domains |
Security Checklist (Pre-Deployment)
Authentication
- Passwords hashed with bcrypt (12+ rounds)
- JWT tokens use RS256, short expiry
- Session cookies are HttpOnly, Secure, SameSite
- Rate limiting on login endpoints (5 attempts/15min)
- Password reset uses secure tokens (expires 1 hour)
Authorization
- All routes check authentication
- Permissions verified server-side
- Default deny (whitelist approach)
- No sensitive operations allowed without re-authentication
Input Validation
- All user input validated with schema (Zod)
- SQL queries use parameterized queries/ORM
- File uploads whitelist types and size limits
- Output escaped based on context
Configuration
- No secrets in code or version control
- Environment variables for all config
- Security headers set (CSP, HSTS, X-Frame-Options)
- CORS configured restrictively
- HTTPS enforced in production
Data Protection
- PII encrypted at rest
- TLS/HTTPS for all connections
- Database backups encrypted
- Secure secret management (Vault, AWS Secrets)
Monitoring
- Audit logging for security events
- Error tracking configured (Sentry)
- Rate limiting on all public endpoints
- Alerts for failed login attempts, 5xx errors
Dependencies
- All dependencies up to date
- Dependabot enabled
- No known vulnerabilities (run
npm audit) - Minimal dependencies (remove unused)
Common Security Mistakes
Mistake 1: Client-Side Authorization
â Wrong:
// Hiding UI doesn't prevent access!
{
user.role === 'admin' && <DeleteButton />
}
â Right:
// Always verify on server
app.delete('/api/users/:id', requireAdmin, deleteUser)
Mistake 2: Trusting Client Data
â Wrong:
// User can manipulate userId in request!
const userId = req.body.userId
await db.order.create({ data: { userId } })
â Right:
// Get userId from authenticated session
const userId = req.user.id // From JWT/session
await db.order.create({ data: { userId } })
Mistake 3: Weak Password Requirements
â Wrong:
if (password.length < 6) throw new Error('Too short')
â Right:
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')
Mistake 4: Insecure Direct Object References
â Wrong:
// User can access any document by changing ID!
const doc = await db.document.findUnique({ where: { id: req.params.id } })
return res.json(doc)
â Right:
// 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)
Framework-Specific Guidance
Next.js Security
// 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 Security
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 Security
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}
Security Testing
Automated Tools
- OWASP ZAP: Web vulnerability scanner
- Snyk: Dependency vulnerability scanning
- npm audit: Check for known vulnerabilities
- Lighthouse: Security audit in Chrome DevTools
Manual Testing
- Try SQL injection:
' OR '1'='1 - Try XSS:
<script>alert('XSS')</script> - Try CSRF: Submit form from different origin
- Try path traversal:
../../etc/passwd - Try auth bypass: Access admin routes without token
When to Use This Skill
Use security-engineer skill when:
- â Implementing authentication/authorization
- â Building API endpoints
- â Handling sensitive data (PII, payments, health)
- â Reviewing code for vulnerabilities
- â Deploying to production
- â Conducting security audits
Related Resources
Skills:
api-designer– API design patterns (pairs with security)testing-strategist– Security testing strategiesdeployment-advisor– Production security configuration
Patterns:
/STANDARDS/architecture-patterns/authentication-patterns.md/STANDARDS/best-practices/security-best-practices.md
External:
Security is everyone’s responsibility. Build it in from day one. ð