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-022 --project .About This Rule
Understanding the vulnerability and how it is detected
Python's asyncio.create_subprocess_shell() is the async equivalent of subprocess.run() with shell=True. It passes the command string to the system shell (/bin/sh on Unix) for interpretation, which means all shell metacharacters are processed before the command executes.
When any part of the command string is derived from untrusted input such as HTTP request parameters or user-provided data, an attacker can inject shell metacharacters to execute additional commands or access the shell's full feature set.
The safe replacement is asyncio.create_subprocess_exec() which accepts a list of arguments and passes them directly to the OS exec() syscall without shell interpretation, exactly like subprocess.run() with shell=False.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Async Shell Command Injection
The same shell injection risks present in subprocess with shell=True apply to asyncio.create_subprocess_shell(). Semicolons, pipes, backticks, and dollar signs in user input allow injection of additional commands executed with the process's privileges.
Concurrent Injection Amplification
In async web servers handling concurrent requests, a shell injection vulnerability in an async subprocess call can be exploited by multiple concurrent attackers simultaneously, amplifying the impact compared to synchronous code.
Event Loop Blocking from Injected Commands
Injected long-running or CPU-intensive commands can block the asyncio event loop, causing denial-of-service for all concurrent requests handled by the same event loop.
Uncaptured Output in Async Context
In async code, subprocess output handling is more complex and errors in output capture can lead to process hangs or output appearing in unexpected contexts, potentially exposing sensitive information.
How to Fix
Recommended remediation steps
- 1Replace asyncio.create_subprocess_shell() with asyncio.create_subprocess_exec() and pass the command as separate arguments to avoid shell interpretation.
- 2Validate all user-controlled values that become process arguments against a strict allowlist or regex before use.
- 3Set a timeout using asyncio.wait_for() to prevent hung child processes from blocking the event loop.
- 4Capture stdout and stderr explicitly to prevent sensitive information from appearing in server logs.
- 5Use absolute executable paths to prevent PATH hijacking attacks in the async context.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule detects calls to asyncio.create_subprocess_shell() and the equivalent asyncio.subprocess.create_subprocess_shell() form. All call sites are flagged since this function always invokes the system shell and is inherently riskier than asyncio.create_subprocess_exec().
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Dangerous subprocess Usage
subprocess calls detected. Ensure command arguments are not user-controlled to prevent OS command injection.
subprocess Called with shell=True
subprocess called with shell=True passes the command through the system shell, enabling command injection when any part of the command contains untrusted input.
Dangerous os.system() or os.popen() Call
os.system() and os.popen() execute shell commands via /bin/sh, enabling command injection when arguments contain untrusted input.
Frequently Asked Questions
Common questions about Dangerous asyncio Shell Execution
New feature
Get these findings posted directly on your GitHub pull requests
The Dangerous asyncio Shell Execution rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.