DEV Community

Mauricio Choqueña Choque
Mauricio Choqueña Choque

Posted on

Applying SAST Tools to Real Applications — A Hands-On Look at Bandit

What is SAST, and Why It Matters

Static Application Security Testing (SAST) analyzes source code — without executing it — to find security vulnerabilities before they ever reach production: hardcoded secrets, SQL injection risks, use of insecure functions, weak cryptography, and more. Unlike dynamic testing (DAST), which probes a running application, SAST catches problems at the earliest possible stage: while the code is being written or committed.

OWASP maintains a well-known list of Source Code Analysis Tools covering many languages and licensing models. This article focuses on Bandit, an open-source SAST tool built specifically for Python codebases, chosen here because it's lightweight, free, and easy to integrate into any CI pipeline without vendor lock-in.

Why Bandit

Bandit was originally developed by the OpenStack Security Project and is now maintained under the Python Code Quality Authority (PyCQA). It works by parsing Python source code into an Abstract Syntax Tree (AST) and running a set of security-focused plugins against it — no code execution involved, which makes it fast and safe to run on any repository, including untrusted third-party code.

Key features:

  • 100% open source, no account or license required.
  • Detects common issues: use of eval()/exec(), hardcoded passwords, insecure subprocess calls, weak hashing algorithms (MD5/SHA1 for security purposes), unsafe YAML loading, SQL string concatenation, and more.
  • Configurable severity/confidence thresholds, so teams can fail builds only on high-severity findings.
  • Native CI/CD integration via exit codes, plus JSON/HTML/CSV report output.

Installing and Running Bandit

pip install bandit

# Scan a single file
bandit example.py

# Scan an entire project recursively
bandit -r ./src

# Generate a JSON report for CI pipelines
bandit -r ./src -f json -o bandit_report.json
Enter fullscreen mode Exit fullscreen mode

A Real Example

Consider this deliberately vulnerable Python snippet — the kind of code that often slips into real projects unnoticed:

import hashlib
import subprocess
import yaml

def hash_password(password):
    # Weak hashing algorithm for security purposes
    return hashlib.md5(password.encode()).hexdigest()

def run_backup(filename):
    # Command injection risk: shell=True with unsanitized input
    subprocess.call(f"tar -cvf backup.tar {filename}", shell=True)

def load_config(path):
    with open(path) as f:
        # Unsafe deserialization
        return yaml.load(f)
Enter fullscreen mode Exit fullscreen mode

Running bandit -r . against this file produces output like:
Each finding includes a rule ID (B303, B602, B506), a severity/confidence rating, and the exact file and line number — enough context for a developer to fix the issue without needing a security background.

Fixing the Findings

import hashlib
import subprocess
import yaml

def hash_password(password):
    # Use a strong, purpose-built hashing function instead of MD5
    return hashlib.sha256(password.encode()).hexdigest()

def run_backup(filename):
    # Avoid shell=True; pass arguments as a list instead
    subprocess.call(["tar", "-cvf", "backup.tar", filename])

def load_config(path):
    with open(path) as f:
        # safe_load restricts deserialization to simple Python objects
        return yaml.safe_load(f)
Enter fullscreen mode Exit fullscreen mode

Re-running Bandit against the fixed file returns zero findings — confirming the issues were resolved.

Integrating Bandit into a CI Pipeline

A minimal GitHub Actions workflow that fails the build on any high-severity finding:

name: SAST Scan with Bandit

on: [push, pull_request]

jobs:
  bandit-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - name: Install Bandit
        run: pip install bandit
      - name: Run Bandit
        run: bandit -r ./src -ll
Enter fullscreen mode Exit fullscreen mode

The -ll flag tells Bandit to only report (and fail on) medium and high severity issues, filtering out noise from low-severity findings during early adoption.

Public Repository Example

You can see Bandit applied to a real, public codebase here:

  • github.com/PyCQA/bandit — the tool's own repository, which also runs Bandit against itself as part of its CI pipeline.
  • github.com/OWASP/django-DefectDojo — a large, real-world Django application (a vulnerability management platform, ironically) where Bandit-style static analysis patterns are commonly applied and can be run directly against the source.

Cloning either repo and running bandit -r . locally shows the tool working against production-scale code, not just toy examples.

Where Bandit Fits Among SAST Tools

Tool Language scope License Notes
Bandit Python only Open source (Apache 2.0) Lightweight, AST-based, fast
Brakeman Ruby on Rails only Open source Rails-specific vulnerability patterns
SpotBugs + FindSecBugs Java Open source Bytecode analysis, plugin-based
CodeQL Multi-language Free for public repos Query-based, very powerful but heavier setup
PMD Java, Apex, others Open source General code quality + some security rules

Compared to multi-language commercial platforms, Bandit trades breadth for simplicity: it does one language well, has no licensing cost, and can be added to a pipeline in minutes.

Conclusion

SAST doesn't require an expensive enterprise platform to start delivering value. A focused, open-source tool like Bandit can catch real, exploitable issues — insecure hashing, command injection, unsafe deserialization — directly in the CI pipeline, before code is merged. For teams working primarily in Python, it's a practical, zero-cost entry point into DevSecOps practices, and a good complement to broader multi-language tools as security maturity grows.

Top comments (0)