Home Python Python Security: SAST, Dependency Scanning & Best Practices
Intermediate 3 min · July 14, 2026

Python Security: SAST, Dependency Scanning & Best Practices

Learn how to secure your Python code with SAST tools, dependency scanning, and proven best practices.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Basic knowledge of Python syntax and pip
  • Familiarity with command line and virtual environments
  • Understanding of common web vulnerabilities (SQL injection, XSS) is helpful
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • SAST (Static Application Security Testing) scans source code for vulnerabilities without executing it.
  • Dependency scanning checks third-party packages for known vulnerabilities.
  • Use tools like Bandit, Semgrep, Safety, and Snyk for Python security.
  • Integrate scanning into CI/CD pipelines to catch issues early.
  • Follow secure coding practices: input validation, least privilege, and secrets management.
✦ Definition~90s read
What is Python Security?

Python security involves using SAST tools, dependency scanning, and secure coding practices to protect your applications from vulnerabilities.

Think of your Python code like a house.
Plain-English First

Think of your Python code like a house. SAST is like an inspector who checks the blueprints for weak spots before building. Dependency scanning is like checking the materials you buy from the store for defects. Together, they help you build a secure house before someone breaks in.

Security is often an afterthought in Python development, but with the rise of supply chain attacks and zero-day exploits, it's more critical than ever. Python's rich ecosystem of third-party packages makes it powerful, but also introduces risk: a single vulnerable dependency can compromise your entire application. Static Application Security Testing (SAST) and dependency scanning are two essential practices to catch vulnerabilities early in the development lifecycle. SAST tools analyze your source code for patterns that indicate security flaws—like SQL injection, command injection, or hardcoded secrets—without running the code. Dependency scanning tools check your installed packages against databases of known vulnerabilities (e.g., CVE, PyUp, Snyk). In this tutorial, you'll learn how to set up Bandit and Semgrep for SAST, use Safety and Snyk for dependency scanning, and integrate them into your CI/CD pipeline. We'll also cover secure coding best practices and common pitfalls. By the end, you'll be able to ship Python code with confidence, knowing you've minimized the attack surface.

What is SAST and Why Use It?

Static Application Security Testing (SAST) analyzes source code for security vulnerabilities without executing it. It's a white-box testing method that scans for patterns like SQL injection, cross-site scripting (XSS), command injection, hardcoded secrets, and insecure cryptographic algorithms. SAST tools are fast and can be integrated early in the development process, even before code is committed. For Python, popular SAST tools include Bandit, Semgrep, and Pyre. Bandit is a simple tool that checks for common security issues in Python code. Semgrep is more powerful, allowing custom rules and multi-language support. SAST complements dependency scanning by focusing on your own code rather than third-party packages.

sast_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
# Example of insecure code that SAST can catch
import os

def get_user_input():
    user_input = input("Enter your name: ")
    # Bad: using user input directly in SQL query
    query = f"SELECT * FROM users WHERE name = '{user_input}'"
    # Bad: hardcoded secret
    api_key = "sk-1234567890abcdef"
    os.system("ls -la")  # Bad: command injection possible
    return query
🔥SAST vs DAST
📊 Production Insight
In production, SAST can be integrated as a pre-commit hook to prevent insecure code from entering the repository.
🎯 Key Takeaway
SAST helps you find vulnerabilities in your own code before it runs, catching issues like injection and hardcoded secrets early.

Setting Up Bandit for Python SAST

Bandit is a security linter for Python that identifies common security issues. It's easy to install and run. First, install Bandit via pip: pip install bandit. Then run it against your codebase: bandit -r .. Bandit will output a report with severity levels (HIGH, MEDIUM, LOW) and confidence. You can also generate a JSON report for CI integration: bandit -r . -f json -o bandit_report.json. Bandit supports custom configuration via a .bandit file or command-line options. For example, you can skip certain tests or exclude directories. Let's create a simple Python file with a vulnerability and run Bandit.

vulnerable.pyPYTHON
1
2
3
4
5
6
7
8
9
10
# vulnerable.py
import subprocess

def run_command(cmd):
    # Potential command injection
    subprocess.call(cmd, shell=True)

if __name__ == "__main__":
    user_input = input("Enter command: ")
    run_command(user_input)
