CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

testland/language-native-sast

Use when a repo needs security linting inside its own toolchain: Bandit for Python, gosec for Go, eslint-plugin-security and no-unsanitized for JavaScript/TypeScript, or PMD's Apex rules for Salesforce, with justified suppressions, baselines for legacy code, SARIF and CI gates.

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

bandit.mdreferences/

Bandit - Python language-native SAST

Per-tool reference for language-native-sast. Bandit is the Python-specific SAST originally from OpenStack Security.

Per bandit.readthedocs.io/en/latest/start.html:

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.

Install

Per bd-start:

pip install bandit[toml]

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

Basic recursive scan

Per bd-start:

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

Severity + confidence filtering

Per bd-start 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.

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.

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/.

False-positive triage (MANDATORY)

Per the canonical Bandit workflow, the 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.

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

Baseline for legacy adoption

Capture current findings once, then gate only on new ones:

bandit -r . -f json -o old-findings.json      # capture
bandit -r . --baseline old-findings.json      # CI: only NEW findings fail

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/)

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
# nosec without rule IDSuppresses ALL rules on that line; over-broad# nosec B602 (specific rule)
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"]
No baseline; every legacy finding blocks CITeam disables BanditUse --baseline old-findings.json against a captured baseline

Limitations

  • Python-only; for Go see gosec.md, for JS/TS see eslint-security.md.
  • 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.

Sources

  • bd-start - install + basic scan reference
  • bandit.readthedocs.io - full documentation
  • bandit.readthedocs.io/en/latest/plugins/ - rule catalog
  • github.com/PyCQA/bandit - repository

SKILL.md

tile.json