Files
spotlightcam/docs/archive/SECURITY_AUDIT.md
Radosław Gierwiało 975d258497 docs: reorganize documentation structure for better context efficiency
Reorganization changes:
1. Moved from root → docs/:
   - QUICKSTART.md
   - QUICK_TEST.md
   - WEBRTC_TESTING_GUIDE.md

2. Created docs/archive/ and moved archival files:
   - COMPLETED.md (completed tasks archive)
   - PHASE_1.5.md (historical phase documentation)
   - RESOURCES.md (learning resources)
   - SECURITY_AUDIT.md (security audit)
   - ADMIN_CLI.md (CLI documentation)

3. Updated all references in:
   - README.md
   - docs/CONTEXT.md
   - docs/TODO.md
   - docs/SESSION_CONTEXT.md
   - docs/DEPLOYMENT.md
   - docs/QUICK_TEST.md

Active docs/ now contains only essential files:
- SESSION_CONTEXT.md (primary for context restoration)
- TODO.md
- CONTEXT.md
- ARCHITECTURE.md
- DEPLOYMENT.md
- MONITORING.md
- QUICKSTART.md
- QUICK_TEST.md
- WEBRTC_TESTING_GUIDE.md

Benefits:
- Reduced token usage when reading docs/ for context
- Clear separation between active and archived documentation
- Better organization for future maintenance
2025-11-20 22:42:06 +01:00

16 KiB

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:

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:

# 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:

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:

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:

npm install express-rate-limit
// 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:

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:

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:

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:

// 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:

body('password')
  .isLength({ min: 6 }) // Too weak!

And in auth.js:441:

if (newPassword.length < 8) // Inconsistent

Vulnerabilities:

  • 6 characters is too weak (should be 8+)
  • No complexity requirements
  • Inconsistent validation (6 vs 8)

Recommendation:

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:

npm install helmet
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:

model User {
  failedLoginAttempts Int      @default(0)
  lockedUntil         DateTime?
}

Implement lockout logic:

// 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:

// 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:

<h2>Hi ${firstName || 'there'}! 👋</h2>

If firstName contains <script>alert('XSS')</script>, it's injected into email.

Recommendation:

npm install dompurify jsdom
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

// Sanitize before using in email
const sanitizedFirstName = DOMPurify.sanitize(firstName || 'there', {
  ALLOWED_TAGS: [],
});

11. Missing CSRF Protection (CWE-352)

Severity: 🟠 HIGH OWASP: A01:2021 - Broken Access Control File: backend/src/app.js

Issue: No CSRF token validation for state-changing operations.

Recommendation:

npm install csurf cookie-parser
const csrf = require('csurf');
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(csrf({ cookie: true }));

// Add endpoint to get CSRF token
app.get('/api/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

// Frontend must include CSRF token in requests
// Headers: { 'X-CSRF-Token': token }

🟡 MEDIUM Issues (P2) - Fix Within 2-4 Weeks

12. Permissive CORS Configuration (CWE-346)

Severity: 🟡 MEDIUM File: backend/src/app.js:7

Issue:

app.use(cors({
  origin: process.env.CORS_ORIGIN || 'http://localhost:8080',
  credentials: true
}));

In production, this could be misconfigured.

Recommendation:

const allowedOrigins = [
  'https://spotlight.cam',
  'https://www.spotlight.cam',
  ...(process.env.NODE_ENV === 'development' ? ['http://localhost:8080'] : []),
];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  maxAge: 86400, // 24 hours
}));

13. Error Information Disclosure (CWE-209)

Severity: 🟡 MEDIUM File: backend/src/app.js:47

Issue:

res.status(err.status || 500).json({
  error: err.message || 'Internal Server Error',
  ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});

Stack traces leak in development if NODE_ENV not set in production.

Recommendation:

app.use((err, req, res, next) => {
  console.error('Error:', err);

  // Log full error internally
  logger.error(err);

  // Return generic error to client in production
  const isDevelopment = process.env.NODE_ENV === 'development';

  res.status(err.status || 500).json({
    error: isDevelopment ? err.message : 'Internal Server Error',
    ...(isDevelopment && { stack: err.stack }),
  });
});

14. No Validation on Email Verification Endpoints

