--- name: security-checklist description: Comprehensive security checklist covering OWASP Top 10, SQL injection, XSS, CSRF, authentication, authorization, secrets management, input validation, and security headers. Use when scanning for vulnerabilities, reviewing security, implementing authentication/authorization, or handling sensitive data. --- # Security Checklist This skill provides comprehensive security guidance to protect your applications from common vulnerabilities and attacks. ## OWASP Top 10 Vulnerabilities ### 1. Broken Access Control **What it is**: Users can access resources or perform actions they shouldn't be authorized for. **Examples**: - Accessing another user's data by changing URL parameter - Elevating privileges (user → admin) - Bypassing authentication checks **Prevention**: ```typescript // ❌ BAD - No authorization check app.get('/api/users/:id', async (req, res) => { const user = await db.user.findUnique({ where: { id: req.params.id } }); res.json(user); }); // ✅ GOOD - Verify ownership or admin app.get('/api/users/:id', authenticate, async (req, res) => { const requestedId = req.params.id; const currentUserId = req.user.id; const isAdmin = req.user.role === 'admin'; if (requestedId !== currentUserId && !isAdmin) { return res.status(403).json({ error: 'Forbidden' }); } const user = await db.user.findUnique({ where: { id: requestedId } }); res.json(user); }); ``` **Checklist**: - [ ] Enforce least privilege (deny by default) - [ ] Verify user permissions on every request - [ ] Never trust user IDs from client - [ ] Log access control failures - [ ] Use centralized access control logic ### 2. Cryptographic Failures **What it is**: Exposing sensitive data due to weak or missing encryption. **Examples**: - Storing passwords in plain text - Using weak hashing algorithms (MD5, SHA1) - Transmitting sensitive data over HTTP **Prevention**: ```typescript import bcrypt from 'bcrypt'; import crypto from 'crypto'; // ✅ Password hashing async function hashPassword(password: string): Promise { const saltRounds = 12; // Increase for more security return await bcrypt.hash(password, saltRounds); } async function verifyPassword(password: string, hash: string): Promise { return await bcrypt.compare(password, hash); } // ✅ Encrypt sensitive data at rest function encrypt(text: string, key: string): string { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-gcm', Buffer.from(key), iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag().toString('hex'); return `${iv.toString('hex')}:${authTag}:${encrypted}`; } ``` **Checklist**: - [ ] Use HTTPS everywhere - [ ] Hash passwords with bcrypt (12+ rounds) - [ ] Encrypt sensitive data at rest - [ ] Use strong encryption algorithms (AES-256) - [ ] Properly manage encryption keys - [ ] Never commit secrets to version control ### 3. Injection Attacks **See SQL Injection Prevention section below** ### 4. Insecure Design **What it is**: Security flaws in the application architecture. **Examples**: - Missing rate limiting - Unrestricted file uploads - Insecure password reset flows **Prevention**: ```typescript // ✅ Rate limiting import rateLimit from 'express-rate-limit'; const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts message: 'Too many login attempts, please try again later', }); app.post('/api/auth/login', loginLimiter, async (req, res) => { // Login logic }); // ✅ Secure file upload import multer from 'multer'; const upload = multer({ limits: { fileSize: 5 * 1024 * 1024, // 5MB max }, fileFilter: (req, file, cb) => { const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(file.mimetype)) { return cb(new Error('Invalid file type')); } cb(null, true); }, }); ``` **Checklist**: - [ ] Implement rate limiting on all endpoints - [ ] Validate file uploads (type, size, content) - [ ] Use secure password reset flows (token-based) - [ ] Implement account lockout after failed attempts - [ ] Log and monitor security events ### 5. Security Misconfiguration **What it is**: Insecure default configurations, unnecessary features enabled. **Prevention**: ```typescript // ✅ Secure Express configuration import express from 'express'; import helmet from 'helmet'; const app = express(); // Security headers app.use(helmet()); // Disable X-Powered-By app.disable('x-powered-by'); // Environment-specific settings if (process.env.NODE_ENV === 'production') { app.set('trust proxy', 1); // Trust first proxy } // CORS configuration import cors from 'cors'; const corsOptions = { origin: process.env.ALLOWED_ORIGINS?.split(',') || [], credentials: true, optionsSuccessStatus: 200, }; app.use(cors(corsOptions)); ``` **Checklist**: - [ ] Remove default accounts and passwords - [ ] Disable directory listing - [ ] Remove unnecessary features/endpoints - [ ] Keep all software updated - [ ] Use security headers (see section below) ### 6. Vulnerable and Outdated Components **Prevention**: ```bash # Check for vulnerabilities npm audit # Fix vulnerabilities npm audit fix # Update dependencies npm update # Use automated tools npm install -g snyk snyk test snyk monitor ``` **Checklist**: - [ ] Regularly update dependencies - [ ] Monitor security advisories - [ ] Remove unused dependencies - [ ] Use dependabot or renovate - [ ] Pin dependency versions in production ### 7. Identification and Authentication Failures **See Authentication Best Practices section below** ### 8. Software and Data Integrity Failures **Prevention**: ```typescript // ✅ Verify package integrity // package-lock.json ensures same versions // ✅ Use Subresource Integrity (SRI) for CDN // ✅ Validate data from external sources import { z } from 'zod'; const ExternalDataSchema = z.object({ id: z.string().uuid(), amount: z.number().positive(), status: z.enum(['pending', 'completed', 'failed']), }); function processExternalData(data: unknown) { const validated = ExternalDataSchema.parse(data); // Now safe to use } ``` ### 9. Security Logging and Monitoring Failures **Prevention**: ```typescript import winston from 'winston'; const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), ], }); // ✅ Log security events logger.warn('Failed login attempt', { email: req.body.email, ip: req.ip, userAgent: req.get('user-agent'), timestamp: new Date().toISOString(), }); logger.error('SQL injection attempt detected', { query: sanitizedQuery, ip: req.ip, endpoint: req.path, }); ``` **Checklist**: - [ ] Log authentication events (login, logout, failures) - [ ] Log authorization failures - [ ] Log input validation failures - [ ] Monitor for suspicious patterns - [ ] Set up alerts for security events ### 10. Server-Side Request Forgery (SSRF) **Prevention**: ```typescript import validator from 'validator'; // ❌ BAD - Allows SSRF app.post('/api/fetch-url', async (req, res) => { const url = req.body.url; const response = await fetch(url); res.json(await response.json()); }); // ✅ GOOD - Validate and whitelist app.post('/api/fetch-url', async (req, res) => { const url = req.body.url; // Validate URL format if (!validator.isURL(url, { protocols: ['https'] })) { return res.status(400).json({ error: 'Invalid URL' }); } // Whitelist allowed domains const allowedDomains = ['api.trusted-service.com']; const urlObj = new URL(url); if (!allowedDomains.includes(urlObj.hostname)) { return res.status(403).json({ error: 'Domain not allowed' }); } // Block internal IPs const hostname = urlObj.hostname; if ( hostname === 'localhost' || hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.') ) { return res.status(403).json({ error: 'Internal IPs not allowed' }); } const response = await fetch(url); res.json(await response.json()); }); ``` ## SQL Injection Prevention ### Parameterized Queries ```typescript // ❌ VULNERABLE - String concatenation const email = req.body.email; const query = `SELECT * FROM users WHERE email = '${email}'`; // Attacker can input: ' OR '1'='1 // Resulting query: SELECT * FROM users WHERE email = '' OR '1'='1' // ✅ SAFE - Parameterized query const email = req.body.email; const query = 'SELECT * FROM users WHERE email = ?'; const user = await db.execute(query, [email]); // ✅ SAFE - ORM (Prisma) const user = await prisma.user.findUnique({ where: { email: req.body.email }, }); ``` ### Input Validation ```typescript import { z } from 'zod'; const UserQuerySchema = z.object({ email: z.string().email(), id: z.string().uuid().optional(), name: z.string().max(100).optional(), }); app.get('/api/users', async (req, res) => { try { const params = UserQuerySchema.parse(req.query); const users = await db.user.findMany({ where: params }); res.json(users); } catch (error) { res.status(400).json({ error: 'Invalid query parameters' }); } }); ``` ## XSS (Cross-Site Scripting) Prevention ### Types of XSS **1. Stored XSS**: Malicious script stored in database **2. Reflected XSS**: Malicious script in URL/input reflected back **3. DOM-based XSS**: Script manipulates DOM directly ### Prevention ```typescript // ✅ React automatically escapes by default function Comment({ text }: { text: string }) { return

