Skip to main content

Multi-ecosystem CycloneDX 1.6 SBOM scanner with HTML report and CVE scanning

Project description

SBOM Scanner

A modular, multi-ecosystem SBOM scanner for software projects. Produces a CycloneDX 1.6 SBOM of all dependencies (including transitive) and generates reports in multiple formats with CVE scanning and version analysis.

Features

  • Multi-ecosystem — npm, yarn, PyPI, Dart/Flutter, Maven/Gradle, Rust/Cargo, C#/NuGet
  • Multiroot / Monorepo — multiple lockfiles per ecosystem, with labels and tags
  • Gradle subprojects — automatic detection and scanning of all submodules
  • C#/.NET solutions — parses .sln files, discovers all .csproj subprojects, supports both modern PackageReference and legacy packages.config
  • Ecosystem-specific options — include/exclude dev deps, choose dependency tree method, select Gradle configurations
  • Zero-config quickstart — auto-detects ecosystems from lockfiles, no config file needed
  • Interactive configuration wizard — fancy TUI with cursor navigation, color-coded status table, per-ecosystem options, and live config preview (powered by rich + InquirerPy) — falls back to simple text menu without dependencies
  • CVE scanning — pluggable scanner architecture with grype and osv-scanner built in
  • License compliance — fetches license info from package registries (npm, PyPI, Cargo), color-coded badges (permissive/copyleft/unknown), dedicated license overview tab with clickable filter buttons
  • Latest-version check — parallel lookups against npm, PyPI, crates.io, Maven Central, Google Maven, NuGet
  • Multiple output formats — HTML (interactive), simple HTML, PDF, JSON, CSV
  • Dependency tree — expandable, animated, outdated descendants bubble up, sub-tabs per ecosystem/module
  • Fully modular — three plugin registries: ecosystems, scanners, renderers — each extensible with one file + registry entry
  • Library API — directly importable (from sbom_scanner import generate_sbom, generate_report)
  • i18n — English by default, German translation included (--lang de)
  • Installablepip install with a single sbom compound command

Quickstart

# Install
pip install sbom-scanner[all]

# 1. Generate config (interactive — scans the project and asks)
sbom configure --project-dir /path/to/project

# 2. Scan dependencies and generate SBOM
sbom scan --project-dir /path/to/project

# 3. Generate report
sbom report --project-dir /path/to/project
sbom report --project-dir /path/to/project --format json
sbom report --project-dir /path/to/project --format csv
sbom report --project-dir /path/to/project --format pdf

# Open report
open /path/to/project/sbom-report.html

Step 1 is optional — without a config file, ecosystems are auto-detected with default paths. Config is recommended for monorepos or subdirectory layouts.

Installation

# From PyPI
pip install sbom-scanner[all]

# From the repository
pip install .[all]

# For development
pip install -e .[all]

Optional extras:

Extra Installs When needed
yaml PyYAML YAML config and Dart/Flutter
python pipdeptree Transitive Python deps and dependency graph
pdf weasyprint PDF export (alternative: Chrome headless)
tui rich, InquirerPy Fancy interactive configurator
all all of the above Recommended

External tools (optional):

Tool When needed
grype CVE scanning
osv-scanner CVE scanning
Google Chrome / Chromium PDF export (alternative to weasyprint)
dart CLI Dart/Flutter outdated check

Output Formats

Format Flag Description
html --format html (default) Interactive report with tabs, search, filters, dependency tree, dark mode
simple-html --format simple-html Flat HTML without JavaScript — suitable for email or archiving
pdf --format pdf PDF via weasyprint, Chrome headless, or wkhtmltopdf
json --format json Structured JSON with metadata, vulnerabilities, and per-ecosystem package data
csv --format csv Flat CSV for import into Excel, Google Sheets, or databases

Backward-compatible aliases: --simple = --format simple-html, --pdf = --format pdf

Configuration

Create an sbom.config.yaml in the project directory (or let sbom configure generate it):

Minimal config

project:
  name: my-app
  version: "1.0.0"

Full config

project:
  name: my-app
  version: "2.0.0"                    # fixed version, or omit for auto-detect
  # version_script: ./get-version.sh  # custom script that prints version to stdout
  description: "My application"