Output
>> bandit -r .
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.10.0
Run started:2025-03-20 12:00:00
Test results:
>> Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified
Severity: HIGH Confidence: HIGH
Location: vulnerable.py:5:4
More Info: https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html
Files analyzed: 1
Total lines: 8
Total issues: 1
💡Bandit Configuration
📊 Production Insight
Bandit can be run as a pre-commit hook using pre-commit framework. Add a .pre-commit-config.yaml entry for Bandit.
🎯 Key Takeaway
Bandit quickly finds high-severity issues like shell injection. Integrate it into your CI to block insecure code.

Advanced SAST with Semgrep

Semgrep is a powerful static analysis tool that supports custom rules and multiple languages. It's like grep but for code patterns. You can write rules to detect specific vulnerabilities, enforce coding standards, or find deprecated APIs. Semgrep rules are YAML files that define patterns to match. For example, to detect hardcoded passwords, you can write a rule that matches string assignments to variables named 'password'. Semgrep also has a registry of community rules. Install Semgrep via pip: pip install semgrep. Run it with: semgrep --config=auto . to use the default rules. Let's create a custom rule to detect insecure hashing (MD5).

semgrep_rule.yamlPYTHON
1
2
3
4
5
6
7
8
# insecure-hash.yaml
rules:
  - id: insecure-hash
    pattern: |
      hashlib.md5(...)
    message: "MD5 is insecure for cryptographic purposes. Use SHA256 or better."
    languages: [python]
    severity: ERROR
Output
>> semgrep --config=insecure-hash.yaml .
Scanning 1 file.
Findings:
app.py
insecure-hash
> hashlib.md5(password.encode())
1 finding
⚠ Semgrep Performance
📊 Production Insight
In production, run Semgrep as part of CI with --error to fail the build on any finding. Use --json output for integration with dashboards.
🎯 Key Takeaway
Semgrep allows custom rules to catch project-specific vulnerabilities, making it flexible for teams.

Dependency Scanning with Safety and Snyk

Dependency scanning checks your Python packages against known vulnerability databases. Safety is a simple CLI tool that checks your installed packages against the Safety DB (free tier). Install: pip install safety. Run: safety check. It will list vulnerable packages with CVE IDs and recommended fixes. Snyk is a more comprehensive tool that also provides fix advice and integrates with CI. You can use Snyk CLI: npm install -g snyk (or via pip snyk). Run: snyk test. Snyk also supports container scanning and IaC. Both tools can generate reports in JSON or SARIF format. Let's see an example of Safety output.

safety_check.pyPYTHON
1
2
3
4
5
6
7
8
# Run safety check on requirements.txt
# pip install safety
# safety check -r requirements.txt

# Example requirements.txt
flask==1.0.0
requests==2.25.0
Output
>> safety check -r requirements.txt
+==============================================================================+
| SAFETY REPORT |
+==============================================================================+
| package | installed | affected | ID |
+==============================================================================+
| flask | 1.0.0 | <2.0.0 | CVE-2023-12345 |
| requests | 2.25.0 | <2.28.0 | CVE-2023-67890 |
+==============================================================================+
Recommendations:
- Upgrade flask to 2.0.0 or later
- Upgrade requests to 2.28.0 or later
🔥Free vs Paid
📊 Production Insight
Automate dependency scanning in CI. Use safety check --continue-on-error to allow builds to pass but notify the team.
🎯 Key Takeaway
Regular dependency scanning prevents known vulnerabilities from entering your production environment.

Integrating Security Tools into CI/CD

To make security a continuous process, integrate SAST and dependency scanning into your CI/CD pipeline. For GitHub Actions, you can add steps for Bandit, Semgrep, and Safety. Here's an example workflow that runs on every push. It installs dependencies, runs Bandit, Semgrep, and Safety, and fails if any high-severity issues are found. You can also use tools like Snyk GitHub Action for deeper scanning. For GitLab CI, similar jobs can be defined. The key is to fail the build on critical vulnerabilities but allow warnings to be reviewed. Let's create a GitHub Actions workflow.

.github/workflows/security.ymlPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
name: Security Scan

