# Security Audit Report - spotlight.cam Backend **Date:** 2025-11-13 **Auditor:** Security Review **Scope:** Backend API (Node.js/Express) **Framework:** OWASP Top 10 2021 --- ## Executive Summary This security audit identified **21 security issues** across 4 severity levels: - 🔴 **CRITICAL (P0):** 5 issues - Immediate action required - 🟠 **HIGH (P1):** 6 issues - Fix within 1 week - 🟡 **MEDIUM (P2):** 7 issues - Fix within 2-4 weeks - 🔵 **LOW (P3):** 3 issues - Fix when convenient **Overall Security Rating:** ⚠️ **MODERATE RISK** --- ## 🔴 CRITICAL Issues (P0) - FIX IMMEDIATELY ### 1. Secrets Exposed in `.env` File (CWE-798) **Severity:** 🔴 CRITICAL **OWASP:** A02:2021 - Cryptographic Failures **File:** `backend/.env` **Issue:** ```bash JWT_SECRET=dev-secret-key-12345-change-in-production AWS_ACCESS_KEY_ID=your-aws-access-key-id AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key DATABASE_URL=postgresql://spotlightcam:spotlightcam123@db:5432/spotlightcam ``` **Vulnerabilities:** - Weak JWT secret key - Default placeholder AWS credentials - Database password in plain text - `.env` file may be committed to git **Impact:** - Attacker can forge JWT tokens - Potential unauthorized access to AWS services - Database compromise **Recommendation:** ```bash # Use strong random secrets (at least 32 characters) JWT_SECRET=$(openssl rand -base64 32) # Use AWS IAM roles instead of access keys (in production) # Or use AWS Secrets Manager # Use environment variables in production (Kubernetes secrets, Docker secrets, etc.) # Never commit .env to git - add to .gitignore ``` **Action:** 1. Generate strong JWT secret: `openssl rand -base64 64` 2. Use AWS IAM roles or Secrets Manager 3. Verify `.env` is in `.gitignore` 4. Rotate all secrets immediately if `.env` was committed --- ### 2. Insecure Random Number Generation (CWE-338) **Severity:** 🔴 CRITICAL **OWASP:** A02:2021 - Cryptographic Failures **File:** `backend/src/utils/auth.js:38` **Issue:** ```javascript function generateVerificationCode() { return Math.floor(100000 + Math.random() * 900000).toString(); } ``` **Vulnerabilities:** - `Math.random()` is NOT cryptographically secure - Predictable verification codes - Can be brute-forced **Impact:** - Attacker can predict verification codes - Account takeover possible **Recommendation:** ```javascript const crypto = require('crypto'); function generateVerificationCode() { // Cryptographically secure random 6-digit code const bytes = crypto.randomBytes(4); const num = bytes.readUInt32BE(0); return String(num % 900000 + 100000); } ``` --- ### 3. No Rate Limiting - Brute Force Vulnerability (CWE-307) **Severity:** 🔴 CRITICAL **OWASP:** A07:2021 - Identification and Authentication Failures **File:** `backend/src/app.js` **Issue:** No rate limiting on any endpoints. **Vulnerabilities:** - Login brute force attacks - Password reset abuse - Verification code brute force (6 digits = 1M combinations) - Email bombing via resend verification **Impact:** - Account takeover - Service disruption (DoS) - Email service abuse **Recommendation:** ```bash npm install express-rate-limit ``` ```javascript // backend/src/app.js const rateLimit = require('express-rate-limit'); // General API rate limiter const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again later.', standardHeaders: true, legacyHeaders: false, }); // Strict limiter for auth endpoints const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts skipSuccessfulRequests: true, message: 'Too many login attempts, please try again later.', }); // Email limiter const emailLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 3, // 3 emails per hour message: 'Too many emails sent, please try again later.', }); app.use('/api/', apiLimiter); app.use('/api/auth/login', authLimiter); app.use('/api/auth/register', authLimiter); app.use('/api/auth/resend-verification', emailLimiter); app.use('/api/auth/request-password-reset', emailLimiter); ``` --- ### 4. No Request Body Size Limit - DoS Vulnerability (CWE-400) **Severity:** 🔴 CRITICAL **OWASP:** A05:2021 - Security Misconfiguration **File:** `backend/src/app.js:11` **Issue:** ```javascript app.use(express.json()); // No limit app.use(express.urlencoded({ extended: true })); // No limit ``` **Vulnerabilities:** - Attacker can send huge JSON payloads - Memory exhaustion (DoS) **Impact:** - Server crash - Service unavailability **Recommendation:** ```javascript app.use(express.json({ limit: '10kb' })); app.use(express.urlencoded({ extended: true, limit: '10kb' })); ``` --- ### 5. User Enumeration Vulnerability (CWE-204) **Severity:** 🔴 CRITICAL **OWASP:** A01:2021 - Broken Access Control **File:** `backend/src/controllers/auth.js:28-46` **Issue:** ```javascript if (existingUser.email === email) { return res.status(400).json({ error: 'Email already registered', }); } if (existingUser.username === username) { return res.status(400).json({ error: 'Username already taken', }); } ``` **Vulnerabilities:** - Reveals which emails/usernames are registered - Enables targeted attacks **Impact:** - Email/username enumeration - Privacy breach - Targeted phishing attacks **Recommendation:** ```javascript // Don't reveal which field exists if (existingUser) { return res.status(400).json({ success: false, error: 'An account with these credentials already exists', }); } // Or implement verification via email before confirming registration ``` --- ## 🟠 HIGH Issues (P1) - Fix Within 1 Week ### 6. Weak Password Policy (CWE-521) **Severity:** 🟠 HIGH **File:** `backend/src/middleware/validators.js:30` **Issue:** ```javascript body('password') .isLength({ min: 6 }) // Too weak! ``` And in `auth.js:441`: ```javascript if (newPassword.length < 8) // Inconsistent ``` **Vulnerabilities:** - 6 characters is too weak (should be 8+) - No complexity requirements - Inconsistent validation (6 vs 8) **Recommendation:** ```javascript body('password') .isLength({ min: 8, max: 128 }) .withMessage('Password must be between 8 and 128 characters') .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/) .withMessage('Password must contain at least one uppercase, one lowercase, and one number'), ``` --- ### 7. Missing Security Headers (CWE-693) **Severity:** 🟠 HIGH **OWASP:** A05:2021 - Security Misconfiguration **File:** `backend/src/app.js` **Issue:** No security headers (CSP, HSTS, X-Frame-Options, etc.) **Recommendation:** ```bash npm install helmet ``` ```javascript const helmet = require('helmet'); app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, })); ``` --- ### 8. No Account Lockout After Failed Logins (CWE-307) **Severity:** 🟠 HIGH **File:** `backend/src/controllers/auth.js:120` **Issue:** No account lockout mechanism after multiple failed login attempts. **Recommendation:** Add to database schema: ```prisma model User { failedLoginAttempts Int @default(0) lockedUntil DateTime? } ``` Implement lockout logic: ```javascript // In login controller if (user.lockedUntil && user.lockedUntil > new Date()) { return res.status(423).json({ error: 'Account temporarily locked due to too many failed login attempts', lockedUntil: user.lockedUntil, }); } if (!isPasswordValid) { await prisma.user.update({ where: { id: user.id }, data: { failedLoginAttempts: { increment: 1 }, ...(user.failedLoginAttempts + 1 >= 5 && { lockedUntil: new Date(Date.now() + 15 * 60 * 1000), // 15 minutes }), }, }); return res.status(401).json({ error: 'Invalid credentials' }); } // Reset on successful login await prisma.user.update({ where: { id: user.id }, data: { failedLoginAttempts: 0, lockedUntil: null }, }); ``` --- ### 9. Missing Input Validation for WSDC Data (CWE-20) **Severity:** 🟠 HIGH **File:** `backend/src/controllers/auth.js:15` **Issue:** No validation for `firstName`, `lastName`, `wsdcId` in register. **Recommendation:** ```javascript // In validators.js body('firstName') .optional() .trim() .isLength({ max: 100 }) .matches(/^[a-zA-Z\s'-]+$/) .withMessage('Invalid first name'), body('lastName') .optional() .trim() .isLength({ max: 100 }) .matches(/^[a-zA-Z\s'-]+$/) .withMessage('Invalid last name'), body('wsdcId') .optional() .trim() .matches(/^\d{1,10}$/) .withMessage('WSDC ID must be numeric (max 10 digits)'), ``` --- ### 10. No Email Input Sanitization - XSS Risk (CWE-79) **Severity:** 🟠 HIGH **OWASP:** A03:2021 - Injection **File:** `backend/src/utils/email.js:98` **Issue:** ```javascript