Apache-2.0🖥️ Self-hostable

Bandit

Bandit

Bandit — the security linter from PyCQA that reads Python source as a syntax tree and reports the constructs that tend to become vulnerabilities — is the tool that made static security analysis a routine part of Python development. Originally written inside the OpenStack Security Project and later rehomed to the Python Code Quality Authority (PyCQA), Bandit parses each file with Python’s own ast module, runs a library of plugins over the resulting nodes, and prints a report of what it found. It is free software under the Apache-2.0 licence, published on PyPI as bandit, and the current release is 1.9.4 (February 2026); the repository is under active development with more than 8,300 stars. Where a linter such as Ruff or flake8 polices style, Bandit polices behaviour: hardcoded passwords, disabled TLS verification, shell=True subprocesses, weak hashing, unsafe YAML deserialisation, SQL built by string concatenation and template engines with autoescaping switched off. It needs no account, no service and no build server — pip install bandit and a target directory are enough.

Key Features

  • AST-based analysis, not pattern matching — Bandit parses the code with the same ast module the interpreter uses and runs plugins against the tree nodes. A call is judged by what it actually is — its fully-qualified name, its arguments, its context — so feedback is far more precise than a grep for suspicious strings, and it can only ever inspect code that is syntactically valid.
  • A library of checks grouped by class — plugin IDs are organised by family: B1xx miscellaneous (assert_used, exec_used, set_bad_file_permissions, hardcoded_password_string, try_except_pass, request_without_timeout), B2xx application and framework misconfiguration (flask_debug_true, tarfile_unsafe_members), B3xx and B4xx blacklisted calls and imports (hashlib), B5xx cryptography (request_with_no_cert_validation, ssl_with_bad_version, weak_cryptographic_key, yaml_load, ssh_no_host_key_verification), B6xx injection (subprocess_popen_with_shell_equals_true, hardcoded_sql_expressions, django_rawsql_used, pytorch_load, huggingface_unsafe_download) and B7xx XSS (jinja2_autoescape_false, django_mark_safe, markupsafe_markup_xss).
  • Severity and confidence on every finding — each issue carries a severity and a confidence level (low, medium, high), and the command line can filter on either, so a pipeline can block only on high-severity, high-confidence results while a developer still sees the long tail.
  • Targeted suppression that ages well — a line that has been reviewed can be marked # nosec to silence everything on it, or scoped to specific tests and names — # nosec B602, B607 or # nosec assert_used. The narrow form is deliberate: it keeps suppressing what you reviewed while still catching a different vulnerability later added to the same line.
  • Configurable per project — a .bandit INI file in the project root (picked up automatically when Bandit runs with -r), or a YAML/TOML file passed with -c, can set targets, exclude/exclude_dirs, skips and tests, and override individual plugins. Bandit 1.0+ made the config file optional and understands [tool.bandit] in pyproject.toml.
  • Config generation from the installed plugins — bandit-config-generator walks every discovered plugin and emits a template config block for each, so you can start from the real defaults and delete what you accept instead of guessing option names.
  • Machine-readable output — formatters for text (also screen), json, csv, xml, yaml, html and sarif. The SARIF formatter is what lets Bandit findings appear inline on a GitHub pull-request diff via code scanning, like any other security tool.
  • Baseline mode for inherited codebases — -b takes a previous report as a baseline so only new issues are reported; that is how a team adds Bandit to a large legacy repository without drowning in pre-existing findings.
  • Extensible by design — write a function decorated with @checks('Call'), @checks('Import', 'ImportFrom') or @checks('Str') and register it through the bandit.plugins entry point: in-house rules ship as a normal Python package and plug straight into the same report.
  • Container images and signed releases — an official image is published on GitHub Container Registry at ghcr.io/pycqa/bandit/bandit for amd64 and arm64 (plus armv7/armv8), each signed with Sigstore cosign so the provenance of the image can be verified, and packaged for Ubuntu, Homebrew and FreeBSD.
  • A funded, maintained project — development is supported by sponsors including Mercedes-Benz, Tidelift and Stacklok; there is a Discord community, documentation on Read the Docs, and a documented public API for calling Bandit programmatically from Python.

