Crlf Injection in Nestjs with Api Keys
Crlf Injection in Nestjs with Api Keys — 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 sent to the client. In NestJS, this typically arises when dynamic values—often sourced from request headers, query parameters, or user-controlled payloads—are used to construct custom HTTP headers or status codes without sanitization. When an API key is used for identification or rate-limiting, developers sometimes embed the key or key-derived values directly into headers or logs, inadvertently providing a pathway for injection if those values are attacker-influenced.
Consider an endpoint that reads an apiKey from a header and reflects it in a custom header in the response. If the apiKey value contains \r\n sequences and the server concatenates it into a header line, an attacker can inject additional headers or split the response, leading to HTTP response splitting, cache poisoning, or cross-site scripting in downstream intermediaries. For example:
const raw = request.headers['apikey'] || '';
res.set('X-API-Key-Echo', raw);If the apiKey is attacker-controlled and contains \r\nSet-Cookie: injected=1, the injected line can create a new header, bypassing intended defenses. NestJS does not inherently sanitize custom header values derived from user input, so the developer must validate and encode any data that will be placed into headers or status text. The risk is compounded in logging or error handling where apiKey values are written into status lines or headers that later get forwarded to logs or monitoring systems, potentially enabling log injection or header smuggling.
Another scenario involves using the apiKey to select a downstream service or tenant, where the key is reflected in a Location header during a redirect. If the key or a derived value contains \r\n, an attacker can inject a new Location, causing open redirects or proxy confusion. Because API keys are often treated as opaque identifiers, developers may assume they are safe, but their use in header construction without validation introduces a Crlf Injection surface that can compromise integrity and isolation.
Api Keys-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on strict validation, canonicalization, and safe construction of headers and status codes. Never directly concatenate user-supplied values into headers or status lines. Instead, enforce a strict character set for apiKey values and use framework utilities to set headers safely. Below are concrete examples demonstrating secure handling in NestJS.
1. Validate and sanitize apiKey values
Treat apiKey as an opaque token and reject any value containing control characters or whitespace. Use a strict allowlist (e.g., alphanumeric plus a limited set of safe symbols) and enforce length constraints.
import { Injectable, BadRequestException } from '@nestjs/common';
export function validateApiKey(apiKey: string): string {
if (!apiKey) {
throw new BadRequestException('Missing apiKey');
}
// Allow only safe characters and enforce length
if (!/^[A-Za-z0-9\-._~+]+$/.test(apiKey)) {
throw new BadRequestException('Invalid apiKey format');
}
if (apiKey.length < 16 || apiKey.length > 128) {
throw new BadRequestException('Invalid apiKey length');
}
return apiKey;
}
@Injectable()
export class ApiKeyService {
verify(key: string): boolean {
const safeKey = validateApiKey(key);
// proceed with lookup
return true;
}
}2. Use framework facilities to set headers
Avoid manually constructing header lines. Use NestJS’s built-in methods to set headers, which do not interpret \r\n as line breaks. For custom headers derived from validated keys, assign the value directly without concatenation.
import { Controller, Get, Header, Req } from '@nestjs/common';
@Controller('resource')
export class ResourceController {
@Get()
@Header('X-API-Key-Echo', (req) => {
const raw = req.headers['apikey'] || '';
return validateApiKey(raw); // validated safe value
})
getResource() {
return { data: 'ok' };
}
}
// Alternatively, set headers after validation in the handler:
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
@Injectable()
export class SafeResourceService {
getWithHeader(apiKey: string) {
const safeKey = validateApiKey(apiKey);
// Return data and let the layer set headers safely
return { safeKey };
}
}
// In a route handler:
import { Res } from '@nestjs/common';
import { Response } from 'express';
@Get('profile')
profile(@Res() res: Response, @Req() req) {
const safeKey = validateApiKey(req.headers['apikey']);
res.set('X-API-Key-Echo', safeKey);
res.json({ profile: 'ok' });
}3. Avoid using apiKey-derived values in redirects and status text
When using the apiKey in redirects, canonicalize and encode the target URL. Do not embed raw key values into Location headers. For status codes, use numeric constants and never interpolate user input into the reason phrase.
import { HttpException, HttpStatus } from '@nestjs/common';
@Injectable()
export class RedirectService {
redirectWithSafeKey(apiKey: string, target: string) {
const safeKey = validateApiKey(apiKey);
// Use a canonical redirect URL; do not append raw key as a header
const url = new URL(target, 'https://api.example.com');
url.searchParams.set('key_id', safeKey.substring(0, 8)); // safe subset
throw new HttpException(url.toString(), HttpStatus.FOUND);
}
}By combining strict input validation, safe header-setting practices, and avoidance of user-controlled data in status lines, you mitigate Crlf Injection risks while still leveraging apiKey-based identification securely.