Python security code review
When it applies
You're reading Python source (a repo, a PR, a service). This is the language-specific companion to
code-review-methodology — the exact sinks and framework gotchas to grep and trace.
Why it works
Most severe Python bugs come from a known set of sinks plus framework misuse. Grep the sinks, trace
each argument to a user source, and check the framework's safe-vs-unsafe API was used.
Sinks & patterns (grep, then trace to user input)
- Command exec:
os.system, subprocess.*(..., shell=True), os.popen — shell=True + user input = injection.
- Code eval:
eval, exec, pickle.loads, yaml.load (without SafeLoader), marshal — deserialization/eval RCE.
- SQL: raw/f-string queries,
.raw(), .extra(), cursor.execute("... %s" % x) — use params, not formatting.
- SSRF:
requests.get/urllib/httpx on a user URL (→ web-ssrf).
- Path/upload:
open/send_file/os.path.join with user paths (traversal); zip extraction (zip-slip).
- Template (SSTI):
render_template_string, Jinja from user input (→ web-ssti).
- Secrets: hardcoded keys/passwords;
DEBUG=True in prod.
Framework specifics
- Django:
mark_safe/|safe (XSS), .raw()/.extra() (SQLi), DEBUG=True (info leak),
SECRET_KEY exposure (session forgery), missing @login_required/object-level checks (IDOR),
ALLOWED_HOSTS='*', pickle session serializer.
- Flask:
render_template_string (SSTI), debug=True (Werkzeug console RCE), weak SECRET_KEY
(session tampering — flask-unsign), send_file traversal.
- FastAPI: missing dependency-injected auth on routes, over-broad CORS, Pydantic not enforcing
server-side authz (BOLA at the data layer).
Method
rg -n "shell=True|eval\(|exec\(|pickle.loads|yaml.load\(|render_template_string|\.raw\(|\.extra\(";
run bandit -r . and semgrep --config auto; triage by exploitable source→sink.
Gotchas
yaml.safe_load and parameterized ORM queries are the safe variants — confirm which is used.
- Bandit is noisy; rank by user-controllable input reaching the sink.
References
Bandit; Semgrep Python rules; OWASP Django/Flask cheat sheets.
1---2name: code-review-python3description: Security review of Python code — dangerous sinks and framework-specific pitfalls (Django/Flask/ FastAPI). Load when reviewing a Python codebase/PR, on .py source in scope, or "review this Python". Signals: requirements.txt/pyproject, Django/Flask/FastAPI, ORMs, pickle/yaml, subprocess.4---56# Python security code review78## When it applies9You're reading Python source (a repo, a PR, a service). This is the language-specific companion to10`code-review-methodology` — the exact sinks and framework gotchas to grep and trace.1112## Why it works13Most severe Python bugs come from a known set of sinks plus framework misuse. Grep the sinks, trace14each argument to a user source, and check the framework's safe-vs-unsafe API was used.1516## Sinks & patterns (grep, then trace to user input)17- **Command exec**: `os.system`, `subprocess.*(..., shell=True)`, `os.popen` — shell=True + user input = injection.18- **Code eval**: `eval`, `exec`, `pickle.loads`, `yaml.load` (without `SafeLoader`), `marshal` — deserialization/eval RCE.19- **SQL**: raw/f-string queries, `.raw()`, `.extra()`, `cursor.execute("... %s" % x)` — use params, not formatting.20- **SSRF**: `requests.get`/`urllib`/`httpx` on a user URL (→ `web-ssrf`).21- **Path/upload**: `open`/`send_file`/`os.path.join` with user paths (traversal); zip extraction (zip-slip).22- **Template (SSTI)**: `render_template_string`, Jinja from user input (→ `web-ssti`).23- **Secrets**: hardcoded keys/passwords; `DEBUG=True` in prod.2425## Framework specifics26- **Django**: `mark_safe`/`|safe` (XSS), `.raw()`/`.extra()` (SQLi), `DEBUG=True` (info leak),27 `SECRET_KEY` exposure (session forgery), missing `@login_required`/object-level checks (IDOR),28 `ALLOWED_HOSTS='*'`, pickle session serializer.29- **Flask**: `render_template_string` (SSTI), `debug=True` (Werkzeug console RCE), weak `SECRET_KEY`30 (session tampering — `flask-unsign`), `send_file` traversal.31- **FastAPI**: missing dependency-injected auth on routes, over-broad CORS, Pydantic not enforcing32 server-side authz (BOLA at the data layer).3334## Method35`rg -n "shell=True|eval\(|exec\(|pickle.loads|yaml.load\(|render_template_string|\.raw\(|\.extra\("`;36run `bandit -r .` and `semgrep --config auto`; triage by exploitable source→sink.3738## Gotchas39- `yaml.safe_load` and parameterized ORM queries are the safe variants — confirm which is used.40- Bandit is noisy; rank by user-controllable input reaching the sink.4142## References43Bandit; Semgrep Python rules; OWASP Django/Flask cheat sheets.