Why Use Bandit?

Most exploitable bugs are not sophisticated. They are a default password left in a config loader, a verify=False added to make a self-signed certificate work, an eval() on user input, a temporary file written to a predictable path, or a password hashed with a function never designed for it. Each of those has an idiomatic Python shape that Bandit recognises, and each is far cheaper to remove before the code ships than to find in an incident review. Because Bandit works on the syntax tree rather than on text, it reports the construct rather than the string: it knows the difference between a call to subprocess.Popen with a shell and a call to a function that merely happens to be named the same way in your own module. It is also entirely local — no source is sent anywhere, there is no dashboard to buy and no telemetry — which matters when the code being scanned is a client’s or a regulated workload. The practical reason it has lasted since the OpenStack years is the granularity of its output: severity and confidence are separate axes, suppression can be scoped to a single test, and a baseline turns a thousand legacy findings into a short list of new ones. That is what makes it adoptable on day one and tolerable on day one thousand.

Use Cases

  • Run bandit -r src/ as a quick security pass before opening a pull request.
  • Gate CI on high-severity, high-confidence findings only — a scan that returns non-zero only when it matters, so the build stays a signal rather than noise.
  • Upload SARIF output to GitHub code scanning so results annotate the changed lines of the diff.
  • Adopt it incrementally on an existing codebase: generate a baseline from today’s report and fail the build only on issues introduced afterwards.
  • Add it as a pre-commit hook or a Ruff S rule set so new code is checked as it is written, not only in CI.
  • Audit a Django, Flask, PyTorch or Hugging Face project for the framework-specific traps their plugins cover.
  • Review a dependency or a vendored library before you decide to trust it.
  • Correlate a code-scan finding with a live exposure — for example a hardcoded_bind_all_interfaces hit next to a development server started without a debug flag.
  • Teach, not only police: the plugin documentation explains why each construct is dangerous, which makes a report a useful code-review companion.
  • Keep a private ruleset in-house: package internal checks as a Bandit plugin and run them through the same CLI, config and formatters as the upstream tests.

Similar

  • Semgrep — a general-purpose static analyser with a rules language that spans many languages; broader in reach and better for cross-language policy, whereas Bandit is Python-only but understands the language natively and ships a curated security test set out of the box.
  • Gitleaks — the credential-scanner counterpart. Gitleaks hunts secrets committed to a repository or filesystem; Bandit hunts dangerous constructs in the code itself. They answer different questions and are usually run together.
  • Ruff — the fast Python linter and formatter that reimplements flake8-bandit as its S rule set. If your project already runs Ruff, enabling those rules gives Bandit-equivalent checks in the same pass; standalone Bandit remains the more complete implementation, with config generation, baselines, SARIF output and the plugin entry-point API. Not listed in the directory yet.
  • Falco, CrowdSec and osquery — runtime counterparts. Those watch a running host or cluster for malicious behaviour; Bandit reads the code before anyone runs it.

Platform

Any platform with Python 3.10 or newer — Linux, macOS and Windows. Install from PyPI with pip install bandit (the bandit[toml] extra enables TOML configuration), from the official container image ghcr.io/pycqa/bandit/bandit, or from the distribution packages on Ubuntu, Homebrew and FreeBSD. It also runs as a pre-commit hook, a GitHub Action, a Jenkins plugin, or inside the editors that integrate it — VS Code, Sublime Text, Vim/Neovim and Emacs.

Licence

Apache-2.0 — the Bandit library and its plugins are provided under the terms of the Apache License 2.0. There is no open-core split, no paid tier and no service to subscribe to; the same code is the product.

Website

documentation on Read the Docs · source on GitHub · package on PyPI · also see the Security & Privacy category for more tools that put guardrails around code and systems, or browse the full directory.

Explore More Categories

Sign In

Register

Reset Password

Please enter your username or email address, you will receive a link to create a new password via email.