bandit review
Bandit 1.9.4 is a Python source scanner built around the standard library AST. It visits parsed nodes, runs the matching B-numbered checks, and reports a location plus separate severity and confidence ratings. That catches known-dangerous calls such as unsafe deserialization, shell-prone subprocess use, and weak hashes without executing the program. The current release repairs the B613 stdin crash, a B615 false alarm for revisions held in variables, and B106 line placement on multiline calls. Our Python 3.12 sandbox loaded the module successfully with no pip-audit findings. It does not trace values between functions or inspect dependency CVEs.
Bandit 1.9.4 installed in 0.3 seconds, occupied 11 MB, and produced 0 audit findings in our sandbox, making it a cheap Python-only CI check. Install it for known bad call patterns; use other tools for taint flow, secrets, and dependency risk.
We installed it
| Install | ✓ · 0.3s | 7 packages on disk · 11 MB |
| Import | ✓ | import bandit in 0.56s · pure Python · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does bandit install cleanly?
Yes. In a fresh container with an empty cache, pip install bandit finished in 0.3s, leaving 7 packages and 11 MB on disk. pip-audit reported no known vulnerabilities.
What does bandit need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import bandit succeeded in 0.56s.
bandit or semgrep: which should you use?
semgrep: Pick Semgrep for multi-language rules or patterns that need more context than a Bandit AST check. Bandit 1.9.4 installed in 0.3 seconds, occupied 11 MB, and produced 0 audit findings in our sandbox, making it a cheap Python-only CI check.
When should you not use bandit?
You need proof that request data reaches a dangerous sink across several functions. An AST plugin sees local syntax, not an application-wide taint path.
Use it if
- Every pull request should get Python security checks without booting the service or supplying test data.
- Review comments need B-rule IDs, exact source locations, and distinct severity and confidence values.
- A legacy repository needs a JSON baseline while CI rejects findings introduced after that snapshot.
- You can express company-specific dangerous calls as AST plugins and keep their settings with the scan policy.
- You need proof that request data reaches a dangerous sink across several functions. An AST plugin sees local syntax, not an application-wide taint path.
- Your actual target is vulnerable dependencies or committed secrets. Bandit scans Python code and leaves lockfiles and repository history alone.
- CI still runs Python 3.9 or older. The 1.9.4 distribution requires Python 3.10 or later.
- The scanner interpreter cannot parse the project's syntax level. Bandit relies on that interpreter's `ast`, so parsing can fail before a rule examines the file.
- The proposed policy is to silence failures with bare `# nosec`. That marker suppresses every result on its line, while an ID such as `# nosec B603` limits the exception.
Setup reality
Our install of Bandit 1.9.4 finished in 0.3 seconds inside a fresh Python 3.12 Bookworm container. It left 7 packages and 11 MB on disk. We counted 17 direct dependencies; the code is pure Python, needs Python 3.10 or newer, and has no py.typed marker. Importing bandit took 0.56 seconds, and pip-audit reported 0 known vulnerabilities.
bandit -r src needs neither credentials nor a service account. CI policy is the work: severity and confidence are independent thresholds, so choose both explicitly. Recursive scans discover .bandit, but YAML and TOML policies need -c. INI calls the exclusion option exclude; YAML and TOML call it exclude_dirs. Reading TOML may require the package extra.
The Python interpreter running Bandit must understand every syntax form in the target. Bandit feeds files to that interpreter's ast parser, and a syntax mismatch prevents rule evaluation. Release 1.9.4 no longer crashes B613 when source arrives on stdin. Stdin still has weaker filename context than an ordinary file scan.
Baselines must be JSON. Matching old findings disappear from later baseline-aware reports, so give the baseline an owner and remove entries as code changes. SARIF output and baseline tooling bring optional dependencies. Use rule-specific comments such as # nosec B603 with a reason. A separate dependency audit and secret scanner still belong in the pipeline.
Patterns
Check an application package scan-package-tree
bandit -r src/my_appThe `-r` flag makes directory traversal explicit. Target the application tree instead of feeding Bandit virtual environments or vendored copies.
Fail on high and certain results gate-high-confidence-findings
bandit -r src --severity-level high --confidence-level highBoth filters apply. A high-severity result with medium confidence stays out of this report.
Read rules from pyproject.toml configure-pyproject
# pyproject.toml
[tool.bandit]
exclude_dirs = ["tests/fixtures", "build"]
skips = ["B101"]
# shell
bandit -c pyproject.toml -r srcTOML policy is opt-in through `-c`; automatic lookup does not cover pyproject.toml. The parser can require the `toml` extra.
Let a recursive run find .bandit configure-ini
# .bandit
[bandit]
exclude = tests/fixtures,build
tests = B201,B301
# shell
bandit -r .Recursive mode looks for `.bandit`. Its INI syntax says `exclude`, while the YAML and TOML formats say `exclude_dirs`.
Run a narrow set of checks choose-rule-set
bandit -r src -t B301,B302,B303 -s B302CLI IDs merge with configured selections and skips. The same test cannot appear on both configured sides.
Suppress only B603 suppress-one-rule
result = subprocess.run(
["/usr/bin/git", "status", "--short"],
check=True,
) # nosec B603: executable and arguments are fixedA named ID leaves any other finding on this line visible. The inline reason tells a reviewer why this fixed command is accepted.
Freeze existing results as JSON create-json-baseline
bandit -r src -f json -o bandit-baseline.json
bandit -r src -b bandit-baseline.jsonThe baseline input format is JSON only. Give suppressed entries a cleanup plan because baseline matching removes them from normal output.
Write SARIF for code scanning export-sarif
python -m pip install 'bandit[sarif]==1.9.4'
bandit -r src -f sarif -o bandit.sarifSARIF support comes from an extra, so the CI environment must install that extra at the pinned 1.9.4 version.
Use the same policy in pre-commit run-pre-commit-hook
repos:
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]The isolated hook needs its own TOML dependency and config argument. A fixed revision prevents local scans drifting from CI.
Scan a historical file through stdin scan-standard-input
git show origin/main:src/legacy.py | bandit -B613 no longer crashes on stdin in 1.9.4. Bandit still lacks the normal on-disk filename when applying path-aware behavior.
Start a plugin policy file generate-policy-template
bandit-config-generator -o bandit.yamlThe generated YAML contains plugin defaults. Delete blocks the project does not change so deliberate policy remains obvious.
Run the signed container image run-signed-container
docker run --rm \
-v "$PWD:/code:ro" \
ghcr.io/pycqa/bandit/bandit:1.9.4 \
-r /code/srcPyCQA publishes signed images for 4 architectures. CI should use a release tag or digest instead of the moving `latest` tag.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| semgrep | PyPI | Pick Semgrep for multi-language rules or patterns that need more context than a Bandit AST check. |
| ruff | PyPI | Pick Ruff when Bandit-derived S rules should share one fast pass with ordinary Python linting. |
| safety | PyPI | Pick Safety when installed packages and lockfiles are the concern rather than dangerous Python source patterns. |
More security guides
cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

