Skip to main content
interlace
Plugin: nestjs-securityRules

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

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-20 OWASP:A06 CVSS:7.5
Issue DescriptionSpecific vulnerabilityImproper Input Validation detected
Severity & ComplianceImpact assessmentHIGH [SOC2,PCI-DSS,HIPAA,GDPR,ISO27001]
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP 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

OptionTypeDefaultDescription
allowInTestsbooleantrueSkip this rule in *.test.* / *.spec.* files
detectGlobalPipesbooleantrueLook for a globally registered ValidationPipe before reporting
assumeGlobalPipesbooleanfalseAssume a global ValidationPipe exists even if none is found
requireExplicitPipebooleanfalseRequire 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 any when 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, under requireExplicitPipe.

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).

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, set assumeGlobalPipes: 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
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-nestjs-security"],  "rules": {    "nestjs-security/no-missing-validation-pipe": "error"  }}
vite.config.ts
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.