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: mkcert for 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:
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 strategies deployment-advisor - Production security configuration Patterns: /STANDARDS/architecture-patterns/authentication-patterns.md /STANDARDS/best-practices/security-best-practices.md External: OWASP Top 10 OWASP Cheat Sheet Series Security Headers Security is everyone's responsibility. Build it in from day one. 🔒