Python Security Audit
Purpose
Perform a comprehensive, depth-first security audit of Python codebases.
This skill provides the complete knowledge of Bandit's 50+ security checks,
organized by category and severity, plus framework-specific patterns for
Django, Flask, FastAPI, and emerging ML/AI attack surfaces.
Use view_file and grep_search exclusively. No terminal commands.
Audit Workflow
Phase 1: Reconnaissance
- Identify the Python framework in use (Django, Flask, FastAPI, Tornado, aiohttp, raw stdlib)
- Check
requirements.txt / pyproject.toml / Pipfile for dangerous dependencies
- Map entry points: URL routes, CLI commands, message consumers, scheduled tasks
- Identify configuration files and secrets management approach
Phase 2: Systematic Check — By Category
Work through each category below. For each check, use grep_search to find all instances,
then view_file to trace the data flow and confirm exploitability.
B1xx — Miscellaneous Checks
| ID |
Name |
Severity |
What to Search |
| B101 |
assert_used |
Low |
assert statements used for security checks (removed with -O flag) |
| B102 |
exec_used |
Medium |
exec() calls — trace if input is user-controlled |
| B103 |
set_bad_file_permissions |
Medium |
os.chmod() with overly permissive modes (0o777, 0o666) |
| B104 |
hardcoded_bind_all_interfaces |
Medium |
Binding to 0.0.0.0 — exposes service on all interfaces |
| B105 |
hardcoded_password_string |
Low |
Strings assigned to variables named password, secret, key, token |
| B106 |
hardcoded_password_funcarg |
Low |
Password-like strings passed as function arguments |
| B107 |
hardcoded_password_default |
Low |
Default parameter values containing password-like strings |
| B108 |
hardcoded_tmp_directory |
Low |
Hardcoded /tmp paths — race conditions, symlink attacks |
| B109 |
password_config_option_not_marked_secret |
Low |
Config options with password/secret that aren't marked as sensitive |
| B110 |
try_except_pass |
Low |
except: pass — silently swallowing errors including security exceptions |
| B111 |
execute_with_run_as_root_equals_true |
Medium |
Functions called with run_as_root=True |
| B112 |
try_except_continue |
Low |
except: continue — same problem as B110 |
| B113 |
request_without_timeout |
Medium |
requests.get/post() without timeout= parameter — DoS via hang |
Audit Depth for B1xx
- B101: Check if
assert guards authentication or authorization. If so, HIGH severity.
- B102: Trace
exec() input — if user-controlled, escalate to CRITICAL (RCE).
- B105/106/107: Check if the hardcoded credentials are for production systems or test fixtures.
- B113: Check all HTTP client calls (
requests, httpx, urllib3, aiohttp) for timeout.
B2xx — Application/Framework Misconfiguration
| ID |
Name |
Severity |
What to Search |
| B201 |
flask_debug_true |
High |
app.run(debug=True) — enables Werkzeug debugger (RCE) |
| B202 |
tarfile_unsafe_members |
High |
tarfile.extractall() without filter= — path traversal via tar |
Audit Depth for B2xx
- B201: Check if
debug=True is conditional on environment or always on. Check for WERKZEUG_DEBUG_PIN.
- B202: Any
tarfile.open() + extractall() from user-uploaded files = CRITICAL path traversal.
B3xx — Dangerous Function Calls (Blacklists)
| ID |
Name |
Severity |
What to Search |
| B324 |
hashlib |
Medium |
Use of md5(), sha1() for security-sensitive operations (password hashing, integrity) |
Extended B3xx Checks (Eresus additions)
hashlib.md5() / hashlib.sha1() for password storage → escalate to HIGH
- Use of
random module instead of secrets for security tokens → HIGH
string.Template with user input → potential template injection
B5xx — Cryptography
| ID |
Name |
Severity |
What to Search |
| B501 |
request_with_no_cert_validation |
High |
requests.get(url, verify=False) — TLS downgrade |
| B502 |
ssl_with_bad_version |
High |
ssl.SSLContext(ssl.PROTOCOL_SSLv2) or SSLv3 |
| B503 |
ssl_with_bad_defaults |
Medium |
SSLContext with insecure default protocol |
| B504 |
ssl_with_no_version |
Medium |
SSLContext created without explicit protocol |
| B505 |
weak_cryptographic_key |
High |
RSA < 2048 bits, DSA < 2048 bits, EC < 224 bits |
| B506 |
yaml_load |
Medium |
yaml.load() without Loader=SafeLoader — deserialization RCE |
| B507 |
ssh_no_host_key_verification |
High |
Paramiko set_missing_host_key_policy(AutoAddPolicy) |
| B508 |
snmp_insecure_version |
Medium |
SNMPv1/v2 without authentication |
| B509 |
snmp_weak_cryptography |
Medium |
SNMPv3 with weak crypto |
Audit Depth for B5xx
- B501: Check if cert pinning is used. If
verify=False is in production code → CRITICAL.
- B506: This is a deserialization sink. If input comes from HTTP/file upload → CRITICAL RCE.
B6xx — Injection
| ID |
Name |
Severity |
What to Search |
| B601 |
paramiko_calls |
Medium |
Paramiko SSH command execution — trace if command is user-controlled |
| B602 |
subprocess_popen_with_shell_equals_true |
High |
subprocess.Popen(cmd, shell=True) — command injection |
| B603 |
subprocess_without_shell_equals_true |
Low |
subprocess.Popen(cmd) without shell — still check input |
| B604 |
any_other_function_with_shell_equals_true |
Medium |
Any function with shell=True parameter |
| B605 |
start_process_with_a_shell |
High |
os.system(), os.popen() — command injection |
| B606 |
start_process_with_no_shell |
Low |
os.execl(), os.execve() — still trace input |
| B607 |
start_process_with_partial_path |
Low |
Process started without full path — PATH hijacking |
| B608 |
hardcoded_sql_expressions |
Medium |
SQL strings built with + or % or f-strings |
| B609 |
linux_commands_wildcard_injection |
High |
Commands with * glob — wildcard injection (tar, chown, etc.) |
| B610 |
django_extra_used |
Medium |
Django QuerySet.extra() — raw SQL injection |
| B611 |
django_rawsql_used |
Medium |
Django RawSQL() — raw SQL injection |
| B612 |
logging_config_insecure_listen |
Medium |
logging.config.listen() — arbitrary code execution |
| B613 |
trojansource |
High |
Unicode bidirectional control characters — trojan source attack |
| B614 |
pytorch_load |
High |
torch.load() — uses pickle internally, RCE if untrusted |
| B615 |
huggingface_unsafe_download |
High |
HuggingFace model downloads without safety checks |
Audit Depth for B6xx
- B602/B605: Trace the command string backwards. If ANY part is user-controlled → CRITICAL RCE.
- B608: Check if the SQL is parameterized elsewhere. Raw f-string SQL = HIGH SQLi.
- B609:
tar cf archive.tar * in /tmp with user-created files → argument injection.
- B614:
torch.load() from user-uploaded model file → CRITICAL RCE via pickle.
- B615: ML model supply chain attack — check if
trust_remote_code=True.
B7xx — XSS / Template Injection
| ID |
Name |
Severity |
What to Search |
| B701 |
jinja2_autoescape_false |
High |
jinja2.Environment(autoescape=False) — stored/reflected XSS |
| B702 |
use_of_mako_templates |
Medium |
Mako templates — no auto-escaping by default |
| B703 |
django_mark_safe |
Medium |
mark_safe(user_input) — bypasses Django auto-escaping |
| B704 |
markupsafe_markup_xss |
Medium |
Markup(user_input) — bypasses escaping |
Audit Depth for B7xx
- B701: If
autoescape=False and template renders user input → CRITICAL XSS.
- B703: Trace what data is passed to
mark_safe(). If user-controlled → HIGH XSS.
Framework-Specific Deep Checks
Django
- Check
ALLOWED_HOSTS configuration (empty = open redirect)
- Check
CSRF_COOKIE_HTTPONLY, SESSION_COOKIE_SECURE, SECURE_BROWSER_XSS_FILTER
- Check for
@csrf_exempt decorators on sensitive views
- Check
DEBUG = True in production settings
- Check
SECRET_KEY hardcoded or in version control
- Check
MIDDLEWARE ordering (SecurityMiddleware should be first)
- Check
AUTH_PASSWORD_VALIDATORS configuration
Flask
- Check
SECRET_KEY generation (must be cryptographically random)
- Check
app.run(debug=True) in production
- Check for
@app.before_request authentication enforcement
- Check session cookie configuration (
SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY)
- Check file upload handling (
werkzeug.utils.secure_filename)
FastAPI
- Check for missing input validation (
Body(), Query(), Path() without constraints)
- Check CORS configuration (
allow_origins=["*"])
- Check authentication dependency injection (missing
Depends())
- Check
FileResponse / StreamingResponse path traversal
- Check OAuth2 implementation (token validation, scope enforcement)
ML/AI Attack Surface
torch.load() — pickle-based, RCE from untrusted models
transformers.pipeline(trust_remote_code=True) — arbitrary code execution
pickle.loads() in model serialization pipelines
- Prompt injection in LLM-based applications
- Model poisoning via untrusted training data
Severity Matrix
| Confidence \ Severity |
LOW |
MEDIUM |
HIGH |
| HIGH |
Info |
Medium |
Critical |
| MEDIUM |
Low |
Medium |
High |
| LOW |
Info |
Low |
Medium |
Report Format
For each finding, report:
### [B-ID]: [Check Name]
**Severity**: [LOW/MEDIUM/HIGH/CRITICAL]
**Confidence**: [LOW/MEDIUM/HIGH]
**File**: [path]:[line]
**Vulnerable Code**:
[show the code]
**Data Flow**:
[source] → [intermediaries] → [sink]
**Impact**: [what an attacker achieves]
**Remediation**: [specific fix with code example]
**Bandit Reference**: B[xxx]
Tooling Constraints
Use ONLY:
view_file — read source code
grep_search — find patterns across the codebase
Do NOT use any terminal commands.
1---2name: eresus-python-audit3description: Deep Python-specific security audit skill with 50+ vulnerability class coverage across 7 categories. Trigger when auditing Python code: "audit this Python app", "find Python security issues", "check Flask/Django for vulnerabilities", "Python SAST review", "check for pickle vulnerabilities", "review this FastAPI code". Covers misconfiguration, injection, crypto, XSS, deserialization, and ML/AI attack surfaces. Includes scripts/rules.json for programmatic rule lookup.4---5
6# Python Security Audit
7
8## Purpose
9
10Perform a comprehensive, depth-first security audit of Python codebases.
11This skill provides the complete knowledge of Bandit's 50+ security checks,
12organized by category and severity, plus framework-specific patterns for
13Django, Flask, FastAPI, and emerging ML/AI attack surfaces.
14
15Use `view_file` and `grep_search` exclusively. No terminal commands.
16
17---
18
19## Audit Workflow
20
21### Phase 1: Reconnaissance
22
231. Identify the Python framework in use (Django, Flask, FastAPI, Tornado, aiohttp, raw stdlib)
242. Check `requirements.txt` / `pyproject.toml` / `Pipfile` for dangerous dependencies
253. Map entry points: URL routes, CLI commands, message consumers, scheduled tasks
264. Identify configuration files and secrets management approach
27
28### Phase 2: Systematic Check — By Category
29
30Work through each category below. For each check, use `grep_search` to find all instances,
31then `view_file` to trace the data flow and confirm exploitability.
32
33---
34
35## B1xx — Miscellaneous Checks
36
37| ID | Name | Severity | What to Search |
38|----|------|----------|----------------|
39| B101 | `assert_used` | Low | `assert` statements used for security checks (removed with `-O` flag) |
40| B102 | `exec_used` | Medium | `exec()` calls — trace if input is user-controlled |
41| B103 | `set_bad_file_permissions` | Medium | `os.chmod()` with overly permissive modes (0o777, 0o666) |
42| B104 | `hardcoded_bind_all_interfaces` | Medium | Binding to `0.0.0.0` — exposes service on all interfaces |
43| B105 | `hardcoded_password_string` | Low | Strings assigned to variables named `password`, `secret`, `key`, `token` |
44| B106 | `hardcoded_password_funcarg` | Low | Password-like strings passed as function arguments |
45| B107 | `hardcoded_password_default` | Low | Default parameter values containing password-like strings |
46| B108 | `hardcoded_tmp_directory` | Low | Hardcoded `/tmp` paths — race conditions, symlink attacks |
47| B109 | `password_config_option_not_marked_secret` | Low | Config options with password/secret that aren't marked as sensitive |
48| B110 | `try_except_pass` | Low | `except: pass` — silently swallowing errors including security exceptions |
49| B111 | `execute_with_run_as_root_equals_true` | Medium | Functions called with `run_as_root=True` |
50| B112 | `try_except_continue` | Low | `except: continue` — same problem as B110 |
51| B113 | `request_without_timeout` | Medium | `requests.get/post()` without `timeout=` parameter — DoS via hang |
52
53### Audit Depth for B1xx
54- B101: Check if `assert` guards authentication or authorization. If so, **HIGH** severity.
55- B102: Trace `exec()` input — if user-controlled, escalate to **CRITICAL** (RCE).
56- B105/106/107: Check if the hardcoded credentials are for production systems or test fixtures.
57- B113: Check all HTTP client calls (`requests`, `httpx`, `urllib3`, `aiohttp`) for timeout.
58
59---
60
61## B2xx — Application/Framework Misconfiguration
62
63| ID | Name | Severity | What to Search |
64|----|------|----------|----------------|
65| B201 | `flask_debug_true` | High | `app.run(debug=True)` — enables Werkzeug debugger (RCE) |
66| B202 | `tarfile_unsafe_members` | High | `tarfile.extractall()` without `filter=` — path traversal via tar |
67
68### Audit Depth for B2xx
69- B201: Check if `debug=True` is conditional on environment or always on. Check for `WERKZEUG_DEBUG_PIN`.
70- B202: Any `tarfile.open()` + `extractall()` from user-uploaded files = **CRITICAL** path traversal.
71
72---
73
74## B3xx — Dangerous Function Calls (Blacklists)
75
76| ID | Name | Severity | What to Search |
77|----|------|----------|----------------|
78| B324 | `hashlib` | Medium | Use of `md5()`, `sha1()` for security-sensitive operations (password hashing, integrity) |
79
80### Extended B3xx Checks (Eresus additions)
81- `hashlib.md5()` / `hashlib.sha1()` for password storage → escalate to **HIGH**
82- Use of `random` module instead of `secrets` for security tokens → **HIGH**
83- `string.Template` with user input → potential template injection
84
85---
86
87## B5xx — Cryptography
88
89| ID | Name | Severity | What to Search |
90|----|------|----------|----------------|
91| B501 | `request_with_no_cert_validation` | High | `requests.get(url, verify=False)` — TLS downgrade |
92| B502 | `ssl_with_bad_version` | High | `ssl.SSLContext(ssl.PROTOCOL_SSLv2)` or SSLv3 |
93| B503 | `ssl_with_bad_defaults` | Medium | SSLContext with insecure default protocol |
94| B504 | `ssl_with_no_version` | Medium | SSLContext created without explicit protocol |
95| B505 | `weak_cryptographic_key` | High | RSA < 2048 bits, DSA < 2048 bits, EC < 224 bits |
96| B506 | `yaml_load` | Medium | `yaml.load()` without `Loader=SafeLoader` — deserialization RCE |
97| B507 | `ssh_no_host_key_verification` | High | Paramiko `set_missing_host_key_policy(AutoAddPolicy)` |
98| B508 | `snmp_insecure_version` | Medium | SNMPv1/v2 without authentication |
99| B509 | `snmp_weak_cryptography` | Medium | SNMPv3 with weak crypto |
100
101### Audit Depth for B5xx
102- B501: Check if cert pinning is used. If `verify=False` is in production code → **CRITICAL**.
103- B506: This is a deserialization sink. If input comes from HTTP/file upload → **CRITICAL** RCE.
104
105---
106
107## B6xx — Injection
108
109| ID | Name | Severity | What to Search |
110|----|------|----------|----------------|
111| B601 | `paramiko_calls` | Medium | Paramiko SSH command execution — trace if command is user-controlled |
112| B602 | `subprocess_popen_with_shell_equals_true` | High | `subprocess.Popen(cmd, shell=True)` — command injection |
113| B603 | `subprocess_without_shell_equals_true` | Low | `subprocess.Popen(cmd)` without shell — still check input |
114| B604 | `any_other_function_with_shell_equals_true` | Medium | Any function with `shell=True` parameter |
115| B605 | `start_process_with_a_shell` | High | `os.system()`, `os.popen()` — command injection |
116| B606 | `start_process_with_no_shell` | Low | `os.execl()`, `os.execve()` — still trace input |
117| B607 | `start_process_with_partial_path` | Low | Process started without full path — PATH hijacking |
118| B608 | `hardcoded_sql_expressions` | Medium | SQL strings built with `+` or `%` or f-strings |
119| B609 | `linux_commands_wildcard_injection` | High | Commands with `*` glob — wildcard injection (tar, chown, etc.) |
120| B610 | `django_extra_used` | Medium | Django `QuerySet.extra()` — raw SQL injection |
121| B611 | `django_rawsql_used` | Medium | Django `RawSQL()` — raw SQL injection |
122| B612 | `logging_config_insecure_listen` | Medium | `logging.config.listen()` — arbitrary code execution |
123| B613 | `trojansource` | High | Unicode bidirectional control characters — trojan source attack |
124| B614 | `pytorch_load` | High | `torch.load()` — uses pickle internally, RCE if untrusted |
125| B615 | `huggingface_unsafe_download` | High | HuggingFace model downloads without safety checks |
126
127### Audit Depth for B6xx
128- B602/B605: Trace the command string backwards. If ANY part is user-controlled → **CRITICAL** RCE.
129- B608: Check if the SQL is parameterized elsewhere. Raw f-string SQL = **HIGH** SQLi.
130- B609: `tar cf archive.tar *` in `/tmp` with user-created files → argument injection.
131- B614: `torch.load()` from user-uploaded model file → **CRITICAL** RCE via pickle.
132- B615: ML model supply chain attack — check if `trust_remote_code=True`.
133
134---
135
136## B7xx — XSS / Template Injection
137
138| ID | Name | Severity | What to Search |
139|----|------|----------|----------------|
140| B701 | `jinja2_autoescape_false` | High | `jinja2.Environment(autoescape=False)` — stored/reflected XSS |
141| B702 | `use_of_mako_templates` | Medium | Mako templates — no auto-escaping by default |
142| B703 | `django_mark_safe` | Medium | `mark_safe(user_input)` — bypasses Django auto-escaping |
143| B704 | `markupsafe_markup_xss` | Medium | `Markup(user_input)` — bypasses escaping |
144
145### Audit Depth for B7xx
146- B701: If `autoescape=False` and template renders user input → **CRITICAL** XSS.
147- B703: Trace what data is passed to `mark_safe()`. If user-controlled → **HIGH** XSS.
148
149---
150
151## Framework-Specific Deep Checks
152
153### Django
154- Check `ALLOWED_HOSTS` configuration (empty = open redirect)
155- Check `CSRF_COOKIE_HTTPONLY`, `SESSION_COOKIE_SECURE`, `SECURE_BROWSER_XSS_FILTER`
156- Check for `@csrf_exempt` decorators on sensitive views
157- Check `DEBUG = True` in production settings
158- Check `SECRET_KEY` hardcoded or in version control
159- Check `MIDDLEWARE` ordering (SecurityMiddleware should be first)
160- Check `AUTH_PASSWORD_VALIDATORS` configuration
161
162### Flask
163- Check `SECRET_KEY` generation (must be cryptographically random)
164- Check `app.run(debug=True)` in production
165- Check for `@app.before_request` authentication enforcement
166- Check session cookie configuration (`SESSION_COOKIE_SECURE`, `SESSION_COOKIE_HTTPONLY`)
167- Check file upload handling (`werkzeug.utils.secure_filename`)
168
169### FastAPI
170- Check for missing input validation (`Body()`, `Query()`, `Path()` without constraints)
171- Check CORS configuration (`allow_origins=["*"]`)
172- Check authentication dependency injection (missing `Depends()`)
173- Check `FileResponse` / `StreamingResponse` path traversal
174- Check OAuth2 implementation (token validation, scope enforcement)
175
176### ML/AI Attack Surface
177- `torch.load()` — pickle-based, RCE from untrusted models
178- `transformers.pipeline(trust_remote_code=True)` — arbitrary code execution
179- `pickle.loads()` in model serialization pipelines
180- Prompt injection in LLM-based applications
181- Model poisoning via untrusted training data
182
183---
184
185## Severity Matrix
186
187| Confidence \ Severity | LOW | MEDIUM | HIGH |
188|----------------------|-----|--------|------|
189| **HIGH** | Info | Medium | Critical |
190| **MEDIUM** | Low | Medium | High |
191| **LOW** | Info | Low | Medium |
192
193---
194
195## Report Format
196
197For each finding, report:
198
199```
200### [B-ID]: [Check Name]
201
202**Severity**: [LOW/MEDIUM/HIGH/CRITICAL]
203**Confidence**: [LOW/MEDIUM/HIGH]
204**File**: [path]:[line]
205
206**Vulnerable Code**:
207[show the code]
208
209**Data Flow**:
210[source] → [intermediaries] → [sink]
211
212**Impact**: [what an attacker achieves]
213**Remediation**: [specific fix with code example]
214**Bandit Reference**: B[xxx]
215```
216
217---
218
219## Tooling Constraints
220
221Use ONLY:
222- `view_file` — read source code
223- `grep_search` — find patterns across the codebase
224
225Do NOT use any terminal commands.