Why Bandit for Python Security?
Bandit is an open-source SAST tool developed by the OpenStack Security Project that specializes in analyzing Python code for common security issues. It’s particularly valuable because:
- Python-specific analysis – Understands Python idioms and common patterns
- Plugin-based architecture – Extensible with custom checks
- CI/CD friendly – Designed for automation
- Zero cost – Completely free and open-source
Common Vulnerabilities Bandit Detects
- SQL injection vulnerabilities
- Shell injection risks
- Hardcoded passwords and secrets
- Use of insecure modules
- Input validation issues
- Information disclosure risks
Hands-On: Implementing Bandit
Installation
# Install using pip
pip install bandit
# Or install from source
git clone https://github.com/PyCQA/bandit.git
cd bandit
pip install .
Basic Usage
# Scan a single file
bandit my_script.py
# Scan an entire directory
bandit -r my_project/
# Generate HTML report
bandit -r my_project/ -f html -o report.html
# Scan with specific security level
bandit -r my_project/ -l high
Sample Vulnerable Python Code
# vulnerable_app.py
import os
import pickle
import subprocess
import sqlite3
def process_user_input():
# Vulnerability: Code injection
user_input = input("Enter command: ")
os.system(user_input) # B602: subprocess_popen_with_shell_equals_true
def database_operations():
# Vulnerability: SQL injection
username = input("Enter username: ")
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'") # B608: hardcoded_sql_expressions
def data_deserialization():
# Vulnerability: Insecure deserialization
data = input("Enter serialized data: ")
obj = pickle.loads(data.encode()) # B301: blacklist
Running Bandit Scan
bandit -r vulnerable_app.py -f txt
Sample Bandit Output
Results generated:
>> Issue: [B602:subprocess_popen_with_shell_equals_true] Using subprocess with shell=True
Severity: High Confidence: High
Location: vulnerable_app.py:8
More Info: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html#b602-subprocess-popen-with-shell-equals-true
7 user_input = input("Enter command: ")
8 os.system(user_input)
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
Severity: Medium Confidence: Medium
Location: vulnerable_app.py:15
15 cursor.execute(f"SELECT * FROM users WHERE username="{username}"")
>> Issue: [B301:blacklist] Use of unsafe deserialization function.
Severity: High Confidence: High
Location: vulnerable_app.py:20
20 obj = pickle.loads(data.encode())
Advanced Bandit Configuration
Custom Configuration File
# bandit.yml
exclude_dirs: ['tests', 'venv', 'migrations']
skips: ['B101', 'B102']
tests: ['B301', 'B302', 'B601', 'B602']
targets:
- src/
- app/
output_format: json
verbose: true
Using Configuration File
bandit -c bandit.yml -r my_project/
CI/CD Integration (GitHub Actions)
name: Security Scan with Bandit
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install Bandit
run: pip install bandit
- name: Run Bandit Security Scan
run: bandit -r . -f json -o bandit-report.json
- name: Upload Bandit Report
uses: actions/upload-artifact@v3
with:
name: bandit-report
path: bandit-report.json
Bandit Test Types and Severity Levels
Severity Levels
- Low: Code quality issues, minor security concerns
- Medium: Potential security vulnerabilities
- High: Critical security vulnerabilities
Common Test IDs
- B1xx: Various general tests
- B2xx: Application/framework-specific issues
- B3xx: Blacklisted imports and functions
- B4xx: Using insecure random generators
- B5xx: SSL/TLS issues
- B6xx: Shell injection vulnerabilities
- B7xx: Pickle and YAML deserialization
Custom Bandit Plugins
Creating Custom Tests
# custom_checks.py
import bandit
from bandit.core import test_properties as test
@test.checks('Call')
@test.test_id('B901')
def hardcoded_api_key(context):
"""Check for hardcoded API keys"""
suspicious_strings = ['api_key', 'secret_key', 'password']
if context.call_function_name_qual in suspicious_strings:
return bandit.Issue(
severity=bandit.HIGH,
confidence=bandit.MEDIUM,
text="Potential hardcoded API key detected"
)
Using Custom Plugins
bandit -r my_project/ -p custom_checks.py
Best Practices for Bandit Implementation
1. Integrate Early in Development
# Pre-commit hook example
# .git/hooks/pre-commit
#!/bin/bash
bandit -r . -l high -i
2. Regular Scheduled Scans
# GitHub Actions scheduled scan
on:
schedule:
- cron: '0 2 * * 1' # Weekly scan
3. Baseline Establishment
# Establish baseline ignoring existing issues
bandit -r . --baseline baseline.json
4. Quality Gates
# Fail build on high severity issues
bandit -r . -l high --exit-zero
Limitations and Considerations
While Bandit is powerful, it’s important to understand its limitations:
- Static analysis only – Cannot detect runtime issues
- Python-specific – Only works with Python code
- Pattern-based – May produce false positives/negatives
- No data flow analysis – Limited context awareness
Conclusion
Bandit provides an excellent open-source SAST solution for Python applications. Its ease of use, comprehensive vulnerability detection, and seamless CI/CD integration make it an essential tool for any Python developer concerned with security.
By implementing Bandit in your development workflow, you can catch common security issues early, reduce remediation costs, and build more secure Python applications.