From a5fa522a025fac74ceb8dcd70208f396fa78d724 Mon Sep 17 00:00:00 2001 From: riya chandra Date: Wed, 28 Jan 2026 17:46:24 +0530 Subject: [PATCH 1/3] docs: add NestJS security cheatsheet covering DTOs and Helmet --- cheatsheets/NestJs_Security_Cheat_Sheet.md | 485 +++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 cheatsheets/NestJs_Security_Cheat_Sheet.md diff --git a/cheatsheets/NestJs_Security_Cheat_Sheet.md b/cheatsheets/NestJs_Security_Cheat_Sheet.md new file mode 100644 index 0000000000..138edfcbd6 --- /dev/null +++ b/cheatsheets/NestJs_Security_Cheat_Sheet.md @@ -0,0 +1,485 @@ +# NestJS Security Cheat Sheet + +## Introduction + +NestJS is a progressive Node.js framework built with TypeScript. While it provides powerful abstractions through decorators and dependency injection, these features can introduce security vulnerabilities if misconfigured. This cheat sheet focuses on NestJS-specific security patterns that complement the [Node.js Security Cheat Sheet](Nodejs_Security_Cheat_Sheet.md). + +**Key Principle:** NestJS security relies on understanding the request lifecycle and applying controls at the correct layer. + +## Request Lifecycle + +Security controls must be applied in the correct order: + +``` +Request → Middleware → Guards → Pipes → Handler → Response + ↓ ↓ ↓ + CORS Auth/Authz Validation + Helmet +``` + +**Critical:** Misplacing security logic breaks defense-in-depth. Guards protect *who* accesses resources; Pipes protect *what* data enters your system. + +## Mass Assignment Protection + +### The Problem + +DTOs alone provide no protection. Without proper ValidationPipe configuration, attackers can inject arbitrary fields. + +### Solution + +**Install Dependencies:** +```bash +npm install class-validator class-transformer +``` + +**Create DTOs with Validation:** +```typescript +// create-user.dto.ts +import { IsEmail, IsString, MinLength } from 'class-validator'; + +export class CreateUserDto { + @IsEmail() + email: string; + + @IsString() + @MinLength(12) + password: string; + + @IsString() + name: string; + // Note: 'role' is NOT defined - any attempt to pass it will be rejected +} +``` + +**Configure Global ValidationPipe:** +```typescript +// main.ts +import { ValidationPipe } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, // Strip non-whitelisted properties + forbidNonWhitelisted: true, // Throw error if extra properties exist + transform: true, // Transform to DTO instances + }), + ); + + await app.listen(3000); +} +``` + +**CRITICAL:** Using only `whitelist: true` is insufficient. It silently removes properties, allowing attackers to send large payloads that waste validation resources. Always use `forbidNonWhitelisted: true` for active rejection (defense-in-depth). + +**Attack Prevention Example:** +```bash +# Attacker payload +POST /users +{ "email": "user@test.com", "password": "pass", "role": "admin" } + +# With forbidNonWhitelisted: true +→ 400 Bad Request: "property role should not exist" + +# Without forbidNonWhitelisted: true +→ 201 Created (silently strips 'role', wastes resources) +``` + +## Authentication and Authorization + +### JWT Authentication + +**Install Dependencies:** +```bash +npm install @nestjs/jwt @nestjs/passport passport passport-jwt +``` + +**Configure JWT Module:** +```typescript +// auth.module.ts +import { JwtModule } from '@nestjs/jwt'; + +@Module({ + imports: [ + JwtModule.register({ + secret: process.env.JWT_SECRET, // Never hardcode + signOptions: { + expiresIn: '15m', // Short-lived tokens + algorithm: 'HS256', + }, + }), + ], +}) +export class AuthModule {} +``` + +**Create JWT Guard:** +```typescript +// jwt-auth.guard.ts +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + handleRequest(err: any, user: any) { + if (err || !user) { + throw new UnauthorizedException('Invalid token'); + } + return user; + } +} +``` + +### Role-Based Access Control + +**Define Roles:** +```typescript +// roles.decorator.ts +import { SetMetadata } from '@nestjs/common'; + +export enum Role { + User = 'user', + Admin = 'admin', +} + +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); +``` + +**Create Roles Guard:** +```typescript +// roles.guard.ts +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.get(ROLES_KEY, context.getHandler()); + if (!requiredRoles) return true; + + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some(role => user.roles?.includes(role)); + } +} +``` + +**Apply Guards (Order Matters!):** +```typescript +@Controller('admin') +@UseGuards(JwtAuthGuard, RolesGuard) // Auth BEFORE Roles! +export class AdminController { + @Roles(Role.Admin) + @Get('data') + getAdminData() { + return { data: 'sensitive' }; + } +} +``` + +**CRITICAL:** Always apply `JwtAuthGuard` before `RolesGuard`. Reversing the order allows unauthenticated users to probe role requirements. + +## Security Headers with Helmet + +**Install Helmet:** +```bash +npm install helmet +``` + +**Configure in main.ts:** +```typescript +import helmet from 'helmet'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + }, + }, + hsts: { + maxAge: 31536000, + includeSubDomains: true, + }, + })); + + await app.listen(3000); +} +``` + +**Apply Helmet FIRST:** Place before `app.enableCors()` and other middleware to ensure headers are set on all responses. + +## CORS Configuration + +**Secure CORS Setup:** +```typescript +// main.ts +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.enableCors({ + origin: process.env.ALLOWED_ORIGINS?.split(','), + methods: ['GET', 'POST', 'PUT', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + credentials: true, + maxAge: 3600, + }); + + await app.listen(3000); +} +``` + +**Environment Variable:** +```env +ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com +``` + +**Common Mistake:** +```typescript +// NEVER do this! +app.enableCors({ + origin: '*', // Dangerous! + credentials: true, // Invalid combination +}); +``` + +When `credentials: true`, origin cannot be wildcard. Browsers will block this configuration. + +## Rate Limiting + +**Install Throttler:** +```bash +npm install @nestjs/throttler +``` + +**Global Configuration:** +```typescript +// app.module.ts +import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; +import { APP_GUARD } from '@nestjs/core'; + +@Module({ + imports: [ + ThrottlerModule.forRoot([{ + ttl: 60000, // 60 seconds + limit: 100, // 100 requests per window + }]), + ], + providers: [ + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + ], +}) +export class AppModule {} +``` + +**Custom Limits for Auth Endpoints:** +```typescript +import { Throttle } from '@nestjs/throttler'; + +@Controller('auth') +export class AuthController { + @Post('login') + @Throttle({ default: { limit: 5, ttl: 60000 } }) // 5 attempts/min + login(@Body() loginDto: LoginDto) { + return this.authService.login(loginDto); + } +} +``` + +## SQL Injection Prevention + +**Safe: Use Repository Pattern** +```typescript +// users.service.ts +async findByEmail(email: string): Promise { + return this.usersRepository.findOne({ where: { email } }); +} +``` + +**Safe: Query Builder with Parameters** +```typescript +async findActiveUsers(minAge: number): Promise { + return this.usersRepository + .createQueryBuilder('user') + .where('user.age >= :minAge', { minAge }) + .getMany(); +} +``` + +**Vulnerable: String Concatenation** +```typescript +// NEVER DO THIS! +async unsafeQuery(email: string) { + return this.usersRepository.query( + `SELECT * FROM users WHERE email = '${email}'` // SQL Injection! + ); +} +``` + +**Attack Example:** +```typescript +email = "admin' OR '1'='1" +// Result: SELECT * FROM users WHERE email = 'admin' OR '1'='1' +// Returns ALL users! +``` + +## File Upload Security + +**Secure Upload Configuration:** +```typescript +import { diskStorage } from 'multer'; +import { extname } from 'path'; + +export const imageUploadOptions = { + storage: diskStorage({ + destination: './uploads', + filename: (req, file, cb) => { + const uniqueName = `${Date.now()}${extname(file.originalname)}`; + cb(null, uniqueName); + }, + }), + fileFilter: (req, file, cb) => { + const allowedMimes = ['image/jpeg', 'image/png']; + if (!allowedMimes.includes(file.mimetype)) { + return cb(new Error('Invalid file type'), false); + } + cb(null, true); + }, + limits: { + fileSize: 5 * 1024 * 1024, // 5MB + }, +}; +``` + +**Apply to Controller:** +```typescript +import { FileInterceptor } from '@nestjs/platform-express'; + +@Post('upload') +@UseInterceptors(FileInterceptor('file', imageUploadOptions)) +uploadFile(@UploadedFile() file: Express.Multer.File) { + return { filename: file.filename }; +} +``` + +## Environment Variables + +**Install Config Module:** +```bash +npm install @nestjs/config joi +``` + +**Configure with Validation:** +```typescript +// app.module.ts +import { ConfigModule } from '@nestjs/config'; +import * as Joi from 'joi'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + validationSchema: Joi.object({ + NODE_ENV: Joi.string().valid('development', 'production').required(), + JWT_SECRET: Joi.string().min(32).required(), + DATABASE_URL: Joi.string().required(), + }), + }), + ], +}) +export class AppModule {} +``` + +**Never hardcode secrets:** +```typescript +const secret = 'my-secret-key'; + +const secret = process.env.JWT_SECRET; +``` + +## Error Handling + +**Global Exception Filter:** +```typescript +import { ExceptionFilter, Catch, HttpException, HttpStatus } from '@nestjs/common'; + +@Catch() +export class GlobalExceptionFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + const status = exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + const message = process.env.NODE_ENV === 'production' + ? 'An error occurred' + : exception instanceof HttpException + ? exception.message + : 'Internal server error'; + + response.status(status).json({ + statusCode: status, + message, + timestamp: new Date().toISOString(), + }); + } +} +``` + +**Apply in main.ts:** +```typescript +app.useGlobalFilters(new GlobalExceptionFilter()); +``` + +**Never expose stack traces in production.** + +## Security Checklist + +### Before Production: +- [ ] ValidationPipe configured with `whitelist: true` AND `forbidNonWhitelisted: true` +- [ ] All DTOs use `class-validator` decorators +- [ ] JWT secret in environment variables (min 32 chars) +- [ ] Guards applied in correct order (Auth → Roles) +- [ ] Helmet configured and applied first +- [ ] CORS allows only specific origins +- [ ] Rate limiting enabled globally +- [ ] File uploads validate type and size +- [ ] All database queries use parameterization +- [ ] Error messages sanitized for production +- [ ] HTTPS enforced +- [ ] Dependencies scanned with `npm audit` + +## Quick Reference + +| Security Control | Where to Apply | Key Setting | +|-----------------|----------------|-------------| +| Input Validation | Global Pipe | `forbidNonWhitelisted: true` | +| Authentication | Guard | Apply before RolesGuard | +| CORS | Middleware | Specific origins only | +| Rate Limiting | Global Guard | Stricter on auth routes | +| Security Headers | Middleware | Apply Helmet first | +| SQL Injection | Repository/Query Builder | Always parameterize | +| File Upload | Interceptor | Validate MIME + size | + +## Additional Resources + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Node.js Security Cheat Sheet](Nodejs_Security_Cheat_Sheet.md) +- [NestJS Security Documentation](https://docs.nestjs.com/security/helmet) +- [TypeORM Security](https://typeorm.io/) + +## Conclusion + +NestJS security requires understanding the framework's lifecycle and applying controls at the correct layer. The key is configuring ValidationPipe, Guards, and middleware in the right order with the right settings. Always use defense-in-depth: multiple layers of security controls working together. From c045dadfaa3223d1bd2b641af224c24d43df6963 Mon Sep 17 00:00:00 2001 From: riya chandra Date: Thu, 29 Jan 2026 21:32:11 +0530 Subject: [PATCH 2/3] docs: add NestJS security cheatsheet covering DTOs and Helmet --- Index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Index.md b/Index.md index 2b92c4f232..09d0d8b3c8 100644 --- a/Index.md +++ b/Index.md @@ -140,6 +140,8 @@ ## N +[NestJS Security Cheat Sheet](cheatsheets/NestJS_Security_Cheat_Sheet.md) + [NodeJS Docker Cheat Sheet](cheatsheets/NodeJS_Docker_Cheat_Sheet.md) [NPM Security Cheat Sheet](cheatsheets/NPM_Security_Cheat_Sheet.md) From a074d13c1cd6751eec7cb0f9f44de40405900501 Mon Sep 17 00:00:00 2001 From: riya chandra Date: Mon, 2 Feb 2026 14:36:46 +0530 Subject: [PATCH 3/3] fix: resolve markdown lint errors --- cheatsheets/NestJs_Security_Cheat_Sheet.md | 44 +++++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/cheatsheets/NestJs_Security_Cheat_Sheet.md b/cheatsheets/NestJs_Security_Cheat_Sheet.md index 138edfcbd6..70c0140087 100644 --- a/cheatsheets/NestJs_Security_Cheat_Sheet.md +++ b/cheatsheets/NestJs_Security_Cheat_Sheet.md @@ -10,7 +10,7 @@ NestJS is a progressive Node.js framework built with TypeScript. While it provid Security controls must be applied in the correct order: -``` +```text Request → Middleware → Guards → Pipes → Handler → Response ↓ ↓ ↓ CORS Auth/Authz Validation @@ -28,11 +28,13 @@ DTOs alone provide no protection. Without proper ValidationPipe configuration, a ### Solution **Install Dependencies:** + ```bash npm install class-validator class-transformer ``` **Create DTOs with Validation:** + ```typescript // create-user.dto.ts import { IsEmail, IsString, MinLength } from 'class-validator'; @@ -52,6 +54,7 @@ export class CreateUserDto { ``` **Configure Global ValidationPipe:** + ```typescript // main.ts import { ValidationPipe } from '@nestjs/common'; @@ -75,6 +78,7 @@ async function bootstrap() { **CRITICAL:** Using only `whitelist: true` is insufficient. It silently removes properties, allowing attackers to send large payloads that waste validation resources. Always use `forbidNonWhitelisted: true` for active rejection (defense-in-depth). **Attack Prevention Example:** + ```bash # Attacker payload POST /users @@ -92,11 +96,13 @@ POST /users ### JWT Authentication **Install Dependencies:** + ```bash npm install @nestjs/jwt @nestjs/passport passport passport-jwt ``` **Configure JWT Module:** + ```typescript // auth.module.ts import { JwtModule } from '@nestjs/jwt'; @@ -116,6 +122,7 @@ export class AuthModule {} ``` **Create JWT Guard:** + ```typescript // jwt-auth.guard.ts import { Injectable, UnauthorizedException } from '@nestjs/common'; @@ -135,6 +142,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') { ### Role-Based Access Control **Define Roles:** + ```typescript // roles.decorator.ts import { SetMetadata } from '@nestjs/common'; @@ -149,6 +157,7 @@ export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); ``` **Create Roles Guard:** + ```typescript // roles.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @@ -169,6 +178,7 @@ export class RolesGuard implements CanActivate { ``` **Apply Guards (Order Matters!):** + ```typescript @Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) // Auth BEFORE Roles! @@ -186,11 +196,13 @@ export class AdminController { ## Security Headers with Helmet **Install Helmet:** + ```bash npm install helmet ``` **Configure in main.ts:** + ```typescript import helmet from 'helmet'; @@ -220,6 +232,7 @@ async function bootstrap() { ## CORS Configuration **Secure CORS Setup:** + ```typescript // main.ts async function bootstrap() { @@ -238,11 +251,13 @@ async function bootstrap() { ``` **Environment Variable:** + ```env ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com ``` **Common Mistake:** + ```typescript // NEVER do this! app.enableCors({ @@ -256,11 +271,13 @@ When `credentials: true`, origin cannot be wildcard. Browsers will block this co ## Rate Limiting **Install Throttler:** + ```bash npm install @nestjs/throttler ``` **Global Configuration:** + ```typescript // app.module.ts import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; @@ -284,6 +301,7 @@ export class AppModule {} ``` **Custom Limits for Auth Endpoints:** + ```typescript import { Throttle } from '@nestjs/throttler'; @@ -299,7 +317,8 @@ export class AuthController { ## SQL Injection Prevention -**Safe: Use Repository Pattern** +### Safe - Use Repository Pattern + ```typescript // users.service.ts async findByEmail(email: string): Promise { @@ -307,7 +326,8 @@ async findByEmail(email: string): Promise { } ``` -**Safe: Query Builder with Parameters** +### Safe - Query Builder with Parameters + ```typescript async findActiveUsers(minAge: number): Promise { return this.usersRepository @@ -317,7 +337,8 @@ async findActiveUsers(minAge: number): Promise { } ``` -**Vulnerable: String Concatenation** +### Vulnerable - String Concatenation + ```typescript // NEVER DO THIS! async unsafeQuery(email: string) { @@ -328,6 +349,7 @@ async unsafeQuery(email: string) { ``` **Attack Example:** + ```typescript email = "admin' OR '1'='1" // Result: SELECT * FROM users WHERE email = 'admin' OR '1'='1' @@ -337,6 +359,7 @@ email = "admin' OR '1'='1" ## File Upload Security **Secure Upload Configuration:** + ```typescript import { diskStorage } from 'multer'; import { extname } from 'path'; @@ -363,6 +386,7 @@ export const imageUploadOptions = { ``` **Apply to Controller:** + ```typescript import { FileInterceptor } from '@nestjs/platform-express'; @@ -376,11 +400,13 @@ uploadFile(@UploadedFile() file: Express.Multer.File) { ## Environment Variables **Install Config Module:** + ```bash npm install @nestjs/config joi ``` **Configure with Validation:** + ```typescript // app.module.ts import { ConfigModule } from '@nestjs/config'; @@ -401,15 +427,19 @@ export class AppModule {} ``` **Never hardcode secrets:** + ```typescript +// Bad const secret = 'my-secret-key'; +// Good const secret = process.env.JWT_SECRET; ``` ## Error Handling **Global Exception Filter:** + ```typescript import { ExceptionFilter, Catch, HttpException, HttpStatus } from '@nestjs/common'; @@ -439,6 +469,7 @@ export class GlobalExceptionFilter implements ExceptionFilter { ``` **Apply in main.ts:** + ```typescript app.useGlobalFilters(new GlobalExceptionFilter()); ``` @@ -447,7 +478,8 @@ app.useGlobalFilters(new GlobalExceptionFilter()); ## Security Checklist -### Before Production: +### Before Production + - [ ] ValidationPipe configured with `whitelist: true` AND `forbidNonWhitelisted: true` - [ ] All DTOs use `class-validator` decorators - [ ] JWT secret in environment variables (min 32 chars) @@ -464,7 +496,7 @@ app.useGlobalFilters(new GlobalExceptionFilter()); ## Quick Reference | Security Control | Where to Apply | Key Setting | -|-----------------|----------------|-------------| +| --------------- | -------------- | ----------- | | Input Validation | Global Pipe | `forbidNonWhitelisted: true` | | Authentication | Guard | Apply before RolesGuard | | CORS | Middleware | Specific origins only |