1. Introduction
In modern software development, shipping features fast often comes at the cost of security. Developers write hundreds of lines of code per day, and subtle vulnerabilities can slip through unnoticed โ until an attacker finds them first.
This is exactly where SAST (Static Application Security Testing) tools come in. Unlike dynamic analysis, SAST tools scan your source code without executing it, catching security flaws early in the development cycle before they ever reach production.
In this article, I'll walk you through applying Bandit โ one of the most popular open-source SAST tools for Python โ to the backend of a real web vulnerability scanner application built with FastAPI. I'll show you the exact commands I ran, the findings Bandit reported, and how I fixed the most critical vulnerability.
2. Tool Overview: What is Bandit?
Bandit is an open-source SAST tool developed by the PyCQA (Python Code Quality Authority) community. It was originally created at OpenStack and is now one of the most widely used security linters for Python projects.
How it works:
Bandit parses each Python file into an Abstract Syntax Tree (AST) and runs a set of security-focused plugins against the tree nodes. Each plugin maps to a specific vulnerability class (e.g., hardcoded passwords, insecure SSL, dangerous function calls).
Key facts:
- ๐ Language: Python only
- ๐ฆ Install:
pip install bandit - ๐ Output formats: TXT, JSON, HTML, XML, CSV, SARIF
- ๐ IDE integration: VS Code, PyCharm (via plugins)
- โ CI/CD ready: GitHub Actions, GitLab CI, Jenkins
- ๐ License: Apache 2.0 (free and open-source)
Vulnerability categories covered:
- Hardcoded credentials and secrets
- Insecure use of cryptography
- SQL injection patterns
- Dangerous function calls (
eval,exec,os.system) - SSL/TLS misconfigurations
- Insecure temp file usage
- Use of
assertin production code
3. Target Application
The application analyzed is VulnScan Pro, a web vulnerability scanner platform built as part of a university security engineering project (Course: SI784 - 2026).
Stack:
- Backend: Python 3.12 + FastAPI
- Database: SQLite (development) / MySQL (production)
- Authentication: JWT + bcrypt
- AI Integration: DeepSeek AI for vulnerability analysis
The backend contains approximately 2,410 lines of Python code spread across these key modules:
| File | Purpose |
|---|---|
main.py |
FastAPI app setup, CORS, middleware |
auth.py |
JWT authentication, bcrypt hashing |
scanner.py |
Core vulnerability scanning engine (737 lines) |
models.py |
SQLAlchemy database models |
routes/ |
API route handlers |
vulnerable_app.py |
Intentionally vulnerable test application |
4. Installation & Setup
Installing Bandit is straightforward with pip:
# Install Bandit
pip install bandit
# Verify installation
bandit --version
# bandit 1.9.4
Optional: Create a Bandit configuration file (.bandit) to skip test directories:
[bandit]
exclude_dirs = ['tests', '__pycache__']
skips = ['B101']
Tip: You can also install Bandit as a pre-commit hook to catch issues before every commit:
# .pre-commit-config.yaml repos: - repo: https://github.com/PyCQA/bandit rev: 1.9.4 hooks: - id: bandit args: ["-ll"]
5. Running the Analysis
I ran Bandit against the entire backend directory using the following commands:
Full scan (all severity levels):
bandit -r ./backend -f txt
Scan targeting medium and high severity only:
bandit -r ./backend -f txt --severity-level medium
Export as HTML report:
bandit -r ./backend -f html -o bandit_report.html
Real output summary from the scan:
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 55
Medium: 1
High: 2
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 3
High: 55
Total lines of code: 2,410
Total lines skipped (#nosec): 0
Files skipped: 0
Bandit scanned 2,410 lines of Python code across all backend files and found 58 total issues in under 2 seconds: 2 High, 1 Medium, and 55 Low severity findings.
6. Results & Findings
Summary Table
| # | Severity | Rule ID | Description | File | OWASP Top 10 |
|---|---|---|---|---|---|
| 1 | ๐ด HIGH | B501 | SSL cert verification disabled | scanner.py:31 |
A02 - Cryptographic Failures |
| 2 | ๐ด HIGH | B501 | SSL cert verification disabled | scanner.py:610 |
A02 - Cryptographic Failures |
| 3 | ๐ก MEDIUM | B108 | Insecure temp directory /tmp
|
report_routes.py:17 |
A05 - Security Misconfiguration |
| 4-58 | ๐ข LOW | B101 |
assert used in test code |
tests/*.py |
โ |
๐ด Finding #1 โ HIGH โ SSL Certificate Verification Disabled
Rule: B501 request_with_no_cert_validation
File: scanner.py, lines 31 and 610
OWASP Top 10: A02:2021 - Cryptographic Failures
CWE: CWE-295 โ Improper Certificate Validation
# VULNERABLE CODE โ scanner.py, lines 24-35
def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
try:
return requests.get(
url,
timeout=timeout,
headers=HEADERS_UA,
allow_redirects=allow_redirects,
verify=False, # โ THIS IS THE PROBLEM
**kwargs
)
except RequestException:
return None
verify=False completely disables SSL certificate validation. This means the scanner itself is vulnerable to Man-in-the-Middle (MITM) attacks โ an attacker intercepting traffic between the scanner and a target site could inject malicious responses, causing the scanner to report false results or leak sensitive scan data.
๐ก Finding #2 โ MEDIUM โ Insecure Temp Directory Usage
Rule: B108 hardcoded_tmp_directory
File: routes/report_routes.py, line 17
OWASP Top 10: A05:2021 - Security Misconfiguration
CWE: CWE-377 โ Insecure Temporary File
# VULNERABLE CODE โ report_routes.py, line 17
REPORTS_DIR = os.getenv("REPORTS_DIR", "/tmp/vulnscan_reports")
Using /tmp as a fallback for storing security reports is a risk. On shared Linux systems, /tmp is world-readable, meaning other processes could read generated vulnerability reports containing sensitive scan results about target systems.
๐ข Findings #4-58 โ LOW โ assert in Test Code
Rule: B101 assert_used
Files: tests/test_scanner.py
CWE: CWE-703
All 55 Low-severity findings are assert statements in the test suite โ which is standard Python testing practice. These are false positives in this context and can be suppressed by configuring Bandit to skip the tests/ directory.
7. Fixing the Critical Vulnerability
Fixing B501 โ SSL Certificate Verification
Before (Vulnerable):
def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
try:
return requests.get(
url,
timeout=timeout,
headers=HEADERS_UA,
allow_redirects=allow_redirects,
verify=False, # SSL verification disabled โ MiTM risk!
**kwargs
)
except RequestException:
return None
After (Fixed):
import certifi
def safe_get(url: str, timeout: int = TIMEOUT, allow_redirects: bool = True, **kwargs):
"""
Safe HTTP GET with proper SSL verification using Mozilla's CA bundle.
"""
try:
return requests.get(
url,
timeout=timeout,
headers=HEADERS_UA,
allow_redirects=allow_redirects,
verify=certifi.where(), # Use trusted Mozilla CA bundle
**kwargs
)
except requests.exceptions.SSLError:
# SSL error โ log it, don't silently ignore
return None
except RequestException:
return None
# Install certifi
pip install certifi
# Re-run Bandit to confirm the fix
bandit scanner.py --severity-level medium
# Result: No issues identified. โ
Why
certifi.where()? Thecertifipackage provides Mozilla's curated collection of root certificates, ensuring reliable SSL validation across all platforms without depending on the OS certificate store.
Fixing B108 โ Temp Directory
# BEFORE
REPORTS_DIR = os.getenv("REPORTS_DIR", "/tmp/vulnscan_reports")
# AFTER โ use an application-specific directory with restricted permissions
import tempfile, pathlib
DEFAULT_REPORTS_DIR = pathlib.Path(__file__).parent / "reports"
REPORTS_DIR = pathlib.Path(os.getenv("REPORTS_DIR", str(DEFAULT_REPORTS_DIR)))
REPORTS_DIR.mkdir(mode=0o700, parents=True, exist_ok=True) # Owner-only permissions
8. Pros & Cons of Bandit
โ Strengths
| Feature | Detail |
|---|---|
| Zero configuration | Works instantly with pip install bandit
|
| Extremely fast | Analyzed 2,410 LOC in under 2 seconds |
| SARIF output | Native GitHub Code Scanning integration |
| Granular control | Skip specific rules, files, or directories |
| Educational | Every finding links to CWE, OWASP, and detailed docs |
โ ๏ธ Limitations
| Limitation | Impact |
|---|---|
| Python only | Cannot analyze JS, TypeScript, Go, etc. |
| High Low-severity noise | 55 of 58 findings were test assert statements |
| No data flow tracking | Cannot follow tainted data across function calls |
| No dependency analysis | Won't catch CVEs in requirements.txt packages |
| No runtime context | May flag dead code or unreachable paths |
Effective false positive rate: ~95% of Low findings in this scan were test-file noise. With a proper .bandit config, the actionable issue count drops to exactly 3 real findings.
9. Conclusion
Applying Bandit to VulnScan Pro's FastAPI backend in under 5 minutes revealed 3 real security issues in 2,410 lines of code โ most critically, an SSL certificate verification bypass (verify=False) in the core scanning engine that could expose the tool to MITM attacks.
The irony here is significant: a security scanner was itself vulnerable to the kind of attack it was designed to detect. This underscores why SAST is essential even for security-focused projects.
Key lessons from this analysis:
-
verify=Falseinrequestsis a red flag โ always usecertifior your system CA bundle - Never use
/tmpas a default for sensitive data without explicit permission restrictions - Configure Bandit to skip test directories to eliminate false positive noise
For any Python project, adding Bandit to your CI/CD pipeline takes less than 10 minutes and can catch critical security issues automatically on every push:
# .github/workflows/sast.yml
- name: Run Bandit SAST
run: bandit -r ./backend --severity-level medium -f sarif -o bandit.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: bandit.sarif
SAST is not a silver bullet, but it is the cheapest and fastest layer of your DevSecOps defense-in-depth strategy. Ship secure code โ scan before you push.
Written by Mariela Ramos ยท SI784 Security Engineering ยท 2026
Tools: Bandit 1.9.4 ยท Python 3.12 ยท FastAPI 0.111 ยท OWASP Top 10 2021
Top comments (0)