Testland
Browse all skills & agents

bandit-python

Configures and runs Bandit - Python-only SAST covering 60+ rule IDs across 7 categories (B1xx-B7xx: misc, app, crypto, imports, injections, XSS); `bandit -r .` scan, `--severity-level` + `--confidence-level` filtering, `# nosec`/`# nosec B404` per-line and per-rule suppression, `pyproject.toml [tool.bandit]` config. Use for a focused, low-overhead Python SAST in pre-commit / CI. Python-only: for Go use gosec-go, for cross-language pattern SAST use semgrep-rules; to merge Bandit findings with other scanners into one gate use multi-tool-finding-triage - not this for non-Python code.

Install with skills.sh (any agent)

npx skills add testland/qa --skill bandit-python
View source

bandit-python

Overview

Per bandit.readthedocs.io/en/latest/start.html (opens in new window):

Bandit is the Python-specific SAST originally from OpenStack Security. Each finding has two dimensions:

  • Severity (LOW / MEDIUM / HIGH) - how dangerous if exploited
  • Confidence (LOW / MEDIUM / HIGH) - how certain Bandit is the finding is real

The two-dimensional scoring lets you tune false-positive vs false-negative tradeoff per project.

When to use

  • Repo has Python source (Bandit is Python-only).
  • The team needs a focused Python SAST integrated with pre-commit
    • standard CI.
  • Quick triage of low-effort security wins (hardcoded passwords, shell=True in subprocess, weak crypto).
  • Pair with semgrep-rules (broader, cross-language) for layered coverage.

Step 1 - Install

Per bd-start (opens in new window):

pip install bandit[toml]

The [toml] extra enables pyproject.toml config support.

Step 2 - Basic recursive scan

Per bd-start (opens in new window):

bandit -r path/to/your/code

Common usage:

bandit -r .                              # current dir, recursive
bandit -r src/ tests/                    # multiple paths
bandit -r . -x tests,vendor              # exclude dirs
bandit -r . -ll                           # minimum LOW confidence + LOW severity

Step 3 - Severity + confidence filtering

Per bd-start (opens in new window) verbatim CLI usage:

