Practical guide to generating SBOMs for Python applications using pip, Poetry, Conda, and common security workflows.

Updated:

Python SBOM Guide for pip, Poetry, and Conda

Introduction: Understanding Python's Dependency Ecosystem

Python applications rely heavily on third-party packages from PyPI (Python Package Index) and other repositories like Anaconda. The Python ecosystem is vast, with over 400,000 packages on PyPI alone, making it the second-largest package repository after npm. This richness comes with complexity - a typical Python web application might depend on 50-100 direct packages, which in turn bring in hundreds of transitive dependencies.

The challenge of managing Python dependencies is compounded by Python's flexible packaging ecosystem. Unlike languages with a single standard package manager, Python offers multiple tools - pip, Poetry, Conda, pipenv - each with different approaches to dependency resolution and environment management. This diversity makes generating accurate SBOMs essential but also more complex, as different tools store dependency information in different formats.

Generating accurate SBOMs for Python projects is now a practical requirement for vulnerability management, customer security reviews, and supplier documentation. Recent PyPI package incidents showed how quickly dependency issues can spread across environments. The useful workflow is to generate a machine-readable SBOM, validate it with the SBOM Validator, and connect it to CI/CD and container pipelines.

Why SBOMs Matter for Python Applications

The Unique Challenges of Python Dependencies

Python's dependency management presents unique challenges that make SBOM generation particularly important. Unlike compiled languages where dependencies are bundled at build time, Python applications typically install dependencies at deployment time. This dynamic nature means the exact dependencies can vary between environments if not carefully managed.

Python projects typically include multiple dependency layers that must all be captured in a comprehensive SBOM:

  • Direct dependencies: Packages explicitly listed in requirements.txt, pyproject.toml, or setup.py. These are the packages your code directly imports and uses.
  • Transitive dependencies: Dependencies of your dependencies, often making up 70-80% of your total dependencies. These hidden dependencies are where many vulnerabilities lurk.
  • Development dependencies: Testing frameworks, linters, build tools that aren't deployed but could introduce supply chain risks during development.
  • System dependencies: OS-level packages and libraries that Python packages depend on, particularly important for packages with C extensions.
  • Conda packages: From Anaconda, conda-forge, or other channels, which may include both Python packages and system libraries.

This creates a complex software supply chain where vulnerabilities in any component can impact your entire application. For example, a vulnerability in a transitive dependency five levels deep in your dependency tree can be just as dangerous as one in a direct dependency. Without comprehensive SBOMs, these deep dependencies remain invisible, creating significant security blind spots.

Package Manager Support

Understanding Python Package Managers

Python's ecosystem includes multiple package managers, each with strengths for different use cases. Understanding these tools and their SBOM generation capabilities is crucial for implementing effective supply chain security. The choice of package manager affects not just how dependencies are installed, but also how they're tracked, updated, and secured.

pip and virtualenv

pip (Pip Installs Packages) is the standard package manager for Python, included with Python 3.4+. It's often used with virtual environments (venv, virtualenv) to isolate project dependencies. While pip is simple and universal, it historically lacked features like lock files, making reproducible builds challenging. Modern pip (version 20.3+) has improved dependency resolution, but SBOM generation still requires additional tools.

Virtual environments are crucial for Python SBOM accuracy. Without them, SBOM tools might capture system-wide packages that aren't actually used by your application. Always generate SBOMs from within the appropriate virtual environment to ensure accuracy:

Using CycloneDX for pip

CycloneDX has become the de facto standard for Python SBOM generation due to its comprehensive support for Python packaging formats and integration with security tools. The cyclonedx-bom tool understands pip's various configuration files and can generate SBOMs from multiple sources, ensuring nothing is missed.

The tool can work with different Python project structures, from simple scripts with requirements.txt to complex applications using setup.py or modern pyproject.toml files:

# Install CycloneDX Python library
pip install cyclonedx-bom

# Generate SBOM from requirements.txt
cyclonedx-py -r requirements.txt -o sbom.json

# Generate from installed packages
cyclonedx-py -p -o sbom.json

# Generate from setup.py or pyproject.toml
cyclonedx-py -pip -o sbom.json

# Include development dependencies
cyclonedx-py -r requirements.txt -r requirements-dev.txt -o sbom-complete.json

# Generate XML format
cyclonedx-py -r requirements.txt -o sbom.xml --format xml

Advanced pip SBOM Generation

Beyond basic SBOM generation, advanced techniques help capture the complete dependency picture. Including package hashes ensures integrity verification, while multi-environment scanning captures dependencies across development, testing, and production. These advanced approaches are essential for enterprise environments where security and compliance requirements are stringent:

