import { Injectable, type NestMiddleware } from '@nestjs/common'; import type { NextFunction, Request, Response } from 'express'; import sanitizeHtml from 'sanitize-html'; const SANITIZE_OPTIONS: sanitizeHtml.IOptions = { allowedTags: [], allowedAttributes: {}, disallowedTagsMode: 'recursiveEscape', }; function sanitizeValue(value: unknown): unknown { if (typeof value === 'string') { return sanitizeHtml(value, SANITIZE_OPTIONS); } if (Array.isArray(value)) { return value.map(sanitizeValue); } if (value !== null && typeof value === 'object') { return sanitizeObject(value as Record); } return value; } function sanitizeObject(obj: Record): Record { const sanitized: Record = {}; for (const [key, val] of Object.entries(obj)) { sanitized[key] = sanitizeValue(val); } return sanitized; } /** * Strips HTML tags from all string values in request body, query, and params * to prevent stored XSS attacks. */ @Injectable() export class SanitizeInputMiddleware implements NestMiddleware { use(req: Request, _res: Response, next: NextFunction): void { if (req.body && typeof req.body === 'object') { req.body = sanitizeObject(req.body as Record); } if (req.query && typeof req.query === 'object') { for (const [key, val] of Object.entries(req.query)) { (req.query as Record)[key] = sanitizeValue(val); } } if (req.params && typeof req.params === 'object') { for (const [key, val] of Object.entries(req.params)) { (req.params as Record)[key] = sanitizeValue(val); } } next(); } }