python-bandit

SKILL.md

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:

# Base installation
pip install bandit

# With TOML config support (pyproject.toml)
pip install "bandit[toml]"

# With SARIF output (for GitHub Advanced Security)
pip install "bandit[sarif]"

# With baseline support
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
# or shorthand: -lll (high), -ll (medium+), -l (low+)
bandit -r . -lll

Scan with confidence filter:

bandit -r . --confidence-level high
# shorthand: -iii (high), -ii (medium+), -i (low+)

Target specific test IDs only:

bandit -r . -t B105,B106,B107   # hardcoded password checks only

Skip specific test IDs:

bandit -r . -s B101             # skip assert_used (common in tests)

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"]          # assert_used — acceptable in test suites
tests = []                # empty = run all (minus skips)

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']

# Override plugin-specific defaults
try_except_pass:
  check_typed_exception: true

Run: bandit -c bandit.yaml -r .

Generate a Config Template

bandit-config-generator > bandit.yaml
# Then edit — remove sections you don't need, adjust defaults

Suppressing False Positives

Mark individual lines with # nosec to suppress all findings:

self.process = subprocess.Popen('/bin/echo', shell=True)  # nosec

Suppress specific test IDs only (preferred — avoids hiding future issues):

self.process = subprocess.Popen('/bin/ls *', shell=True)  # nosec B602, B607

Use the full test name as an alternative to the ID:

assert yaml.load("{}") == []  # nosec assert_used

Always add a comment explaining why the suppression is justified.

Output Formats

bandit -r . -f json -o report.json      # JSON (required for baseline)
bandit -r . -f sarif -o report.sarif    # SARIF (GitHub Advanced Security)
bandit -r . -f csv -o report.csv        # CSV
bandit -r . -f xml -o report.xml        # XML
bandit -r . -f html -o report.html      # HTML
bandit -r . -f screen                   # Terminal (default)
bandit -r . -f yaml -o report.yaml      # YAML

Baseline Workflow

Use baselines to track only new issues, ignoring pre-existing findings:

# 1. Generate a baseline from the current state of the codebase
bandit -r . -f json -o .bandit-baseline.json

# 2. Commit the baseline to version control
git add .bandit-baseline.json

# 3. Future scans compare against the baseline
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.

# Bad
assert user.is_admin, "Not authorized"

# Good
if not user.is_admin:
    raise PermissionError("Not authorized")

B105/B106/B107 — Hardcoded password

# Bad
password = "hunter2"
connect(password="secret")

# Good — read from environment or secrets manager
import os
password = os.environ["DB_PASSWORD"]

B324 — Weak hash (MD5/SHA1)

# Bad
import hashlib
hashlib.md5(data)

# Good — use SHA-256 or higher for security contexts
hashlib.sha256(data)
# If MD5 is for non-security use (checksums), suppress with comment:
hashlib.md5(data).hexdigest()  # nosec B324 — used for cache key, not security

B506 — yaml.load()

# Bad — arbitrary code execution risk
import yaml
yaml.load(data)

# Good
yaml.safe_load(data)
# or
yaml.load(data, Loader=yaml.SafeLoader)

B602 — subprocess with shell=True

# Bad — shell injection vector
subprocess.Popen(user_input, shell=True)

# Good — pass args as a list, avoid shell
subprocess.Popen(["ls", "-l", path])

B608 — Hardcoded SQL

# Bad
query = "SELECT * FROM users WHERE name = '" + name + "'"

# Good — use parameterized queries
cursor.execute("SELECT * FROM users WHERE name = ?", (name,))

B501 — No certificate validation

# Bad
requests.get(url, verify=False)

# Good
requests.get(url)                        # verify=True by default
requests.get(url, verify="/path/to/ca-bundle.crt")

Severity Triage Workflow

  1. Run Bandit with JSON output format and save results to a file (see Output Formats section above).
  2. Fix all HIGH severity + HIGH confidence findings first — these are near-certain vulnerabilities.
  3. Evaluate MEDIUM severity findings for false positives; suppress with documented # nosec if safe.
  4. Decide team policy on LOW severity — consider skipping known false-positive-heavy tests via skips.
  5. 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
Weekly Installs
3
GitHub Stars
1
First Seen
1 day ago
Installed on
opencode3
cline2
github-copilot2
codex2
kimi-cli2
gemini-cli2