# Generate with package hashes
pip freeze > requirements-exact.txt
cyclonedx-py -r requirements-exact.txt --include-hashes -o sbom-with-hashes.json

# Generate for multiple environments
cyclonedx-py \
  -r requirements.txt \
  -r requirements-dev.txt \
  -r requirements-test.txt \
  -o multi-env-sbom.json

# Include system packages (use with caution)
cyclonedx-py -p --include-system -o system-sbom.json

# Generate with vulnerability data
pip-audit --format=cyclonedx --output=sbom-with-vulns.json

Using pip-audit

pip-audit, developed by Trail of Bits and maintained by the Python Packaging Authority, combines SBOM generation with vulnerability scanning. Unlike generic SBOM tools, pip-audit understands Python-specific vulnerability databases and can identify issues specific to Python packages. It integrates with the OSV (Open Source Vulnerabilities) database, providing comprehensive vulnerability coverage:

pip-audit is specifically designed for Python security and SBOM generation:

# Install pip-audit
pip install pip-audit

# Generate SBOM with vulnerability scanning
pip-audit --format=cyclonedx --output=sbom.json

# Scan requirements file
pip-audit -r requirements.txt --format=cyclonedx --output=sbom.json

# Include vulnerability details
pip-audit --format=cyclonedx --output=sbom-with-vulns.json --desc

# Scan and ignore specific vulnerabilities
pip-audit --ignore-vuln GHSA-xxxx-xxxx-xxxx --format=cyclonedx --output=sbom.json

Poetry Package Manager

Poetry represents a modern approach to Python dependency management, addressing many of pip's historical limitations. It provides deterministic builds through poetry.lock files, integrated virtual environment management, and sophisticated dependency resolution. Poetry's lock file contains exact versions of all dependencies, including hashes, making it ideal for generating accurate, reproducible SBOMs.

Poetry's pyproject.toml file clearly separates production and development dependencies, allowing SBOM tools to generate appropriate SBOMs for different contexts. This separation is crucial for security analysis, as vulnerabilities in development dependencies might have different risk profiles than those in production:

Basic Poetry SBOM Generation

Generating SBOMs from Poetry projects is straightforward because Poetry maintains complete dependency information in its lock file. The lock file includes not just versions but also dependency relationships, source repositories, and file hashes. This rich metadata enables more comprehensive SBOMs that include supply chain provenance information:

# Install CycloneDX for Poetry
pip install cyclonedx-bom

# Generate from pyproject.toml and poetry.lock
cyclonedx-py --poetry -o sbom.json

# Include dev dependencies
cyclonedx-py --poetry --include-dev -o sbom-complete.json

# Generate from specific Poetry project
cyclonedx-py --poetry --project-path /path/to/project -o sbom.json

Poetry with Custom Script

While command-line tools work well for simple SBOM generation, complex projects often need custom scripts to handle special requirements. Custom scripts can integrate SBOM generation with other build processes, add organization-specific metadata, or handle private package repositories. This Python script demonstrates how to programmatically generate SBOMs for Poetry projects:

# generate_sbom.py
import json
import subprocess
from pathlib import Path

def generate_poetry_sbom():
    # Get Poetry dependencies
    result = subprocess.run(
        ["poetry", "show", "--format=json"], 
        capture_output=True, 
        text=True
    )
    dependencies = json.loads(result.stdout)

    # Generate SBOM using CycloneDX
    subprocess.run([
        "cyclonedx-py", 
        "--poetry", 
        "--output", "sbom.json"
    ])

    print("SBOM generated successfully!")

if __name__ == "__main__":
    generate_poetry_sbom()
# Run the script
python generate_sbom.py

Poetry CI/CD Integration

# .github/workflows/sbom.yml
name: Generate SBOM

on:
  push:
    branches: [main]

jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'

      - name: Install Poetry
        uses: snok/install-poetry@v1
        with:
          version: latest
          virtualenvs-create: true
          virtualenvs-in-project: true

      - name: Install dependencies
        run: poetry install

      - name: Generate SBOM
        run: |
          poetry add cyclonedx-bom --group dev
          poetry run cyclonedx-py --poetry -o sbom.json

      - name: Upload SBOM
        uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.json

Conda Package Manager

Conda is popular in data science and scientific computing communities.

Basic Conda SBOM Generation

# Install CycloneDX support
conda install -c conda-forge cyclonedx-python-lib

# Generate SBOM from environment
conda list --json > conda-packages.json
cyclonedx-py --conda-packages conda-packages.json -o sbom.json

# Generate from environment.yml
cyclonedx-py --conda-env environment.yml -o sbom.json

# Include pip packages in conda environment
cyclonedx-py --conda-env environment.yml --include-pip -o complete-sbom.json

Advanced Conda Integration

# Export current environment
conda env export > environment.yml

# Generate SBOM with full environment info
conda list --explicit > conda-spec.txt
cyclonedx-py --conda-explicit conda-spec.txt -o sbom.json

# Cross-platform environment handling
conda env export --no-builds > environment-cross-platform.yml
cyclonedx-py --conda-env environment-cross-platform.yml -o sbom.json

Conda with Docker

# Dockerfile for Conda-based Python app
FROM continuumio/miniconda3:latest

WORKDIR /app

# Copy environment file
COPY environment.yml .

# Create environment
RUN conda env create -f environment.yml

# Activate environment
SHELL ["conda", "run", "-n", "myapp", "/bin/bash", "-c"]

# Generate SBOM
RUN conda install -c conda-forge cyclonedx-python-lib && \
    cyclonedx-py --conda-env environment.yml -o sbom.json

# Copy application
COPY . .

# Run app
CMD ["conda", "run", "-n", "myapp", "python", "app.py"]

Universal Tools

Syft - Universal SBOM Generator

Syft works excellently with Python projects:

# Install Syft
curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin

# Generate SBOM from directory
syft dir:. -o cyclonedx-json=sbom.json

# Generate from Python wheel
syft python:dist/myapp-1.0.0-py3-none-any.whl -o spdx-json=wheel-sbom.json

# Generate from requirements.txt
syft python:requirements.txt -o cyclonedx-json=requirements-sbom.json

# Analyze site-packages directory
syft dir:/path/to/venv/lib/python3.11/site-packages -o cyclonedx-json=installed-sbom.json

# Generate from Docker container
syft docker:my-python-app:latest -o cyclonedx-json=container-sbom.json

SPDX Tools for Python

Generate SPDX format SBOMs:

# Install SPDX tools
pip install spdx-tools

# Generate SPDX from directory
spdx-tools convert-project . --output sbom.spdx.json

# Convert between formats
spdx-tools convert --from cyclonedx sbom.json --to spdx sbom.spdx.json

Framework-Specific Considerations

Django Applications

# Django project SBOM generation
cd my-django-app

# Generate from requirements
cyclonedx-py -r requirements.txt -o django-sbom.json

# Include development and testing requirements
cyclonedx-py \
  -r requirements.txt \
  -r requirements-dev.txt \
  -r requirements-test.txt \
  -o django-complete-sbom.json

# Add to Django settings for runtime access
# settings.py
import json
import os

SBOM_FILE = os.path.join(BASE_DIR, 'sbom.json')
if os.path.exists(SBOM_FILE):
    with open(SBOM_FILE) as f:
        SBOM_DATA = json.load(f)
else:
    SBOM_DATA = None

# Create view to serve SBOM
# views.py
from django.http import JsonResponse
from django.conf import settings

def sbom_view(request):
    if settings.SBOM_DATA:
        return JsonResponse(settings.SBOM_DATA)
    return JsonResponse({'error': 'SBOM not available'}, status=404)

Flask Applications

# Flask project setup
cd my-flask-app

# Generate SBOM
cyclonedx-py -r requirements.txt -o flask-sbom.json

# Add SBOM endpoint
# app.py
from flask import Flask, jsonify
import json
import os

app = Flask(__name__)

@app.route('/sbom')
def get_sbom():
    sbom_path = os.path.join(app.root_path, 'sbom.json')
    if os.path.exists(sbom_path):
        with open(sbom_path) as f:
            return jsonify(json.load(f))
    return jsonify({'error': 'SBOM not available'}), 404

if __name__ == '__main__':
    app.run()

FastAPI Applications

# FastAPI with SBOM endpoint
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import json
import os
from pathlib import Path

app = FastAPI()

@app.get("/sbom")
async def get_sbom():
    sbom_path = Path("sbom.json")
    if sbom_path.exists():
        with open(sbom_path) as f:
            sbom_data = json.load(f)
        return JSONResponse(content=sbom_data)
    raise HTTPException(status_code=404, detail="SBOM not available")

# Generate SBOM in CI/CD
# requirements.txt should include:
# cyclonedx-bom>=4.0.0

Data Science Projects

# Jupyter notebook project
cd my-data-science-project

# Generate SBOM including scientific packages
cyclonedx-py -r requirements.txt -o datascience-sbom.json

# Common data science requirements.txt
echo "numpy>=1.21.0
pandas>=1.3.0
scikit-learn>=1.0.0
matplotlib>=3.4.0
jupyter>=1.0.0
notebook>=6.4.0" > requirements.txt

# Generate comprehensive SBOM
cyclonedx-py -r requirements.txt --include-hashes -o comprehensive-sbom.json

# For conda-based data science environments
conda env export > environment.yml
cyclonedx-py --conda-env environment.yml -o conda-datascience-sbom.json

CI/CD Integration

GitHub Actions

name: Python SBOM Generation

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  generate-sbom:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11']

    steps:
    - uses: actions/checkout@v4

    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install cyclonedx-bom pip-audit
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

    - name: Generate SBOM with CycloneDX
      run: |
        cyclonedx-py -r requirements.txt -o sbom-cyclonedx.json
        cyclonedx-py -r requirements.txt -o sbom-cyclonedx.xml --format xml

    - name: Generate SBOM with pip-audit
      run: |
        pip-audit --format=cyclonedx --output=sbom-with-vulns.json

    - name: Generate SBOM with Syft
      run: |
        curl -sSfL https://get.anchore.io/syft | sh -s -- -b /usr/local/bin
        syft dir:. -o cyclonedx-json=sbom-syft.json

    - name: Upload SBOM artifacts
      uses: actions/upload-artifact@v4
      with:
        name: sbom-files-python-${{ matrix.python-version }}
        path: |
          sbom-*.json
          sbom-*.xml

    - name: Security scan
      run: |
        pip-audit --format=table
        # Add your security scanning tools here
        # grype sbom-cyclonedx.json
        # snyk test --file=requirements.txt

GitLab CI

stages:
  - install
  - build
  - sbom
  - security

variables:
  PYTHON_VERSION: "3.11"

cache:
  paths:
    - .pip-cache/

install-dependencies:
  stage: install
  image: python:${PYTHON_VERSION}
  script:
    - python -m pip install --upgrade pip
    - pip install --cache-dir .pip-cache -r requirements.txt
    - pip install --cache-dir .pip-cache cyclonedx-bom pip-audit
  artifacts:
    expire_in: 1 hour
    paths:
      - .pip-cache/

generate-sbom:
  stage: sbom
  image: python:${PYTHON_VERSION}
  dependencies:
    - install-dependencies
  script:
    - pip install --cache-dir .pip-cache cyclonedx-bom pip-audit
    - cyclonedx-py -r requirements.txt -o sbom.json
    - pip-audit --format=cyclonedx --output=sbom-with-vulns.json
  artifacts:
    expire_in: 1 week
    paths:
      - sbom.json
      - sbom-with-vulns.json
    reports:
      cyclonedx: sbom.json

security-scan:
  stage: security
  dependencies:
    - generate-sbom
  script:
    - pip install --cache-dir .pip-cache pip-audit
    - pip-audit --format=table --output=security-report.txt || true
    - echo "Security scan completed"
  artifacts:
    expire_in: 1 week
    paths:
      - security-report.txt
  allow_failure: true

Jenkins Pipeline

pipeline {
    agent any

    parameters {
        choice(name: 'PYTHON_VERSION', choices: ['3.9', '3.10', '3.11'], description: 'Python version')
    }

    environment {
        VIRTUAL_ENV = "${WORKSPACE}/venv"
        PATH = "${VIRTUAL_ENV}/bin:${PATH}"
    }

    stages {
        stage('Setup Python Environment') {
            steps {
                sh """
                    python${params.PYTHON_VERSION} -m venv ${VIRTUAL_ENV}
                    pip install --upgrade pip
                    pip install cyclonedx-bom pip-audit
                """
            }
        }

        stage('Install Dependencies') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }

        stage('Generate SBOM') {
            parallel {
                stage('CycloneDX SBOM') {
                    steps {
                        sh '''
                            cyclonedx-py -r requirements.txt -o sbom-cyclonedx.json
                            cyclonedx-py -r requirements.txt -o sbom-cyclonedx.xml --format xml
                        '''
                    }
                }
                stage('Security SBOM') {
                    steps {
                        sh 'pip-audit --format=cyclonedx --output=sbom-security.json'
                    }
                }
                stage('Syft SBOM') {
                    steps {
                        sh '''
                            curl -sSfL https://get.anchore.io/syft | sh -s -- -b ${WORKSPACE}/bin
                            ${WORKSPACE}/bin/syft dir:. -o cyclonedx-json=sbom-syft.json
                        '''
                    }
                }
            }
        }

        stage('Validate SBOM') {
            steps {
                script {
                    sh '''
                        # Basic JSON validation
                        python -m json.tool sbom-cyclonedx.json > /dev/null
                        echo "SBOM validation passed"

                        # Component count check
                        COMPONENT_COUNT=$(jq '.components | length' sbom-cyclonedx.json)
                        echo "Found $COMPONENT_COUNT components in SBOM"

                        if [ "$COMPONENT_COUNT" -lt 1 ]; then
                            echo "Warning: SBOM contains no components"
                            exit 1
                        fi
                    '''
                }
            }
        }

        stage('Security Analysis') {
            steps {
                sh '''
                    pip-audit --format=table --output=security-report.txt || true
                    if [ -f security-report.txt ]; then
                        echo "Security report:"
                        cat security-report.txt
                    fi
                '''
            }
            post {
                always {
                    archiveArtifacts artifacts: 'security-report.txt', allowEmptyArchive: true
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'sbom-*.json, sbom-*.xml', fingerprint: true

            publishHTML([
                allowMissing: false,
                alwaysLinkToLastBuild: true,
                keepAll: true,
                reportDir: '.',
                reportFiles: 'sbom-cyclonedx.json',
                reportName: 'SBOM Report'
            ])
        }
        success {
            script {
                if (env.BRANCH_NAME == 'main') {
                    // Upload to security platform
                    sh '''
                        curl -X POST \
                             -H "Authorization: Bearer ${SECURITY_TOKEN}" \
                             -F "sbom=@sbom-cyclonedx.json" \
                             https://your-security-platform.com/api/sbom
                    '''
                }
            }
        }
    }
}

Docker Integration

Multi-stage Docker Build

# Multi-stage Python build with SBOM
FROM python:3.11-slim as base

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Dependencies stage
FROM base as deps

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# SBOM generation stage
FROM deps as sbom-generator

RUN pip install cyclonedx-bom
RUN cyclonedx-py -r requirements.txt -o sbom.json

# Production stage
FROM base as production

# Copy installed packages
COPY --from=deps /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=deps /usr/local/bin /usr/local/bin

# Copy SBOM
COPY --from=sbom-generator /app/sbom.json /app/sbom.json

# Copy application code
COPY . .

# Add SBOM endpoint (for web applications)
EXPOSE 8000

# Health check that includes SBOM
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD python -c "import json; print('SBOM available:', bool(json.load(open('sbom.json'))))"

CMD ["python", "app.py"]

Container SBOM Generation

# Build container
docker build -t my-python-app .

# Generate SBOM from container
syft docker:my-python-app -o cyclonedx-json=container-sbom.json

# Analyze specific layers
docker history my-python-app --format "table {{.ID}}\t{{.Size}}\t{{.CreatedBy}}"
syft docker:my-python-app --layer 0 -o cyclonedx-json=base-layer-sbom.json

# Generate SBOM with Grype for vulnerabilities
grype docker:my-python-app -o cyclonedx-json=container-vuln-sbom.json

Best Practices

Essential Best Practices for Python SBOM Generation

Successful SBOM generation for Python projects requires more than just running tools - it requires establishing consistent processes that ensure accuracy, reproducibility, and maintainability. These best practices have been developed through experience managing Python projects ranging from simple scripts to complex enterprise applications with hundreds of dependencies.

1. Virtual Environment Management

Virtual environments are fundamental to accurate Python SBOM generation. Without proper environment isolation, SBOM tools may capture system-wide packages that aren't actually part of your application, leading to inaccurate SBOMs. Even worse, different developers might generate different SBOMs for the same project due to their local Python installations.

Always create a clean virtual environment specifically for SBOM generation to ensure consistency:

# Always use virtual environments
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Generate SBOM from clean state
pip install -r requirements.txt
cyclonedx-py -p -o clean-sbom.json

# Pin exact versions
pip freeze > requirements-exact.txt
cyclonedx-py -r requirements-exact.txt -o pinned-sbom.json

2. Dependency Pinning

Pinning dependencies to exact versions is crucial for reproducible builds and consistent SBOMs. While it might seem convenient to use version ranges (>=, ~=), this flexibility can lead to different dependencies being installed at different times, making security tracking and incident response nearly impossible.

The trade-off is that pinned dependencies require active management to receive security updates. Use automated tools like Dependabot, Renovate, or Safety to alert you to available updates:

# requirements.txt with pinned versions
Django==4.2.4
requests==2.31.0
psycopg2-binary==2.9.7
celery==5.3.1
redis==4.6.0
gunicorn==21.2.0

# Development dependencies (requirements-dev.txt)
pytest==7.4.0
black==23.7.0
flake8==6.0.0
mypy==1.5.1
cyclonedx-bom==4.1.0

3. Multi-environment SBOM Generation

Python projects often have different dependencies for development, testing, and production environments. Generating separate SBOMs for each environment provides accurate visibility into what's actually deployed versus what's used during development. This separation is crucial for security assessments, as a vulnerability in a test-only dependency has different risk implications than one in production:

# Generate environment-specific SBOMs
cyclonedx-py -r requirements.txt -o production-sbom.json
cyclonedx-py -r requirements-dev.txt -o development-sbom.json
cyclonedx-py -r requirements-test.txt -o testing-sbom.json

# Combined SBOM for all environments
cyclonedx-py \
  -r requirements.txt \
  -r requirements-dev.txt \
  -r requirements-test.txt \
  -o complete-sbom.json

4. SBOM Validation and Quality

Validating generated SBOMs ensures they meet format specifications and contain necessary information for security and compliance use cases. Invalid or incomplete SBOMs can cause failures in security scanning tools, compliance audits, and downstream systems. Implement validation as part of your SBOM generation process to catch issues early:

# validate_sbom.py
import json
import jsonschema
import requests
from pathlib import Path

def validate_cyclonedx_sbom(sbom_file):
    """Validate CycloneDX SBOM against schema"""

    # Load SBOM
    with open(sbom_file) as f:
        sbom = json.load(f)

    # Get CycloneDX schema
    schema_version = sbom.get('specVersion', '1.5')
    schema_url = f"https://raw.githubusercontent.com/CycloneDX/specification/master/schema/cyclonedx-{schema_version}.schema.json"

    try:
        schema_response = requests.get(schema_url)
        schema_response.raise_for_status()
        schema = schema_response.json()

        # Validate
        jsonschema.validate(sbom, schema)
        print(f"✅ SBOM {sbom_file} is valid")

        # Quality checks
        components = sbom.get('components', [])
        print(f"📊 Found {len(components)} components")

        # Check for missing version info
        missing_versions = [c['name'] for c in components if not c.get('version')]
        if missing_versions:
            print(f"⚠️  Components missing version info: {missing_versions}")

        # Check for license information
        components_with_licenses = [c for c in components if c.get('licenses')]
        print(f"📄 {len(components_with_licenses)}/{len(components)} components have license info")

    except Exception as e:
        print(f"❌ SBOM validation failed: {e}")
        return False

    return True

if __name__ == "__main__":
    validate_cyclonedx_sbom("sbom.json")

Security Considerations

1. Vulnerability Scanning

# Using pip-audit for vulnerability scanning
pip-audit --format=cyclonedx --output=sbom-with-vulns.json

# Using safety
pip install safety
safety check --json --output safety-report.json

# Using bandit for code security
pip install bandit
bandit -r . -f json -o bandit-report.json

# Combine with SBOM for comprehensive security
cyclonedx-py -r requirements.txt -o base-sbom.json
# Merge security reports with SBOM (custom tooling required)

2. License Compliance

# Generate SBOM with license information
cyclonedx-py -r requirements.txt --include-license-text -o sbom-with-licenses.json

# Extract license information
jq '.components[] | {name: .name, version: .version, licenses: .licenses}' sbom-with-licenses.json

# Check for problematic licenses
pip install pip-licenses
pip-licenses --format=json --output-file=licenses.json

3. Supply Chain Security

# supply_chain_analysis.py
import json
from collections import Counter
from datetime import datetime, timedelta

def analyze_supply_chain(sbom_file):
    """Analyze Python SBOM for supply chain risks"""

    with open(sbom_file) as f:
        sbom = json.load(f)

    components = sbom.get('components', [])

    analysis = {
        'total_components': len(components),
        'unique_publishers': len(set(c.get('publisher', 'Unknown') for c in components)),
        'license_types': Counter(),
        'age_analysis': {'old_packages': [], 'recent_packages': []},
        'risk_indicators': []
    }

    # License analysis
    for component in components:
        licenses = component.get('licenses', [])
        for license_info in licenses:
            if 'license' in license_info:
                license_id = license_info['license'].get('id', 'Unknown')
                analysis['license_types'][license_id] += 1

    # Age analysis (if modification dates available)
    cutoff_date = datetime.now() - timedelta(days=365*2)  # 2 years old

    for component in components:
        modified = component.get('modified')
        if modified:
            mod_date = datetime.fromisoformat(modified.replace('Z', '+00:00'))
            if mod_date < cutoff_date:
                analysis['age_analysis']['old_packages'].append({
                    'name': component['name'],
                    'version': component.get('version'),
                    'modified': modified
                })
            else:
                analysis['age_analysis']['recent_packages'].append(component['name'])

    # Risk indicators
    if len(analysis['age_analysis']['old_packages']) > len(components) * 0.3:
        analysis['risk_indicators'].append("High number of outdated packages")

    if analysis['unique_publishers'] < len(components) * 0.1:
        analysis['risk_indicators'].append("Low publisher diversity")

    return analysis

# Usage
analysis = analyze_supply_chain('sbom.json')
print(json.dumps(analysis, indent=2, default=str))

Frequently Asked Questions (FAQ)

General Python SBOM Questions

Q: Should I use requirements.txt or pyproject.toml for dependency management?

A: For new projects, use pyproject.toml as it's the modern Python standard (PEP 518/621). It provides better metadata, clear separation of dependencies, and tool configuration in one place. For existing projects using requirements.txt, you can continue using it, but consider migrating to pyproject.toml for better dependency management. If using Poetry or other modern tools, they handle pyproject.toml automatically.

Q: How do I handle dependencies installed from git repositories or local paths?

A: Git and local dependencies require special handling in SBOMs. For git dependencies, include the repository URL and commit hash in the SBOM metadata. For local packages, use file:// URLs with absolute paths. CycloneDX supports these through external references:

# Git dependency in requirements.txt
git+https://github.com/org/repo.git@v1.0.0#egg=package

# Local dependency
file:///path/to/local/package

Some SBOM tools may not fully support these dependency types, so document them separately if needed.

Q: What about Python packages installed via system package managers (apt, yum)?

A: System-installed Python packages (like python3-numpy on Ubuntu) won't appear in pip-based SBOMs. If your application depends on system packages, you need a multi-layer approach:

  1. Generate Python SBOM for pip/conda packages
  2. Generate system SBOM using system-specific tools
  3. Document the relationship between them
  4. Consider containerizing to capture all dependencies
Q: How do I generate SBOMs for Jupyter notebooks?

A: Jupyter notebooks present unique challenges as dependencies might be installed ad-hoc during development. Best practices:

  1. Export notebook requirements: pip freeze > requirements.txt
  2. Use tools like pigar to detect imports: pigar generate
  3. Consider using papermill or nbconvert to parameterize notebooks
  4. Generate SBOMs from the kernel environment, not the Jupyter server environment

Tool-Specific Questions

Q: Why does cyclonedx-bom show different results than pip-audit?

A: These tools use different approaches:

  • cyclonedx-bom reads from requirements files and pip metadata
  • pip-audit performs actual dependency resolution and includes vulnerability data
  • pip-audit might show more dependencies due to its resolution process
  • Use both tools for comprehensive coverage
Q: Can I generate SBOMs for Python 2.7 projects?

A: Yes, but with limitations. Python 2.7 reached end-of-life in 2020, and many tools have dropped support. Options:

  1. Use older versions of SBOM tools that still support Python 2.7
  2. Use Syft which can scan Python 2.7 projects
  3. Consider this a security risk and prioritize migration to Python 3
  4. Document the Python 2.7 dependency as a known risk in your SBOM
*Q: How do I handle namespace packages (like zope. or google.)? A: Namespace packages can cause issues with some SBOM tools as they're distributed across multiple packages. Ensure your SBOM tool supports PEP 420 (implicit namespace packages). When using cyclonedx-bom, it handles namespace packages correctly if you're scanning installed packages (-p flag) rather than just requirements files.

Security and Compliance Questions

Q: How do I identify and handle typosquatting risks in Python packages?

A: Typosquatting is a significant risk in PyPI. Protect against it:

  1. Use tools like pypi-scan to check for potential typosquats
  2. Validate package names against known good sources
  3. Check package download statistics - legitimate packages usually have more downloads
  4. Use private package indexes for internal packages
  5. Include package source verification in your SBOM
Q: What about compiled Python extensions (.so, .pyd files)?

A: Compiled extensions require special consideration:

  1. They may include bundled C/C++ libraries not visible to Python SBOM tools
  2. Use binary scanning tools like Syft in addition to Python-specific tools
  3. Document build-time dependencies separately
  4. Consider security implications of binary components
  5. Include compiler and build environment information in SBOM metadata
Q: How often should I regenerate SBOMs for Python projects?

A: Regenerate SBOMs:

  • With every dependency change (add, update, remove)
  • Before each release or deployment
  • After security patches
  • Weekly for actively developed projects
  • When switching Python versions
  • After pip/setuptools updates (they affect dependency resolution)

Troubleshooting

Common Issues and Solutions

Even with proper setup, you may encounter issues generating SBOMs for Python projects. These problems often stem from Python's flexible packaging ecosystem, environment inconsistencies, or tool limitations. This troubleshooting guide addresses the most common issues with practical solutions.

Common Issues

  1. Missing system dependencies
# Install system packages needed for Python packages
   apt-get update && apt-get install -y \
     gcc g++ \
     python3-dev \
     libffi-dev \
     libssl-dev

   # Then regenerate SBOM
   cyclonedx-py -r requirements.txt -o sbom.json
  1. Virtual environment confusion
# Ensure you're in the correct virtual environment
   which python
   which pip

   # Deactivate and recreate if needed
   deactivate
   rm -rf venv
   python -m venv venv
   source venv/bin/activate
   pip install -r requirements.txt
   cyclonedx-py -p -o sbom.json
  1. Package installation issues
# Clear pip cache
   pip cache purge

   # Reinstall with no cache
   pip install --no-cache-dir -r requirements.txt

   # Generate SBOM
   cyclonedx-py -r requirements.txt -o sbom.json
  1. Large dependency trees
# Analyze dependency tree
   pip install pipdeptree
   pipdeptree --json > dependency-tree.json

   # Generate minimal SBOM (production only)
   pip freeze --exclude-editable > prod-requirements.txt
   cyclonedx-py -r prod-requirements.txt -o minimal-sbom.json

Advanced Use Cases

Monorepo Python Projects

# Generate SBOMs for multiple Python projects
projects=("api" "worker" "cli" "shared")

for project in "${projects[@]}"; do
    echo "Generating SBOM for $project"
    cd $project
    cyclonedx-py -r requirements.txt -o "../sboms/${project}-sbom.json"
    cd ..
done

# Merge SBOMs if needed (requires custom tooling)
python merge_sboms.py sboms/*.json > combined-sbom.json

Machine Learning Projects

# ML project with GPU dependencies
# requirements-gpu.txt
echo "torch>=2.0.0
torchvision>=0.15.0
tensorflow>=2.13.0
scikit-learn>=1.3.0
pandas>=2.0.0
numpy>=1.24.0
matplotlib>=3.7.0
jupyter>=1.0.0" > requirements-gpu.txt

# Generate SBOM for ML environment
cyclonedx-py -r requirements-gpu.txt -o ml-sbom.json

# Include CUDA dependencies (if analyzing container)
syft docker:my-ml-app:gpu -o cyclonedx-json=ml-container-sbom.json

Serverless Python Applications

# AWS Lambda SBOM generation
# lambda_sbom.py
import json
import subprocess
import zipfile
from pathlib import Path

def generate_lambda_sbom():
    """Generate SBOM for AWS Lambda deployment package"""

    # Install dependencies locally
    subprocess.run([
        "pip", "install", "-r", "requirements.txt", 
        "-t", "./lambda-package"
    ])

    # Generate SBOM
    subprocess.run([
        "cyclonedx-py", "-r", "requirements.txt", 
        "-o", "lambda-sbom.json"
    ])

    # Create deployment package with SBOM
    with zipfile.ZipFile('lambda-deployment.zip', 'w') as zipf:
        # Add lambda function
        zipf.write('lambda_function.py')

        # Add dependencies
        for root, dirs, files in os.walk('./lambda-package'):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, './lambda-package')
                zipf.write(file_path, arcname)

        # Add SBOM
        zipf.write('lambda-sbom.json', 'sbom.json')

    print("Lambda package with SBOM created: lambda-deployment.zip")

if __name__ == "__main__":
    generate_lambda_sbom()

Conclusion

Generating accurate SBOMs for Python projects has become essential in today's software development landscape. The Python ecosystem's complexity - with multiple package managers, dependency sources, and deployment models - makes comprehensive SBOM generation both challenging and critical. This guide has covered all aspects of Python SBOM generation, from basic pip projects to complex enterprise deployments using Poetry, Conda, and custom environments.

The key to successful Python SBOM generation is establishing consistent processes that work across your development lifecycle. Start by choosing the right tools for your package manager, implement proper virtual environment management, and integrate SBOM generation into your CI/CD pipeline. Remember that SBOM generation is not a one-time activity but an ongoing process that should evolve with your application.

As Python continues to evolve with new packaging standards (PEP 517/518/621) and security requirements, your SBOM practices should adapt accordingly. The investment in proper SBOM generation pays dividends through improved security posture, faster incident response, and smoother compliance audits.

Next Steps

After establishing Python SBOM generation in your projects, consider these advanced steps to enhance your software supply chain security:

  1. Integrate with security tools: Combine SBOM generation with tools like pip-audit, safety, or bandit for continuous vulnerability monitoring
  2. Automate dependency updates: Set up Dependabot, Renovate, or PyUp to automatically propose dependency updates with security fixes
  3. Monitor runtime dependencies: Use tools like py-spy or pyflame to identify dependencies actually used in production
  4. Implement policy gates: Create automated checks that fail builds when critical vulnerabilities or banned licenses are detected
  5. Share with stakeholders: Provide SBOMs to customers, security teams, and compliance officers as part of your software delivery

---

Last updated: March 9, 2026 Reading time: 20 minutes Expertise level
*: Beginner to Advanced