Severity: 🟡 MEDIUM File: backend/src/controllers/auth.js:233

Issue: No input validation for verifyEmailByCode.

Recommendation: Add validation:

body('email').trim().isEmail().normalizeEmail(),
body('code').trim().matches(/^\d{6}$/).withMessage('Code must be 6 digits'),

15. No Logging for Security Events (CWE-778)

Severity: 🟡 MEDIUM File: All controllers

Issue: Only console.log() used. No structured logging.

Recommendation:

npm install winston
const winston = require('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: 'security.log', level: 'warn' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

// Log security events
logger.warn('Failed login attempt', { email, ip: req.ip });
logger.warn('Account locked', { userId, ip: req.ip });

16. Missing JWT Token Blacklist/Revocation (CWE-613)

Severity: 🟡 MEDIUM File: backend/src/utils/auth.js

Issue: No way to revoke JWT tokens before expiry.

Recommendation: Implement token blacklist using Redis:

// When user logs out or changes password
await redis.setex(`blacklist:${token}`, 86400, '1');

// In auth middleware
const isBlacklisted = await redis.get(`blacklist:${token}`);
if (isBlacklisted) {
  return res.status(401).json({ error: 'Token revoked' });
}

17. No Password History Check (CWE-521)

Severity: 🟡 MEDIUM File: backend/src/controllers/auth.js:429

Issue: Users can reuse old passwords.

Recommendation: Add password history tracking (prevent reuse of last 5 passwords).


18. Timing Attack Vulnerability in Token Comparison (CWE-208)

Severity: 🟡 MEDIUM File: backend/src/controllers/auth.js:178

Issue: String comparison for tokens may leak timing information.

Recommendation:

const crypto = require('crypto');

function timingSafeEqual(a, b) {
  if (typeof a !== 'string' || typeof b !== 'string') return false;
  if (a.length !== b.length) return false;

  return crypto.timingSafeEqual(
    Buffer.from(a, 'utf8'),
    Buffer.from(b, 'utf8')
  );
}

// Use in comparisons
if (!timingSafeEqual(user.verificationToken, token)) {
  return res.status(404).json({ error: 'Invalid token' });
}

🔵 LOW Issues (P3) - Fix When Convenient

19. No Security Monitoring/Alerts

Severity: 🔵 LOW

Recommendation: Implement monitoring for:

  • Multiple failed login attempts
  • Password reset requests
  • Account lockouts
  • Unusual API usage patterns

20. Missing HTTP Strict Transport Security (HSTS)

Severity: 🔵 LOW

Recommendation: Add via helmet (see issue #7).


21. No Password Strength Meter Feedback

Severity: 🔵 LOW

Recommendation: Implement server-side password strength validation:

npm install zxcvbn
const zxcvbn = require('zxcvbn');

const result = zxcvbn(password);
if (result.score < 3) {
  return res.status(400).json({
    error: 'Password too weak',
    suggestions: result.feedback.suggestions,
  });
}

Security Best Practices Checklist

  • Use environment-specific secrets
  • Implement rate limiting on all endpoints
  • Use helmet.js for security headers
  • Enable CSRF protection
  • Add request body size limits
  • Implement account lockout mechanism
  • Use cryptographically secure random generation
  • Add input validation for all endpoints
  • Sanitize user inputs (XSS prevention)
  • Implement structured logging
  • Add security monitoring and alerts
  • Use HTTPS in production
  • Implement JWT token revocation
  • Add password strength requirements
  • Use timing-safe comparisons for sensitive data
  • Prevent user enumeration
  • Implement proper CORS configuration
  • Regular security audits
  • Dependency vulnerability scanning (npm audit)
  • Keep dependencies up to date

Immediate Action Items (This Week)

  1. Generate and rotate all secrets
  2. Install and configure rate limiting (express-rate-limit)
  3. Install and configure helmet (helmet)
  4. Fix cryptographic random generation in generateVerificationCode()
  5. Add request body size limits
  6. Strengthen password requirements (8+ chars, complexity)
  7. Fix user enumeration in registration
  8. Add input validation for all fields

References


Report Generated: 2025-11-13 Next Audit Recommended: 2025-12-13 (or after major changes)