# https://github.com/PyCQA/bandit Project Manual

Generated at: 2026-06-19 17:25:33 UTC

## Table of Contents

- [Overview, Installation & CLI Usage](#page-1)
- [Core Engine Architecture & Data Flow](#page-2)
- [Security Plugins & Built-in Checks](#page-3)
- [Configuration, Output Formatters & Extensibility](#page-4)

<a id='page-1'></a>

## Overview, Installation & CLI Usage

### Related Pages

Related topics: [Core Engine Architecture & Data Flow](#page-2), [Configuration, Output Formatters & Extensibility](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)
- [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py)
- [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)
- [bandit/__main__.py](https://github.com/PyCQA/bandit/blob/main/bandit/__main__.py)
- [bandit/__init__.py](https://github.com/PyCQA/bandit/blob/main/bandit/__init__.py)
- [README.rst](https://github.com/PyCQA/bandit/blob/main/README.rst)
</details>

# Overview, Installation & CLI Usage

## Purpose and Scope

Bandit is a tool designed to find common security issues in Python code. It is a static analysis linter that walks the Python abstract syntax tree (AST) of each file and runs plugin-based checks against the nodes. Source: [bandit/cli/main.py:1-7]() and [bandit/__init__.py](). The project ships several command-line entry points that cover the typical security-review workflow: the primary `bandit` scanner, a `bandit-baseline` diff tool for comparing commits, and `bandit-config-generator` for producing configuration templates.

The primary entry point is exposed as both a script and a module — running `python -m bandit` invokes `bandit/__main__.py`, which then delegates to the CLI handler in `bandit/cli/main.py`. Source: [bandit/__main__.py]().

## Installation

Bandit is distributed as a standard Python package and can be installed with `pip`. The CLI scripts become available on `PATH` after installation. The baseline subcommand requires an additional Git dependency (provided by the `baseline` extra), which is what allows `bandit-baseline` to manipulate a working tree. Source: [bandit/cli/baseline.py:64-73]().

Common extras include:

| Extra      | Provides                | Used by                        |
|------------|-------------------------|--------------------------------|
| `baseline` | `GitPython` integration | `bandit-baseline` subcommand   |
| `test`     | Test runner dependencies | Repository CI                  |
| `toml`     | TOML config support     | `pyproject.toml` configuration |

Support for `pyproject.toml` as a configuration format has been a long-standing community request (issue #550), and a `toml` extra is shipped to support it.

## The Main `bandit` CLI

The primary command-line interface is defined in `bandit/cli/main.py`. The argument parser wires targets, test selection, severity/confidence filtering, output format, exclusion patterns, and baseline comparison into a single `argparse.ArgumentParser`. Source: [bandit/cli/main.py:202-399]().

### Key Argument Groups

| Argument | Purpose |
|----------|---------|
| `targets` | Positional list of files or directories to scan. |
| `-r`, `--recursive` | Discover files in subdirectories. |
| `-a`, `--aggregate` | Aggregate output by `file` (default) or `vuln`. |
| `-t`, `--tests` | Comma-separated list of test IDs to run. |
| `-s`, `--skip` | Comma-separated list of test IDs to skip. |
| `-ll`, `-lll` | Filter by severity / confidence via repeated flags. |
| `-f`, `--format` | Output format (`txt`, `json`, `html`, `csv`, `custom`, `screen`, ...). |
| `--msg-template` | Custom formatter template (only with `--format custom`). |
| `-x`, `--exclude` | Comma-separated glob patterns to exclude. |
| `-b`, `--baseline` | JSON baseline to compare against. |
| `--ini` | Path to a `.bandit` config file. |
| `--exit-zero` | Always exit with code 0, even with findings. |

Source: [bandit/cli/main.py:213-399]().

### Execution Flow

The CLI drives a three-step pipeline managed by `bandit.core.manager.BanditManager`:

```mermaid
flowchart LR
    A[Parse args + .bandit] --> B[discover_files]
    B --> C[run_tests via plugins]
    C --> D[output_results]
    D --> E{Results above\nseverity/conf?}
    E -- yes & !exit-zero --> F[exit 1]
    E -- no --> G[exit 0]
```

Source: [bandit/cli/main.py:470-509]().

Logger output is initialized before the parser runs so that errors during argument resolution are visible. Source: [bandit/cli/main.py:186-200]().

### Output Formats and Color

The default output format is chosen automatically: `screen` when stdout is a TTY and `NO_COLOR`/`TERM=dumb` are not set, otherwise `txt`. Source: [bandit/cli/main.py:340-355]().

The `custom` formatter accepts a Python `str.format()`-style template. Available tags include `{abspath}`, `{relpath}`, `{line}`, `{col}`, `{test_id}`, `{severity}`, `{msg}`, `{confidence}`, and `{range}`. Source: [bandit/cli/main.py:160-180]().

## The `.bandit` Project File

Bandit automatically discovers a `.bandit` file in any scanned directory tree and merges its values into the CLI defaults. The discovery is performed by walking `targets` and collecting files matching `.bandit`. Source: [bandit/cli/main.py:74-95]().

A typical file looks like:

```ini
[bandit]
exclude: ./tests,./.tox
tests: B101,B102
skips: B404
```

When multiple `.bandit` files are found, Bandit emits a warning. The supported keys map to long-form CLI options (`level`, `confidence`, `format`, `msg-template`, `output`, `verbose`, `debug`, `quiet`, `ignore-nosec`, `exclude`, `skips`, `tests`, `ini-options`, etc.). Source: [bandit/cli/main.py:130-190]().

> **Community note:** Excluding paths via `.bandit` has had regressions over time — issue #488 reports that `bandit -x ./tests/` was not respected after 1.6.0, and issue #657 reports that the `exclude:` value in the `.bandit` file was not being applied in 1.6.3. Always double-check with `--verbose` to confirm the resolved exclude list.

The tool also supports `pyproject.toml` and `setup.cfg` as configuration sources (issues #212 and #550), letting projects consolidate dev-tool settings into a single file.

## Baseline Comparison

`bandit-baseline` compares the current working tree against a parent commit in a Git repository. It performs the following sequence:

1. Validates that the working directory is a clean Git repo (with `GitPython`). Source: [bandit/cli/baseline.py:14-19](), [bandit/cli/baseline.py:174-185]().
2. Resolves the current and parent commit hashes. Source: [bandit/cli/baseline.py:100-118]().
3. Runs `bandit` against the parent commit, writing JSON results to a temporary file. Source: [bandit/cli/baseline.py:120-149]().
4. Resets to the current commit, runs `bandit` with `-b <tmpfile>`, and reports only new findings. Source: [bandit/cli/baseline.py:120-149]().
5. Restores the original `HEAD` on exit (via the `baseline_setup` context manager). Source: [bandit/cli/baseline.py:152-158]().

The tool enforces that the working tree is clean, that `git` is on `PATH`, that no pre-existing `bandit_baseline_result.*` file is present, and that the user did not pass `-o` (which would clash with the managed report filename). Source: [bandit/cli/baseline.py:163-190]().

Valid output formats for the final report are `txt`, `html`, and `json`; `terminal` is the default and prints directly to stdout. Source: [bandit/cli/baseline.py:27-141]().

## Configuration Generation

`bandit-config-generator` emits a templated YAML profile listing every loaded plugin, its default settings, and optional `include`/`skip` lists. Use `--show-defaults` to print defaults to stdout without writing a file. Source: [bandit/cli/config_generator.py:60-95]().

The generator inspects each plugin's module for a `gen_config` function and, if present, dumps its output into the YAML template. Source: [bandit/cli/config_generator.py:97-114](). Plugin IDs supplied via `--tests`/`--skips` are validated against the extension manager and rejected if unknown. Source: [bandit/cli/config_generator.py:67-87]().

## Common Failure Modes and Gotfalls

- **Stale `.bandit` files:** When multiple `.bandit` files are present, only one is honored and a warning is logged. Move the file to the project root.
- **`-o` with baseline:** `bandit-baseline` refuses to run if `-o` is in `argv`, because it manages its own report filename.
- **Dirty tree with baseline:** The baseline tool requires a clean working directory so it can `git reset` safely.
- **Per-file skips:** The community has repeatedly requested a one-liner to skip a test only in files matching a glob (issue #346). At present, the supported approaches are `-s B101` (global skip) and per-line `# nosec` comments combined with `--ignore-nosec` control. Source: [bandit/cli/main.py:381-390]().

## See Also

- Plugin authoring and AST node checks
- `bandit.core.manager` reference
- Severity and confidence ranking
- CI/CD integration recipes

###TASK_COMPLETED###

---

<a id='page-2'></a>

## Core Engine Architecture & Data Flow

### Related Pages

Related topics: [Overview, Installation & CLI Usage](#page-1), [Security Plugins & Built-in Checks](#page-3), [Configuration, Output Formatters & Extensibility](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)
- [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py)
- [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)
</details>

# Core Engine Architecture & Data Flow

## Overview and Purpose

Bandit is a static analysis tool that scans Python source code for common security issues. The "core engine" is the orchestration layer that ties together user input (CLI flags, INI config files, baseline files), the plugin/test ecosystem (extensions, blacklists, formatters), the file discovery walk, and the test execution pipeline that ultimately produces a security report.

While `bandit.core` houses the test runner primitives (`BanditManager`, `TestSet`, testers, AST visitor), the CLI entry points in `bandit.cli` are what wire those primitives to user input and drive the end-to-end data flow. This page focuses on that wiring, since it is what determines how arguments, configuration, files, and findings actually flow through the system. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

The engine has three CLI sub-commands that share core machinery:

| Sub-command | Module | Role |
|---|---|---|
| `bandit` | `bandit.cli.main` | Primary analyzer: discover → test → report |
| `bandit-baseline` | `bandit.cli.baseline` | Compare current commit findings to parent commit |
| `bandit-conf-generator` | `bandit.cli.config_generator` | Emit a YAML config skeleton with plugin defaults |

## High-Level Data Flow

The CLI follows a linear pipeline: bootstrap logging and extensions, merge configuration sources, walk the filesystem, run AST-based tests against each file, then format and write the results. The pipeline is shown below.

```mermaid
flowchart TD
    A[main: parse CLI args] --> B[_get_options_from_ini: load .bandit]
    B --> C[_log_option_source: merge CLI > INI > defaults]
    C --> D[_init_extensions: load plugins/formatters]
    D --> E[BanditManager: discover_files]
    E --> F[BanditManager: run_tests]
    F --> G[BanditManager: output_results]
    G --> H[Exit code: 0 clean / 1 findings / 2 error]
```

Each stage is small and explicit, which is why most user-visible regressions (e.g. the exclude-path bugs reported in issues #488 and #657) trace back to a specific stage in this flow rather than to the test plugins themselves.

## Stage-by-Stage Breakdown

### Logging and Extension Bootstrap

`main()` raises the log level to `DEBUG` if `--debug` is present, otherwise defaults to `INFO`, then calls `_init_logger()` which clears any existing handlers, attaches a `StreamHandler` to `stderr`, and enables `logging.captureWarnings(True)` so Python warnings surface in the report. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

`_init_extensions()` returns an `entrypoints`-backed extension manager. The CLI uses it twice: first to enumerate plugins and blacklists for the `--help` epilog (so users can see "the following tests were discovered and loaded"), and again to discover formatters that advertise `_accepts_baseline` for the `--baseline` flow. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

### Argument Parsing and Config Merging

Bandit accepts an INI file via `--ini <path>` or by walking targets for a `.bandit` file. `_get_options_from_ini()` performs this discovery and returns a dict of values; if more than one `.bandit` is found during the walk it logs a warning and picks one deterministically. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

The `_log_option_source()` helper is the merge point. For each option it prefers (in order) the CLI value, then the INI value, then the argparse default, and emits an `INFO` log line naming the winning source. This is the mechanism that drove issues #212 ("Add config via setup.cfg") and #550 ("Support for pyproject.toml as config file format") — community members want additional config sources plugged into this same merge function. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

Recognized INI keys include `level`, `confidence`, `format`, `msg-template`, `output`, `verbose`, `debug`, `quiet`, `ignore-nosec`, `tests`, `targets`, `recursive`, `aggregate`, `number`, `profile`, and `exclude`. The `exclude` key is particularly load-bearing for issues #488 ("Bandit 1.6.0 no longer respects excluded directories") and #657 ("Bandit 1.6.3 does not respect excluded paths from .bandit file"); both were caused by mis-merges between CLI `-x/--exclude` and INI `exclude`. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

### File Discovery

After argument merging, the CLI instantiates the `BanditManager` and calls `b_mgr.discover_files(args.targets, args.recursive, args.excluded_paths)`. The manager walks the targets, applies glob-style exclusion against `args.excluded_paths` (which is the union of CLI `-x`, INI `exclude`, and `constants.EXCLUDE`), and populates `b_mgr.b_ts.tests`. If no tests remain after filtering, `main()` logs `"No tests would be run, please check the profile."` and exits with code 2. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

### Test Execution

`b_mgr.run_tests()` is where the AST is parsed and every registered tester walks it. Severities and confidences are translated from numeric levels (`1=all`, `2=low`, `3=medium`, `4=high`) into the `constants.RANKING` index used downstream. Issues like #346 ("One-liner in bandit config to skip B101 assert_used in files matching a filter") point at this stage — the current engine only supports global include/skip via `-t`/`-s` and per-path exclusion via `-x`, with no per-file test skipping hook. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

### Results Output and Exit Codes

`b_mgr.output_results(context_lines, sev_level, conf_level, output_file, output_format, msg_template)` renders findings through the chosen formatter. If `output_format == "custom"` is set, `args.msg_template` may use any of `{abspath}, {relpath}, {line}, {col}, {test_id}, {severity}, {msg}, {confidence}, {range}` with Python `str.format` style specifiers; otherwise the template flag is rejected by `parser.error()`. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

The exit code is derived from `results_count(sev_filter, conf_filter) > 0` combined with `--exit-zero`: findings present and not suppressed ⇒ exit 1, otherwise 0. A configuration or discovery failure exits 2. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py).

## Baseline Sub-Command

`bandit-baseline` reuses the same engine twice against different git revisions. `baseline_setup()` creates a temp directory and is guaranteed (via `@contextlib.contextmanager`) to remove it and reset `repo.head` to `current_commit` after the run. The two steps are:

1. Reset `repo.head` to the parent commit, run `bandit -f json -o <tmpfile> <args>`.
2. Reset `repo.head` back to the current commit, run `bandit -b <tmpfile> -f <format> <args>`.

Pre-flight checks abort with exit 2 if GitPython is missing, the cwd is not a repo, the working tree is dirty, the report file already exists, the temp file already exists, or `-o` was passed (which would clash with baseline-managed output). The final exit code is whatever the second Bandit invocation returned. Source: [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py).

## Config Generator Sub-Command

`bandit-conf-generator` (`bandit.cli.config_generator`) walks every loaded plugin that exposes `_takes_config` and calls its module-level `gen_config()` to collect default settings, then serializes the result with `yaml.safe_dump`. The `--show-defaults` flag prints to stdout; `-o` writes a fully annotated profile template containing an `# Available tests:` comment list, optional `tests`/`skips` selections, and per-plugin settings blocks. Source: [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py).

## See Also

- [Configuration File Support & Merge Semantics](Configuration-File-Support-and-Merge-Semantics)
- [Plugin and Blacklist Architecture](Plugin-and-Blacklist-Architecture)
- [Baseline Reporting Workflow](Baseline-Reporting-Workflow)
- Issue #488 and #657 for the historical exclude-path regressions discussed above.

---

<a id='page-3'></a>

## Security Plugins & Built-in Checks

### Related Pages

Related topics: [Core Engine Architecture & Data Flow](#page-2), [Configuration, Output Formatters & Extensibility](#page-4)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [bandit/plugins/__init__.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/__init__.py)
- [bandit/plugins/asserts.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/asserts.py)
- [bandit/plugins/exec.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/exec.py)
- [bandit/plugins/general_hardcoded_password.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/general_hardcoded_password.py)
- [bandit/plugins/injection_shell.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/injection_shell.py)
- [bandit/plugins/injection_sql.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/injection_sql.py)
- [bandit/core/extension_loader.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/extension_loader.py)
- [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)
- [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)
</details>

# Security Plugins & Built-in Checks

Bandit ships as a library of pluggable AST-walking security checks. The `bandit/plugins/` package contains every check that runs by default, and the project's CLI, configuration, and baseline tools are all written to consume that plugin set through a single extension manager. This page documents how the plugin system is organized, what categories of checks are provided, and how to select or configure them in practice.

## Plugin Architecture

Every Bandit check is a Python module that exposes one or more decorated test functions. These are discovered at import time by the core extension loader, exposed as `extension_loader.MANAGER.plugins`, and surfaced to the CLI as a flat list of `id` + `name` pairs.

```mermaid
flowchart LR
    A[bandit/plugins/*.py] -->|decorated test fns| B(extension_loader.MANAGER)
    B --> C[PluginRegistry<br/>plugins + blacklist]
    C --> D[bandit/cli/main.py<br/>--tests / --skip / -t / -s]
    C --> E[bandit/cli/config_generator.py<br/>gen_config]
    C --> F[BanditManager<br/>profile include/exclude]
    F --> G[AST visitor results]
```

The registry distinguishes two kinds of entries. `extension_mgr.plugins_by_id` holds the AST checks that run against the source, while `extension_mgr.blacklist` holds blacklist-style lookups such as hardcoded-password string detection. The CLI builds its `Available tests` help text by walking both collections, which is why a single `bandit --help` output lists plugin IDs and blacklist IDs together. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)

Test IDs are short alphanumeric tokens such as `B101`, `B406`, or `B613`. They are stable across releases and are what users put in `.bandit` config files, `setup.cfg`, or `pyproject.toml` to opt in or out of a check. Source: [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)

## Built-in Check Categories

The built-in checks are organized by vulnerability family. Each family lives in its own module under `bandit/plugins/` and registers one or more test functions with a common prefix.

| Plugin module | Test ID prefix | What it flags |
|---|---|---|
| `asserts.py` | B101 | Use of `assert` in production code paths |
| `exec.py` | B102 | Calls to `exec`, `eval`, or `compile` of dynamic code |
| `general_hardcoded_password.py` | (blacklist) | String literals that look like passwords or secrets |
| `injection_shell.py` | B6xx | `os.system`, `subprocess` with `shell=True`, unquoted format-string shell calls |
| `injection_sql.py` | B6xx | String-built SQL passed to `execute` / `executemany` |

These are the families exercised by the Bandit test suite and referenced in the release notes. Source: [bandit/plugins/asserts.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/asserts.py), [bandit/plugins/exec.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/exec.py), [bandit/plugins/general_hardcoded_password.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/general_hardcoded_password.py), [bandit/plugins/injection_shell.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/injection_shell.py), [bandit/plugins/injection_sql.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/injection_sql.py)

A practical example: `B101 assert_used` reports any `assert` statement, which is a common source of false positives in `test_*.py` files. Community discussion in issue #346 has long requested a per-file-pattern override so that B101 can stay enabled globally but be muted inside pytest test trees. Source: [bandit/plugins/asserts.py](https://github.com/PyCQA/bandit/blob/main/bandit/plugins/asserts.py)

## Selecting and Configuring Checks

Bandit provides three layered ways to control which plugins run.

### CLI flags

The `-t/--tests` and `-s/--skip` flags accept comma-separated test IDs. The CLI parses them, merges them into the active profile, and calls `extension_mgr.validate_profile(profile)` to ensure no test appears in both lists. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)

### Project config files

A `.bandit` file, a `setup.cfg` `[bandit]` section, or a `pyproject.toml` `[tool.bandit]` table can all carry the same options. The config layer is read by `_get_options_from_ini`, which walks target directories looking for a `.bandit` file unless `--ini` is passed explicitly. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)

Note that two long-standing community pain points live here: issue #488 and #657 report that excluded paths from `.bandit` have been silently ignored in 1.6.x releases, and issue #212 plus #550 request first-class support for `setup.cfg` and `pyproject.toml`. Users on recent 1.9.x releases should re-verify the `exclude` and `skips` keys after upgrading.

### Per-plugin configuration

Plugins that need configuration expose a `_takes_config` decorator and a `gen_config(function._takes_config)` helper. The config generator CLI walks `extension_loader.MANAGER.plugins`, imports each plugin's module, and dumps a YAML template containing every available knob with its default. This is the supported way to tune things like the SQL injection wordlist or the shell-injection skipping sets. Source: [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)

A minimal config file that turns on only the shell and SQL injection families looks like:

```yaml
tests:
  - B602
  - B603
  - B604
  - B605
  - B606
  - B607
skips:
  - B101
```

## Severity, Confidence, and `# nosec`

Once a plugin reports a finding, the result carries two integer ranks (severity and confidence). The CLI converts them with the `constants.RANKING` table and `args.severity` / `args.confidence` counters, so `-i`/`-ii`/`-iii` and `-l`/`-ll`/`-lll` filter findings before they reach the report. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)

Individual lines can be opted out with a trailing `# nosec` comment. The `--ignore-nosec` flag disables that escape hatch globally, which is useful for CI gates that want every check to be answered explicitly. Source: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)

## Community Notes

- The most frequently discussed "annoyance" is B101 in pytest trees (#346). The supported workaround today is the global `-s B101` flag or a per-file `# nosec` marker; per-glob disabling inside `.bandit` is still an open request.
- Configuration portability is an active theme. Issues #212 and #550 track `setup.cfg` and `pyproject.toml` support respectively. On 1.9.x the `.bandit` file remains the most reliably parsed format, but `setup.cfg` sections are also accepted via the same INI parser path.
- The `bandit-baseline` subcommand is implemented in [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py). It shells out to `bandit` twice (parent commit, current commit), then diffs JSON output. Baseline files must therefore be JSON, and the `-o` flag is rejected by the baseline tool to avoid clobbering its own report file.
- The 1.9.4 release notes mention a crash fix for B613 when reading from stdin. If a check is fed `python -c` content or `bandit -`, the AST visitor must be able to handle a missing filename; recent plugin updates cover this case.

## See Also

- Bandit CLI entry point and flag reference: [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)
- Config generator and per-plugin defaults: [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)
- Baseline diffing workflow: [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py)
- Plugin discovery and the test ID registry: [bandit/core/extension_loader.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/extension_loader.py)

---

<a id='page-4'></a>

## Configuration, Output Formatters & Extensibility

### Related Pages

Related topics: [Overview, Installation & CLI Usage](#page-1), [Core Engine Architecture & Data Flow](#page-2), [Security Plugins & Built-in Checks](#page-3)

<details>
<summary>Related Source Files</summary>

The following source files were used to generate this page:

- [bandit/cli/main.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)
- [bandit/cli/baseline.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py)
- [bandit/cli/config_generator.py](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)
- [bandit/core/config.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/config.py)
- [bandit/core/constants.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/constants.py)
- [bandit/core/extension_loader.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/extension_loader.py)
- [bandit/core/manager.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/manager.py)
- [bandit/core/utils.py](https://github.com/PyCQA/bandit/blob/main/bandit/core/utils.py)
</details>

# Configuration, Output Formatters & Extensibility

Bandit is a static analyzer that scans Python code for common security issues. To make it usable across projects of varying scale, Bandit exposes three orthogonal extension surfaces: a layered **configuration system** (CLI flags, `.bandit` INI files, and YAML profiles), a **formatter plugin system** that controls how findings are presented, and a generic **extension manager** that discovers and registers test plugins, blacklists, and formatters at runtime. This page documents how those surfaces are implemented and how they interact.

## 1. Configuration System

### 1.1 Configuration Sources and Precedence

Bandit accepts configuration from three distinct sources. The order in which they are merged is implemented in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py):

1. **Built-in defaults** registered through [`bandit/core/constants.py`](https://github.com/PyCQA/bandit/blob/main/bandit/core/constants.py) (for example `EXCLUDE`, `log_format_string`).
2. **Project-level `.bandit` INI file**, discovered by walking the target tree for files literally named `.bandit` via `fnmatch.filter(filenames, ".bandit")` in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py). The file may also be supplied explicitly with `--ini`.
3. **Command-line arguments** parsed by `argparse`.

The helper `_log_option_source` in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) implements the precedence rule: an explicit CLI value always wins; otherwise an INI value is preferred over the default. The function also emits an `INFO` log line such as `Using command line arg for excluded paths` so users can audit which source actually took effect.

### 1.2 The `.bandit` INI Format

The INI parser reads a `[bandit]` section. Recognized keys include `exclude`, `tests`, `skips`, `targets`, `recursive`, `aggregate`, `number`, `profile`, `level`, `confidence`, `format`, `msg-template`, `output`, `verbose`, `debug`, `quiet`, `ignore-nosec`, and `baseline` (mapped via repeated `_log_option_source` calls in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py)). The community has repeatedly asked for richer filtering inside `.bandit`, for example in issue #346 requesting the ability to skip `B101` only for files matching a glob — that capability is not implemented today; exclusions are path-glob based only.

### 1.3 YAML Profiles and the Config Generator

When a richer, per-plugin configuration is needed (for example tuning the blacklist word lists for `B324`), Bandit reads a YAML profile through [`bandit/core/config.py`](https://github.com/PyCQA/bandit/blob/main/bandit/core/config.py) and the `BanditConfig` class. Profiles are selected with `-p PROFILE` and merged on top of the INI layer. `_get_profile` in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) looks up the named profile under `config.get_option("profiles")`, falling back to the global `tests`/`skips` options when no profile name is supplied.

The CLI ships a sibling tool, [`bandit/cli/config_generator.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py), that introspects every loaded plugin via `extension_loader.MANAGER.plugins`, calls `gen_config(plugin._takes_config)` for plugins that expose configuration metadata, and dumps a fully-commented YAML template using `yaml.safe_dump`. This lets users bootstrap a profile without hand-writing keys.

## 2. Output Formatters

### 2.1 Built-in Formats

Formatters are loaded by [`bandit/core/extension_loader.py`](https://github.com/PyCQA/bandit/blob/main/bandit/core/extension_loader.py) through the `extension_mgr.formatter_names` registry, which is consumed by the `--format` argparse choice list in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py). The default is detected dynamically: `screen` is used when `sys.stdout.isatty()` and `NO_COLOR` is unset, otherwise `txt` is chosen, ensuring CI logs remain plain.

The custom format is intentionally minimal — it only enables formatting when `--format custom` is set together with `--msg-template`, otherwise `argparse.error` is raised. The epilog in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) lists the available template tags: `{abspath}`, `{relpath}`, `{line}`, `{col}`, `{test_id}`, `{severity}`, `{msg}`, `{confidence}`, and `{range}`. Python `str.format` mini-language is fully supported, so width and alignment specifiers work.

### 2.2 Output Targets and Aggregation

Two flags shape output delivery:

| Flag | Purpose | Source |
| --- | --- | --- |
| `-o / --output` | Redirects the rendered report to a file (defaults to `sys.stdout`) | [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) |
| `-a / --aggregate` | Aggregates findings by `file` (default) or by `vuln` (issue identity) | [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) |
| `-n / --number` | Limits the code-context window printed around each finding | [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) |

### 2.3 Baseline Comparison

For diff-driven reviews, the [`bandit-cli-baseline`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/baseline.py) entry point runs Bandit against both `HEAD~1` and `HEAD`, writes the parent run to a JSON file (`-f json -o bandit_baseline.json_`), then invokes the current run with `-b baseline.json_` so only newly introduced issues are reported. The tool uses `git.Repo(os.getcwd())`, requires a clean working tree, and refuses to run if the JSON baseline file already exists.

## 3. Extensibility Architecture

Bandit's extensibility rests on a single `extension_loader` namespace created by `bandit.core.extension_loader` ([source](https://github.com/PyCQA/bandit/blob/main/bandit/core/extension_loader.py)). It exposes three registries:

```mermaid
flowchart LR
    A[Python entry points<br/>bandit.plugins / bandit.formatters / bandit.blacklists] --> B[extension_loader.MANAGER]
    B --> C[Test Plugins<br/>extension_mgr.plugins_by_id]
    B --> D[Formatters<br/>extension_mgr.formatter_names]
    B --> E[Blacklists<br/>extension_mgr.blacklist / blacklist_by_id]
    C --> F[BanditManager]
    D --> F
    E --> F
    F --> G[Issue results]
```

The `BanditManager` in [`bandit/core/manager.py`](https://github.com/PyCQA/bandit/blob/main/bandit/core/manager.py) consumes these registries to populate its `_plugins`, `_blacklist_by_test_id`, and formatter maps. Each plugin may declare `_takes_config`, which is what the config generator iterates over. Plugins may also expose `gen_config` to advertise their defaults (see the loop in [`bandit/cli/config_generator.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/config_generator.py)).

External packages add capabilities simply by registering Python entry points under the `bandit.plugins`, `bandit.formatters`, and `bandit.blacklists` groups; no Bandit source changes are required. The `_log_info` function in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) prints which tests are included via the profile versus the CLI, which is the canonical way to confirm a third-party plugin was discovered.

## 4. Known Limitations and Community Workarounds

Several pain points surface repeatedly in the issue tracker and are worth documenting so contributors do not rediscover them:

- **`-x` exclude precedence**: Issue #488 reported that the `exclude` key in `.bandit` was silently overridden by the default `EXCLUDE` constant when `-x` was also passed. The current `_log_option_source` logic in [`bandit/cli/main.py`](https://github.com/PyCQA/bandit/blob/main/bandit/cli/main.py) fixes this by clearly preferring the CLI when present, but the same logic was briefly regressed (issue #657), so users upgrading from 1.6.x should re-test exclusions.
- **Alternate config files**: Issues #212 and #550 ask for `setup.cfg` and `pyproject.toml` support. Neither is currently implemented; the only project-level config file Bandit auto-discovers is `.bandit`.
- **Per-file skip rules**: Issue #346 requests a "one-liner" to skip `B101` only for `tests/` files. As of release 1.9.4 this requires either splitting the scan into multiple invocations or using per-line `# nosec` comments respected via the `ignore_nosec` flag.

## See Also

- [Core Architecture & Manager](architecture.md)
- [Built-in Plugins Reference](plugins.md)
- [Output Formatters Reference](formatters.md)

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Pitfall Log

Project: PyCQA/bandit

Summary: Found 8 structured pitfall item(s), including 1 high/blocking item(s). Top priority: Security or permission risk - Security or permission risk requires verification.

## 1. Security or permission risk - Security or permission risk requires verification

- Severity: high
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/PyCQA/bandit/issues/1397

## 2. Capability evidence risk - Capability evidence risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: README/documentation is current enough for a first validation pass.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: capability.assumptions | https://github.com/PyCQA/bandit

## 3. Maintenance risk - Maintenance risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a maintenance risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PyCQA/bandit

## 4. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: downstream_validation.risk_items | https://github.com/PyCQA/bandit

## 5. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: no_demo
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: risks.scoring_risks | https://github.com/PyCQA/bandit

## 6. Security or permission risk - Security or permission risk requires verification

- Severity: medium
- Evidence strength: source_linked
- Finding: Project evidence flags a security or permission risk. Review the linked source before relying on this workflow.
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: community_evidence:github | https://github.com/PyCQA/bandit/issues/1432

## 7. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: issue_or_pr_quality=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PyCQA/bandit

## 8. Maintenance risk - Maintenance risk requires verification

- Severity: low
- Evidence strength: source_linked
- Finding: release_recency=unknown。
- User impact: May increase setup, validation, or first-run risk for the user.
- Evidence: evidence.maintainer_signals | https://github.com/PyCQA/bandit

<!-- canonical_name: PyCQA/bandit; human_manual_source: deepwiki_human_wiki -->