on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      - name: Install dependencies
        run: |
          pip install bandit semgrep safety
      - name: Run Bandit
        run: bandit -r . -f json -o bandit_report.json || true
      - name: Run Semgrep
        run: semgrep --config=auto --error . || true
      - name: Run Safety
        run: safety check -r requirements.txt --continue-on-error || true
      - name: Upload reports
        uses: actions/upload-artifact@v3
        with:
          name: security-reports
          path: bandit_report.json
💡Fail on High Severity
📊 Production Insight
Consider using a dedicated security dashboard like DefectDojo to aggregate findings from multiple tools.
🎯 Key Takeaway
CI integration ensures that security checks are automated and consistent, catching issues before they reach production.

Secure Coding Best Practices in Python

Beyond tools, following secure coding practices reduces vulnerabilities. Key practices include: 1) Input validation: always validate and sanitize user input. Use libraries like pydantic or marshmallow. 2) Parameterized queries: never use string formatting for SQL. Use ORMs or cursor.execute with placeholders. 3) Secrets management: never hardcode secrets. Use environment variables or a vault like HashiCorp Vault. 4) Least privilege: run your application with minimal permissions. Use separate users for different services. 5) Secure defaults: disable debug mode in production, use HTTPS, set secure cookie flags. 6) Update dependencies regularly: use tools like pip-audit or dependabot. Let's see examples of insecure vs secure code.

secure_example.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Insecure
import sqlite3

def get_user(username):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)
    return cursor.fetchall()

# Secure
def get_user_secure(username):
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    query = "SELECT * FROM users WHERE username = ?"
    cursor.execute(query, (username,))
    return cursor.fetchall()
⚠ Common Pitfall
📊 Production Insight
Use a secrets manager like AWS Secrets Manager or Azure Key Vault to rotate secrets automatically.
🎯 Key Takeaway
Secure coding practices are the first line of defense. Tools help, but they can't replace good habits.

Common Vulnerabilities in Python Web Applications

Python web frameworks like Flask and Django are popular but have common vulnerabilities. SQL injection, XSS, CSRF, and insecure deserialization are top risks. For example, using pickle.loads on untrusted data can lead to remote code execution. Another is using eval() or exec() with user input. Also, misconfigured CORS headers can expose APIs. SAST tools can catch many of these. Let's look at an insecure Flask app and how to fix it.

flask_insecure.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, request, render_template_string
import pickle

app = Flask(__name__)

@app.route('/')
def index():
    # Insecure: user input in template
    name = request.args.get('name', '')
    return render_template_string(f"<h1>Hello {name}</h1>")

@app.route('/load')
def load():
    # Insecure: unpickling user data
    data = request.args.get('data', '')
    obj = pickle.loads(bytes.fromhex(data))
    return str(obj)
🔥Fix for XSS
📊 Production Insight
Use a web application firewall (WAF) in front of your app to block common attacks before they reach your code.
🎯 Key Takeaway
Web applications are a common attack vector. Always use framework security features like CSRF tokens and auto-escaping.

Supply Chain Security: Protecting Your Dependencies

Supply chain attacks target dependencies. Recent incidents like the colors and faker library typosquatting show the risk. To protect your supply chain: 1) Pin exact versions in requirements.txt (e.g., requests==2.28.0). 2) Use hash checking with pip install --require-hashes. 3) Use private package repositories (e.g., AWS CodeArtifact, JFrog Artifactory) to vet packages. 4) Regularly audit dependencies with tools like pip-audit or safety. 5) Monitor for malicious packages using tools like guarddog. Let's see how to use hash checking.

requirements_with_hashes.txtPYTHON
1
2
3
4
# requirements.txt with hashes
requests==2.28.0 --hash=sha256:abc123...
flask==2.2.0 --hash=sha256:def456...
⚠ Hash Checking
📊 Production Insight
Consider using a software composition analysis (SCA) tool like Snyk or Black Duck for continuous monitoring.
🎯 Key Takeaway
Supply chain security is about trust. Verify the integrity of your dependencies and limit exposure to untrusted sources.
● Production incidentPOST-MORTEMseverity: high

The Leaky Environment Variable: How a Hardcoded Secret Cost $10,000

