Aggregate and deduplicate vulnerability scan reports from Grype and Trivy
Project description
CVE Report Aggregation and Deduplication Tool
A Python package for aggregating and deduplicating Grype and Trivy vulnerability scan reports.
Features
- Self-Contained Docker Image: Includes all scanning tools (Grype, Syft, Trivy, UDS CLI) in a single hardened Alpine-based image
- Production-Ready Package: Installable via pip/pipx with proper dependency management
- Rich Terminal Output: Beautiful, color-coded tables and progress indicators using the Rich library
- Multi-Scanner Support: Works with both Grype and Trivy scanners
- SBOM Auto-Scan: Automatically detects and scans Syft SBOM files with Grype
- Auto-Conversion: Automatically converts Grype reports to CycloneDX format for Trivy scanning
- CVE Deduplication: Combines identical vulnerabilities across multiple scans
- Automatic Null CVSS Filtering: Filters out invalid CVSS scores (null, N/A, or zero) from all vulnerability reports
- CVSS 3.x-Based Severity Selection: Optional mode to select highest severity based on actual CVSS 3.x base scores
- Scanner Source Tracking: Identifies which scanner (Grype or Trivy) provided the vulnerability data
- Occurrence Tracking: Counts how many times each CVE appears
- Flexible CLI: Click-based interface with rich-click styling and sensible defaults
- Full Test Coverage: Comprehensive test suite with pytest
- Security Hardened: Non-root user (UID 1001), minimal Alpine base, pinned dependencies, and vulnerability-scanned
Prerequisites
Optional (depending on scanner choice):
- grype - For Grype scanning (default scanner)
- syft - For converting reports to CycloneDX format (Trivy workflow)
- trivy - For Trivy scanning
# Install Grype
brew install grype
# Install syft (for Trivy workflow)
brew install syft
# Install trivy
brew install aquasecurity/trivy/trivy
Installation
Using Docker (Recommended)
The easiest way to use CVE Report Aggregator is via the pre-built Docker image, which includes all necessary scanning tools (Grype, Syft, Trivy, UDS CLI):
# Build the image
docker build -t cve-report-aggregator .
# Or use Docker Compose
docker compose run cve-aggregator --help
# Run with mounted volumes
docker run --rm \
-v $(pwd)/reports:/workspace/reports:ro \
cve-report-aggregator \
--input-dir /workspace/reports \
--output-file /workspace/output/unified-report.json \
--verbose
Docker Credentials Management
The Docker container supports two methods for providing registry credentials:
- Build-Time Secrets
- Environment Variables
Method 1: Build-Time Secrets (Recommended)
Best for: Private container images where credentials can be baked in securely.
Create a credentials file in JSON format with username, password, and registry fields:
cat > docker/config.json <<EOF
{
"username": "myuser",
"password": "mypassword",
"registry": "ghcr.io"
}
EOF
chmod 600 docker/config.json
Important: Always encrypt the credentials file with SOPS before committing:
# Encrypt the credentials file
sops -e docker/config.json.dec > docker/config.json.enc
# Or encrypt in place
sops -e docker/config.json.dec > docker/config.json.enc
Build the image with the secret:
# If using encrypted file, decrypt first
sops -d docker/config.json.enc > docker/config.json.dec
# Build with the decrypted credentials
docker buildx build \
--secret id=credentials,src=./docker/config.json.dec \
-f docker/Dockerfile \
-t cve-report-aggregator:latest .
# Remove decrypted file after build
rm docker/config.json.dec
Or build directly with unencrypted file (for local development):
docker buildx build \
--secret id=credentials,src=./docker/config.json \
-f docker/Dockerfile \
-t cve-report-aggregator:latest .
The credentials will be stored in the image at $DOCKER_CONFIG/config.json (defaults to
/home/cve-aggregator/.docker/config.json) in proper Docker authentication format with base64-encoded credentials.
Run the container (no runtime credentials needed - uses baked-in config.json):
docker run --rm cve-report-aggregator:latest --help
Important: This method bakes credentials into the image. Only use for private registries and never push images with credentials to public registries.
Method 2: Environment Variables (Development Only)
Warning: This method exposes the password in process listings and Docker inspect output. Only use for development/testing.
docker run -it --rm \
-e REGISTRY_URL="$UDS_URL" \
-e UDS_USERNAME="$UDS_USERNAME" \
-e UDS_PASSWORD="$UDS_PASSWORD" \
cve-report-aggregator:latest --help
How Credentials Are Handled
The entrypoint.sh script checks for Docker authentication on startup:
-
Docker config.json (Build-Time): Checks if
$DOCKER_CONFIG/config.jsonexists- If found: Skips all credential checks and login - uses existing Docker auth
- Location:
/home/cve-aggregator/.docker/config.json
-
Environment Variables (if config.json not found): Requires all three variables:
REGISTRY_URL- Registry URL (e.g.,registry.defenseunicorns.com)UDS_USERNAME- Registry usernameUDS_PASSWORD- Registry password
If config.json doesn't exist and environment variables are not provided, the container exits with an error.
From Source
# Clone the repository
git clone https://github.com/mkm29/cve-report-aggregator.git
cd cve-report-aggregator
# Install in development mode
pip install -e .
# Or install with dev dependencies
pip install -e ".[dev]"
From PyPI (when published)
# Install globally
pip install cve-report-aggregator
# Or install with pipx (recommended)
pipx install cve-report-aggregator
Usage
Basic Usage (Default Locations)
Process reports from ./reports/ and output to ./unified-report.json:
cve-report-aggregator
Use Trivy Scanner
Automatically convert reports to CycloneDX and scan with Trivy:
cve-report-aggregator --scanner trivy
Process SBOM Files
The script automatically detects and scans Syft SBOM files:
cve-report-aggregator -i /path/to/sboms -v
Custom Input and Output
cve-report-aggregator -i /path/to/reports -o /path/to/output.json
Verbose Mode
Enable detailed processing output:
cve-report-aggregator -v
Combined Options
cve-report-aggregator -i ./scans -o ./results/unified.json --scanner trivy -v
Use Highest Severity Across Scanners
When scanning with multiple scanners (or multiple runs of the same scanner), automatically select the highest severity rating:
# Scan the same image with both Grype and Trivy, use highest severity
grype myapp:latest -o json > reports/grype-app.json
trivy image myapp:latest -f json -o reports/trivy-app.json
cve-report-aggregator -i reports/ --mode highest-score -o unified.json
This is particularly useful when:
- Combining results from multiple scanners with different severity assessments
- Ensuring conservative (worst-case) severity ratings for compliance
- Aggregating multiple scans over time where severity data may have been updated
Command-Line Options
| Option | Short | Description | Default |
|---|---|---|---|
--input-dir |
-i |
Input directory containing scan reports or SBOMs | ./reports |
--output-file |
-o |
Output file path for unified report | ./unified-report.json |
--scanner |
-s |
Scanner type to process (grype or trivy) |
grype |
--verbose |
-v |
Enable verbose output with detailed processing | false |
--mode |
-m |
Aggregation mode: highest-score, first-occurrence, grype-only, trivy-only |
highest-score |
--help |
-h |
Show help message and exit | N/A |
--version |
Show version and exit | N/A |
Output Format
The unified report includes:
Metadata
- Generation timestamp
- Scanner type and version
- Source report count and filenames
Summary
- Total vulnerability occurrences
- Unique vulnerability count
- Severity breakdown (Critical, High, Medium, Low, Negligible, Unknown)
- Per-image scan results
Vulnerabilities (Deduplicated)
For each unique CVE/GHSA:
- Vulnerability ID
- Occurrence count
- Selected scanner (which scanner provided the vulnerability data)
- Severity and CVSS scores
- Fix availability and versions
- All affected sources (images and artifacts)
- Detailed match information
Development
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=cve_report_aggregator --cov-report=html
# Run specific test file
pytest tests/test_severity.py
Code Quality
# Format code
black src/ tests/
# Lint code
ruff check src/ tests/
# Type checking
mypy src/
Building the Package
# Build distribution packages
python -m build
# Install locally
pip install dist/cve_report_aggregator-0.1.0-py3-none-any.whl
Project Structure
cve-report-aggregator/
├── src/
│ └── cve_report_aggregator/
│ ├── __init__.py # Package exports and metadata
│ ├── main.py # CLI entry point
│ ├── models.py # Type definitions
│ ├── utils.py # Utility functions
│ ├── severity.py # CVSS and severity logic
│ ├── scanner.py # Scanner integrations
│ ├── aggregator.py # Deduplication engine
│ └── report.py # Report generation
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Pytest fixtures
│ ├── test_severity.py # Severity tests
│ └── test_aggregator.py # Aggregation tests
├── pyproject.toml # Project configuration
├── README.md # This file
└── LICENSE # MIT License
Example Workflows
Docker E2E Workflow
# Scan container images and aggregate with Docker
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-v $(pwd)/reports:/workspace/reports \
-v $(pwd)/output:/workspace/output \
cve-report-aggregator bash -c "\
grype nginx:latest -o json > /workspace/reports/nginx.json && \
grype postgres:15 -o json > /workspace/reports/postgres.json && \
cve-report-aggregator --input-dir /workspace/reports \
--output-file /workspace/output/unified.json --verbose"
# View results
jq '.summary' output/unified.json
Grype Workflow (Default)
# Scan multiple container images with Grype
grype registry.io/app/service1:v1.0 -o json > reports/service1.json
grype registry.io/app/service2:v1.0 -o json > reports/service2.json
grype registry.io/app/service3:v1.0 -o json > reports/service3.json
# Aggregate all reports
cve-report-aggregator -v
# Query results with jq
jq '.summary' unified-report.json
jq '.vulnerabilities[] | select(.vulnerability.severity == "Critical")' unified-report.json
SBOM Workflow
# Generate SBOMs with Syft (or use Zarf-generated SBOMs)
syft registry.io/app/service1:v1.0 -o json > sboms/service1.json
syft registry.io/app/service2:v1.0 -o json > sboms/service2.json
# Script automatically detects and scans SBOMs with Grype
cve-report-aggregator -i ./sboms -v
# Results include all vulnerabilities found
jq '.summary.by_severity' unified-report.json
Trivy Workflow
# Start with Grype reports (script will convert to CycloneDX)
grype registry.io/app/service1:v1.0 -o json > reports/service1.json
grype registry.io/app/service2:v1.0 -o json > reports/service2.json
# Aggregate and scan with Trivy (auto-converts to CycloneDX)
cve-report-aggregator --scanner trivy -v
# Or scan SBOMs directly with Trivy
cve-report-aggregator -i ./sboms --scanner trivy -o trivy-unified.json -v
License
MIT License - See LICENSE file for details
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Changelog
See CHANGELOG.md for version history and changes.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cve_report_aggregator-0.5.2.tar.gz.
File metadata
- Download URL: cve_report_aggregator-0.5.2.tar.gz
- Upload date:
- Size: 243.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a63d04c3a59b4ff34a9b8e46d07486f818032848a7b1a61e5ecd0be2a83034f
|
|
| MD5 |
c1e266f2cd026adb2fdc972276df22df
|
|
| BLAKE2b-256 |
f60c2b1a618432867e873cd7317301cbd98f603a780f704cb58607fddd550900
|
File details
Details for the file cve_report_aggregator-0.5.2-py3-none-any.whl.
File metadata
- Download URL: cve_report_aggregator-0.5.2-py3-none-any.whl
- Upload date:
- Size: 63.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3622d8d1f4089657d603fbdbad99b995d68a19e6843e18b59d25c4587ced99dd
|
|
| MD5 |
99a943c4eba45bcf5f53fe8ed7bc6d5d
|
|
| BLAKE2b-256 |
868c4da77ae4846c68043cbcabfa6c0c14c11a690b6f6c72f21401073b439243
|