| name | shell-injection-prevention |
| description | Use execFileSync with args array instead of execSync with string concatenation to prevent shell injection |
| lastReviewed | 2026-04-30T00:00:00.000Z |
Shell Injection Prevention
Category: Security
Time Saved: 1+ hour debugging, prevents incidents
Battle-tested: Yes — multiple projects
The Problem
Your Node.js script needs to run external commands (git, npm, az cli). You use execSync("git status") and it works. Then someone passes a filename with a semicolon and your server runs arbitrary commands.
Why It Happens
execSync(command) passes the string to a shell (/bin/sh or cmd.exe), which interprets metacharacters like ;, |, $(), and backticks as command separators or substitutions.
The Rule
Use execFileSync(executable, argsArray) instead of execSync(string)
const output = execSync(`git log --oneline ${filename}`);
const output = execFileSync('git', ['log', '--oneline', filename]);
When Each Is Appropriate
| Use Case | Method | Why |
|---|
| Known executable + args | execFileSync | No shell, no injection |
| Need shell features (pipes, globs) | execSync with allowlist | Validate all inputs |
| User-provided command | Never | Don't execute user commands |
Implementation Patterns
Basic Safe Pattern
const { execFileSync } = require('child_process');
function gitStatus(repoPath) {
return execFileSync('git', ['status', '--porcelain'], {
cwd: repoPath,
encoding: 'utf8',
});
}
When You Need Shell Features
const ALLOWED_BRANCHES = ['main', 'develop', 'staging'];
function gitCheckout(branch) {
if (!ALLOWED_BRANCHES.includes(branch)) {
throw new Error(`Invalid branch: ${branch}`);
}
return execSync(`git checkout ${branch}`, { encoding: 'utf8' });
}
Async Equivalent
const { execFile } = require('child_process');
const { promisify } = require('util');
const execFileAsync = promisify(execFile);
async function runGit(args) {
const { stdout } = await execFileAsync('git', args);
return stdout;
}
Dangerous Metacharacters
| Shell | Dangerous Characters |
|---|
| Bash | `; |
| cmd.exe | `& |
| PowerShell | `; |
Common Vulnerable Patterns
execSync(`cat ${userProvidedFilename}`);
execSync(`git clone ${userUrl}`);
execSync(`npm install ${packageName}`);
Verification Checklist
Bonus: Slightly Faster
execFileSync is marginally faster because it doesn't spawn a shell process. For scripts running many commands, this adds up.
Related Skills
allowlist-over-blocklist — Input validation
path-traversal-prevention — File path safety