NestJS Attack Probe
Authorized probe of a NestJS 10.x/11.x app the user owns. Follow shared probing conventions — discover base URL from main.ts app.listen(...), process.env.PORT, Dockerfile EXPOSE, or nest-cli.json. Never hardcode.
NestJS-specific attack surface
- Validation is opt-in:
app.useGlobalPipes(new ValidationPipe()) without whitelist: true allows extra fields to flow through to handlers, defeating DTO-based validation.
- Guards are opt-in:
@UseGuards() on a controller class is per-class; per-method overrides + a missed method = anonymous endpoint.
@nestjs/swagger auto-mounts /api (or configured path) — by default unauthenticated.
- WebSocket Gateways don't use the HTTP guard chain; they need their own auth in
handleConnection.
@ApiBearerAuth() decorator is documentation only; it does not enforce the bearer token.
Procedure
- Authorization preflight + base URL discovery.
- Fetch Swagger if exposed: try
/api, /api/docs, /swagger, /docs. The OpenAPI JSON is usually at <docs>/json or /api-json.
- Probe per rule table.
Rules
| ID |
Severity |
Probe |
Confirmed when |
| NEST-DOC-001 |
medium |
GET /api, /swagger, /docs |
200 Swagger UI = docs exposed (use as enumeration aid) |
| NEST-AUTH-001 |
critical |
For each mutating route in OpenAPI without a security requirement, send unauthenticated request |
2xx = @UseGuards missing |
| NEST-AUTH-002 |
high |
For routes with security: bearerAuth, send obviously-malformed token |
2xx = guard returns true instead of throwing |
| NEST-PIPE-001 |
high |
Submit a body with extra fields not in DTO ({"role": "admin", ...valid...}) |
Response shows extra field accepted = whitelist: true not set; combined with mass-assignment lookup → high |
| NEST-PIPE-002 |
medium |
Submit body with type-confusion (string where number expected) |
500 instead of 422 = transformation/validation pipe partially configured |
| NEST-DTO-001 |
medium |
Find a DTO without class-validator decorators (parse OpenAPI: properties with no constraints); send semantically invalid data (e.g., email: "not-email") |
2xx = no @IsEmail() enforcement |
| NEST-WS-001 |
high |
If /socket.io or custom gateway endpoint advertised, connect without auth header |
Connection upgraded + message received = no canActivate in handleConnection |
| NEST-CORS-001 |
high |
OPTIONS /api/* with Origin: https://evil.test and credentials |
ACAO + ACAC: true = enableCors({ origin: '*' or true, credentials: true }) |
| NEST-EXC-001 |
medium |
Force an error via malformed JSON / DB constraint violation |
500 with full stack / class names = exception filter leaking |
| NEST-COOKIE-001 |
high |
Login, capture cookie. Test httpOnly, secure, sameSite flags via Set-Cookie header |
Missing flags = misconfigured cookie-parser/session |
| NEST-RATE-001 |
medium |
10 rapid POST /auth/login |
No 429 = @nestjs/throttler not registered |
Wrong vs. right
NEST-PIPE-001 (extra-field bypass)
// ❌ main.ts — pipe registered without whitelist
app.useGlobalPipes(new ValidationPipe());
// ✅
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
NEST-AUTH-001 (per-method guard miss)
// ❌ Class guard, but one method missing decoration override + decorator removed
@Controller("orders")
@UseGuards(JwtAuthGuard)
export class OrdersController {
@Get() list() { /* guarded */ }
@Public() // someone added this for tests, forgot to remove
@Delete(":id") remove() { /* now anonymous */ }
}
// ✅
@Controller("orders")
@UseGuards(JwtAuthGuard, RolesGuard)
export class OrdersController {
@Get() list() { /* ... */ }
@Roles("admin")
@Delete(":id") remove() { /* ... */ }
}
References
1---2name: nestjs-attack-probe3description: Authorized self-pentest probe targeting NestJS-specific weaknesses. Tests global ValidationPipe gaps, missing @UseGuards on controller methods, default Swagger/OpenAPI exposure, WebSocket Gateway auth bypass, and TypeORM/Prisma raw query injection points discovered via OpenAPI. Use when the user asks to "pentest" their own NestJS app.4---56# NestJS Attack Probe78Authorized probe of a NestJS 10.x/11.x app the user owns. Follow [shared probing conventions](../../../PROBING.md) — discover base URL from `main.ts` `app.listen(...)`, `process.env.PORT`, `Dockerfile EXPOSE`, or `nest-cli.json`. Never hardcode.910## NestJS-specific attack surface1112- **Validation is opt-in:** `app.useGlobalPipes(new ValidationPipe())` without `whitelist: true` allows extra fields to flow through to handlers, defeating DTO-based validation.13- **Guards are opt-in:** `@UseGuards()` on a controller class is per-class; per-method overrides + a missed method = anonymous endpoint.14- **`@nestjs/swagger`** auto-mounts `/api` (or configured path) — by default unauthenticated.15- **WebSocket Gateways** don't use the HTTP guard chain; they need their own auth in `handleConnection`.16- **`@ApiBearerAuth()` decorator** is documentation only; it does not enforce the bearer token.1718## Procedure19201. Authorization preflight + base URL discovery.212. Fetch Swagger if exposed: try `/api`, `/api/docs`, `/swagger`, `/docs`. The OpenAPI JSON is usually at `<docs>/json` or `/api-json`.223. Probe per rule table.2324## Rules2526| ID | Severity | Probe | Confirmed when |27|----|----------|-------|----------------|28| NEST-DOC-001 | medium | `GET /api`, `/swagger`, `/docs` | 200 Swagger UI = docs exposed (use as enumeration aid) |29| NEST-AUTH-001 | critical | For each mutating route in OpenAPI without a `security` requirement, send unauthenticated request | 2xx = `@UseGuards` missing |30| NEST-AUTH-002 | high | For routes with `security: bearerAuth`, send obviously-malformed token | 2xx = guard returns `true` instead of throwing |31| NEST-PIPE-001 | high | Submit a body with extra fields not in DTO (`{"role": "admin", ...valid...}`) | Response shows extra field accepted = `whitelist: true` not set; combined with mass-assignment lookup → high |32| NEST-PIPE-002 | medium | Submit body with type-confusion (string where number expected) | 500 instead of 422 = transformation/validation pipe partially configured |33| NEST-DTO-001 | medium | Find a DTO without class-validator decorators (parse OpenAPI: `properties` with no constraints); send semantically invalid data (e.g., `email: "not-email"`) | 2xx = no `@IsEmail()` enforcement |34| NEST-WS-001 | high | If `/socket.io` or custom gateway endpoint advertised, connect without auth header | Connection upgraded + message received = no `canActivate` in `handleConnection` |35| NEST-CORS-001 | high | `OPTIONS /api/*` with `Origin: https://evil.test` and credentials | `ACAO` + `ACAC: true` = `enableCors({ origin: '*' or true, credentials: true })` |36| NEST-EXC-001 | medium | Force an error via malformed JSON / DB constraint violation | 500 with full stack / class names = exception filter leaking |37| NEST-COOKIE-001 | high | Login, capture cookie. Test `httpOnly`, `secure`, `sameSite` flags via `Set-Cookie` header | Missing flags = misconfigured `cookie-parser`/session |38| NEST-RATE-001 | medium | 10 rapid `POST /auth/login` | No 429 = `@nestjs/throttler` not registered |3940## Wrong vs. right4142### NEST-PIPE-001 (extra-field bypass)4344```ts45// ❌ main.ts — pipe registered without whitelist46app.useGlobalPipes(new ValidationPipe());47```4849```ts50// ✅51app.useGlobalPipes(new ValidationPipe({52 whitelist: true,53 forbidNonWhitelisted: true,54 transform: true,55}));56```5758### NEST-AUTH-001 (per-method guard miss)5960```ts61// ❌ Class guard, but one method missing decoration override + decorator removed62@Controller("orders")63@UseGuards(JwtAuthGuard)64export class OrdersController {65 @Get() list() { /* guarded */ }66 @Public() // someone added this for tests, forgot to remove67 @Delete(":id") remove() { /* now anonymous */ }68}69```7071```ts72// ✅73@Controller("orders")74@UseGuards(JwtAuthGuard, RolesGuard)75export class OrdersController {76 @Get() list() { /* ... */ }77 @Roles("admin")78 @Delete(":id") remove() { /* ... */ }79}80```8182## References8384- NestJS Validation: https://docs.nestjs.com/techniques/validation85- NestJS Guards: https://docs.nestjs.com/guards86- NestJS WebSockets: https://docs.nestjs.com/websockets/gateways87- @nestjs/swagger: https://docs.nestjs.com/openapi/introduction