DEV Community

Hariharan
Hariharan

Posted on

DevSecOps in Practice: Tools That Actually Catch Vulnerabilities - Part 3 - SCA with pip-audit

Parts 1 and 2 covered the code you write — secrets and static vulnerabilities in app.py. But modern applications are mostly made up of code you didn't write. Every package in requirements.txt is someone else's code running in your app. If any of those packages have known vulnerabilities, your app inherits them.
That's what SCA is for.

Code repo: https://github.com/pkkht/devsecops-demo/

What SCA is
SCA stands for Software Composition Analysis. It looks at your dependency list, checks each package version against public vulnerability databases, and reports any known CVEs. It doesn't analyse your code — it analyses what your code depends on. This matters because a lot of real-world breaches don't come from custom code at all. They come from a vulnerable library that nobody noticed was outdated.

The tool: pip-audit
pip-audit is maintained by the Python Packaging Authority (PyPA) — the same group that maintains pip itself. It queries the Python Packaging Advisory Database (PyPA Advisory DB) and the OSV database for known vulnerabilities. It's free, open source, and requires no account or API key.

pip install pip-audit
pip-audit --version
Enter fullscreen mode Exit fullscreen mode

The demo dependencies
The requirements.txt in the repo contains intentionally outdated packages with known CVEs:

Flask==1.1.2
Jinja2==2.11.3
Werkzeug==1.0.1
requests==2.20.0
itsdangerous==1.1.0
SQLAlchemy==1.3.20
click==7.1.2
Enter fullscreen mode Exit fullscreen mode

These are real versions that were in production use a few years ago. A lot of projects still have dependency files that haven't been updated in that kind of timeframe.

Running pip-audit

pip-audit -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

37 known vulnerabilities across 6 packages. That's from 7 packages in
requirements.txt — only one came back clean. Some of the findings worth noting:

  • Flask 1.1.2 — 2 vulnerabilities, fixed in 2.2.5
  • Jinja2 2.11.3 — 4 vulnerabilities including CVE-2024-22195 and CVE-2024-34064, fixed in 3.1.3+
  • Werkzeug 1.0.1 — 13 vulnerabilities, the most of any package, fixed versions ranging up to 3.1.6
  • requests 2.20.0 — 5 vulnerabilities including CVE-2024-35195 and CVE-2026-25645, fixed in 2.31.0+
  • urllib3 — 13 vulnerabilities flagged as a transitive dependency (pulled in by requests)

The fix version column tells you exactly what to upgrade to. That's the
output you hand to a developer — not a vague warning, but a specific action.

Generating a JSON report

pip-audit -r requirements.txt -f json -o pip-audit-report.json
Enter fullscreen mode Exit fullscreen mode

Same findings, machine-readable output. Add it to .gitignore so it doesn't get committed to the repo:

GitHub Actions workflow

Create .github/workflows/sca.yml:

name: SCA - pip-audit

on:
  push:
    branches: ["**"]
  pull_request:
    branches: ["**"]

jobs:
  pip-audit:
    name: pip-audit SCA Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install pip-audit
        run: pip install pip-audit

      - name: Run pip-audit
        run: pip-audit -r requirements.txt -f json -o pip-audit-report.json

      - name: Upload pip-audit Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: pip-audit-report
          path: pip-audit-report.json
Enter fullscreen mode Exit fullscreen mode

Push and watch it run:

The pipeline fails at the Run pip-audit step — "Found 37 known
vulnerabilities in 6 packages", exit code 1. The report is still uploaded
as an artifact via the if: always() step so the findings are available even though the build failed.
Again — this is the correct behaviour. The pipeline found real vulnerabilities and stopped the build. In a real project the fix is straightforward: update the packages to the versions shown in the Fix Versions column, push again, and the build passes.

What we've built so far
Four layers now in place:

  • Gitleaks pre-commit — blocks secrets at commit time
  • Gitleaks GitHub Actions — catches secrets at push time
  • Bandit GitHub Actions — catches code vulnerabilities, gates on HIGH severity
  • pip-audit GitHub Actions — catches vulnerable dependencies

Top comments (0)