{text}

; // Safe - React escapes HTML } // ❌ DANGEROUS - dangerouslySetInnerHTML function Comment({ html }: { html: string }) { return

; // UNSAFE } // ✅ Sanitize HTML before rendering import DOMPurify from 'dompurify'; function Comment({ html }: { html: string }) { const clean = DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'], }); return

; } // ✅ Server-side sanitization import sanitizeHtml from 'sanitize-html'; app.post('/api/comments', async (req, res) => { const clean = sanitizeHtml(req.body.content, { allowedTags: ['b', 'i', 'em', 'strong', 'a'], allowedAttributes: { 'a': ['href'], }, }); const comment = await db.comment.create({ data: { content: clean }, }); res.json(comment); }); ``` ### Content Security Policy (CSP) ```typescript app.use( helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", 'https://trusted-cdn.com'], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], connectSrc: ["'self'", 'https://api.example.com'], fontSrc: ["'self'", 'https://fonts.gstatic.com'], objectSrc: ["'none'"], upgradeInsecureRequests: [], }, }) ); ``` ## CSRF (Cross-Site Request Forgery) Protection ### CSRF Token Pattern ```typescript import csrf from 'csurf'; import cookieParser from 'cookie-parser'; const csrfProtection = csrf({ cookie: true }); app.use(cookieParser()); // Get CSRF token app.get('/api/csrf-token', csrfProtection, (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); // Protect state-changing endpoints app.post('/api/users', csrfProtection, async (req, res) => { // Token automatically verified by middleware const user = await createUser(req.body); res.json(user); }); ``` ### SameSite Cookies ```typescript // ✅ Set SameSite attribute res.cookie('sessionId', token, { httpOnly: true, secure: true, // HTTPS only sameSite: 'strict', // or 'lax' maxAge: 24 * 60 * 60 * 1000, // 24 hours }); ``` ## Authentication Best Practices ### Password Requirements ```typescript import { z } from 'zod'; const PasswordSchema = z .string() .min(12, 'Password must be at least 12 characters') .regex(/[A-Z]/, 'Password must contain uppercase letter') .regex(/[a-z]/, 'Password must contain lowercase letter') .regex(/[0-9]/, 'Password must contain number') .regex(/[^A-Za-z0-9]/, 'Password must contain special character'); // Check against common passwords import { isCommonPassword } from 'common-passwords'; function validatePassword(password: string): boolean { if (isCommonPassword(password)) { throw new Error('Password is too common'); } return true; } ``` ### JWT Best Practices ```typescript import jwt from 'jsonwebtoken'; // ✅ Short-lived access tokens function generateAccessToken(userId: string): string { return jwt.sign( { userId, type: 'access' }, process.env.JWT_SECRET!, { expiresIn: '15m' } // Short expiry ); } // ✅ Long-lived refresh tokens function generateRefreshToken(userId: string): string { return jwt.sign( { userId, type: 'refresh' }, process.env.JWT_REFRESH_SECRET!, { expiresIn: '7d' } ); } // ✅ Verify token function verifyAccessToken(token: string) { try { const payload = jwt.verify(token, process.env.JWT_SECRET!); if (payload.type !== 'access') { throw new Error('Invalid token type'); } return payload; } catch (error) { throw new Error('Invalid token'); } } // ✅ Blacklist tokens on logout const tokenBlacklist = new Set(); app.post('/api/auth/logout', authenticate, (req, res) => { const token = req.headers.authorization?.split(' ')[1]; if (token) { tokenBlacklist.add(token); } res.json({ message: 'Logged out' }); }); ``` ### Multi-Factor Authentication (MFA) ```typescript import speakeasy from 'speakeasy'; import QRCode from 'qrcode'; // Generate MFA secret async function enableMFA(userId: string) { const secret = speakeasy.generateSecret({ name: `MyApp (${user.email})`, }); // Generate QR code const qrCode = await QRCode.toDataURL(secret.otpauth_url!); // Save secret to database (encrypted) await db.user.update({ where: { id: userId }, data: { mfaSecret: encrypt(secret.base32) }, }); return { secret: secret.base32, qrCode }; } // Verify MFA token function verifyMFAToken(secret: string, token: string): boolean { return speakeasy.totp.verify({ secret, encoding: 'base32', token, window: 2, // Allow 2 time steps before/after }); } ``` ## Authorization Patterns ### Role-Based Access Control (RBAC) ```typescript enum Role { USER = 'user', MODERATOR = 'moderator', ADMIN = 'admin', } const permissions = { [Role.USER]: ['read:own', 'write:own'], [Role.MODERATOR]: ['read:own', 'write:own', 'read:all', 'delete:others'], [Role.ADMIN]: ['*'], // All permissions }; function authorize(requiredPermission: string) { return (req: Request, res: Response, next: NextFunction) => { const userRole = req.user.role; const userPermissions = permissions[userRole]; if (!userPermissions.includes('*') && !userPermissions.includes(requiredPermission)) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; } // Usage app.delete('/api/posts/:id', authenticate, authorize('delete:others'), async (req, res) => { await deletePost(req.params.id); res.json({ success: true }); }); ``` ### Attribute-Based Access Control (ABAC) ```typescript interface AccessPolicy { subject: { role: string; department?: string }; resource: { type: string; owner?: string }; action: string; conditions?: Record; } function checkAccess( user: User, resource: Resource, action: string ): boolean { // User can access own resources if (resource.ownerId === user.id) { return true; } // Admins can access everything if (user.role === 'admin') { return true; } // Department managers can access department resources if ( user.role === 'manager' && user.department === resource.department ) { return true; } return false; } ``` ## Secrets Management ### Environment Variables ```typescript // ✅ .env file (NOT committed to git) DATABASE_URL=postgresql://user:password@localhost:5432/db JWT_SECRET=super-secret-key-change-in-production API_KEY=sk_live_abc123xyz // ✅ .env.example (committed to git) DATABASE_URL= JWT_SECRET= API_KEY= // ✅ Load environment variables import dotenv from 'dotenv'; dotenv.config(); // ✅ Validate required env vars const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'API_KEY']; for (const envVar of requiredEnvVars) { if (!process.env[envVar]) { throw new Error(`Missing required environment variable: ${envVar}`); } } ``` ### Key Vault Integration ```typescript // ✅ AWS Secrets Manager import { SecretsManager } from '@aws-sdk/client-secrets-manager'; async function getSecret(secretName: string): Promise { const client = new SecretsManager({ region: 'us-east-1' }); const response = await client.getSecretValue({ SecretId: secretName }); return response.SecretString!; } // ✅ Azure Key Vault import { SecretClient } from '@azure/keyvault-secrets'; async function getAzureSecret(secretName: string): Promise { const client = new SecretClient(vaultUrl, credential); const secret = await client.getSecret(secretName); return secret.value!; } ``` ## Input Validation ### Validation Schema ```typescript import { z } from 'zod'; const CreateUserSchema = z.object({ email: z.string().email().max(255), password: z.string().min(12).max(100), name: z.string().min(1).max(100), age: z.number().int().min(18).max(120), phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(), website: z.string().url().optional(), }); app.post('/api/users', async (req, res) => { try { const data = CreateUserSchema.parse(req.body); const user = await createUser(data); res.json(user); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation failed', details: error.errors, }); } throw error; } }); ``` ### Sanitization ```typescript import validator from 'validator'; function sanitizeInput(input: string): string { // Trim whitespace let clean = input.trim(); // Escape HTML entities clean = validator.escape(clean); // Remove null bytes clean = clean.replace(/\0/g, ''); return clean; } ``` ## Security Headers ### Comprehensive Header Configuration ```typescript import helmet from 'helmet'; app.use( helmet({ // Content Security Policy contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", 'data:', 'https:'], }, }, // HTTP Strict Transport Security hsts: { maxAge: 31536000, // 1 year includeSubDomains: true, preload: true, }, // X-Frame-Options frameguard: { action: 'deny', // or 'sameorigin' }, // X-Content-Type-Options noSniff: true, // X-XSS-Protection xssFilter: true, // Referrer-Policy referrerPolicy: { policy: 'strict-origin-when-cross-origin', }, }) ); // Additional custom headers app.use((req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); next(); }); ``` ## Rate Limiting ### Endpoint-Specific Rate Limits ```typescript import rateLimit from 'express-rate-limit'; // Strict limit for authentication const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, message: 'Too many authentication attempts', standardHeaders: true, legacyHeaders: false, }); // Moderate limit for API endpoints const apiLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 60, message: 'Too many requests', }); // Apply rate limiters app.post('/api/auth/login', authLimiter, loginHandler); app.post('/api/auth/register', authLimiter, registerHandler); app.use('/api', apiLimiter); ``` ## Logging Security Events ### What NOT to Log **❌ Never log**: - Passwords (plain or hashed) - Credit card numbers - Social Security numbers - API keys/secrets - Session tokens - Personally identifiable information (PII) in clear text ```typescript // ❌ BAD - Logs sensitive data logger.info('User login', { email, password }); // ✅ GOOD - Logs non-sensitive data logger.info('User login attempt', { email, ip: req.ip, userAgent: req.get('user-agent'), success: true, }); ``` ### Security Event Logging ```typescript enum SecurityEvent { LOGIN_SUCCESS = 'login_success', LOGIN_FAILURE = 'login_failure', PASSWORD_RESET = 'password_reset', ACCOUNT_LOCKED = 'account_locked', PERMISSION_DENIED = 'permission_denied', SUSPICIOUS_ACTIVITY = 'suspicious_activity', } function logSecurityEvent( event: SecurityEvent, userId: string | null, metadata: Record ) { logger.warn({ type: 'security', event, userId, timestamp: new Date().toISOString(), ip: metadata.ip, userAgent: metadata.userAgent, ...metadata, }); } ``` ## Dependency Scanning ### Automated Security Scanning ```yaml # .github/workflows/security.yml name: Security Scan on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 0 * * 0' # Weekly jobs: security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - 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: '.' ``` ## Security Checklist Summary ### Pre-Deployment Checklist **Authentication & Authorization**: - [ ] Passwords hashed with bcrypt (12+ rounds) - [ ] JWT tokens have short expiry (15 minutes) - [ ] Refresh tokens properly implemented - [ ] MFA available for sensitive accounts - [ ] Authorization checks on all endpoints - [ ] Rate limiting on auth endpoints **Input Validation**: - [ ] All inputs validated with schemas - [ ] SQL queries use parameterized statements - [ ] File uploads restricted (type, size) - [ ] HTML content sanitized - [ ] No eval() or Function() with user input **Security Headers**: - [ ] HTTPS enforced - [ ] CSP configured - [ ] HSTS enabled - [ ] X-Frame-Options set - [ ] X-Content-Type-Options set **Data Protection**: - [ ] Sensitive data encrypted at rest - [ ] Secrets stored in environment variables/vault - [ ] No secrets in version control - [ ] PII handled according to regulations - [ ] Database backups encrypted **Logging & Monitoring**: - [ ] Security events logged - [ ] No sensitive data in logs - [ ] Failed auth attempts monitored - [ ] Unusual activity alerts configured - [ ] Log retention policy defined **Dependencies**: - [ ] All dependencies up to date - [ ] No known vulnerabilities (npm audit) - [ ] Dependabot/Renovate configured - [ ] Unused dependencies removed **API Security**: - [ ] CORS properly configured - [ ] CSRF protection enabled - [ ] Rate limiting implemented - [ ] API keys rotated regularly - [ ] Endpoints documented ## When to Use This Skill Use this skill when: - Conducting security audits - Implementing authentication/authorization - Reviewing code for vulnerabilities - Setting up new projects - Handling sensitive data - Responding to security incidents - Training team on security - Preparing for compliance audits - Deploying to production - Integrating third-party services --- **Remember**: Security is not a one-time task but an ongoing process. Stay informed about new vulnerabilities, keep dependencies updated, and always assume malicious actors are trying to exploit your application.