| name | vulnerability-csv-reporting |
| description | Generate structured CSV security audit reports from Trivy or other vulnerability scanner JSON output. Use this skill whenever the user asks to write vulnerability results to a CSV file, create a security audit report, or export CVE findings with fields like Package, Version, CVE_ID, Severity, CVSS_Score, Fixed_Version, Title, Url.
|
Vulnerability CSV Reporting
Convert vulnerability scan JSON output into a well-structured CSV report using Python's
csv.DictWriter for reliable quoting and escaping.
CSV Schema
Standard security audit columns:
| Column | Source in Trivy JSON | Notes |
|---|
| Package | PkgName | e.g. "lodash" |
| Version | InstalledVersion | e.g. "4.17.20" |
| CVE_ID | VulnerabilityID | e.g. "CVE-2021-23337" |
| Severity | Severity | HIGH or CRITICAL |
| CVSS_Score | CVSS.nvd.V3Score (with fallback) | float or "N/A" |
| Fixed_Version | FixedVersion | version string or "N/A" if missing |
| Title | Title (fallback: Description[:80]) | Short description of the vuln |
| Url | References[0] | First reference URL or "N/A" |
Complete Python script
import csv
import json
FIELDNAMES = ["Package", "Version", "CVE_ID", "Severity", "CVSS_Score",
"Fixed_Version", "Title", "Url"]
def extract_cvss_score(vuln):
cvss = vuln.get("CVSS", {})
for source in ["nvd", "ghsa", "redhat"]:
scores = cvss.get(source, {})
if scores.get("V3Score") is not None:
return str(scores["V3Score"])
if scores.get("V2Score") is not None:
return str(scores["V2Score"])
for source, scores in cvss.items():
for key in ["V3Score", "V2Score"]:
if scores.get(key) is not None:
return str(scores[key])
return "N/A"
def vuln_to_row(vuln):
title = vuln.get("Title") or vuln.get("Description", "")[:120]
refs = vuln.get("References") or []
url = refs[0] if refs else "N/A"
return {
"Package": vuln.get("PkgName", "N/A"),
"Version": vuln.get("InstalledVersion", "N/A"),
"CVE_ID": vuln.get("VulnerabilityID", "N/A"),
"Severity": vuln.get("Severity", "N/A"),
"CVSS_Score": extract_cvss_score(vuln),
"Fixed_Version": vuln.get("FixedVersion") or "N/A",
"Title": title,
"Url": url,
}
def write_csv(results_path, output_path, severities=("HIGH", "CRITICAL")):
with open(results_path) as f:
data = json.load(f)
rows = []
for result in data.get("Results", []):
for vuln in result.get("Vulnerabilities") or []:
if vuln.get("Severity") in severities:
rows.append(vuln_to_row(vuln))
with open(output_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
writer.writeheader()
writer.writerows(rows)
print(f"Wrote {len(rows)} vulnerabilities to {output_path}")
write_csv("/root/trivy-results.json", "/root/security_audit.csv")
Key patterns
- Use
csv.DictWriter — it handles quoting/escaping automatically
FixedVersion is often absent; always use .get("FixedVersion") or "N/A"
References may be None or empty list; guard with or []
Title can be empty; fall back to truncated Description
- Filter severity before writing — don't include LOW/MEDIUM
Deduplication (optional)
If the same CVE appears in multiple result targets, deduplicate on (CVE_ID, Package, Version):
seen = set()
for vuln in vulns:
key = (vuln["VulnerabilityID"], vuln["PkgName"], vuln["InstalledVersion"])
if key not in seen:
seen.add(key)
rows.append(vuln_to_row(vuln))