A shell command built from user input
When server code pastes request text into a shell command, a visitor who types the right characters runs their own command on your machine. Agents reach for exec with a template string because it works on the first try.
What can go wrong
Command injection means an attacker extends or replaces the command your server runs by putting shell metacharacters into a form field, query parameter, or file name.
Common shapes:
- A template-string
execcall with a request value likereq.query.hostinside the shell string subprocess.run(cmd, shell=True)with the command assembled from user input- A converter, thumbnailer, or "run this tool on the upload" route that passes the file name straight to the shell
One ;, |, or $(...) in the input and the visitor's text stops being an argument and becomes a second command, running with your server's permissions and your server's credentials.
It happened for real
GitLab passed user-uploaded images to its bundled ExifTool, which mishandled DjVu metadata: unauthenticated command injection, CVSS 10.0 (Rapid7 on CVE-2021-22205). Months after the patch, tens of thousands of servers were still unpatched, and thousands of compromised instances were herded into a botnet (Help Net Security, Nov 2021).
The class is current: Microsoft documented paths in AI agent frameworks where a single crafted input became a host-level shell command in May 2026 (Microsoft Security Blog).
How to check yours
Seatbelt flags this automatically. A request value reaching a shell call with no allow-list or escaping between is a must-fix hard gate. The scan catches it on one line and split across lines in the same file: the value bound in one place, the shell call below. Static read of the code path, not a live probe.
Ask your agent: "List every place server code runs a shell command (exec, execSync, spawn with shell, subprocess with shell=True, os.system, backticks). For each, trace the command string back. Flag any that include request input."
Manual check: Search server files for exec and subprocess calls built with template strings or concatenation. If a request field appears inside the command string, it is injectable.
Fix direction
Never build a shell string from user input. Pass arguments as an array with no shell (execFile, spawn without shell, subprocess.run with a list), or map the input onto a fixed allowlist of commands.
Paste into your agent: "Replace every shell command built from request input with execFile or spawn using an argument array and no shell. Where the command itself varies, map input to a fixed allowlist. Show before and after for each call site."