uv-sbom
Generate SBOMs (Software Bill of Materials) for Python projects managed by uv.
Features
- 📦 Parses
uv.lockfiles to extract dependency information - 🔍 Automatically fetches license information from PyPI with retry logic
- 🛡️ Checks for known vulnerabilities using OSV API (Markdown format only)
- 📋 License compliance policy check with configurable allow/deny lists and wildcard support
- 🔎 Vulnerability Resolution Guide - Identifies which direct dependency introduces each vulnerable transitive package
- 📊 Outputs in multiple formats:
- CycloneDX 1.6 JSON format (standard SBOM format)
- Markdown format with direct and transitive dependencies clearly separated
- 🚀 Fast and standalone - written in Rust
- 💾 Output to stdout or file
- 🛡️ Robust error handling with helpful error messages and suggestions
- 📈 Progress tracking during license information retrieval
- 🏗️ Built with Hexagonal Architecture (Ports and Adapters) + Domain-Driven Design for maintainability and testability
- ✅ Comprehensive test coverage (Unit, Integration, E2E)
Scope and Key Differences from CycloneDX
SBOM Scope
This tool generates SBOMs based on uv.lock file contents, which includes:
- Direct runtime dependencies
- Transitive runtime dependencies
- Development dependencies (if locked in uv.lock)
What's NOT included:
- Build system dependencies (e.g., hatchling, setuptools)
- Publishing tools (e.g., twine, build)
- Dependencies only present in the virtual environment but not locked in uv.lock
Comparison with CycloneDX Official Tools
As of v7.2.1, the official cyclonedx-python library does not yet provide direct support for uv. When generating SBOMs for Python projects:
| Aspect | uv-sbom (this tool) | CycloneDX Official Tools |
|---|---|---|
| Data Source | uv.lock file |
.venv virtual environment |
| Scope | Production runtime dependencies only | Entire supply chain including build/dev tools |
| Package Count | Typically fewer (e.g., 16 packages) | Typically more (e.g., 38+ packages) |
| Use Case | Production security scanning | Comprehensive supply chain audit |
| Accuracy | Reflects locked dependencies | Reflects installed packages |
Which Tool Should You Use?
- For production security scanning: Use
uv-sbomto focus on dependencies that will be deployed to production - For comprehensive supply chain audit: Use CycloneDX official tools to include all development and build-time dependencies
- For regulatory compliance: Check your specific requirements - some regulations may require the comprehensive approach
The focused approach of uv-sbom reduces noise in security vulnerability scanning by excluding build-time dependencies that don't ship with the final application.
Installation
Cargo (Recommended for Rust users)
Install from crates.io:
uv tool (Python users)
Install the Python wrapper package:
Or via pip:
After installation, use the uv-sbom command:
Note: The package name is uv-sbom-bin, but the installed command is uv-sbom.
Pre-built Binaries
Download pre-built binaries from GitHub Releases:
macOS (Apple Silicon):
macOS (Intel):
Linux (x86_64):
Windows:
Download uv-sbom-x86_64-pc-windows-msvc.zip from the releases page and extract to your desired location.
From Source
# Clone the repository
# Build and install
Verify Installation
Usage
Basic usage
Generate a CycloneDX JSON SBOM for the current directory:
Output formats
Generate a Markdown table with direct and transitive dependencies:
Generate a CycloneDX JSON (default):
Output language
Use the --lang option to switch the output language for human-readable formats (Markdown). The default is English (en).
# Generate a Japanese Markdown SBOM report
# Generate an English Markdown SBOM report (default)
Supported values: en (English, default), ja (Japanese)
Note: The --lang option affects section headers, table column names, and status labels in Markdown output. Package names, CVE IDs, and SPDX license identifiers always remain in their original form regardless of --lang.
Specify project path
Analyze a project in a different directory:
Save to file
Output to a file instead of stdout:
Combined options
Excluding packages
You can exclude specific packages from the SBOM using the --exclude or -e option:
# Exclude a single package
# Exclude multiple packages
# Use wildcards to exclude patterns
# Combine with other options
Pattern Syntax:
- Use
*as a wildcard to match zero or more characters - Patterns are case-sensitive
- Maximum 64 patterns per invocation
Preventing Information Leakage:
Use the --exclude option to skip specific internal or proprietary libraries. This prevents their names from being sent to external registries (like PyPI) during metadata retrieval, ensuring your internal project structure remains private.
Configuration file
You can use a configuration file (uv-sbom.config.yml) to set default options instead of passing them on the command line every time.
Generating a config template
Use the --init option to generate a template configuration file with all available fields as commented examples:
# Generate template in the current directory
# Generate template in a specific directory
This creates a uv-sbom.config.yml file with inline documentation for every option. If the file already exists, the command exits with an error to prevent accidental overwriting.
Auto-discovery: Place a uv-sbom.config.yml file in your project directory (where uv.lock is located). The tool automatically detects and loads it.
Explicit path: Use --config / -c to specify a config file at a custom location.
# Auto-discovered config file (place in project directory)
# Explicit config file path
Example configuration file (uv-sbom.config.yml):
# Output format: json or markdown
format: markdown
# Packages to exclude from SBOM (supports wildcards)
exclude_packages:
- "pytest"
- "mypy"
- "*-dev"
# Enable CVE vulnerability checking
check_cve: true
# Severity threshold for vulnerability check (low/medium/high/critical)
severity_threshold: high
# CVSS threshold for vulnerability check (0.0-10.0)
# cvss_threshold: 7.0
# CVEs to ignore (with optional reason)
ignore_cves:
- id: CVE-2024-1234
reason: "False positive for our use case"
- id: CVE-2024-5678
reason: "Mitigated by network configuration"
# License compliance policy
license_policy:
allow:
deny:
unknown: "warn" # "warn" | "deny" | "allow"
Config File Schema Reference
| Field | Type | Required | Description |
|---|---|---|---|
format |
string | No | Output format (json / markdown) |
exclude_packages |
string[] | No | Package exclusion patterns (supports wildcards) |
check_cve |
bool | No | Override CVE checking behavior. Defaults to true when unset |
severity_threshold |
string | No | Severity threshold (low / medium / high / critical) |
cvss_threshold |
number | No | CVSS threshold (0.0 - 10.0) |
ignore_cves |
object[] | No | List of CVEs to ignore |
ignore_cves[].id |
string | Yes | CVE ID (e.g., CVE-2024-1234) |
ignore_cves[].reason |
string | No | Reason for ignoring |
license_policy |
object | No | License compliance policy configuration |
license_policy.allow |
string[] | No | Allowed license patterns (supports wildcards) |
license_policy.deny |
string[] | No | Denied license patterns (supports wildcards) |
license_policy.unknown |
string | No | Unknown license handling (warn / deny / allow) |
Priority and Merge Rules
- CLI arguments override config file values for scalar fields (
format,severity_threshold,cvss_threshold) check_cvedefaults to true when unset. Set to false in config to disable. Use --no-check-cve CLI flag to opt outexclude_packagesare merged from both CLI and config file, then deduplicatedignore_cvesare merged from both CLI (--ignore-cve) and config file, deduplicated by ID (CLI entry takes precedence for duplicates)check_licenseis enabled if set via CLI flag OR config file (logical OR, same ascheck_cve)--license-allowand--license-denyCLI options override config filelicense_policy.allow/license_policy.denyentirely (not merged)
Ignoring specific CVEs
You can ignore specific CVEs from the command line using --ignore-cve / -i:
# Ignore specific CVEs from CLI
# Short form
# Combine config file and CLI ignores (both sources are merged)
Checking for vulnerabilities
CVE vulnerability checking is enabled by default using the OSV (Open Source Vulnerability) database. No flag is required:
# Check for vulnerabilities in Markdown output (CVE check runs automatically)
# Save vulnerability report to file
# Combine with exclude patterns
# Opt out of CVE checking
Disabling CVE Checking
CVE vulnerability checking is enabled by default. To opt out, use the --no-check-cve flag:
# Generate SBOM without CVE checking
# Disable via config file
# check_cve: false (in uv-sbom.config.yml)
Note:
--no-check-cveconflicts with--severity-threshold,--cvss-threshold, and--suggest-fix.
License Compliance Check
Use the --check-license option to check packages against a configurable license policy:
# Enable license compliance check (using config file policy)
# With inline policy (overrides config file)
# Combined with vulnerability check
How it works:
- Deny takes precedence over allow: If a license matches both lists, it is denied
- Wildcard patterns: Use
BSD-*,AGPL-*etc. for pattern matching (case-insensitive) - Unknown license handling: Configure how licenses not in either list are treated:
warn(default): Report as warning but don't faildeny: Treat unknown licenses as violationsallow: Silently allow unknown licenses
- Exit code: Returns exit code 1 when policy violations are detected
Vulnerability Threshold Options
You can control which vulnerabilities trigger a non-zero exit code using threshold options:
# Check for any vulnerabilities (exits with 1 if found)
# Check for High or Critical severity only
# Check for Critical severity only
# Check for CVSS >= 7.0 only
# Check for CVSS >= 9.0 (Critical) only
Threshold Options:
--severity-threshold <LEVEL>: Filter by severity level (low, medium, high, critical)--cvss-threshold <SCORE>: Filter by CVSS score (0.0-10.0)
Notes:
- Only one threshold option can be used at a time
- Cannot be used with
--no-check-cve - Vulnerabilities below the threshold are still shown in the report but don't trigger exit code 1
- When using
--cvss-threshold, vulnerabilities without CVSS scores (N/A) are excluded from threshold evaluation
PyPI Link Verification
Use the --verify-links option to validate that packages exist on PyPI before generating hyperlinks. Packages that don't exist on PyPI will be rendered as plain text:
# Generate Markdown with verified PyPI links
# Combine with other options
Behavior:
- Without
--verify-links: All package names get PyPI hyperlinks (default, fast) - With
--verify-links: Only verified packages get hyperlinks; unverified packages render as plain text - Network errors gracefully fall back to plain text (no crash)
- Requests are executed in parallel (max 10 concurrent) for performance
CI Integration
Use vulnerability thresholds for CI/CD pipeline integration:
# GitHub Actions example
- name: Generate SBOM
run: uv-sbom --format markdown --output sbom.md
- name: Security Check (High and Critical only)
run: uv-sbom --format markdown --severity-threshold high
- name: Security Check (CVSS >= 7.0)
run: uv-sbom --format markdown --cvss-threshold 7.0
# GitHub Actions - License Compliance Check
- name: License Compliance Check
run: uv-sbom --check-license --format markdown
- name: Combined Security and License Check
run: uv-sbom --check-license --severity-threshold high
# GitLab CI example
security_scan:
script:
- uv-sbom --format markdown --severity-threshold high
allow_failure: false
Important Notes:
- Vulnerability checking is only available for Markdown format
- Requires internet connection to query OSV API
- Not available in
--dry-runmode (skips network operations) - Use
--excludeto prevent internal packages from being sent to OSV API
Example Output:
When vulnerabilities are found, a section like this is added to the Markdown output:
**⚠️ Security Issues Detected**
The following packages have known security vulnerabilities:
*Vulnerability data provided by [OSV](https://osv.dev) under CC-BY 4.0*
Note: Vulnerability IDs (CVE, GHSA, PYSEC, RUSTSEC, etc.) in the vulnerability report are always rendered as hyperlinks, regardless of
--verify-links. These IDs are sourced from the OSV database and link to authoritative vulnerability databases (NVD, GitHub Advisories, OSV.dev), so link verification is unnecessary.
Vulnerability Resolution Guide
When vulnerabilities are detected in transitive dependencies, uv-sbom automatically generates a Vulnerability Resolution Guide. This section shows which direct dependency introduces each vulnerable transitive package, so you know exactly what to upgrade.
Markdown output example
The following transitive dependencies have known vulnerabilities. The table shows which direct dependency introduces each vulnerable package.
CycloneDX JSON output
In CycloneDX format, the introducing dependency is included as a properties entry:
Note: The resolution guide only appears for transitive dependency vulnerabilities. Direct dependency vulnerabilities are shown in the standard vulnerability table, as users can upgrade them directly.
Upgrade Advisor (--suggest-fix)
Use --suggest-fix to automatically suggest which direct dependency version to upgrade to resolve each transitive vulnerability.
Requires:
uvCLI installedpyproject.tomlin the project directory
Example:
Output: Adds a "Recommended Action" column to the Vulnerability Resolution Guide showing:
⬆️ Upgrade requests → 2.32.3 (resolves urllib3 to 2.2.1)— when an upgrade fixes the vulnerability⚠️ Cannot resolve: latest httpx still pins idna < 3.7— when no upgrade helps❓ Could not analyze: <error>— when simulation failed
Try it with the included example:
# This example has transitive CVEs designed to show both Upgradable and Unresolvable outcomes
See examples/suggest-fix-project/README.md for a full walkthrough.
License Compliance Check output example:
When no vulnerabilities are found:
**✅ No Known Vulnerabilities**
No security vulnerabilities were found in the scanned packages.
*Vulnerability data provided by [OSV](https://osv.dev) under CC-BY 4.0*
Validating configuration with dry-run
Use the --dry-run option to validate your configuration before the tool communicates with external registries:
# Verify exclude patterns work correctly
# Test configuration with all options
Why use --dry-run:
- Verify exclude patterns: Ensure your
--excludepatterns correctly match the packages you want to skip - Prevent information leakage: Confirm that sensitive internal packages are excluded BEFORE the tool communicates with PyPI registry
- Fast validation: All input validation happens without network overhead
- Early error detection: Catch configuration issues (missing uv.lock, invalid patterns, etc.) immediately
What happens in dry-run mode:
- ✅ Reads and parses
uv.lockfile - ✅ Validates all command-line arguments
- ✅ Checks exclude patterns and warns about unmatched patterns
- ✅ Outputs success message if no issues found
- ❌ Skips license fetching from PyPI (no network communication)
- ❌ Skips SBOM output generation
Security
Exclude Pattern Input Validation
The -e/--exclude option implements the following security measures to protect against malicious input:
Character Restrictions
Only the following characters are allowed in patterns:
- Alphanumeric characters: a-z, A-Z, 0-9, Unicode letters/numbers
- Hyphens (
-), underscores (_), dots (.): Common in package names - Square brackets (
[,]): For package extras (e.g.,requests[security]) - Asterisks (
*): For wildcard matching
Control characters, shell metacharacters, and path separators are blocked to prevent:
- Terminal escape sequence injection
- Log injection attacks
- Command injection (defense in depth)
Pattern Limits
- Maximum patterns: 64 patterns can be specified per invocation
- Maximum length: 255 characters per pattern
- Minimum content: Patterns must contain at least one non-wildcard character
These limits prevent denial-of-service attacks via:
- Excessive memory consumption
- CPU exhaustion from complex pattern matching
Examples
Valid patterns:
Invalid patterns (rejected with error):
For more detailed security information, including threat model and attack vectors, see SECURITY.md.
Command-line options
Options:
-f, --format <FORMAT> Output format: json or markdown [default: json]
-p, --path <PATH> Path to the project directory [default: current directory]
-o, --output <OUTPUT> Output file path (if not specified, outputs to stdout)
-e, --exclude <PATTERN> Exclude packages matching patterns (supports wildcards: *)
-c, --config <PATH> Path to config file (auto-discovers uv-sbom.config.yml if not specified)
-i, --ignore-cve <CVE_ID> CVE IDs to ignore (can be specified multiple times)
--lang <LANG> Output language for human-readable formats: en or ja [default: en]
--init Generate a uv-sbom.config.yml template file
--dry-run Validate configuration without network communication or output generation
--verify-links Verify PyPI links exist before generating hyperlinks (Markdown format only)
--check-cve [DEPRECATED] CVE checking is now enabled by default. This flag has no effect. Use --no-check-cve to opt out
--no-check-cve Disable CVE vulnerability checking (enabled by default)
--severity-threshold <LEVEL> Severity threshold for vulnerability check (low/medium/high/critical)
Cannot be used with --no-check-cve
--cvss-threshold <SCORE> CVSS threshold for vulnerability check (0.0-10.0)
Cannot be used with --no-check-cve
--suggest-fix Suggest direct dependency upgrade versions to resolve transitive vulnerabilities
Requires uv CLI installed and pyproject.toml in project directory
--check-license Check license compliance against policy
--license-allow <LIST> Comma-separated list of allowed license patterns (overrides config)
--license-deny <LIST> Comma-separated list of denied license patterns (overrides config)
-h, --help Print help
-V, --version Print version
Exit Codes
uv-sbom returns the following exit codes:
| Exit Code | Description | Examples |
|---|---|---|
| 0 | Success | SBOM generated successfully, no vulnerabilities above threshold, --help or --version displayed |
| 1 | Vulnerabilities or license violations detected | Vulnerabilities above threshold detected, license policy violations found |
| 2 | Invalid command-line arguments | Unknown option, invalid argument type |
| 3 | Application error | Missing uv.lock file, invalid project path, invalid exclude pattern, network error, file write error |
Exit Codes with Vulnerability and License Checking
When using CVE or license checking, the exit code behavior changes based on threshold settings:
| Scenario | Exit Code |
|---|---|
| No vulnerabilities found | 0 |
| Vulnerabilities found (no threshold specified) | 1 |
| Vulnerabilities found, all below threshold | 0 |
| Vulnerabilities found, some above threshold | 1 |
| License policy violations detected | 1 |
| Combined: either check fails | 1 |
| Combined: both checks pass | 0 |
Examples:
# Returns 0 if no High/Critical vulnerabilities, even if Low/Medium exist
# Returns 0 if no vulnerabilities have CVSS >= 7.0
Common Error Scenarios
Exit code 3 - Application errors:
# Missing uv.lock file
# Exit code: 3
# Invalid exclude pattern (empty)
# Exit code: 3
# Invalid exclude pattern (invalid characters)
# Exit code: 3
# Nonexistent project path
# Exit code: 3
Exit code 2 - CLI argument errors:
# Unknown option
# Exit code: 2
# Invalid format value
# Exit code: 2
Usage in Scripts
#!/bin/bash
Output Examples
Markdown format
Note: The Markdown format sample is based on the SBOM format from ja-complete v0.1.0.
**Overall: No issues found** ✅
A comprehensive list of all software components and libraries included in this project.
Primary packages explicitly defined in the project configuration(e.g., pyproject.toml).
Secondary dependencies introduced by the primary packages.
CycloneDX JSON format
Requirements
- A Python project managed by
uvwith auv.lockfile - Internet connection for fetching license information from PyPI
Network Requirements
External Domains Accessed
uv-sbom makes HTTP requests to the following external services during SBOM generation:
Required for all operations:
- PyPI (Python Package Index)
- Domain:
https://pypi.org - Purpose: Fetch license information for Python packages
- When: Every SBOM generation (unless using
--dry-run) - Rate limit: No official limit, but tool implements retry logic
- Endpoint:
/pypi/{package_name}/json
- Domain:
Optional (only when using --no-check-cve disables CVE, or --verify-links):
-
PyPI Link Verification
- Domain:
https://pypi.org - Purpose: Verify package existence on PyPI via HTTP HEAD requests
- When: Only when
--verify-linksflag is used - Rate limit: Max 10 concurrent requests
- Endpoint:
/project/{package_name}/
- Domain:
-
OSV (Open Source Vulnerability Database)
- Domain:
https://api.osv.dev - Purpose: Fetch vulnerability information for security scanning
- When: By default (unless
--no-check-cveis used) - Rate limit: Tool implements 10 requests/second limit
- Endpoints:
/v1/querybatch- Batch query for vulnerability IDs/v1/vulns/{vuln_id}- Detailed vulnerability information
- Domain:
Firewall Configuration
If you are behind a corporate firewall or proxy, ensure the following domains are on the allowlist:
# Required
pypi.org
# Optional (for --verify-links only; OSV is accessed by default unless --no-check-cve)
pypi.org # Also used for --verify-links
api.osv.dev # Disabled only with --no-check-cve
Proxy Configuration
The tool respects standard HTTP/HTTPS proxy environment variables:
Offline Mode
To validate configuration without making network requests, use --dry-run:
This mode:
- Validates
uv.lockfile - Validates command-line arguments
- Checks exclude patterns
- Skips license fetching (no PyPI access)
- Skips vulnerability checking (no OSV access)
- Skips SBOM output generation
Error Handling
uv-sbom provides detailed error messages with helpful suggestions:
- Missing uv.lock file: Clear message with suggestions on how to fix
- Invalid project path: Validates directory existence before processing
- License fetch failures: Retries failed requests (up to 3 attempts) and continues processing
- File write errors: Checks directory existence and permissions
- Progress tracking: Shows real-time progress during license information retrieval
Example error message:
❌ An error occurred:
uv.lock file not found: /path/to/project/uv.lock
💡 Hint: uv.lock file does not exist in project directory "/path/to/project".
Please run in the root directory of a uv project, or specify the correct path with the --path option.
Troubleshooting
uv.lock file not found
Ensure you're running the command in a directory containing a uv.lock file, or use the --path option to specify the correct project directory.
License information fetch failures
Some packages may fail to retrieve license information from PyPI. The tool will:
- Automatically retry up to 3 times
- Continue processing other packages
- Display warnings for failed packages
- Include packages in the output without license information if fetching fails
Network issues
If you're behind a proxy or firewall, ensure that you can access https://pypi.org. The tool uses a 10-second timeout for API requests.
Documentation
For Users
For Developers
- DEVELOPMENT.md - Development guide
- ARCHITECTURE.md - Hexagonal Architecture + DDD implementation (layers, ports, adapters, test strategy, ADRs)
- CHANGELOG.md - Change history
For Claude Code Users
- .claude/project-context.md - Complete project context for Claude Code
- .claude/instructions.md - Coding guidelines and instructions for Claude Code
These files provide comprehensive context for AI-assisted development with Claude Code.
Attribution
Vulnerability Data
By default, this tool retrieves vulnerability data from OSV (Open Source Vulnerability), which is provided under the Creative Commons Attribution 4.0 International License (CC-BY 4.0). Use --no-check-cve to opt out of vulnerability checking.
Required Attribution:
- Vulnerability data provided by OSV
- Available at: https://osv.dev
- License: CC-BY 4.0
The OSV database is a collaborative effort to provide comprehensive, accurate, and accessible vulnerability information for open source software.
License
MIT License - see LICENSE file for details.