no-missing-validation-pipe
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
Require ValidationPipe for DTO input parameters
Error Message Format
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
🔒 CWE-20 OWASP:A06 CVSS:7.5 | Improper Input Validation detected | HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001]
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A06_2021/Message Components
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-20 OWASP:A06 CVSS:7.5 |
| Issue Description | Specific vulnerability | Improper Input Validation detected |
| Severity & Compliance | Impact assessment | HIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001] |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
Rule Details
This rule detects NestJS route handlers that accept DTO parameters without ValidationPipe, which can lead to injection attacks through unvalidated input.
OWASP Mapping
- OWASP Top 10 2021: A03:2021 - Injection
- CWE: CWE-20 - Improper Input Validation
- CVSS: 8.6 (High)
❌ Incorrect
@Controller('users')
class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
// No validation - malicious input can pass through!
}
}✅ Correct
import { UsePipes, ValidationPipe } from '@nestjs/common';
// Class-level validation
@Controller('users')
@UsePipes(new ValidationPipe())
class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {}
}
// Or in main.ts (global)
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);Options
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
detectGlobalPipes | boolean | true | Look for a globally registered ValidationPipe before reporting |
assumeGlobalPipes | boolean | false | Assume a global ValidationPipe exists even if none is found |
requireExplicitPipe | boolean | false | Require a per-handler pipe even when a global one is registered |
{
// Skip rule in test files (default: true)
allowInTests?: boolean;
// Scan the project's module and bootstrap files for an app-wide pipe
// (APP_PIPE / app.useGlobalPipes) and stay silent when one exists
// (default: true)
detectGlobalPipes?: boolean;
// Assume a global pipe without scanning (default: false). It counts exactly
// as a detected one: typed DTOs are covered, unvalidatable shapes are not.
assumeGlobalPipes?: boolean;
// Require an explicit per-route pipe even where a global one would validate
// the input. Off by default: where a global pipe exists (detected or
// assumed), the rule reports only shapes no ValidationPipe can validate —
// missing annotation, any, unknown, object, inline type literals
// (default: false)
requireExplicitPipe?: boolean;
}What is reported
An input bound with @Body(), @Query() or @Param() on a controller route,
with no pipe on the parameter, no @UsePipes(ValidationPipe) on the handler or
controller, and:
- no global pipe (none detected, none assumed): reported whatever its type —
a typed DTO is as unvalidated as
anywhen nothing runs the validation. This is the ❌ example above. - a global pipe: reported only when its shape carries no runtime class for
the pipe to validate against (no annotation,
any,unknown,object, an inline type literal) — or always, underrequireExplicitPipe.
A scalar (string, number, boolean, literal types) is coerced by the
framework and never reported. A Parse*Pipe in @UsePipes is not a
ValidationPipe. ValidationPipe is recognised however @nestjs/common was
imported (import { ValidationPipe as VP }, a namespace import).
Recommended ValidationPipe Options
new ValidationPipe({
whitelist: true, // Strip non-decorated properties
forbidNonWhitelisted: true, // Throw on extra properties
transform: true, // Auto-transform to DTO types
});When Not To Use It
- If you have
app.useGlobalPipes(new ValidationPipe())somewhere the cross-file scan cannot see, setassumeGlobalPipes: true— inputs no pipe can validate are still reported.
Cross-File Detection
Registered app-wide
The rule scans the project's module and bootstrap files and stays silent when it finds an app-wide registration, so this is not a false positive:
// main.ts
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));Turn the scan off with detectGlobalPipes: false if you want the routes reported anyway.
What the scan still cannot resolve is a registration built at runtime or
supplied by a library — assumeGlobalPipes: true covers those.
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Conditional Pipe Application
Why: Pipes applied conditionally are not tracked.
// ❌ NOT DETECTED - Conditional validation
if (process.env.NODE_ENV === 'production') {
app.useGlobalPipes(new ValidationPipe());
}Mitigation: Always apply validation unconditionally.
Custom Validation Decorators
Why: Custom decorators wrapping validation are not recognized.
// ❌ NOT DETECTED - Custom decorator includes validation
@CustomValidated() // Internally uses ValidationPipe
class MyController {}Mitigation: Document custom decorators. Use standard @UsePipes.
Use this rule
npm install -D oxlint-plugin-nestjs-security{ "jsPlugins": ["oxlint-plugin-nestjs-security"], "rules": { "nestjs-security/no-missing-validation-pipe": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-nestjs-security'], rules: { 'nestjs-security/no-missing-validation-pipe': 'error', }, },});oxlint-plugin-nestjs-security re-exports eslint-plugin-nestjs-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.