feat(security): add security hardening — Helmet, CORS, rate limiting, input sanitization

- Add Helmet with CSP, HSTS, referrer policy
- Configure CORS with environment-based origins
- Add global validation pipe with whitelist mode
- Add SanitizeInputMiddleware for XSS prevention
- Add ThrottlerBehindProxyGuard for rate limiting
- Add FileValidationPipe for upload security
- Set request body size limit to 1MB

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-08 02:04:13 +07:00
parent 5e44456d11
commit f3081d92fc
6 changed files with 197 additions and 1 deletions

View File

@@ -0,0 +1,61 @@
import { BadRequestException, Injectable, type PipeTransform } from '@nestjs/common';
export interface UploadedFile {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
buffer: Buffer;
}
export interface FileValidationOptions {
/** Max file size in bytes. Default: 5 MB */
maxSizeBytes?: number;
/** Allowed MIME types. Default: common image types + PDF */
allowedMimeTypes?: string[];
}
const DEFAULT_MAX_SIZE = 5 * 1024 * 1024; // 5 MB
const DEFAULT_ALLOWED_MIMES = [
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
'application/pdf',
];
/**
* Validates uploaded files for size and MIME type to prevent
* malicious file uploads and resource exhaustion.
*/
@Injectable()
export class FileValidationPipe implements PipeTransform {
private readonly maxSize: number;
private readonly allowedMimes: string[];
constructor(options?: FileValidationOptions) {
this.maxSize = options?.maxSizeBytes ?? DEFAULT_MAX_SIZE;
this.allowedMimes = options?.allowedMimeTypes ?? DEFAULT_ALLOWED_MIMES;
}
transform(file: UploadedFile): UploadedFile {
if (!file) {
throw new BadRequestException('File is required');
}
if (file.size > this.maxSize) {
throw new BadRequestException(
`File size ${file.size} exceeds maximum allowed size of ${this.maxSize} bytes`,
);
}
if (!this.allowedMimes.includes(file.mimetype)) {
throw new BadRequestException(
`File type '${file.mimetype}' is not allowed. Allowed types: ${this.allowedMimes.join(', ')}`,
);
}
return file;
}
}