Skip to content

Commit cbaaaf7

Browse files
committed
update
1 parent b85330a commit cbaaaf7

5 files changed

Lines changed: 302 additions & 2 deletions

File tree

constructs/mktemp.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
The `tempfile.mktemp()` function in Python's standard library is **deprecated and insecure**. Its use introduces a critical **Time-of-Check to Time-of-Use (TOCTOU)** vulnerability that can be exploited by attackers to compromise your application.
44

55
:::{danger}
6-
**Never use `tempfile.mktemp()` in new code.** The function has been deprecated since Python 2.3 (2003) and removed from Python 3.12+ documentation as a recommended practice. Its continued use represents a significant security risk with no valid justification in modern applications.
6+
**Never use `tempfile.mktemp()` in new code.** The function has been deprecated and removed from Python 3.12+. But its continued use in older code bases represents a significant security risk with no valid justification in modern applications.
77
:::
88

99
## Security Concerns

constructs/subprocess.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Subprocess Statement
2+
3+
4+
Using Python's built-in `subprocess` module is extremely powerful for executing external commands and managing child processes. However, it also introduces significant security risks if misused.
5+
6+
:::{caution}
7+
When using the `subprocess` library, your Python code can invoke arbitrary external executables on the host system.
8+
:::
9+
10+
11+
Python's `subprocess` module provides no mechanism to attach to the standard I/O streams of an already-running external process.
12+
13+
## Security Concerns
14+
15+
Legacy subprocess functions carry a **high** severity rating. You should migrate away from the older APIs to the modern `subprocess.run()` (or `subprocess.check_*` variants where appropriate).
16+
17+
**Functions that should be avoided or reviewed carefully**:
18+
19+
* `subprocess.call()`
20+
* `subprocess.Popen()` (when used directly)
21+
* `subprocess.check_call()`
22+
* `subprocess.check_output()`
23+
* `subprocess.getoutput()`
24+
* `subprocess.getstatusoutput()`
25+
26+
## Rationale
27+
28+
The `subprocess` module allows Python code to execute external system commands. This capability is powerful but inherently dangerous, as it creates a direct bridge between application logic (often fed by untrusted input) and the operating system shell.
29+
30+
Common security vulnerabilities arise when:
31+
32+
- User-controlled input is interpolated into command strings
33+
- The shell is invoked (`shell=True`)
34+
- Arguments are passed as strings instead of lists
35+
- Output or exit codes are trusted without proper validation
36+
- Errors are silently ignored or mishandled
37+
38+
If an attacker can influence any part of the constructed command, they may achieve **command injection**, privilege escalation, data exfiltration, or remote code execution (RCE).
39+
40+
All listed APIs are safe **by default only when used correctly** — but they are very easy to misuse.
41+
42+
## Preventive measures
43+
44+
These guidelines apply to **all** `subprocess` APIs:
45+
46+
1. **Avoid `shell=True` whenever possible**
47+
This is the single biggest risk factor. Shell metacharacters (`;`, `&&`, `|`, `$()`, `` ` ``, etc.) become exploitable when the shell is involved.
48+
49+
2. **Always pass arguments as lists, never as strings**
50+
```python
51+
# Good
52+
subprocess.run(["ls", "-l", "/tmp"])
53+
54+
# Bad
55+
subprocess.run("ls -l /tmp", shell=True)
56+
```
57+
58+
3. **Never concatenate or interpolate user input into command strings**
59+
This applies especially to paths, filenames, flags, or filters.
60+
61+
4. **Validate and sanitise all inputs**
62+
Prefer allowlists over blocklists. Consider using libraries like `shlex.quote()` when string construction is unavoidable.
63+
64+
5. **Explicitly check return codes and handle errors**
65+
Silent failures can mask partial or malicious command execution.
66+
67+
6. **Run subprocesses with the least privileges possible**
68+
Never execute subprocesses as root (or other high-privilege users) unless absolutely necessary.
69+
70+
7. **Prefer high-level Python APIs**
71+
Many common shell operations have safe, pure-Python equivalents in `os`, `pathlib`, `shutil`, etc.
72+
73+
## Example
74+
75+
### Bad example (`subprocess.call`)
76+
77+
Highly dangerous example
78+
79+
```python
80+
import subprocess
81+
subprocess.call("rm -rf " + user_input, shell=True)
82+
```
83+
84+
**Attack scenario**:
85+
```python
86+
user_input = "/tmp/data; rm -rf /"
87+
```
88+
89+
This would delete the entire filesystem (if permissions allow).
90+
91+
### A bit better example
92+
93+
```python
94+
import subprocess
95+
from pathlib import Path
96+
97+
def safe_remove(path: str) -> None:
98+
"""Safely remove a file or directory after validation."""
99+
target = Path(path).resolve()
100+
101+
# Strict validation (example)
102+
if not target.is_relative_to("/safe/directory"):
103+
raise ValueError("Path outside allowed directory")
104+
105+
subprocess.run(["rm", "-rf", str(target)], check=True)
106+
```
107+
108+
109+
:::{warning}
110+
**Important Note**
111+
112+
Even this safer-looking code:
113+
114+
```python
115+
subprocess.run(["rm", "-rf", str(target)], check=True)
116+
```
117+
118+
can still introduce security weaknesses if not implemented correctly. This is why a good Python SAST tool, such as [Python Code Audit](https://github.com/nocomplexity/codeaudit), will flag the use of `subprocess.run` (and similar functions).
119+
120+
An expert security review is still essential, as no automated tool — including AI — can fully understand the specific context in which the code will run.
121+
:::
122+
123+
## Discussion
124+
125+
Executing shell commands or calling external applications from Python is a powerful capability, but it is **inherently dangerous**.
126+
127+
Wherever possible, you should use a native Python API instead of invoking shell commands. In the vast majority of cases, this is not only feasible but also significantly more secure.
128+
129+
`Popen` is the low-level foundation and used for some advanced cases, but use `run()` for most cases if you need this kind of functionality.
130+
131+
Using `Popen` itself is not directly a [vulnerability](vulnerability), but too often crucial checks are missing so within code `Popen` is often a weakness that should be reviewed in depth.
132+
133+
## More information
134+
135+
* [Official subprocess documentation](https://docs.python.org/3/library/subprocess.html)
136+
137+
138+

constructs/systemcalls.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# OS System Calls
2+
3+
Direct use of low-level operating system interfaces from the `os` module can be convenient, but it often bypasses Python’s safer, higher-level abstractions and introduces serious security risks.
4+
5+
:::{danger}
6+
Be suspicious of direct `os.*` system calls in Python code. **Never trust — always verify.**
7+
:::
8+
9+
The following Python `os` system calls should by default ring an alarm bell from a security point of view:
10+
11+
* `os.system()` — executes a command in a subshell (equivalent to `subprocess` with `shell=True`).
12+
* The `os.exec*` family (`os.execl`, `os.execle`, `os.execlp`, `os.execlpe`, `os.execv`, `os.execve`, `os.execvp`, `os.execvpe`) — these replace the current process with a new program and **do not return**.
13+
* `os.fork()` — creates a child process (Unix only); can lead to fork bombs or resource exhaustion if misused.
14+
* `os.write()` / `os.writev()` — low-level writes to raw file descriptors.
15+
* And several other similar low-level functions.
16+
17+
:::{tip}
18+
When shell-like functionality is required, prefer the modern `subprocess` module with proper safeguards (see the Subprocess section). Native Python APIs from `os`, `pathlib`, `shutil`, etc., are almost always safer and more portable.
19+
:::
20+
21+
## Security Concerns
22+
23+
The Python `os` functions above are powerful and easy to use, which makes them attractive — but also dangerous.
24+
25+
**Key risks include:**
26+
27+
- **Command injection** (especially with `os.system()` and similar shell-invoking calls)
28+
- **Process replacement** (the `exec*` family terminates the current Python interpreter)
29+
- **Fork bombs** and resource exhaustion via `os.fork()`
30+
- **File descriptor mismanagement** with `os.write()` / `os.writev()`, which can lead to data corruption, arbitrary file writes, information leakage, or denial of service
31+
- Privilege escalation if these calls run with elevated permissions
32+
33+
These APIs operate at a very low level and provide minimal safety checks compared to higher-level Python constructs.
34+
35+
## Preventive measures
36+
37+
1. **Avoid `os.system()` entirely** — use `subprocess.run()` (or `check_*` variants) with a list of arguments and `shell=False`.
38+
39+
2. **Prefer high-level Python APIs**
40+
Use `pathlib`, `shutil`, `os.makedirs()`, `open()`, etc., instead of shelling out for file and directory operations.
41+
42+
3. **Never pass untrusted input** to any `os` function that executes commands or writes to file descriptors.
43+
44+
4. **Validate all inputs rigorously** (paths, filenames, descriptors, etc.). Use allowlists and resolve paths with `pathlib.Path.resolve()`.
45+
46+
5. **Restrict privileges** — run code with the minimum necessary permissions. Avoid running as root.
47+
48+
6. **For `os.write()` / `os.writev()`**:
49+
- Only use known, validated file descriptors.
50+
- Prefer Python’s file objects (`open()` / `.write()`) which provide better safety and error handling.
51+
52+
7. **Handle `os.fork()` with extreme care** (if unavoidable) — implement proper resource limiting and error handling to prevent fork bombs.
53+
54+
8. **Consider sandboxing** or running untrusted code in isolated environments when executing system-level operations.
55+
56+
## Example
57+
58+
### Bad example (`os.system`)
59+
60+
**Dangerous — shell injection possible:**
61+
62+
```python
63+
import os
64+
65+
66+
os.system("rm -rf " + user_supplied_path)
67+
```
68+
69+
A simple **Attack scenario** can be:
70+
```python
71+
user_supplied_path = "/tmp/data; rm -rf /"
72+
```
73+
74+
### A bit better example
75+
76+
Example of strict allow list-style validation. Not perfect but a bit more secure.
77+
78+
```python
79+
from pathlib import Path
80+
import subprocess
81+
82+
def safe_remove(path: str) -> None:
83+
"""Safely remove a path after strict validation."""
84+
target = Path(path).resolve()
85+
86+
87+
allowed_root = Path("/safe/directory").resolve()
88+
if not target.is_relative_to(allowed_root):
89+
raise ValueError("Path outside allowed directory")
90+
91+
# Use modern subprocess with list arguments
92+
subprocess.run(["rm", "-rf", str(target)], check=True)
93+
```
94+
95+
Please never ever do a `rm` using a Python system call.You do not needed it, there are far better alternatives!
96+
97+
98+
### More secure example (pure Python, no `rm` or subprocess)
99+
100+
```python
101+
from pathlib import Path
102+
import shutil
103+
104+
def safe_remove(str, allowed_root= "/safe/directory"):
105+
"""Safely remove a file or directory after strict validation.
106+
107+
Uses pure Python standard library functions — no subprocess or shell calls.
108+
"""
109+
try:
110+
target = Path(path).resolve(strict=True) # strict=True raises if path doesn't exist
111+
root = Path(allowed_root).resolve(strict=True)
112+
113+
# Strict containment check
114+
if not target.is_relative_to(root):
115+
raise ValueError(f"Path '{target}' is outside the allowed directory '{root}'")
116+
117+
# Optional: additional explicit allowlist of permitted top-level directories
118+
# if target.parent != root: ... # further restrictions if needed
119+
120+
if target.is_file() or target.is_symlink():
121+
target.unlink(missing_ok=True)
122+
elif target.is_dir():
123+
shutil.rmtree(target, ignore_errors=False) # do not ignore errors #nosec - Can be ignored by SAST scan
124+
else:
125+
raise ValueError(f"Path '{target}' is neither a file nor a directory")
126+
127+
except Exception as e:
128+
raise RuntimeError(f"Failed to safely remove '{path}': {e}") from e
129+
```
130+
131+
This example is better and more secure:
132+
133+
- **No subprocess or shell** — completely avoids `rm`, `os.system`, etc.
134+
- Uses `pathlib.Path` + `shutil.rmtree` — the idiomatic, safe Python way.
135+
- `resolve(strict=True)` ensures the path actually exists and resolves symlinks safely.
136+
- Clear exception handling with context.
137+
- `missing_ok=True` on files prevents unnecessary errors.
138+
- `ignore_errors=False` on `rmtree` ensures failures are not silently ignored.
139+
- Easy to extend with more validation (e.g. file type checks, size limits, etc.).
140+
141+
:::{note} Note
142+
143+
**Never use shell commands or low-level system calls when a safe native Python API exists.**
144+
:::
145+
146+
147+
## Discussion
148+
149+
Low-level `os` calls are sometimes necessary for performance or very specific system interactions, but they should be treated as advanced and potentially **hazardous** features.
150+
151+
In most applications, you can achieve the same goals more securely and portably using Python’s standard library abstractions. When process management or command execution is truly needed, the `subprocess` module (used correctly) is the recommended approach.
152+
153+
Always assume that any direct system call may be misused and design your code with defence-in-depth principles.
154+
155+
## More information
156+
157+
* [Official `os` module documentation](https://docs.python.org/3/library/os.html)
158+
* [CWE-78: OS Command Injection](https://cwe.mitre.org/data/definitions/78.html)
159+
* [Fork bomb / Rabbit virus attacks](https://www.imperva.com/learn/ddos/fork-bomb/)
160+

fundamentals/whatissast.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ At a function level, Python Code Audit makes use of a common technique to scan t
125125
Simple good cyber security is possible by [Shift left](https://nocomplexity.com/documents/simplifysecurity/shiftleft.html). By detecting issues early in the SLDC process the cost to solve potential security issues is low.
126126

127127

128-
128+
(vulnerability)=
129129
## Difference Between Weakness and Vulnerability
130130

131131
Every Python SAST tool — including [Python Code Audit](../securitytools/codeaudit.md) — scans your codebase for potential security issues. These tools flag **weaknesses** that *could* lead to exploitable security vulnerabilities.

toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ project:
4646
- file: constructs/ftp.md
4747
- file: constructs/marshal.md
4848
- file: constructs/mktemp.md
49+
- file: constructs/subprocess.md
50+
- file: constructs/systemcalls.md
4951

5052

5153
- file: guidelines/guidelines_intro.md

0 commit comments

Comments
 (0)