Dangerous subprocess Usage

HIGH

subprocess calls detected. Ensure command arguments are not user-controlled to prevent OS command injection.

Rule Information

Language
Python
Category
Python Core
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonsubprocesscommand-injectionprocess-executionCWE-78OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-LANG-SEC-020 --project .
1
2
3
4
5
6
7
8
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

About This Rule

Understanding the vulnerability and how it is detected

Python's subprocess module provides the recommended API for spawning child processes. The module is significantly safer than os.system() when used correctly — passing a list of arguments with shell=False (the default) bypasses the shell entirely and passes arguments directly to the OS exec() syscall.

However, subprocess becomes dangerous when: (1) shell=True is used with a string command containing user input; (2) the first argument in a list is attacker-controlled, pointing to an arbitrary executable; or (3) arguments in the list are derived from untrusted input without proper validation, such as injecting paths that traverse to sensitive files.

This rule audits all subprocess calls to ensure the command and arguments are safe. subprocess.call(), subprocess.check_call(), subprocess.check_output(), subprocess.run(), and subprocess.Popen() all require the same care.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Command Injection via shell=True

When shell=True is combined with a string that includes user input, shell metacharacters in the input can inject additional commands. This is equivalent to calling os.system() and is the most common subprocess-related vulnerability.

2

Arbitrary Executable via First Argument

If the first element of the command list (the executable path) is attacker-controlled, any program on the system can be run. This can be exploited via path traversal or by pointing to attacker-placed binaries.

3

Argument Injection

Even with shell=False, attacker-controlled values in the argument list can inject unintended flags. For example, if a filename from user input starts with "-", it may be interpreted as a command-line option by the target program (e.g., rsync, git, ffmpeg).

4

Sensitive Output Exposure

subprocess commands may output sensitive information. If stdout is not captured, the output appears in server logs or the terminal, potentially exposing credentials, system configuration, or private data.

How to Fix

Recommended remediation steps

  • 1Always use subprocess with a list of arguments and shell=False to prevent shell metacharacter interpretation.
  • 2Validate all user-controlled values that become subprocess arguments against a strict allowlist or regex before use.
  • 3Use absolute paths for executables to prevent PATH hijacking attacks.
  • 4Set a timeout on all subprocess calls to prevent resource exhaustion from hung child processes.
  • 5Capture stdout and stderr explicitly to prevent information leakage and ensure proper error handling.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule detects calls to subprocess.call(), subprocess.check_call(), subprocess.check_output(), subprocess.run(), and subprocess.Popen() from the Python subprocess module. All call sites are flagged for review to ensure shell=False is used and that command arguments are validated.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-78 ranked #5 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
NIST SP 800-53
SI-10: Information Input Validation
PCI DSS v4.0
Requirement 6.2.4 - Protect web-facing applications against injection attacks

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Dangerous subprocess Usage

subprocess.run() with a list and shell=False is safe from shell injection since no shell interprets the arguments. However, it is not safe from argument injection (flags injected via filenames starting with "-") or from running attacker-controlled executables. Always validate argument values and use absolute executable paths.
subprocess with shell=True is acceptable only when the entire command string is a hardcoded literal with no user-controlled components. If any part of the command comes from external input, even after validation, prefer shell=False with a list of arguments to eliminate the shell injection risk entirely.
Argument injection occurs when a value in the subprocess argument list starts with "-" and is interpreted as a flag by the target program. For example, a filename of "--checkpoint-action=exec=evil.sh" passed to tar would inject a malicious option. Prevent this by validating that arguments do not start with "-" or by using "--" to signal the end of options before file arguments (e.g., ["tar", "-czf", "archive.tar", "--", filename]).
shlex.quote() provides some protection for POSIX shells but is not a complete solution. It handles most metacharacters but may miss edge cases in specific shells or contexts. The robust solution is to use shell=False with a list of arguments and avoid shell=True entirely when user input is involved.
Use check=True to raise CalledProcessError on non-zero exit codes, and always set a timeout parameter. Catch subprocess.TimeoutExpired to terminate the child process and handle the error gracefully. Use capture_output=True to prevent output from appearing in server logs. Log the exit code and error output for security monitoring.
subprocess.run() is a high-level wrapper that blocks until the subprocess completes and returns a CompletedProcess object. subprocess.Popen() is lower-level and provides more control for streaming I/O, bidirectional communication, and async process management. For most use cases, subprocess.run() with appropriate parameters is sufficient and safer.

New feature

Get these findings posted directly on your GitHub pull requests

The Dangerous subprocess Usage rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works