Symptom
Users reported slow performance and unexpected AWS charges.
Assumption
The developer assumed the secret key was safe because it was in a .env file that was gitignored.
Root cause
The .env file was accidentally tracked by git due to a typo in .gitignore, and the repo was public.
Fix
Rotated all secrets, removed the file from git history using BFG Repo-Cleaner, and added pre-commit hooks to scan for secrets.
Key lesson
  • Never hardcode secrets; use environment variables or secret managers.
  • Use pre-commit hooks with tools like detect-secrets or git-secrets.
  • Regularly audit your git history for exposed secrets.
  • Implement SAST to catch hardcoded secrets before commit.
  • Use dependency scanning to detect known vulnerabilities in packages.
Production debug guideSymptom to Action4 entries
Symptom · 01
Application crashes with 'ModuleNotFoundError' after update
Fix
Check dependency scanning report for removed or malicious packages. Rollback to previous requirements.txt.
Symptom · 02
Suspicious network traffic from your server
Fix
Scan for hardcoded secrets in codebase. Check for backdoors in dependencies using Snyk or Safety.
Symptom · 03
SQL injection vulnerability reported by pentest
Fix
Run Bandit or Semgrep to find unsafe query patterns. Replace f-strings with parameterized queries.
Symptom · 04
Dependency scanning shows critical CVE
Fix
Update the package to patched version. If no patch, use a workaround or replace the library.
★ Quick Debug Cheat SheetImmediate actions for common security issues
Hardcoded secret in code
Immediate action
Revoke the secret immediately.
Commands
git log -p | grep -i 'secret'
pip install detect-secrets && detect-secrets scan
Fix now
Use environment variables: os.getenv('SECRET')
Vulnerable dependency+
Immediate action
Check if a patched version exists.
Commands
pip install safety && safety check
pip install --upgrade <package>
Fix now
Pin safe version in requirements.txt
SQL injection risk+
Immediate action
Stop using string formatting in SQL queries.
Commands
bandit -r . | grep -i 'sql'
semgrep --config=p/python --pattern='cursor.execute(f"...")'
Fix now
Use parameterized queries: cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
ToolTypeOpen SourceCustom RulesCI Integration
BanditSASTYesLimitedEasy
SemgrepSASTYesExtensiveEasy
SafetyDependency ScanYes (limited)NoEasy
SnykDependency ScanNo (freemium)NoEasy
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
sast_example.pydef get_user_input():What is SAST and Why Use It?
vulnerable.pydef run_command(cmd):Setting Up Bandit for Python SAST
semgrep_rule.yamlrules:Advanced SAST with Semgrep
safety_check.pyflask==1.0.0Dependency Scanning with Safety and Snyk
.githubworkflowssecurity.ymlname: Security ScanIntegrating Security Tools into CI/CD
secure_example.pydef get_user(username):Secure Coding Best Practices in Python
flask_insecure.pyfrom flask import Flask, request, render_template_stringCommon Vulnerabilities in Python Web Applications
requirements_with_hashes.txtrequests==2.28.0 --hash=sha256:abc123...Supply Chain Security

Key takeaways

1
SAST tools like Bandit and Semgrep catch vulnerabilities in your own code early.
2
Dependency scanning with Safety or Snyk prevents known vulnerabilities from entering production.
3
Integrate security scans into CI/CD to automate checks and fail builds on critical issues.
4
Follow secure coding practices
input validation, parameterized queries, and secrets management.
5
Supply chain security requires pinning dependencies, hash verification, and regular audits.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is SAST and how does it differ from dependency scanning?
Q02SENIOR
How would you prevent SQL injection in a Python web application?
Q03SENIOR
Describe a real-world security incident related to Python dependencies a...
Q01 of 03JUNIOR

What is SAST and how does it differ from dependency scanning?

ANSWER
SAST scans your own source code for vulnerabilities like injection flaws. Dependency scanning checks third-party packages for known CVEs. Both are essential for a comprehensive security posture.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is the difference between SAST and DAST?
02
How often should I run dependency scanning?
03
Can SAST tools produce false positives?
04
What is the best SAST tool for Python?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Advanced Python. Mark it forged?

3 min read · try the examples if you haven't

Previous
Publishing Python Packages to PyPI with pyproject.toml
31 / 35 · Advanced Python
Next
asyncio Patterns: Queues, Semaphores, Timeouts, and Cancellation