Shell injection through user-supplied file names is one of the most common security bugs in scripts.
With shell=True, you pass a single string and Python runs it via /bin/sh -c (or cmd.exe on Windows). That gives you shell features: pipes |, redirection >, globbing *.txt, &&, environment variable expansion.
It also means the shell interprets every character in the string. If any part comes from a user, a file name or a web request, an attacker can inject commands:
name = "x; rm -rf ~"
subprocess.run(f"cat {name}", shell=True) # runs rm!Prefer the list form without a shell. Usually you can replace the shell feature in Python: glob or pathlib for wildcards, stdout=open(...) for redirection, env= for variables, chaining two run calls for &&.
If you truly need the shell, quote every untrusted piece with shlex.quote(value), which makes it a single literal word for POSIX shells.
shlex.split("cmd 'two words' x") turns a command-line string into a list the way a POSIX shell would, useful for config files that store commands as strings. shlex.join(list) does the reverse for logging.
shlex follows POSIX rules; Windows quoting is different.
Example
import shlex, subprocess
evil = "notes.txt; echo INJECTED"
subprocess.run(f"echo cat {evil}", shell=True)
# cat notes.txt
# INJECTED <- second command ran
subprocess.run(f"echo cat {shlex.quote(evil)}", shell=True)
# cat notes.txt; echo INJECTED <- one literal argument
subprocess.run(["echo", "cat", evil]) # list form: no shell, always safe
# cat notes.txt; echo INJECTED
args = shlex.split("grep -n 'two words' file.txt")
print(args) # ['grep', '-n', 'two words', 'file.txt']
print(shlex.join(args)) # grep -n 'two words' file.txtRemember
- shell=True runs a string through /bin/sh with all its parsing
- Untrusted text in a shell string allows command injection
- Prefer list args; if a shell is unavoidable, shlex.quote every value
Watch out for
- Building shell strings with f-strings from user input.