Command Injection in Nestjs with Api Keys
Command Injection in Nestjs with Api Keys — how this specific combination creates or exposes the vulnerability
Command injection occurs when an attacker can inject and execute arbitrary system commands through an application. In NestJS applications that accept API keys and use those keys to influence system-level operations, the combination can expose dangerous injection paths if input is not strictly validated and sanitized.
API keys are often passed as HTTP headers (e.g., x-api-key) and may be used to select configurations, backends, or commands. If a developer uses the key unsafely—such as interpolating it into a shell command or passing it to a subprocess without validation—an attacker who can influence the header may achieve command injection. For example, suppose a NestJS service receives an API key and uses it to dynamically select a script or tool to run on the server. If the key is concatenated into a command string, characters like semicolons, ampersands, or backticks can allow an attacker to append additional commands.
Consider a scenario where an endpoint uses the API key to determine which external utility to invoke. If the code builds a command string such as tool --id {apiKey} and passes it to a shell executor without sanitization, an API key like abc; rm -rf / could lead to unintended destructive behavior. Even when the key is expected to be alphanumeric, attackers may try encoded or obfuscated payloads to bypass naive checks. Because API keys are often treated as trusted identifiers, developers may skip input validation, inadvertently creating a command injection vector.
Another angle involves environment-specific behaviors. If the API key influences runtime options (e.g., selecting a region or endpoint), and those options are used to construct shell commands, the risk increases. Attackers may probe for debug modes or verbose output flags that reveal system details. Because the vulnerability arises from how the application uses the API key rather than the key itself, the risk persists even if keys are rotated or rate-limited.
NestJS does not inherently protect against command injection; it is a framework for building Node.js applications. Developers must ensure that any user-influenced data—including API key values—is treated as untrusted. Using built-in validation pipes helps enforce format constraints, but additional safeguards are required when invoking system-level operations. Security checks should reject or escape characters commonly used in command injection, such as &, |, ;, `, and $().
Finally, because this is a black-box scan scenario, middleBrick tests the unauthenticated attack surface and can detect command injection indicators by analyzing input validation and output handling. Findings include severity levels and remediation guidance, helping teams identify risky patterns where API keys interact with system commands.
Api Keys-Specific Remediation in Nestjs — concrete code fixes
Remediation focuses on strict validation, avoiding shell invocation, and using safe execution patterns. Never directly interpolate API key values into shell commands. Instead, use structured, language-native operations and strict schema validation.
First, enforce a strict format for API keys using NestJS validation pipes. For example, allow only alphanumeric characters and reject anything else:
import { ApiKeyValidationPipe } from './api-key-validation.pipe';
// In your controller
@Get('run')
runCommand(@Header('x-api-key', new ApiKeyValidationPipe()) apiKey: string) {
// Use the key safely without shell interpolation
return this.service.executeByKey(apiKey);
}
Create a custom pipe that validates the key format:
@Injectable()
export class ApiKeyValidationPipe implements PipeTransform {
transform(value: string): string {
if (!/^[a-zA-Z0-9_-]{16,64}$/.test(value)) {
throw new BadRequestException('Invalid API key format');
}
return value;
}
}
Second, avoid spawning shells entirely. Use Node.js child process methods that do not invoke a shell by default, or pass arguments as arrays. For instance, prefer execFile over exec:
import { execFile } from 'child_process';
import { promisify } from 'util';
const execFileAsync = promisify(execFile);
export class SafeExecutor {
async executeByKey(apiKey: string): Promise {
// Map the key to a safe internal command; do not embed the key in the command line.
const command = this.mapKeyToCommand(apiKey);
const { stdout } = await execFileAsync(command, [], { timeout: 5000 });
return stdout;
}
private mapKeyToCommand(apiKey: string): string {
// Use a whitelist mapping instead of dynamic construction.
const allowed = {
keyA: '/usr/bin/utility_a',
keyB: '/usr/bin/utility_b',
};
const normalized = apiKey.replace(/[^a-zA-Z0-9_-]/g, '').substring(0, 10);
return allowed[normalized as keyof typeof allowed] || '/usr/bin/default';
}
}
Third, if you must construct dynamic commands, use parameterized approaches and escape all user input. However, this is less safe than the mapping approach:
import { escape } from 'querystring'; // Not for shell; use library-specific escaping if available.
// Example of safer dynamic construction (still prefer whitelisting).
const baseCmd = '/usr/bin/utility';
// Escape is illustrative; prefer execFile and avoid shell.
const safeKey = apiKey.replace(/[^a-zA-Z0-9_-]/g, '');
const finalCmd = `${baseCmd} --id ${safeKey}`;
// Only if shell is unavoidable:
const { execSync } = require('child_process');
execSync(`${baseCmd} --id ${safeKey}`, { stdio: 'pipe' });
Finally, integrate these patterns into your NestJS application structure. Use guards and interceptors to ensure validation runs early, and log suspicious input for monitoring. middleBrick can be added to your CI/CD pipeline using the GitHub Action to automatically check for insecure patterns in your codebase and fail builds if risky constructs are detected.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |