Trivy, a security scanner, can analyze your Python project’s dependencies for known vulnerabilities. It’s particularly adept at parsing requirements.txt files and Pipfile.lock generated by pip and pipenv, respectively.

Let’s see Trivy in action. Imagine you have a simple Python project with a requirements.txt file:

Flask==2.0.1
requests==2.26.0

To scan this with Trivy, you’d run:

trivy fs --skip-db-update --cache-dir=/tmp/trivy-cache .

Trivy will analyze the requirements.txt file, identify the exact versions of Flask and requests, and then query its vulnerability database for any known CVEs affecting those specific versions.

Now, consider a project using pipenv. Your Pipfile.lock might look something like this (truncated for brevity):

{
  "_meta": {
    "hash": {
      "sha256": "..."
    },
    "pipfile-spec": 6,
    "requires": {
      "python_version": "3.9"
    },
    "sources": [
      {
        "name": "pypi",
        "url": "https://pypi.org/",
        "verify_ssl": true
      }
    ]
  },
  "default": {
    "click": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "8.0.3"
    },
    "flask": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "2.0.1"
    },
    "itsdangerous": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "2.0.1"
    },
    "jinja2": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "3.0.2"
    },
    "markupsafe": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "2.0.1"
    },
    "werkzeug": {
      "hashes": [
        "sha256:...",
        "sha256:..."
      ],
      "version": "2.0.2"
    }
  },
  "develop": { ... }
}

Scanning this with Trivy is identical:

trivy fs --skip-db-update --cache-dir=/tmp/trivy-cache .

Trivy intelligently detects the Pipfile.lock and parses the default and develop sections to identify all direct and transitive dependencies. It then uses the pinned versions to check for vulnerabilities.

The core problem Trivy solves here is making the opaque nature of Python dependency management transparent from a security perspective. When you install packages, especially with transitive dependencies, it’s incredibly difficult to manually track every single library and its version. Trivy automates this by reading the lock files, which are designed to provide an exact, reproducible snapshot of your project’s dependencies. The Pipfile.lock is particularly robust because it includes hashes, ensuring the integrity of the downloaded packages. Trivy leverages these pinned versions to perform precise lookups against its vulnerability database, which is constantly updated with information from sources like the National Vulnerability Database (NVD), GitHub Security Advisories, and various language-specific advisories.

The fs command tells Trivy to scan the filesystem. The --skip-db-update flag is useful for air-gapped environments or when you want to ensure you’re using a specific, locally downloaded database. The --cache-dir is crucial for performance; it stores downloaded vulnerability data so subsequent scans are much faster. Trivy’s internal logic for Python involves recognizing requirements.txt, Pipfile, and Pipfile.lock as indicators of Python projects. It then uses specialized parsers for each. For requirements.txt, it’s a straightforward line-by-line parse, extracting package names and version specifiers. For Pipfile.lock, it parses the JSON structure to extract dependencies listed under default and develop sections, prioritizing the pinned versions. It’s important to note that Trivy scans the lock file, not the installed packages in your virtual environment. This is a feature, as it provides a static, auditable record of what should be installed.

A common point of confusion is when Trivy reports vulnerabilities for a package that isn’t explicitly listed in your requirements.txt or Pipfile. This is because Trivy analyzes all dependencies, including transitive ones. For example, if your requirements.txt lists requests==2.26.0, Trivy will also scan charset-normalizer, idna, urllib3, and certifi because requests depends on them. The lock files, especially Pipfile.lock, are invaluable here because they explicitly list these transitive dependencies.

The next step after identifying vulnerabilities is to remediate them. This typically involves updating the vulnerable packages to a patched version. Trivy will often suggest the fixed version in its output. For requirements.txt, you’d manually edit the file, e.g., change Flask==2.0.1 to Flask==2.2.0 (if 2.2.0 is the patched version), and then run pip install -r requirements.txt. For pipenv, you’d run pipenv update <package_name> or pipenv update to update all packages. Trivy’s ability to parse lock files means it can precisely identify which specific version of a transitive dependency needs an update, even if you didn’t list it directly.

The most effective way to ensure Trivy is always finding the latest vulnerabilities is to allow it to update its database regularly. While --skip-db-update is useful for specific scenarios, in a CI/CD pipeline, you’d typically omit this flag to ensure the scanner is always using the most current vulnerability intelligence. The database updates can be substantial, so ensuring sufficient disk space and network bandwidth is important for automated environments.

If you’re using a custom Python package index (PyPI mirror), Trivy will generally not be able to scan it directly without additional configuration. Trivy’s vulnerability database primarily relies on publicly accessible vulnerability feeds. It doesn’t connect to your private package repositories to discover vulnerabilities within them.

Want structured learning?

Take the full Trivy course →