Generate beautiful HTML vulnerability reports from Cloudsmith, Trivy, and Grype scan output
Project description
A simple Python utility that generates beautiful, self-contained HTML reports from various vulnerability scan sources.
Installation
Install from PyPI:
pip install vulnly
This makes the vulnly command available globally.
Features
- Generates polished, self-contained HTML reports from JSON input
- Supports multiple scanner sources: Cloudsmith, Trivy, and Grype (auto-detected or via
--sourceflag) - Repository summary reports — automatically detects Cloudsmith repo-level summaries and generates a dedicated overview report with package status breakdown, aggregate vulnerability counts, and per-package detail table
- Dark and light colour themes via
--themeflag - Interactive doughnut charts showing severity or status distribution (powered by Chart.js)
- Severity stat cards (Total, Critical, High, Medium, Low)
- Auto-generated executive summary and action-required alert box
- Client-side search and filter buttons for the CVE / package table
- Automatic linking for CVE IDs (NVD) and GHSA IDs (GitHub Advisories)
- Customisable logo — supply via
--logoflag or drop an image intoassets/ - Accepts input from a file or stdin (
-), making it easy to pipe from other tools - Input validation with helpful warnings for malformed data
- Zero external dependencies — uses only the Python standard library
Quick Start
vulnly sample_data.json
This reads sample_data.json and writes the report to reports/ (the default output directory).
Specify the scanner source
The tool auto-detects the input format, but you can be explicit:
vulnly scan.json --source trivy
vulnly scan.json --source grype
vulnly scan.json --source cloudsmith
If --source is specified and the input doesn't match the expected format, the tool exits with a clear error message.
Repository summary reports
Cloudsmith repo-level summary JSON is automatically detected and generates a dedicated repository overview report:
vulnly repo_summary.json
The output includes package status cards (vulnerable / no issues / not scanned), aggregate vulnerability severity breakdown, a status distribution chart, and a searchable/filterable package table.
Specify a custom output path
vulnly sample_data.json -o my_report.html
Custom logo
Use the --logo flag to supply a custom logo image. It will be embedded directly in the HTML report header as a base64 data URI:
vulnly sample_data.json --logo my-company-logo.png
Logo requirements:
| Constraint | Limit |
|---|---|
| Max file size | 2 MB |
| Max dimensions | 512 × 512 px |
| Recommended size | 128 × 128 px to 256 × 256 px |
| Supported formats | .png, .jpg, .jpeg, .gif, .svg, .webp, .ico |
If a custom logo exceeds the file size or dimension limits, a warning is printed and the default Vulnly logo is used instead.
If no --logo is provided, the built-in Vulnly logo is used.
Theme
Choose between a dark (default) or light colour theme:
vulnly sample_data.json --theme light
vulnly sample_data.json --theme dark
Read from stdin
Pipe JSON directly from another command:
cat scan_output.json | vulnly -
Cloudsmith CLI
Pipe vulnerability scan results directly from the Cloudsmith CLI:
cloudsmith vulnerabilities WORKSPACE/REPO/PACKAGE_IDENTIFIER --output-format json | vulnly -
Trivy
Scan an image with Trivy and pipe directly to Vulnly:
trivy image -f json nginx:latest | vulnly --source trivy -
Grype
Scan an image with Grype and pipe directly to Vulnly:
grype nginx:latest -o json | vulnly --source grype -
Usage
usage: vulnly [-h] [-o OUTPUT]
[--logo LOGO] [--theme {dark,light}]
[--source SOURCE]
input
Generate an HTML vulnerability report from a JSON input file.
positional arguments:
input Path to the JSON file containing vulnerability data
(use '-' for stdin)
optional arguments:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
Output HTML file path (default: auto-generated in
reports/ subfolder)
--logo LOGO Path to a custom logo image (max 512×512px, 2 MB;
embedded as base64 in the report)
--theme {dark,light} Report colour theme (default: dark)
--source SOURCE Scanner source format: cloudsmith, trivy, grype
(auto-detected if omitted)
Input JSON Format
The generator accepts multiple input formats — it auto-detects which one you provide, or you can specify explicitly with --source.
1. Cloudsmith package format
Pass raw JSON output from the Cloudsmith CLI. The tool extracts namespace, repository, package metadata, scan target, and all vulnerabilities automatically.
The expected structure has a top-level data object containing package, scans, etc.:
{
"data": {
"created_at": "2025-06-09T08:39:20.441354Z",
"identifier": "TZR5N4HaO7nTclhz",
"package": {
"name": "log4j",
"url": "https://example.com/packages/my-org/java/wAoMy00juV6N/",
"version": "28a05d8e..."
},
"scans": [
{
"target": "Java",
"type": "jar",
"results": [
{
"vulnerability_id": "CVE-2021-45046",
"severity": "Critical",
"title": "DoS in log4j 2.x...",
"package_name": "org.apache.logging.log4j:log4j-core",
"affected_version": { "version": "2.8.1" },
"fixed_version": { "version": "2.16.0, 2.12.2" },
"cvss_scores": null
}
]
}
]
}
}
2. Cloudsmith repository summary format
Pass the repo-level summary JSON from Cloudsmith. This is auto-detected and produces a repository overview report instead of a per-package vulnerability report:
{
"data": {
"owner": "my-org",
"packages": [
{
"package": "cloudsmith.io/jdk:9ea72e62...",
"slug_perm": "XXVmdsZn7OEh",
"status": "vulnerable",
"vulnerabilities": {
"critical": 0,
"high": 0,
"low": 1,
"medium": 2,
"unknown": 0
}
},
{
"package": "cloudsmith.io/jdk:5659dd01...",
"slug_perm": "Ft30zuSymLol",
"status": "no_issues_found",
"vulnerabilities": {
"critical": 0,
"high": 0,
"low": 0,
"medium": 0,
"unknown": 0
}
}
],
"repository": "chainguard"
}
}
The report includes status cards (vulnerable, no issues, not scanned), aggregate severity totals, a doughnut chart of package status, and a filterable package table.
Package status values: vulnerable, no_issues_found, no_scan.
3. Trivy format
Pass Trivy's JSON output (trivy image -f json). The tool maps ArtifactName, Results[].Vulnerabilities[], CVSS scores, and fix versions:
trivy image -f json nginx:latest > trivy-output.json
vulnly trivy-output.json
4. Grype format
Pass Grype's JSON output (grype -o json). The tool maps matches[], artifact metadata, CVSS scores, and fix versions:
grype nginx:latest -o json > grype-output.json
vulnly grype-output.json
5. Simplified format
A flat JSON structure is also supported for custom integrations:
{
"scan_date": "2026-03-13",
"repository": "my-org/production-repo",
"package_name": "my-application",
"package_version": "1.4.2",
"package_format": "Docker",
"scan_target": "debian 9.4",
"package_size": "~77.4 MB",
"scan_id": "TZR5N4HaO7nTclhz",
"vulnerabilities": [
{
"severity": "critical",
"identifier": "CVE-2026-1234",
"package": "openssl",
"affected_version": "1.1.1t",
"fixed_version": "1.1.1u",
"title": "Heap buffer overflow in OpenSSL",
"cvss": 9.8
}
]
}
Top-level fields
| Field | Required | Description |
|---|---|---|
scan_date |
Yes | Date the scan was performed (YYYY-MM-DD) |
repository |
Yes | Namespace/repository path (e.g. my-org/production-repo) |
package_name |
Yes | Name of the scanned package |
package_version |
Yes | Version of the scanned package |
package_format |
No | Package format (e.g. Docker, Maven). Defaults to Unknown |
scan_target |
No | Scan target OS/platform (e.g. debian 9.4). Defaults to Unknown |
package_size |
No | Human-readable package size (e.g. ~77.4 MB). Defaults to Unknown |
scan_id |
No | Unique scan identifier. Defaults to N/A |
vulnerabilities |
Yes | Array of vulnerability objects |
Vulnerability object fields
| Field | Required | Description |
|---|---|---|
severity |
Yes | One of critical, high, medium, low (case-insensitive) |
identifier |
Yes | CVE ID or other identifier (e.g. GHSA-...). CVE IDs are auto-linked to NVD; GHSA IDs are linked to GitHub Advisories |
package |
Yes | Name of the affected dependency |
affected_version |
No | The vulnerable version |
fixed_version |
No | The version containing the fix, or "N/A" / omit if no fix is available |
title |
No | Short description of the vulnerability. Auto-generated if omitted |
cvss |
No | CVSS v3 score as a number (e.g. 9.8). Displayed as N/A if omitted |
Requirements
- Python 3.8+
- No third-party packages required
You can also run without installing via:
python -m vulnly scan.json
Terminal Output
The script prints a severity breakdown after generating the report:
Report generated: reports/nginx_nginx_c755eb98e755_grype_vulnerability_report.html (162 vulnerabilities)
HIGH: 13 MEDIUM: 29 LOW: 7
For repository summary reports:
Repo summary generated: reports/my-org_chainguard_cloudsmith_repo_summary_report.html (36 packages, 6 vulnerabilities)
VULNERABLE: 2 NO ISSUES: 22 NOT SCANNED: 12
When no -o is specified, reports are saved to the reports/ subfolder. This directory is git-ignored by default.
License
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
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 vulnly-1.0.0b2.tar.gz.
File metadata
- Download URL: vulnly-1.0.0b2.tar.gz
- Upload date:
- Size: 2.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfdd1f508aa3c8d6cb665a8212032bdb3d7bcaedbe04996b0ff34fcf674cdc37
|
|
| MD5 |
55f4f4f6a5da27df4b69530a97dcfb5d
|
|
| BLAKE2b-256 |
4ce95f080381eaaf2124c1e6b91bd125ba20e503df2ae244651200802c190f9f
|
File details
Details for the file vulnly-1.0.0b2-py3-none-any.whl.
File metadata
- Download URL: vulnly-1.0.0b2-py3-none-any.whl
- Upload date:
- Size: 2.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5afea6283d9cf2a3cd4f252686f20871ea11ac65f07e5d2e0bae6af9f13d24bf
|
|
| MD5 |
3a82a64e0058468b67becf9a610b8db0
|
|
| BLAKE2b-256 |
ae34f8dc959b0aaddd5653738dc1088be7e2a51991fb52b09e2e37867fa7f473
|