# Only specify when paths differ from defaults.
# Omit = auto-detect with default paths.
sources:
  npm:
    package_json: package.json        # default
    lockfile: package-lock.json       # default (also supports yarn.lock)
    include_dev: true                 # include devDependencies (default: true)
    include_optional: true            # include optionalDependencies (default: true)
  pypi:
    requirements: requirements.txt    # default (pip-compile or plain)
    dep_tree_method: auto             # auto | pipdeptree | pip-compile | flat
  pub:
    pubspec_yaml: pubspec.yaml        # default
    pubspec_lock: pubspec.lock        # default
    include_dev: true                 # include dev_dependencies (default: true)
  maven:
    gradle_dir: .                     # default (root with build.gradle)
    configurations:                   # Gradle configurations to scan
      - runtimeClasspath              # default
    include_subprojects: true         # scan subprojects (default: true)
  cargo:
    cargo_toml: Cargo.toml            # default
    lockfile: Cargo.lock              # default
    include_dev: true                 # include [dev-dependencies] (default: true)
    include_build: false              # include [build-dependencies] (default: false)

output:
  sbom: sbom.cyclonedx.json
  report: sbom-report.html
  format: html                        # html | simple-html | pdf | json | csv

options:
  skip_cve: false
  workers: 20

Subdirectory paths

When lockfiles are not in the project root:

project:
  name: my-fullstack-app
  version: "1.0.0"

sources:
  pypi:
    requirements: backend/requirements.txt
  npm:
    package_json: frontend/package.json
    lockfile: frontend/package-lock.json

Multiroot / Monorepo

Multiple instances of the same ecosystem as a list:

project:
  name: my-monorepo
  version: "3.0.0"

sources:
  npm:
    - label: frontend
      package_json: apps/frontend/package.json
      lockfile: apps/frontend/package-lock.json
      tags: [web, react]
      include_dev: false
    - label: admin
      package_json: apps/admin/package.json
      lockfile: apps/admin/package-lock.json
  cargo:
    cargo_toml: services/api/Cargo.toml
    lockfile: services/api/Cargo.lock
  • label — Display name in the report. Fallback: derived from the directory path when multiple entries exist.
  • tags — Applied as CycloneDX tags to each component. Shown as badges in the report and available in the SBOM JSON for automation.

Gradle subprojects

Gradle projects with submodules are detected automatically. All subprojects (:client, :server, :common etc.) are scanned individually. Module filters and dependency tree sub-tabs appear in the report.

sources:
  maven:
    gradle_dir: .
    configurations:
      - runtimeClasspath
      - testRuntimeClasspath
    include_subprojects: true

Auto-Configurator

Instead of writing the config manually:

sbom configure --project-dir /path/to/project

With rich + InquirerPy installed, you get a fancy TUI with cursor navigation, status table, ecosystem-specific options, and a global options submenu. Without these dependencies, a simple text menu is used as fallback (--simple forces it).

Features:

  • Recursively scans, skips node_modules, target, .venv etc.
  • Suggests labels from directory names
  • Reads project name/version from manifest files
  • Loads existing config for editing
  • Per-ecosystem options submenu (dev deps, Gradle configurations, etc.)
  • Global options: CVE scan, PDF, simple report, workers
  • --non-interactive accepts all defaults

CLI Reference

sbom configure

sbom configure [--project-dir DIR] [--output FILE] [--non-interactive] [--simple] [--lang LANG]
Flag Default Description
--project-dir . Project directory to scan
--output sbom.config.yaml Output path (relative to project-dir)
--non-interactive false Accept all defaults
--simple false Force simple text menu (no TUI)
--lang en Language (en, de)

sbom scan

sbom scan [--project-dir DIR] [--config FILE] [--output FILE] [--lang LANG]
Flag Default Description
--project-dir . Project directory
--config sbom.config.yaml Configuration file (in project-dir)
--output from config or sbom.cyclonedx.json Output path
--lang en Language (en, de)

sbom report

sbom report [--project-dir DIR] [--config FILE] [--sbom FILE] [--output FILE] [--format FMT] [--skip-cve] [--lang LANG]
Flag Default Description
--project-dir . Project directory
--config sbom.config.yaml Configuration file
--sbom from config or sbom.cyclonedx.json Input SBOM
--output from config or auto (based on format) Output path
--format html Output format: html, simple-html, pdf, json, csv
--skip-cve false Skip CVE scanning
--lang en Language (en, de)
--simple Alias for --format simple-html
--pdf Alias for --format pdf

sbom version

Shows version and copyright.

Library API

Directly importable in Python:

from pathlib import Path
from sbom_scanner import generate_sbom, generate_report, load_config

project = Path("../my-project")
config = load_config(project / "sbom.config.yaml")
sbom_path = project / "sbom.cyclonedx.json"

