| name | python-bandit |
| description | This skill should be used when the user asks to "scan Python code for security issues", "set up Bandit", "configure bandit security linting", "fix bandit warnings", or needs guidance on Python static security analysis with Bandit. |
Python Bandit Security Scanning
Bandit is a static analysis tool that finds common security issues in Python code. It processes each file, builds an AST, and runs security-focused plugins against AST nodes. Results are categorized by severity (LOW, MEDIUM, HIGH) and confidence (LOW, MEDIUM, HIGH).
Installation
Install the base package or add extras for specific features:
pip install bandit
pip install "bandit[toml]"
pip install "bandit[sarif]"
pip install "bandit[baseline]"
Use the same Python version as the project under scan. Bandit relies on Python's ast module, which can only parse code valid for that interpreter version.
Core Usage
Scan a full project tree:
bandit -r path/to/project/
Scan with severity filter (report only HIGH):
bandit -r . --severity-level high
bandit -r . -lll
Scan with confidence filter:
bandit -r . --confidence-level high
Target specific test IDs only:
bandit -r . -t B105,B106,B107
Skip specific test IDs:
bandit -r . -s B101
Use a named profile:
bandit examples/*.py -p ShellInjection
Scan from stdin:
cat myfile.py | bandit -
Show N lines of context per finding:
bandit -r . -n 3
Configuration
pyproject.toml (Recommended)
Centralize Bandit settings alongside other tooling:
[tool.bandit]
exclude_dirs = ["tests", "migrations", "venv"]
skips = ["B101"]
tests = []
Run with explicit config pointer:
bandit -c pyproject.toml -r .
.bandit (INI — auto-discovered with -r)
[bandit]
exclude = tests,migrations
skips = B101,B601
tests = B201,B301
Bandit auto-discovers .bandit when invoked with -r. No -c flag needed.
YAML Config
exclude_dirs: ['tests', 'path/to/file']
tests: ['B201', 'B301']
skips: ['B101', 'B601']
try_except_pass:
check_typed_exception: true
Run: bandit -c bandit.yaml -r .
Generate a Config Template
bandit-config-generator > bandit.yaml
Suppressing False Positives
Mark individual lines with # nosec to suppress all findings:
self.process = subprocess.Popen('/bin/echo', shell=True)
Suppress specific test IDs only (preferred — avoids hiding future issues):
self.process = subprocess.Popen('/bin/ls *', shell=True)
Use the full test name as an alternative to the ID:
assert yaml.load("{}") == []
Always add a comment explaining why the suppression is justified.
Output Formats
bandit -r . -f json -o report.json
bandit -r . -f sarif -o report.sarif
bandit -r . -f csv -o report.csv
bandit -r . -f xml -o report.xml
bandit -r . -f html -o report.html
bandit -r . -f screen
bandit -r . -f yaml -o report.yaml
Baseline Workflow
Use baselines to track only new issues, ignoring pre-existing findings:
bandit -r . -f json -o .bandit-baseline.json
git add .bandit-baseline.json
bandit -r . -b .bandit-baseline.json
Useful when adopting Bandit on an existing codebase — block only newly introduced issues.
Critical Plugin Categories
Bandit test IDs follow a group scheme:
| Range | Category |
|---|
| B1xx | Miscellaneous |
| B2xx | App/framework misconfiguration |
| B3xx | Blacklisted calls |
| B4xx | Blacklisted imports |
| B5xx | Cryptography |
| B6xx | Injection |
| B7xx | XSS |
High-Priority Checks to Always Enforce
Hardcoded secrets (B105, B106, B107) — passwords assigned to variables, passed as function arguments, or set as default parameters.
Injection (B602, B608) — shell injection via subprocess with shell=True, SQL injection via hardcoded SQL string construction.
Weak cryptography (B324, B501–B505) — MD5/SHA1 use, disabled TLS certificate validation, weak SSL versions, short cryptographic keys.
Unsafe deserialization (B301, B302, B303, B304) — pickle, marshal, yaml.load() without Loader.
Template injection (B701, B703, B704) — Jinja2 autoescape disabled, Django mark_safe, MarkupSafe XSS.
Common Findings and Fixes
B101 — assert_used
Asserts are stripped in optimized mode (python -O). Never use assert for security-critical checks.
assert user.is_admin, "Not authorized"
if not user.is_admin:
raise PermissionError("Not authorized")
B105/B106/B107 — Hardcoded password
password = "hunter2"
connect(password="secret")
import os
password = os.environ["DB_PASSWORD"]
B324 — Weak hash (MD5/SHA1)
import hashlib
hashlib.md5(data)
hashlib.sha256(data)
hashlib.md5(data).hexdigest()
B506 — yaml.load()
import yaml
yaml.load(data)
yaml.safe_load(data)
yaml.load(data, Loader=yaml.SafeLoader)
B602 — subprocess with shell=True
subprocess.Popen(user_input, shell=True)
subprocess.Popen(["ls", "-l", path])
B608 — Hardcoded SQL
query = "SELECT * FROM users WHERE name = '" + name + "'"
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))
B501 — No certificate validation
requests.get(url, verify=False)
requests.get(url)
requests.get(url, verify="/path/to/ca-bundle.crt")
Severity Triage Workflow
- Run Bandit with JSON output format and save results to a file (see Output Formats section above).
- Fix all HIGH severity + HIGH confidence findings first — these are near-certain vulnerabilities.
- Evaluate MEDIUM severity findings for false positives; suppress with documented
# nosec if safe.
- Decide team policy on LOW severity — consider skipping known false-positive-heavy tests via
skips.
- Establish a baseline for legacy codebases to avoid alert fatigue during adoption.
Additional Resources
references/plugin-reference.md — Complete B-code listing with severity, description, and fix pattern per plugin
references/ci-cd-integration.md — Pre-commit hooks, GitHub Actions, and baseline automation workflows