mrkeyoor.com_
Tue 22 Sept 18:49 UTC
PyPISecurityupdated 22 Sept 2026

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.

Verdict

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

Lab card: what happened when we installed banditScreenshot of bandit documentation
Install✓ · 0.3s7 packages on disk · 11 MB
Importimport bandit in 0.56s · pure Python · requires Python >=3.10
Known vulns0(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.

API stability4/5The 1.9.4 command still revolves around recursive targets, B-numbered tests, two independent thresholds, profiles, format selection, baselines, and `# nosec`. Its release fixes three rule outcomes without redesigning that interface: B613 accepts stdin, B615 permits a revision stored in a variable, and B106 reports the right line for multiline calls. Pinning remains sensible because a rule correction can change CI results even when source code stays fixed.
Docs4/5The manual gives concrete commands for recursive scans, stdin, threshold filters, JSON baselines, pre-commit, every config format, suppressions, formatters, and individual B tests. It also spells out automatic `.bandit` discovery and the mismatched `exclude` and `exclude_dirs` keys. Production policy is scattered across several pages, and parts of the FAQ mention Python versions far older than the current 3.10 minimum, so version context needs attention.
Maintenance4/5Release 1.9.4 reached PyPI on 2026-02-25, while the repository received another push on 2026-08-24. GitHub now shows 8,241 stars, an unarchived project, and 260 open issues and pull requests. The release repaired real scanner behavior in B613, B615, and B106. PyCQA's workflow also builds signed images for amd64, arm64, armv7, and armv8, which gives container-based CI a first-party package path.
Ecosystem5/5The package data records 6,871,689 weekly downloads. Bandit fits pre-commit hooks, terminal review, JSON baseline workflows, and SARIF upload jobs, and Ruff republishes many Flake8 Bandit checks under its S codes. Those connections make the rule vocabulary familiar across Python projects. Its ecosystem boundary is equally clear: AST findings stop at Python source, leaving dependency CVEs, leaked credentials, and runtime behavior to separate products.

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.
Skip it if

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_app

The `-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 high

Both 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 src

TOML 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 B302

CLI 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 fixed

A 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.json

The 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.sarif

SARIF 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.yaml

The 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/src

PyCQA publishes signed images for 4 architectures. CI should use a release tag or digest instead of the moving `latest` tag.

Alternatives

PackageRegistryPick it when
semgrepPyPIPick Semgrep for multi-language rules or patterns that need more context than a Bandit AST check.
ruffPyPIPick Ruff when Bandit-derived S rules should share one fast pass with ordinary Python linting.
safetyPyPIPick 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.