HIGH crlf injectionnestjsmutual tls

Crlf Injection in Nestjs with Mutual Tls

Crlf Injection in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

Crlf Injection occurs when an attacker can inject carriage return (CR, \r) and line feed (\n) characters into a header or status line, causing the server to prematurely terminate a line and inject additional headers or split responses. In NestJS, this often arises when user-controlled input is concatenated into HTTP response headers without proper sanitization, for example when reflecting a username or a redirect URL directly into res.set() or res.header() calls.

When Mutual TLS (mTLS) is enforced, the server validates the client certificate before application code runs. This validation can create a false sense of security: operators assume that only trusted clients can interact with the API, so they relax input validation. However, mTLS does not sanitize or validate the content of requests; it only confirms identity. Therefore, an authenticated mTLS client—such as a compromised microservice or a malicious insider with a valid certificate—can still supply malicious header values. Because NestJS applications frequently build dynamic headers (e.g., X-User-ID, Location, Set-Cookie) from request data, the presence of mTLS can lead developers to skip normalization and encoding, inadvertently enabling header splitting via CRLF sequences like %0D%0A or raw \r\n.

The combination of mTLS and permissive header handling also interacts with logging and observability. Access logs and traces may include the reflected header values, allowing injected CRLF sequences to corrupt log formatting and potentially facilitate log injection attacks. In distributed setups behind an mTLS-terminating proxy, if the proxy passes through headers unchecked and the NestJS app trusts the proxied values, an attacker with a valid client certificate can inject newline characters that split the response stream, enabling response smuggling or cache poisoning.

For example, consider a NestJS endpoint that echoes a user-supplied label into a custom response header:

const label = req.query.label || 'default';
res.set('X-Custom-Label', label);
res.json({ ok: true });

If the query parameter label contains example\r\nSet-Cookie: session=attacker, the response becomes:

HTTP/1.1 200 OK
X-Custom-Label: example
Set-Cookie: session=attacker
Content-Type: application/json; charset=utf-8

{"ok":true}

Even with mTLS ensuring only authorized clients connect, this injection can manipulate session handling or hide malicious headers. The OWASP API Top 10 categorizes this as an injection issue, and mappings to PCI-DSS and SOC2 highlight the risk to data integrity and monitoring integrity.

Mutual Tls-Specific Remediation in Nestjs — concrete code fixes

Remediation focuses on strict input validation, output encoding, and architectural safeguards, irrespective of mTLS. Treat authenticated mTLS clients as untrusted for data content, and apply the same header-safety practices as for any public API.

  • Validate and sanitize header inputs: Never reflect raw user input into response headers. Use allowlists and strict patterns. For strings that must appear in headers, remove or encode CR and LF characters.
import { filter, replace } from 'lodash';

function sanitizeHeaderValue(value: string): string {
  // Remove CR and LF to prevent header splitting
  return replace(value, /[\r\n]+/g, '');
}

const rawLabel = req.query.label || 'default';
const safeLabel = sanitizeHeaderValue(rawLabel);
res.set('X-Custom-Label', safeLabel);
res.json({ ok: true });
  • Use NestJS interceptors or middleware for centralized header handling: This ensures consistent validation across all routes and prevents accidental omissions in new endpoints.
@Injectable()
export class HeaderSanitizationInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const ctx = context.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();

    // Example: sanitize a known header reflected from query params
    if (request.query?.label) {
      const safeLabel = request.query.label.replace(/[\r\n]/g, '');
      response.set('X-Custom-Label', safeLabel);
    }
    return next.handle();
  }
}

@Controller()
export class AppController {
  constructor() {}

  @UseInterceptors(HeaderSanitizationInterceptor)
  @Get('echo')
  echo() {
    return { ok: true };
  }
}
  • Explicitly set headers instead of relying on framework defaults: Avoid methods that implicitly append headers with user-controlled values. Prefer res.set() with sanitized values over chaining untrusted input directly.
  • Enforce mTLS at the proxy or gateway and keep application-layer validation independent: Do not rely on the TLS layer to validate header content. Use the NestJS security middleware to enforce Content Security Policy and ensure response headers are hardened against splitting.
// Example of strict header policy in main.ts
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', "default-src 'self'");
  next();
});
  • Leverage OpenAPI/Swagger schema validation to reject unsafe inputs: Define string patterns that exclude control characters. With middleBrick, you can scan your OpenAPI spec and runtime behavior to detect endpoints that reflect headers without validation, ensuring alignment with OWASP API Top 10 and compliance mappings such as PCI-DSS and SOC2.

These measures ensure that even in an mTLS-protected environment, your NestJS application remains resilient against CRLF Injection, preserving log integrity and preventing response smuggling or cache poisoning.

Frequently Asked Questions

Does mutual TLS alone prevent header injection attacks?
No. Mutual TLS authenticates clients but does not validate or sanitize header values. Without input validation and output encoding, authenticated clients can still inject CRLF sequences via reflected headers.
How can middleBrick help detect CRLF Injection risks in a NestJS API secured with mTLS?
middleBrick scans your OpenAPI/Swagger spec and runtime behavior to detect endpoints that reflect user input into headers without validation. It checks the unauthenticated attack surface and maps findings to OWASP API Top 10 and compliance frameworks, providing remediation guidance regardless of transport-layer protections like mTLS.