Managing Dependencies and Static Analysis for Security

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 and requirements.txt files 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:

  1. Tool Installation: Add pip-audit and Bandit as development dependencies.
  2. Dependency Vulnerability Scan: Run pip-audit against your project’s requirements.txt to identify known vulnerabilities in third-party libraries.
  3. Static Code Analysis: Execute Bandit to scan your application’s custom Python code for common security weaknesses.
  4. Issue Remediation: Address identified vulnerabilities and code flaws, demonstrating how to apply fixes.
  5. 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-audit performs 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. Bandit provides 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.

flowchart TD A[Application Code] --> B{Bandit Scan} A --> C[requirements.txt] C --> D{pip-audit Scan} B -->|Code Findings| E[Developer Review] D -->|Dependency Findings| E E -->|Fix Issues| A E -->|Acknowledge Risk| F[Secure Application]

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
bandit

Now, 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.txt

Why 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.txt

Now, run pip-audit against this file:

pip-audit -r requirements.txt

What 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 found

Expected 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-XXXX

How 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.html

The output provides comprehensive details for each finding:

  • The Bandit issue 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}), 200

To 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}), 200

After 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.

  1. Verify pip-audit runs 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 see pip-audit in action, temporarily introduce a known vulnerable package version.

      1. Add requests==2.20.0 to your requirements.txt (this version is known to have vulnerabilities).
      2. Run pip install -r requirements.txt.
      3. Execute pip-audit -r requirements.txt. You should see several vulnerabilities reported for requests.
      4. Now, update requests to a recent, secure version (e.g., requests>=2.31.0,<3.0.0) in requirements.txt.
      5. Run pip install -r requirements.txt again, then pip-audit -r requirements.txt. The vulnerabilities for requests should now be resolved.
  2. Verify Bandit runs 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 assert with if/else checks for B101) have resolved the specific finding. Rerun Bandit after 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.yaml with clear comments.

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-audit and Bandit scans. 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-audit and Bandit can serve as valuable evidence during security audits, showcasing your commitment to secure development practices.
  • Runtime Security Awareness (PEP 551): While pip-audit and Bandit focus 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 like pip-audit contribute 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

  1. Too many findings / Alert Fatigue:

    • ⚠️ What can go wrong: Running Bandit for 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. Use Bandit’s configuration options (e.g., -ll to include low confidence/severity, or -p for specific plugins) to narrow the scope. Gradually address issues, perhaps starting with a specific type of vulnerability. Integrate the tool incrementally. Use a bandit.yaml file to baseline current findings, ignore specific checks that aren’t relevant, or mark known false positives for your project, always with clear justification.
  2. Difficulty updating dependencies:

    • ⚠️ What can go wrong: pip-audit reports 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.
  3. 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-severity pip-audit findings or critical Bandit issues. 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