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-011 --project .About This Rule
Understanding the vulnerability and how it is detected
Python's os.execl(), os.execle(), os.execlp(), os.execlpe(), os.execv(), os.execve(), os.execvp(), and os.execvpe() functions replace the currently running Python process with a new program. Unlike os.system() or subprocess, there is no return from these calls; the Python runtime is completely replaced by the new process.
When the executable path or any argument passed to os.exec*() is derived from untrusted input, an attacker can cause the process to execute arbitrary programs with the current process's privileges, environment, file descriptors, and any capabilities it holds.
The os.exec*() variants that take a list of arguments (execv, execve, execvp, execvpe) do not invoke a shell and are safer than os.system() when arguments are properly separated. However, if the executable path itself is attacker-controlled, any program on the system can be run.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Arbitrary Program Execution
An attacker who controls the executable path can run any program accessible to the process, including /bin/sh, python3, nc (netcat), or any other tool available on the system, with the current process's effective UID, capabilities, and environment.
Process Replacement Without Return
os.exec*() does not return; the Python runtime is completely replaced. Any cleanup code, exception handlers, or security checks that would run after the call are bypassed. The replaced process inherits open file descriptors and environment variables containing secrets.
Environment Variable Injection
The execve and execvpe variants accept an explicit environment dictionary. If this dictionary is constructed from user input, an attacker can set LD_PRELOAD, PATH, or other sensitive environment variables to influence how the new process loads shared libraries and resolves commands.
Privilege Maintenance
Unlike spawning a subprocess, os.exec*() maintains the same PID, credentials, and resource limits. On systems where the Python process has elevated privileges (setuid, capabilities), the replacement program inherits those privileges.
How to Fix
Recommended remediation steps
- 1Replace os.exec*() with subprocess.run() using a list of arguments to spawn a child process that can be monitored and does not replace the Python runtime.
- 2If process replacement is genuinely required, use hardcoded absolute paths for the executable and validate all argument values against strict allowlists.
- 3Never derive the executable path from user input; always use a hardcoded absolute path or a mapping from an allowlisted name to its absolute path.
- 4Use subprocess with a timeout, stdout/stderr capture, and check=True to handle errors gracefully rather than silently replacing the process.
- 5Ensure the current process runs with minimum required privileges to limit what an attacker can do by injecting an executable path.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule detects calls to os.execl(), os.execle(), os.execlp(), os.execlpe(), os.execv(), os.execve(), os.execvp(), and os.execvpe() from the Python os module. All variants are flagged since they all ultimately execute an external program and can be exploited when arguments are attacker-controlled.
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 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.
Dangerous os.spawn*() Call
os.spawn*() spawns a new process and can execute arbitrary programs when the executable path or arguments are derived from untrusted input.
Dangerous subprocess Usage
subprocess calls detected. Ensure command arguments are not user-controlled to prevent OS command injection.
Frequently Asked Questions
Common questions about Dangerous os.exec*() Call
New feature
Get these findings posted directly on your GitHub pull requests
The Dangerous os.exec*() Call rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.