CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/language-native-sast

Language-native SAST linters - the first-party "linter as SAST" family that runs inside each ecosystem's standard toolchain with no separate scanner server: Bandit (Python, 60+ B-rules, severity x confidence filtering), gosec (Go, 40+ G-rules, AST + SSA taint tracking, golangci-lint integration), eslint-plugin-security + eslint-plugin-no-unsanitized (JS/TS, 14 detect-* rules + DOM-sink XSS), and PMD's Apex security ruleset (Salesforce, ApexSOQLInjection / ApexCRUDViolation / ApexSharingViolations). Covers the shared adoption pattern - install as a dev dependency, first scan, suppression-with-justification discipline, baseline-diff adoption for legacy code, SARIF output + CI gating - with per-tool depth in references. Use when a repo needs in-toolchain security linting for Python, Go, JavaScript/TypeScript, or Apex; for cross-language or cross-file taint analysis use semgrep-rules / codeql-queries instead.

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

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