Python Security: SAST, Dependency Scanning & Best Practices
Learn how to secure your Python code with SAST tools, dependency scanning, and proven best practices.
20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.
- ✓Basic knowledge of Python syntax and pip
- ✓Familiarity with command line and virtual environments
- ✓Understanding of common web vulnerabilities (SQL injection, XSS) is helpful
- 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.
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.
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.
.pre-commit-config.yaml entry for Bandit.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).
--error to fail the build on any finding. Use --json output for integration with dashboards.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 --continue-on-error to allow builds to pass but notify the team.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.
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.
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.
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.
The Leaky Environment Variable: How a Hardcoded Secret Cost $10,000
- 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.
git log -p | grep -i 'secret'pip install detect-secrets && detect-secrets scan| File | Command / Code | Purpose |
|---|---|---|
| sast_example.py | def get_user_input(): | What is SAST and Why Use It? |
| vulnerable.py | def run_command(cmd): | Setting Up Bandit for Python SAST |
| semgrep_rule.yaml | rules: | Advanced SAST with Semgrep |
| safety_check.py | flask==1.0.0 | Dependency Scanning with Safety and Snyk |
| .github | name: Security Scan | Integrating Security Tools into CI/CD |
| secure_example.py | def get_user(username): | Secure Coding Best Practices in Python |
| flask_insecure.py | from flask import Flask, request, render_template_string | Common Vulnerabilities in Python Web Applications |
| requirements_with_hashes.txt | requests==2.28.0 --hash=sha256:abc123... | Supply Chain Security |
Key takeaways
Interview Questions on This Topic
What is SAST and how does it differ from dependency scanning?
Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.
That's Advanced Python. Mark it forged?
3 min read · try the examples if you haven't