bandit examples/*.py -n 3 --severity-level=high

Combined two-dimensional filtering:

# Only HIGH severity findings with HIGH confidence
bandit -r . --severity-level=high --confidence-level=high

# All MEDIUM+ severity, any confidence
bandit -r . --severity-level=medium

The two flags compose; neither is a strict subset of the other.

Step 4 - pyproject.toml config

# pyproject.toml
[tool.bandit]
exclude_dirs = ["tests", "vendor", "build"]
skips = ["B101"]                       # skip "assert used" rule globally
tests = ["B201", "B301"]                # only run flask + pickle checks (whitelist mode)

[tool.bandit.assert_used]
skips = ["**/test_*.py", "**/*_test.py"]

tests = [...] activates whitelist mode (run ONLY listed checks); skips = [...] activates blacklist mode (run all checks except listed). They're mutually exclusive.

Step 5 - Rule ID catalog

Bandit rules are organized by category prefix:

PrefixCategoryExamples
B1xxMiscellaneousB101 assert used, B102 exec used, B105 hardcoded password string
B2xxApplication/FrameworkB201 flask debug=True, B202 tarfile unsafe extract
B3xxBlacklists / CryptographyB301 pickle, B303 MD5, B311 random for crypto, B321 ftplib (cleartext), B324 hashlib weak hash, B403 import_pickle
B4xxImportsB401 import_telnetlib, B404 subprocess imported, B405 import_xml_etree, B413 import_pyCrypto
B5xx(less common - varies)
B6xxInjectionsB602 subprocess shell=True, B603 subprocess without shell=False, B608 sql_injection, B610 django extra used (sql injection-prone)
B7xxXSS / templatingB701 jinja2 autoescape false, B703 django mark_safe

Full catalog: bandit.readthedocs.io/en/latest/plugins/.

Step 6 - False-positive triage (MANDATORY)

Per the canonical Bandit workflow, three suppression layers:

MechanismExampleWhen to use
Per-line # nosecsubprocess.run(cmd, shell=True) # nosec B602Single-line exception with rule ID
Per-rule # nosec# nosec B404 (above import statement)Rule-specific suppression
[tool.bandit] skips = ["..."]Per-project rule disableCategorical disable (test fixtures, etc.)
[tool.bandit] exclude_dirs = ["..."]Per-directory excludeGenerated code, vendored libs

Justification template (mandatory in code):

import subprocess
# nosec B602 - Reason: command is statically defined, no user input
# Reviewer: alice@example.com (2026-05-15)
# Expires: 2026-12-15
result = subprocess.run("ls -la /tmp", shell=True, check=True)

For rules that can never apply (e.g., B311 random is fine outside crypto contexts), prefer per-rule disable in pyproject.toml over per-line suppressions - fewer comments to maintain, easier to audit at the project level.

Cadence: every quarter, grep for # nosec patterns lacking # Reason: lines; flag for review.

Step 7 - Output formats

bandit -r . -f txt                      # default human-readable
bandit -r . -f json -o bandit.json      # JSON for finding triage
bandit -r . -f sarif -o bandit.sarif    # SARIF for GitHub Code Scanning
bandit -r . -f xml -o bandit.xml        # JUnit XML
bandit -r . -f html -o bandit.html      # standalone HTML report
bandit -r . -f csv -o bandit.csv        # CSV
bandit -r . -f screen                    # colorized terminal

Step 8 - Pre-commit integration

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.10
    hooks:
      - id: bandit
        args: ["--severity-level=medium", "--confidence-level=medium"]
        files: \.py$
        exclude: ^(tests/|venv/|.venv/)

Step 9 - CI integration

jobs:
  bandit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v5
        with: { python-version: '3.13' }
      - run: pip install bandit[toml]
      - run: bandit -r . -f sarif -o bandit.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: bandit.sarif }

For PR-blocking: pipe through --severity-level high to limit noise.

Anti-patterns

Anti-patternWhy it failsFix
--severity-level=low everywhereNoise overwhelms; team disablesStart --severity-level=medium; ratchet down (Step 3)
# nosec without rule IDSuppresses ALL rules on that line; over-broad# nosec B602 (specific rule, Step 6)
Skip --confidence-level filterLOW confidence findings mostly false positivesPair --severity-level=high --confidence-level=medium for triage
Run on tests directoryTest-only patterns (assert, pickle) trigger noiseexclude_dirs = ["tests"] (Step 4)
No baseline; every legacy finding blocks CITeam disables BanditUse --baseline old-findings.json against a captured baseline

Limitations

  • Python-only; for Go use gosec-go, for JS use Semgrep.
  • Plugin-based detection misses some patterns that Semgrep custom rules can catch.
  • No native cross-file taint analysis (use codeql-queries for that).
  • Rule depth varies - well-maintained for OpenStack-era patterns; newer Python ecosystem (FastAPI, async patterns) coverage thinner.

References

  • bd-start (opens in new window) - install + basic scan reference
  • bandit.readthedocs.io - full documentation
  • bandit.readthedocs.io/en/latest/plugins/ - rule catalog
  • github.com/PyCQA/bandit - repository
  • semgrep-rules, sonarqube-rules, codeql-queries, gosec-go - sister scanners

Related skills

codeql-queries

Configures and runs GitHub CodeQL - semantic-database SAST with queries written in the CodeQL declarative query language; supports `codeql database create` (per-language) + `codeql database analyze` with --format=sarif; ships query packs (`codeql/javascript-queries`, `codeql/python-queries`, `codeql/java-queries`, `codeql/go-queries`, etc.); integrates with GitHub Code Scanning via SARIF upload; suppression via inline comment + sarif-filter + Security-tab dismissal. Use when the team uses GitHub-hosted repos and needs deep semantic SAST beyond pattern matching (cross-file taint flows, dataflow analysis).

eslint-security-rules

Configures and runs `eslint-plugin-security` (14 detect-* rules covering injection, path traversal, ReDoS, unsafe buffers, and bidi trojan-source) plus `eslint-plugin-no-unsanitized` (DOM XSS via `innerHTML`, `outerHTML`, `document.write`, `insertAdjacentHTML`) as the JS/TS first-party SAST layer; covers flat config setup, per-rule suppression with justification templates, SARIF output via `@microsoft/eslint-formatter-sarif` for GitHub Code Scanning upload, and CI gating on ESLint exit code 1. Use when the project is JS or TS and needs an in-process security lint pass without a separate SAST server.

gosec-go

Configures and runs gosec - Go-only SAST covering 40+ rule IDs (G101 hardcoded creds, G104 unhandled errors, G304 path traversal, G401 weak crypto, G601 memory aliasing) via Go AST + SSA taint tracking; `gosec ./...` scan, `#nosec G404 -- justification` suppression, `--fmt sarif|json|junit-xml|html`, golangci-lint integration. Use for a focused Go SAST wired into golangci-lint / CI. Go-only: for Python use bandit-python, for cross-language pattern SAST use semgrep-rules; to merge gosec findings with other scanners into one gate use multi-tool-finding-triage - not this for non-Go code.

multi-tool-finding-triage

Merges two or more security scanner reports into one gate. Use when you need a single BLOCK or PASS decision from multiple scanners instead of reading N separate reports. Normalizes each report into one common finding format (a canonical `Finding`), deduplicates on a per-domain key while recording which scanners agree (`caught_by` consensus), validates a waiver (finding-suppression) file, rejecting any missing `expires:` / `approved_by:` / `reason:` or expired, enriches CVE findings with EPSS (exploit-probability) and CISA KEV (known-exploited catalog), then applies a `fail_on` severity threshold to emit BLOCK or PASS plus a bucketed pull-request comment. Works across static (SAST), dynamic (DAST), secret, dependency (SCA), container, and IaC scanners. To run a single scanner instead use semgrep-rules, codeql-queries, bandit-python, or gosec-go; this runs after them to merge output - the cross-scanner gate, not a single-scanner wrapper.

pmd-apex-rules

Runs PMD's built-in Apex security ruleset (`category/apex/security.xml`) against Salesforce Apex source to detect injection, privilege-escalation, cryptographic, and XSS vulnerabilities; configures custom rulesets for regulated-industry Apex codebases; emits SARIF for GitHub Code Scanning upload; integrates `pmd check` as a PR-blocking CI gate. Use when the codebase contains Salesforce Apex and the team needs SAST coverage for ApexSOQLInjection, ApexCRUDViolation, ApexSharingViolations, or the full 10-rule security category.

semgrep-rules

Configures and runs Semgrep - pattern-based SAST across 30+ languages with the Semgrep Registry rulesets (`p/owasp-top-ten`, `p/default`, `auto`) plus custom YAML rules; integrates `semgrep ci` for PR-blocking gates with `--baseline-commit` diff-aware scanning, per-finding inline `nosemgrep` suppressions, `--exclude` / `--include` path filters, output formats (`--json` / `--sarif` / `--gitlab-sast` / `--junit-xml`), and severity filter (INFO/WARNING/ERROR). Use when the user runs Semgrep, asks about pattern rules, or needs a low-friction SAST gate without semantic-DB setup.

sonarqube-rules

Configures and runs SonarQube / SonarCloud - multi-language SAST + Quality Gate platform with built-in Sonar Way rule profiles + custom rule plugins; integrates `sonar-scanner` with `sonar-project.properties` config; supports Quality Gate definitions including new-code-period blocking, branch + PR analysis, and per-issue suppression via `// NOSONAR` comment or `@SuppressWarnings("squid:RULE_ID")` annotation. Use when the user runs SonarQube Community / Developer / Enterprise edition or SonarCloud, or needs a multi-language SAST + code-quality platform with persistent issue tracking.