escapeshellarg()
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep, psalm, and phpstan — all specialist SAST/static-analysis tools that can detect variable interpolation in shell calls without escapeshellarg. This is not caught by the compiler or default linter, but these tools do identify the pattern.
Closest to 'simple parameterised fix' (e3). The quick_fix describes wrapping each argument with escapeshellarg() — a small, localised change per shell call. However, common_mistakes note that every user-controlled argument must be addressed and the command name itself may also need whitelisting, making it slightly more than a single-line swap but still contained within one component.
Closest to 'localised tax' (b3). The applies_to contexts are web and cli, but the concern is localised to the specific code paths that invoke shell commands. It doesn't impose a codebase-wide structural tax — only the portions of code calling shell functions are affected.
Closest to 'serious trap' (t7). The misconception field states directly that developers believe escapeshellarg() makes any shell command safe, but it only protects arguments — user input in the command name itself remains dangerous. The common_mistakes also note confusion with escapeshellcmd(). This contradicts intuitive expectations and mirrors how similar escaping functions work in other contexts, earning a t7.
Also Known As
TL;DR
Explanation
escapeshellarg($arg) wraps the value in single quotes and escapes any single quotes within, producing a shell-safe single argument. This prevents the argument from being interpreted as multiple arguments or from injecting shell metacharacters (;, |, &, $, etc.). It must be used on every individual argument passed to exec(), system(), shell_exec(), or passthru(). Note: even with escaping, allowing user input in shell commands is high-risk — whitelist known-good values where possible.
Common Misconception
Why It Matters
Common Mistakes
- Using escapeshellarg() on the entire command string instead of individual arguments — the function is for arguments, not commands.
- Not applying escapeshellarg() to every user-controlled value in a shell call, missing one.
- Using escapeshellcmd() instead — it escapes shell metacharacters but still allows argument injection.
- Not considering that even escapeshellarg() cannot make a user-controlled command name safe — validate the command from a whitelist.
Code Examples
// Missing escapeshellarg — command injection:
$filename = $_GET['file'];
exec("convert $filename output.png"); // ?file=x;cat /etc/passwd
// Safe:
exec('convert ' . escapeshellarg($filename) . ' output.png');
// Wraps value in single quotes and escapes internal quotes
\$file = escapeshellarg(\$_POST['filename']);
\$output = shell_exec('file ' . \$file); // safe argument
// escapeshellcmd() escapes the command itself (less common)
\$cmd = escapeshellcmd('convert');
exec(\$cmd . ' ' . escapeshellarg(\$src) . ' ' . escapeshellarg(\$dst));
// Best: proc_open with array — no shell involved at all
\$process = proc_open(
['convert', \$src, \$dst], // array = no shell expansion
[0 => ['pipe','r'], 1 => ['pipe','w'], 2 => ['pipe','w']],
\$pipes
);