# Scan dependencies
generate_sbom(project, config, sbom_path)

# Generate report (default: interactive HTML)
generate_report(sbom_path, project / "sbom-report.html", skip_cve=True)

Supported Ecosystems

Ecosystem Lockfile Registry Options Licenses
npm package-lock.json (v1/v2/v3), yarn.lock (v1) npmjs.org include_dev, include_optional Yes
PyPI requirements.txt (pip-compile or plain) pypi.org dep_tree_method Yes
Dart/Flutter pubspec.lock pub.dev include_dev Yes
Maven/Gradle build.gradle / build.gradle.kts Maven Central + Google Maven configurations (multi-select), include_subprojects
Rust/Cargo Cargo.lock crates.io include_dev, include_build Yes
C#/NuGet .sln + .csproj, packages.config nuget.org include_dev, include_transitive

Plugin Architecture

Three plugin registries, all following the same pattern: base class + registry + one file per plugin.

Adding a New Ecosystem

Just one file + registry entry. No changes to scanners, renderers, or the configurator needed.

  1. Create src/sbom_scanner/ecosystems/composer.py:
from .base import Ecosystem

class ComposerEcosystem(Ecosystem):
    name = "composer"
    display_name = "PHP/Composer"
    cdx_prefix = "cdx:composer"
    purl_type = "composer"
    package_url_template = "https://packagist.org/packages/{name}"
    dep_property = "cdx:composer:dependency"
    latest_property = "cdx:composer:latestVersion"
    dep_labels = {"direct main": "direct", "transitive": "transitive"}

    def scan_pattern(self):
        return {
            "detect_files": ["composer.lock"],
            "companion_files": ["composer.json"],
            "config_keys": {"composer.lock": "lockfile", "composer.json": "composer_json"},
            "icon": "🐘",
        }

    def config_options(self):
        return [
            {"key": "include_dev", "label": "Include dev dependencies", "type": "bool", "default": True},
        ]

    def detect(self, project_dir, config): ...
    def parse(self, project_dir, config): ...
    def fetch_latest_versions(self, packages, workers=20): ...
    def build_component(self, pkg, latest): ...
    def get_direct_purls(self, packages): ...
  1. Register in src/sbom_scanner/ecosystems/__init__.py.

Adding a New CVE Scanner

  1. Create src/sbom_scanner/scanners/trivy.py:
from .base import Scanner

class TrivyScanner(Scanner):
    name = "trivy"
    targets = ["*"]  # or ["npm", "pypi"] for specific ecosystems

    def is_available(self): ...
    def scan(self, sbom_path, lockfiles, project_dir): ...
  1. Register in src/sbom_scanner/scanners/__init__.py.

Built-in scanners:

Scanner Type Targets
grype SBOM-based *
osv Lockfile-based *

Adding a New Report Renderer

  1. Create src/sbom_scanner/renderers/markdown.py:
from .base import Renderer

class MarkdownRenderer(Renderer):
    name = "markdown"
    display_name = "Markdown Report"
    file_extension = ".md"

    def render(self, sbom, vulns, output_path, **kwargs):
        from ..report_data import classify_components, eco_stats, get_eco_config
        # Build markdown output from sbom data
        ...
        return output_path
  1. Register in src/sbom_scanner/renderers/__init__.py.

Built-in renderers:

Renderer Format Description
html Interactive HTML Tabs, search, filters, dependency tree, dark mode
simple-html Flat HTML No JavaScript, suitable for email/archiving
pdf PDF Via weasyprint, Chrome headless, or wkhtmltopdf
json JSON Structured data for automation
csv CSV Flat table for spreadsheets

Project Structure

