Files
flyer-crawler.projectium.com/docs/adr/0016-api-security-hardening.md
Torben Sorensen b777430ff7
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
integration test fixes - claude for the win? try 4 - i have a good feeling
2026-01-09 05:18:19 -08:00

7.6 KiB
Raw Blame History

ADR-016: API Security Hardening

Date: 2025-12-12

Status: Accepted

Context

While authentication (ADR-011) is covered, the API lacks formal policies for protection against common web vulnerabilities and abuse. Security measures like rate limiting, secure HTTP headers, and Cross-Origin Resource Sharing (CORS) are not standardized.

Decision

We will implement a multi-layered security approach for the API:

  1. helmet: Use the helmet library to set crucial security headers (e.g., Content-Security-Policy, X-Content-Type-Options).
  2. Rate Limiting: Apply rate limiting using a library like express-rate-limit on sensitive endpoints (e.g., /login, /register) to prevent brute-force attacks.
  3. CORS: Establish a strict CORS policy to control which domains can access the API.

Consequences

  • Positive: Significantly improves the application's security posture against common web vulnerabilities like XSS, clickjacking, and brute-force attacks.
  • Negative: Requires careful configuration of CORS and rate limits to avoid blocking legitimate traffic. Content-Security-Policy can be complex to configure correctly.

Implementation Status

What's Implemented

  • Helmet - Security headers middleware with CSP, HSTS, and more
  • Rate Limiting - Comprehensive implementation with 17+ specific limiters
  • Input Validation - Zod-based request validation on all routes
  • File Upload Security - MIME type validation, size limits, filename sanitization
  • Error Handling - Production-safe error responses (no sensitive data leakage)
  • Request Timeout - 5-minute timeout protection
  • Secure Cookies - httpOnly and secure flags for authentication cookies

Not Required

  • CORS - Not needed (API and frontend are same-origin)

Implementation Details

Helmet Security Headers

Using helmet v8.x configured in server.ts as the first middleware after app initialization.

Security Headers Applied:

Header Configuration Purpose
Content-Security-Policy Custom directives Prevents XSS, code injection
Strict-Transport-Security 1 year, includeSubDomains, preload Forces HTTPS connections
X-Content-Type-Options nosniff Prevents MIME type sniffing
X-Frame-Options DENY Prevents clickjacking
X-XSS-Protection 0 (disabled) Deprecated, CSP preferred
Referrer-Policy strict-origin-when-cross-origin Controls referrer information
Cross-Origin-Resource-Policy cross-origin Allows external resource loading

Content Security Policy Directives:

contentSecurityPolicy: {
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'"],   // React inline scripts
    styleSrc: ["'self'", "'unsafe-inline'"],    // Tailwind inline styles
    imgSrc: ["'self'", 'data:', 'blob:', 'https:'],  // External images
    fontSrc: ["'self'", 'https:', 'data:'],
    connectSrc: ["'self'", 'https:', 'wss:'],   // API + WebSocket
    frameSrc: ["'none'"],                        // No iframes
    objectSrc: ["'none'"],                       // No plugins
    upgradeInsecureRequests: [],                 // Production only
  },
}

HSTS Configuration:

  • Max-age: 1 year (31536000 seconds)
  • Includes subdomains
  • Preload-ready for browser HSTS lists

Rate Limiting

Using express-rate-limit v8.2.1 with a centralized configuration in src/config/rateLimiters.ts.

Standard Configuration:

const standardConfig = {
  standardHeaders: true,  // Sends RateLimit-* headers
  legacyHeaders: false,
  skip: shouldSkipRateLimit,  // Disabled in test environment
};

Rate Limiters by Category:

Category Limiter Window Max Requests
Authentication loginLimiter 15 min 5
registerLimiter 1 hour 5
forgotPasswordLimiter 15 min 5
resetPasswordLimiter 15 min 10
refreshTokenLimiter 15 min 20
logoutLimiter 15 min 10
Public/User Read publicReadLimiter 15 min 100
userReadLimiter 15 min 100
userUpdateLimiter 15 min 100
Sensitive Operations userSensitiveUpdateLimiter 1 hour 5
adminTriggerLimiter 15 min 30
AI/Costly aiGenerationLimiter 15 min 20
geocodeLimiter 1 hour 100
priceHistoryLimiter 15 min 50
Uploads adminUploadLimiter 15 min 20
aiUploadLimiter 15 min 10
batchLimiter 15 min 50
Tracking trackingLimiter 15 min 200
reactionToggleLimiter 15 min 150

Test Environment Handling:

Rate limiting is automatically disabled in test environment via shouldSkipRateLimit utility (src/utils/rateLimit.ts). Tests can opt-in to rate limiting by setting the x-test-rate-limit-enable: true header.

Input Validation

Zod Schema Validation (src/middleware/validation.middleware.ts):

  • Type-safe parsing and coercion for params, query, and body
  • Applied to all API routes via validateRequest() middleware
  • Returns structured validation errors with field-level details

Filename Sanitization (src/utils/stringUtils.ts):

// Removes dangerous characters from uploaded filenames
sanitizeFilename(filename: string): string

File Upload Security

Multer Configuration (src/middleware/multer.middleware.ts):

  • MIME type validation via imageFileFilter (only image/* allowed)
  • File size limits (2MB for logos, configurable per upload type)
  • Unique filenames using timestamps + random suffixes
  • User-scoped storage paths

Error Handling

Production-Safe Responses (src/middleware/errorHandler.ts):

  • Production mode: Returns generic error message with tracking ID
  • Development mode: Returns detailed error information
  • Sensitive error details are logged but never exposed to clients

Request Security

Timeout Protection (server.ts):

  • 5-minute request timeout via connect-timeout middleware
  • Prevents resource exhaustion from long-running requests

Secure Cookies:

// Cookie configuration for auth tokens
{
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000  // 7 days for refresh token
}

Request Logging

Per-request structured logging (ADR-004):

  • Request ID tracking
  • User ID and IP address logging
  • Failed request details (4xx+) logged with headers and body
  • Unhandled errors assigned unique error IDs

Key Files

  • server.ts - Helmet middleware configuration (security headers)
  • src/config/rateLimiters.ts - Rate limiter definitions (17+ limiters)
  • src/utils/rateLimit.ts - Rate limit skip logic for testing
  • src/middleware/validation.middleware.ts - Zod-based request validation
  • src/middleware/errorHandler.ts - Production-safe error handling
  • src/middleware/multer.middleware.ts - Secure file upload configuration
  • src/utils/stringUtils.ts - Filename sanitization

Future Enhancements

  1. Configure CORS (if needed for cross-origin access):

    npm install cors @types/cors
    

    Add to server.ts:

    import cors from 'cors';
    app.use(cors({
      origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
      credentials: true,
    }));
    
  2. Redis-backed rate limiting: For distributed deployments, use rate-limit-redis store

  3. CSP Nonce: Generate per-request nonces for stricter script-src policy

  4. Report-Only CSP: Add Content-Security-Policy-Report-Only header for testing policy changes