bandit
Bandit is a static security checker for Python source. It parses each file into Python's abstract syntax tree, runs security-focused plugins against relevant nodes, and reports findings with test IDs, severity, confidence, source context, and machine-readable formats. It catches recognizable dangerous calls and configurations such as weak hashes, risky subprocess use, unsafe deserialization, hard-coded secrets, and insecure TLS choices. It is a fast lint layer, not proof that an application is secure.
Bandit is worth adding as one cheap Python security signal, especially when CI records explicit rule IDs and scoped suppressions. Do not sell a green Bandit run as a security review or a replacement for dependency, secret, and data-flow analysis.
Use it if
- You want a low-cost Python security check in pre-commit and CI
- Your team can review stable B-series rule IDs and record narrow suppressions
- You need JSON or SARIF output for baselines and code-scanning systems
- You want to write or configure AST plugins for organization-specific insecure patterns
- You expect interprocedural data flow, taint tracking, dependency vulnerability scanning, secret scanning, or runtime testing; the documented engine visits AST nodes with plugins
- Your codebase is not Python source: Bandit parses Python syntax and does not scan JavaScript, containers, infrastructure definitions, or lockfiles
- You cannot budget for triage and configuration; common test code, subprocess wrappers, and deliberate cryptographic compatibility paths can produce findings that need scoped decisions
- You plan to silence broad areas with bare `# nosec`; the config guide says that marker skips results on the line and supports narrower test-specific suppressions
- Your project must run tooling on Python 3.9 or older; Bandit 1.9.4 requires Python 3.10 or later
Setup reality
Install with `pip install bandit`; version 1.9.4 requires Python 3.10 or later and brings PyYAML, stevedore, Rich, plus Colorama on Windows. Basic use is `bandit -r src`, but a useful rollout starts by choosing what counts as a failure. Severity and confidence are separate thresholds, so decide both rather than assuming high severity implies high confidence. Configuration can live in YAML, TOML, or an INI section, but the keys differ: INI uses `exclude`, while YAML and TOML use `exclude_dirs`; the config path is not always auto-discovered, so pass `-c pyproject.toml` or `-c bandit.yaml` explicitly. When pre-commit uses a TOML config, the documented hook must receive the `-c` argument and any needed TOML dependency. Baselines accept only Bandit's JSON output and can hide existing findings while new work is gated, but stale baselines turn into permanent debt. SARIF and baseline commands have optional extras, `bandit[sarif]` and `bandit[baseline]`. Suppressions should say `# nosec B602, B607`, not a blanket marker, and reviews should verify the justification. Excluding all tests may remove noisy `assert` findings but also misses risky fixtures and helpers. Pin the tool independently from application runtime dependencies, upload reports as CI artifacts, and remember that a passing AST scan says nothing about vulnerable third-party packages, leaked credentials, authorization bugs, or dynamic code paths.
Patterns
Scan a Python package recursivelyscan-source-tree
bandit -r srcRecursive mode is required for directories; scope the target to maintained source rather than scanning virtual environments and generated files.
Fail CI on high-severity, high-confidence findingsset-ci-thresholds
bandit -r src --severity-level high --confidence-level highSeverity and confidence are independent filters; this fast gate should complement a broader report reviewed outside the critical path.
Configure targets and skipped tests in YAMLconfigure-yaml
# bandit.yaml
exclude_dirs:
- tests
- .venv
tests:
- B201
- B301
skips:
- B101
# run it
bandit -c bandit.yaml -r .YAML and TOML use `exclude_dirs`; INI configuration uses the different key `exclude`.
Read configuration from pyproject.tomlconfigure-pyproject
# pyproject.toml
[tool.bandit]
exclude_dirs = ['tests', '.venv']
skips = ['B101']
# Bandit requires the path explicitly
bandit -c pyproject.toml -r srcDo not assume pyproject.toml is discovered automatically; pass it with `-c`.
Run only selected tests and skip oneselect-rule-set
bandit -r src -t B301,B302,B303 -s B302Command-line test and skip lists combine with config lists; putting the same ID in both config lists is an error.
Suppress only justified rule IDssuppress-specific-finding
process = subprocess.Popen(
['/trusted/tool', '--version'],
shell=False,
) # nosec B603Name the exact test instead of bare `# nosec`, and require a review comment explaining why the flagged pattern is safe here.
Create and apply a JSON baselinecreate-baseline
bandit -r src -f json -o bandit-baseline.json
bandit -r src -b bandit-baseline.jsonOnly JSON reports are accepted as baselines; schedule cleanup or known findings can remain hidden indefinitely.
Produce SARIF for a code-scanning platformemit-sarif
python -m pip install 'bandit[sarif]'
bandit -r src -f sarif -o bandit.sarifSARIF support is an optional extra, so add it to the pinned CI tool environment rather than relying on a developer machine.
Run Bandit through pre-commitrun-pre-commit
repos:
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
hooks:
- id: bandit
args: ['-c', 'pyproject.toml', '-r', 'src']Pin the hook revision and pass the config path explicitly so local and CI scans use the same policy.
Scan Python source from standard inputscan-standard-input
git show HEAD:src/legacy.py | bandit -Stdin is useful for generated or historical content, but path-based exclusions and filenames are no longer available to the scan.
Generate a complete starter configurationgenerate-config
bandit-config-generator -o bandit.yamlThe generator includes defaults for detected test and blacklist plugins; delete untouched sections to keep the reviewed policy understandable.
Scan with the official container imagerun-container-image
docker run --rm -v "$PWD:/code:ro" ghcr.io/pycqa/bandit/bandit:latest -r /code/srcThe project publishes signed images for several architectures, but production CI should pin an immutable version or digest instead of `latest`.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| semgrep | PyPI | You need multi-language rules and deeper pattern matching across a polyglot repository |
| ruff | PyPI | You want very fast Python linting with Flake8 Bandit-derived S rules in one tool |
| pylint | PyPI | You need broad Python correctness and design analysis with a smaller security component |