sbom-scanner/
├── pyproject.toml
├── README.md
├── LICENSE                             # MIT
├── CONTRIBUTING.md
├── sbom.config.yaml                    # Example config
└── src/
    └── sbom_scanner/
        ├── __init__.py                 # Version + library API
        ├── cli.py                      # Compound CLI: sbom <command>
        ├── i18n.py                     # Internationalization (gettext)
        ├── configure.py                # sbom configure (TUI + simple fallback)
        ├── generate_sbom.py            # sbom scan (ecosystem-agnostic)
        ├── generate_sbom_report.py     # sbom report (orchestrator)
        ├── report_data.py              # Shared data processing for renderers
        ├── ecosystems/
        │   ├── __init__.py             # Registry
        │   ├── base.py                 # Base class (Ecosystem)
        │   ├── npm.py                  # npm + yarn
        │   ├── pypi.py                 # PyPI + pipdeptree
        │   ├── pub.py                  # Dart/Flutter
        │   ├── maven.py                # Maven/Gradle + subprojects
        │   └── cargo.py                # Rust/Cargo
        ├── scanners/
        │   ├── __init__.py             # Registry
        │   ├── base.py                 # Base class (Scanner)
        │   ├── grype.py                # grype (SBOM-based)
        │   └── osv.py                  # osv-scanner (lockfile-based)
        ├── renderers/
        │   ├── __init__.py             # Registry
        │   ├── base.py                 # Base class (Renderer)
        │   ├── html.py                 # Interactive HTML report
        │   ├── simple_html.py          # Simple flat HTML report
        │   ├── pdf.py                  # PDF conversion
        │   ├── json_report.py          # JSON output
        │   └── csv_report.py           # CSV output
        └── locales/
            └── de/LC_MESSAGES/         # German translation (.po/.mo)

Docker

Pre-built image with grype and osv-scanner included:

# Scan
docker run -v $(pwd):/project ghcr.io/frmwrk-gmbh/sbom-scanner scan

# Report
docker run -v $(pwd):/project ghcr.io/frmwrk-gmbh/sbom-scanner report --skip-cve

# JSON report
docker run -v $(pwd):/project ghcr.io/frmwrk-gmbh/sbom-scanner report --format json

# Interactive configure
docker run -it -v $(pwd):/project ghcr.io/frmwrk-gmbh/sbom-scanner configure

# Version
docker run ghcr.io/frmwrk-gmbh/sbom-scanner version

The image mounts your project at /project. All output files (SBOM, reports) are written there.

CI/CD Integration

GitLab CI

sbom:
  stage: test
  script:
    - pip install sbom-scanner[all]
    - sbom scan --project-dir .
    - sbom report --project-dir . --skip-cve
    - sbom report --project-dir . --format json --output sbom-report.json --skip-cve
  artifacts:
    paths:
      - sbom.cyclonedx.json
      - sbom-report.html
      - sbom-report.json

GitHub Actions

- name: SBOM Scan
  run: |
    pip install sbom-scanner[all]
    sbom scan --project-dir .
    sbom report --project-dir . --format html --skip-cve
    sbom report --project-dir . --format json --skip-cve

With Docker (includes CVE scanners)

sbom:
  image: ghcr.io/frmwrk-gmbh/sbom-scanner:latest
  script:
    - sbom scan --project-dir .
    - sbom report --project-dir .
  artifacts:
    paths:
      - sbom.cyclonedx.json
      - sbom-report.html

With pip + CVE scanning

before_script:
  - pip install sbom-scanner[all]
  - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
script:
  - sbom scan --project-dir .
  - sbom report --project-dir .

License

MIT — © 2026 Frmwrk GmbH

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sbom_scanner-1.2.0.tar.gz (70.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sbom_scanner-1.2.0-py3-none-any.whl (82.2 kB view details)

Uploaded Python 3

File details

Details for the file sbom_scanner-1.2.0.tar.gz.

File metadata

  • Download URL: sbom_scanner-1.2.0.tar.gz
  • Upload date:
  • Size: 70.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sbom_scanner-1.2.0.tar.gz
Algorithm Hash digest
SHA256 81ea2fa9f9237283390d0541cefd23cc167eb0b4502e7961fa2eae7b17ba7a13
MD5 cd82a8f43846e040c9661bc4bfedba63
BLAKE2b-256 f418dab238732695891c4d20b80b27cde4b6e8df73a39e3f2ceacb730a0e5b22

See more details on using hashes here.

Provenance

The following attestation bundles were made for sbom_scanner-1.2.0.tar.gz:

Publisher: pypi.yml on Frmwrk-GmbH/sbom-scanner

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sbom_scanner-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: sbom_scanner-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 82.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sbom_scanner-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5d6d3836a9c750bb969033d9ac76cf9bfd9e4cbbb98948a5c44bf08ad625bd37
MD5 e5e4bc6b94a2370493165a4c57adc2ca
BLAKE2b-256 944f23b4ed53f1e8b8de13d69d004cb7f43fca5b607cec8c9d5eb3464a6b8aa6

See more details on using hashes here.

Provenance

The following attestation bundles were made for sbom_scanner-1.2.0-py3-none-any.whl:

Publisher: pypi.yml on Frmwrk-GmbH/sbom-scanner

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page