In this chapter, you’ll harden your Flask application’s security posture by integrating proactive security checks directly into your development workflow. We’re moving beyond reactive fixes to building a secure foundation where vulnerabilities are identified and addressed as early as possible. By the end, your project will automatically scan for known weaknesses in third-party libraries and common security flaws in your custom Python code. This is a critical step towards shipping production-ready, resilient applications.
This milestone is about establishing a continuous security feedback loop. As you add new features and dependencies, you’ll have automated mechanisms to validate the security health of your codebase. You’ll learn how to interpret scan results, prioritize findings, and systematically mitigate risks, ensuring that security is an ongoing concern, not an afterthought.
Project Overview: Securing the Supply Chain and Codebase
The overall project aims to build a secure Python Flask web application demonstrating best practices in user authentication, input validation, and secure deployment. This chapter focuses on two vital aspects of application security:
- Software Supply Chain Security: Ensuring the integrity and security of all third-party components your application relies on. A single vulnerable library can expose your entire system.
- Codebase Security: Identifying common coding errors and patterns in your own application code that could lead to security vulnerabilities.
By integrating automated tools for these areas, you shift security left, making it an inherent part of your development process rather than a separate, end-of-lifecycle activity. This approach is highly effective for reducing the attack surface and improving overall application robustness.
Tech Stack & Tooling
To achieve our goals in this chapter, we’ll leverage the following components:
- Python (3.12+): The core language for our application. We’ll use a virtual environment for dependency isolation.
- Flask (3.0+): Our web framework, whose dependencies we will audit.
pip-audit: A dedicated command-line tool for auditing Python environments andrequirements.txtfiles for known vulnerabilities. It queries the Python Package Index (PyPI) Advisory Database.- Latest Version (as of 2026-07-13): Exact future stable versions are not predictable. Always check the official PyPI page for the latest release: pip-audit on PyPI.
Bandit: A security linter for Python that identifies common security issues by scanning source code. It’s excellent for catching insecure configurations, potential injection points, and other OWASP Top 10 related flaws.- Latest Version (as of 2026-07-13): Exact future stable versions are not predictable. Always check the official PyPI page for the latest release: Bandit on PyPI.
These tools will be installed as development dependencies, ensuring they don’t bloat your production environment while being readily available for local development and CI/CD pipelines.
Build Plan: Integrating Security Scans
This chapter is structured around the following key milestones:
- Tool Installation: Add
pip-auditandBanditas development dependencies. - Dependency Vulnerability Scan: Run
pip-auditagainst your project’srequirements.txtto identify known vulnerabilities in third-party libraries. - Static Code Analysis: Execute
Banditto scan your application’s custom Python code for common security weaknesses. - Issue Remediation: Address identified vulnerabilities and code flaws, demonstrating how to apply fixes.
- Verification: Confirm that the tools run successfully and that issues are correctly identified and resolved.
Planning & Design: The Security Feedback Loop
Proactive security involves “shifting left,” meaning we identify and fix security issues as early as possible in the development process. Software Composition Analysis (SCA) and Static Application Security Testing (SAST) are two cornerstone practices for achieving this.
Software Composition Analysis (SCA): This practice identifies and evaluates the open-source and third-party components (dependencies) used in your application for known security vulnerabilities. Modern applications often rely on hundreds of external libraries, and a single vulnerable dependency can create a critical attack vector.
pip-auditperforms SCA for Python projects.Static Application Security Testing (SAST): This technique analyzes your application’s source code, bytecode, or binary code for security vulnerabilities without actually executing the program. It’s like having an automated security expert review every line of your code for common pitfalls and insecure patterns.
Banditprovides SAST for Python.
The goal is to incorporate these tools into a repeatable process. Initially, you’ll run them manually to understand their output and remediation steps. Eventually, these steps are ideal candidates for automation within Continuous Integration (CI) pipelines, failing builds if critical vulnerabilities or code flaws are detected.
Figure 1: Security Tool Integration Flow
This diagram illustrates the security feedback loop: your application’s source code and its requirements.txt are independently scanned by Bandit and pip-audit. Any findings are routed for developer review, leading either to code fixes and dependency upgrades or, in rare cases, documented risk acknowledgment. This iterative process continuously improves the application’s security posture.
Step-by-Step Implementation
We’ll install pip-audit and Bandit, then run them against your existing Flask application.
1. Install Security Tools
First, ensure your Python virtual environment is active. We’ll add these tools to a separate requirements-dev.txt file. This practice clearly distinguishes development-only dependencies from the packages required for your application to run in production, helping to keep your production environment lean and secure.
Create a new file named requirements-dev.txt in your project’s root directory:
requirements-dev.txt
pip-audit
banditNow, install these development dependencies:
# Ensure your virtual environment is active.
# If not, activate it:
# For Linux/macOS: source venv/bin/activate
# For Windows: venv\Scripts\activate
pip install -r requirements-dev.txtWhy this choice?
pip-audit: It provides a straightforward and officially supported way to check for known vulnerabilities in your Python package dependencies. Its integration with PyPI’s advisory database ensures up-to-date vulnerability information.Bandit: As a Python-specific static analysis tool, it’s highly effective at identifying security-centric issues. Its configurability makes it suitable for integration into various development workflows, including pre-commit hooks and CI/CD pipelines.
2. Perform Dependency Vulnerability Scanning with pip-audit
pip-audit checks your installed Python packages against public vulnerability databases.
First, ensure your requirements.txt file accurately reflects your application’s runtime dependencies. If you haven’t already, generate or update it to capture all currently installed production dependencies:
pip freeze > requirements.txtNow, run pip-audit against this file:
pip-audit -r requirements.txtWhat it does:
The -r requirements.txt flag instructs pip-audit to analyze the packages listed in that file. It fetches advisories from data sources like the PyPI Advisory Database and reports any matches, detailing the vulnerability, affected versions, and potential fixes.
Expected Output (example with no vulnerabilities):
If your dependencies are clean, pip-audit will report:
No vulnerabilities foundExpected Output (example with vulnerabilities):
If pip-audit finds issues, the output will list each vulnerability, including the package name, affected versions, severity, a description, and a link to the advisory.
Found 1 vulnerability in your dependencies:
package-name == 1.0.0
Vulnerability: PYSEC-XXXX-XXXX
Description: [Description of vulnerability, e.g., Cross-site Scripting (XSS)]
Affected versions: < 1.0.1
Fixed versions: >= 1.0.1
Link: https://example.com/advisory/PYSEC-XXXX-XXXXHow to address findings:
The most common and effective way to fix dependency vulnerabilities is to upgrade the affected package to a version where the vulnerability is patched. The pip-audit report usually suggests “Fixed versions.”
For example, if package-name has a vulnerability fixed in version 1.0.1, you would update your requirements.txt to specify a compatible, secure version, and then reinstall:
# Locate 'package-name==1.0.0' in requirements.txt and change it to:
# package-name>=1.0.1,<2.0.0 # Use a version range to allow minor updates,
# or a specific version if strict control is needed.
pip install -r requirements.txt
pip-audit -r requirements.txt # Rerun to verify the fix🧠 Important: Sometimes, a direct upgrade isn’t feasible due to breaking changes or compatibility issues with other libraries. In such scenarios, you might need to:
- Evaluate Risk: Determine if the vulnerability is actually exploitable in your specific application context. Document this assessment thoroughly.
- Find Alternatives: Replace the vulnerable library with a more secure, actively maintained alternative.
- Implement Compensating Controls: Apply application-level mitigations to prevent exploitation, even if the underlying library remains vulnerable.
- Accept Risk: As a last resort, after a formal risk assessment and sign-off, you might accept the risk, but this should be rare and well-documented.
3. Perform Static Code Analysis with Bandit
Bandit scans your Python source code for common security issues, covering many of the OWASP Top 10 categories.
Run Bandit against your Flask application’s code directory (e.g., app/):
bandit -r app/What it does:
The -r app/ flag tells Bandit to recursively scan the app/ directory. Bandit will analyze your code for patterns that indicate potential security vulnerabilities, such as the use of eval(), hardcoded secrets, SQL injection possibilities, or insecure cryptography.
Expected Output (example with findings):
[main] INFO: Available metrics types:
...
[main] INFO: Results found:
>> Issue: [B101:assert_used] Use of assert detected. Asserts should not be used for data validation.
Severity: Medium Confidence: High
Location: app/routes.py:15:5
More Info: https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.htmlThe output provides comprehensive details for each finding:
- The
Banditissue ID (e.g.,B101). - A concise description of the vulnerability.
- Severity and confidence levels (helping you prioritize).
- The exact file and line number where the issue was found.
- A link to
Bandit’s documentation for deeper insight into the vulnerability and suggested remediation.
How to address findings (Example: B101 - Use of assert for validation)
Bandit commonly flags the use of assert statements for validation (B101). While assert is useful for debugging and internal consistency checks, it should not be used for validating user input or enforcing security constraints in production code. Python’s assert statements can be completely removed from the bytecode when running the interpreter with the -O or -OO optimization flags. This means your security checks would simply disappear in a production environment, leading to critical vulnerabilities.
Let’s assume you have a routes.py file with an assert used incorrectly for input validation:
app/routes.py (before fix)
# app/routes.py
from flask import Blueprint, request, jsonify
main_bp = Blueprint('main', __name__)
@main_bp.route('/data', methods=['POST'])
def post_data():
user_data = request.json
assert 'value' in user_data, "Value must be provided" # Bandit will flag this (B101)
# ... process user_data ...
return jsonify({"message": "Data received", "data": user_data}), 200To fix this B101 finding, replace the assert with robust, explicit validation logic. This typically involves if/else checks, raising appropriate exceptions, or returning specific HTTP error codes.
app/routes.py (after fix)
# app/routes.py
from flask import Blueprint, request, jsonify
main_bp = Blueprint('main', __name__)
@main_bp.route('/data', methods=['POST'])
def post_data():
user_data = request.json
if not user_data or 'value' not in user_data: # Proper validation using if/else
return jsonify({"error": "Value must be provided"}), 400 # Return a 400 Bad Request
# ... process user_data ...
return jsonify({"message": "Data received", "data": user_data}), 200After implementing the change, rerun bandit -r app/ to confirm that the B101 issue is no longer reported for that specific line. You will likely encounter other Bandit findings; review each one, understand its implications, and apply appropriate fixes.
⚡ Quick Note: Bandit is highly configurable. You can use a bandit.yaml file to ignore certain checks (e.g., if they are false positives in your context), include or exclude specific directories, or change its reporting format. This allows you to tailor the tool to your project’s specific needs and reduce noise from irrelevant warnings. Refer to the official Bandit documentation for advanced configuration options.
Testing & Verification
Verification for this chapter involves confirming that pip-audit and Bandit run successfully, you can interpret their output, and you’ve taken action on findings.
Verify
pip-auditruns clean:Ensure all critical production dependencies are up-to-date and listed in
requirements.txt.Run
pip-audit -r requirements.txt.Confirm the output states “No vulnerabilities found” or that any reported vulnerabilities are thoroughly understood, documented, and formally accepted as a risk (with strong justification).
⚡ Self-test:To actively seepip-auditin action, temporarily introduce a known vulnerable package version.- Add
requests==2.20.0to yourrequirements.txt(this version is known to have vulnerabilities). - Run
pip install -r requirements.txt. - Execute
pip-audit -r requirements.txt. You should see several vulnerabilities reported forrequests. - Now, update
requeststo a recent, secure version (e.g.,requests>=2.31.0,<3.0.0) inrequirements.txt. - Run
pip install -r requirements.txtagain, thenpip-audit -r requirements.txt. The vulnerabilities forrequestsshould now be resolved.
- Add
Verify
Banditruns and you’ve addressed a finding:- Run
bandit -r app/. - Carefully review the output. Understand the issue ID, description, severity, and location for each finding.
- Confirm that the code fixes you’ve implemented (e.g., replacing
assertwithif/elsechecks forB101) have resolved the specific finding. RerunBanditafter a fix and verify the issue no longer appears. - The immediate goal isn’t necessarily to achieve zero findings, but to understand and systematically address them. Some findings might be false positives or acceptable risks in your specific context; these should be explicitly reviewed, documented, and potentially suppressed in
bandit.yamlwith clear comments.
- Run
Production Considerations
Integrating dependency scanning and static analysis is not just a development-time luxury; it’s a critical component of a robust production application lifecycle.
- Automated CI/CD Integration: The most impactful way to use these tools is to integrate them into your Continuous Integration/Continuous Deployment (CI/CD) pipeline. Every pull request, code merge, or deployment should automatically trigger
pip-auditandBanditscans. Crucially, configure your pipeline to fail the build if high-severity vulnerabilities or critical code flaws are detected. This prevents insecure code from ever reaching production environments. - Regular Scanning: The threat landscape constantly evolves. New vulnerabilities are discovered daily. Your application’s dependencies, even if clean today, might have a new advisory tomorrow. Implement scheduled, regular automated scans (e.g., daily, weekly) on your main branches and even on deployed production environments. This ensures continuous monitoring of your security posture.
- Managing False Positives: Static analysis tools can sometimes produce false positives – warnings about issues that aren’t actually exploitable in your specific context. It’s important to understand the tool’s output and differentiate between real issues and benign warnings. Over-suppressing warnings can lead to overlooked real issues, while ignoring them can lead to “alert fatigue,” where developers become desensitized to security alerts. Use configuration files (e.g.,
bandit.yaml) to fine-tune rules or mark specific lines of code for exclusion with comments, but always add a clear justification for each suppression. - Compliance and Reporting: In regulated industries or for enterprise applications, demonstrating that you perform these types of security checks is often a compliance requirement. Automated reports from
pip-auditandBanditcan serve as valuable evidence during security audits, showcasing your commitment to secure development practices. - Runtime Security Awareness (PEP 551): While
pip-auditandBanditfocus on static analysis, it’s also important to be aware of the broader Python runtime security landscape. PEP 551 discusses security transparency, aiming to provide better visibility into the security characteristics of Python environments. Tools likepip-auditcontribute to this by providing transparency into your package dependencies. For a deeper dive, explore PEP 551 – Security transparency in the Python runtime.
Common Issues & Solutions
Too many findings / Alert Fatigue:
⚠️ What can go wrong:RunningBanditfor the first time on an existing codebase can yield hundreds of warnings, making it overwhelming and leading to developers ignoring the output.Solution:Start by focusing on high-severity and high-confidence findings. UseBandit’s configuration options (e.g.,-llto include low confidence/severity, or-pfor specific plugins) to narrow the scope. Gradually address issues, perhaps starting with a specific type of vulnerability. Integrate the tool incrementally. Use abandit.yamlfile to baseline current findings, ignore specific checks that aren’t relevant, or mark known false positives for your project, always with clear justification.
Difficulty updating dependencies:
⚠️ What can go wrong:pip-auditreports a critical vulnerability, but upgrading the dependency to a fixed version causes breaking changes in your application, leading to development delays.Solution:This is a common challenge in dependency management. First, thoroughly evaluate the actual risk and exploitability of the vulnerability in your specific use case. Can you implement a compensating control at the application layer? If not, you may need to refactor your code to adapt to the newer, secure version of the library, or find an alternative, secure library. Prioritize critical vulnerabilities that are easily exploitable. This highlights the importance of keeping dependencies as up-to-date as possible to minimize the impact of future upgrades.
Overlooking scan results:
⚠️ What can go wrong:The security tools run, but their output is not regularly reviewed, or identified findings are ignored, leading to unpatched vulnerabilities in production.Solution:Integrate these checks into your CI/CD pipeline with strict rules. For example, configure the pipeline to fail the build on any high-severitypip-auditfindings or criticalBanditissues. Assign clear responsibility for reviewing scan results to specific team members or security champions. Use issue tracking systems (e.g., Jira, GitHub Issues) to log and follow up on identified vulnerabilities, treating them like any other bug. Regular security reviews should include a discussion of recent scan results.
Summary & Next Step
You’ve now equipped your project with fundamental security analysis capabilities, moving beyond reactive security to a proactive stance. By integrating pip-audit for dependency vulnerability scanning and Bandit for static code analysis, you’ve taken a significant step towards building a secure Python application. This systematic approach of continuously identifying and mitigating vulnerabilities in both your dependencies and your custom code is a hallmark of mature secure development practices.
With these essential security checks in place, your application’s foundation is stronger and more resilient. The next critical step is to ensure consistency and isolation for your development and production environments. In the next chapter, we will containerize your application using Docker, setting up a robust local development environment that mirrors production and integrates seamlessly with PostgreSQL.
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.
References
- pip-audit Official Documentation
- Bandit Official Documentation
- Python Package Index (PyPI) Advisory Database
- OWASP Top 10 Web Application Security Risks
- PEP 551 – Security transparency in the Python runtime
- Securing Python Runtimes | Our Success Stories
- Deploy secure applications on Microsoft Azure | Microsoft Learn