diff --git a/middleware/src/security/security-headers.config.ts b/middleware/src/security/security-headers.config.ts new file mode 100644 index 00000000..cc97f873 --- /dev/null +++ b/middleware/src/security/security-headers.config.ts @@ -0,0 +1,20 @@ +export const SECURITY_HEADERS_CONFIG = { + common: { + 'X-Content-Type-Options': 'nosniff', + 'X-Frame-Options': 'DENY', + 'X-XSS-Protection': '1; mode=block', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + 'Permissions-Policy': 'geolocation=(), microphone=(), camera=(), payment=(), usb=()', + 'X-DNS-Prefetch-Control': 'off', + }, + hsts: { + production: 'max-age=31536000; includeSubDomains; preload', + development: null, // disabled in dev + }, + cacheControl: { + dynamic: 'no-cache, no-store, must-revalidate', + static: 'public, max-age=31536000', + private: 'private, no-cache', + }, + removeHeaders: ['X-Powered-By', 'Server', 'X-AspNet-Version', 'X-AspNetMvc-Version'], +}; diff --git a/middleware/src/security/security-headers.middleware.ts b/middleware/src/security/security-headers.middleware.ts new file mode 100644 index 00000000..f2f82a4d --- /dev/null +++ b/middleware/src/security/security-headers.middleware.ts @@ -0,0 +1,39 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import { SECURITY_HEADERS_CONFIG } from './security-headers.config'; + +@Injectable() +export class SecurityHeadersMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + // Apply common security headers + for (const [header, value] of Object.entries(SECURITY_HEADERS_CONFIG.common)) { + res.setHeader(header, value); + } + + // Apply HSTS only in production + if (process.env.NODE_ENV === 'production' && SECURITY_HEADERS_CONFIG.hsts.production) { + res.setHeader('Strict-Transport-Security', SECURITY_HEADERS_CONFIG.hsts.production); + } + + // Remove sensitive headers + SECURITY_HEADERS_CONFIG.removeHeaders.forEach((header) => { + res.removeHeader(header); + }); + + // Cache control based on content type + res.on('finish', () => { + const contentType = res.getHeader('Content-Type') as string; + if (!contentType) return; + + if (contentType.includes('application/json')) { + res.setHeader('Cache-Control', SECURITY_HEADERS_CONFIG.cacheControl.dynamic); + } else if (contentType.startsWith('text/') || contentType.includes('javascript') || contentType.includes('css')) { + res.setHeader('Cache-Control', SECURITY_HEADERS_CONFIG.cacheControl.static); + } else { + res.setHeader('Cache-Control', SECURITY_HEADERS_CONFIG.cacheControl.private); + } + }); + + next(); + } +}