Vulnerability Scan Report Template
A template for summarizing vulnerability scan findings, including asset coverage, severity distribution, and remediation tracking.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
A Vulnerability Scan Report summarizes the findings of automated vulnerability scans across infrastructure, applications, and cloud services. It helps security teams, engineering managers, and executives understand current exposure, prioritize remediation, and track progress over time. This template supports both technical and executive audiences.
When to Use
- After a scheduled vulnerability scan across production or staging.
- Before a release or compliance audit.
- During monthly or quarterly security reviews.
- When onboarding a new asset or service to the scan program.
- After a breach or incident to assess exposed systems.
Prerequisites
- A vulnerability scanner configured and executed, such as Nessus, Qualys, or Tenable.
- An asset inventory with owners, environments, and criticality ratings.
- A severity classification scheme, such as CVSS or your own risk matrix.
- Remediation SLAs defined by severity.
Solution
Template
1. Executive Summary
| Field | Value |
|---|---|
| Reporting period | 2026-06-01 to 2026-06-30 |
| Total assets scanned | 142 |
| Assets with findings | 38 |
| Critical findings | 3 |
| High findings | 12 |
| Medium findings | 47 |
| Low findings | 89 |
| Remediation rate | 78% of findings closed from prior period |
| Aggregate trend | Improving |
2. Scan Scope and Coverage
| Asset Group | Assets Scanned | Coverage % | Scan Type | Owner |
|---|---|---|---|---|
| Production servers | 54 | 100% | Authenticated network scan | Platform team |
| Cloud workloads | 36 | 95% | Agent-based scan | Cloud team |
| Web applications | 18 | 100% | DAST | Application security |
| Containers | 24 | 80% | Registry + runtime scan | DevOps team |
| Databases | 10 | 100% | Configuration scan | DBA team |
3. Findings by Severity
| Severity | Count | Open | In Progress | Closed | Avg Days to Remediate |
|---|---|---|---|---|---|
| Critical | 3 | 1 | 1 | 1 | 2 |
| High | 12 | 4 | 3 | 5 | 7 |
| Medium | 47 | 15 | 12 | 20 | 21 |
| Low | 89 | 30 | 25 | 34 | 45 |
4. Top Critical Findings
| Finding | CVE | Affected Assets | Severity | Exploit Available | Remediation | Owner | Due Date |
|---|---|---|---|---|---|---|---|
| Unpatched OpenSSL | CVE-2026-XXXX | api-01, api-02 | Critical | Yes | Upgrade to 3.0.9 | Backend team | 2026-07-02 |
| Exposed RDP service | N/A | jump-host-legacy | Critical | Yes | Disable RDP, use bastion | Network team | 2026-07-01 |
| Default admin account | N/A | staging-db | Critical | No | Remove or rename account | DBA team | 2026-07-03 |
5. Remediation Tracking
| Finding ID | Title | Severity | Owner | Opened | Due Date | Status | Notes |
|---|---|---|---|---|---|---|---|
| VULN-042 | Unpatched OpenSSL | Critical | Backend team | 2026-06-15 | 2026-07-02 | In progress | Patch staged for release |
| VULN-043 | Exposed SMB port | High | Network team | 2026-06-20 | 2026-07-05 | Open | Firewall rule pending approval |
| VULN-044 | Outdated TLS version | Medium | Platform team | 2026-06-10 | 2026-07-10 | In progress | Config tested in staging |
| VULN-045 | Missing security header | Low | Frontend team | 2026-06-25 | 2026-08-01 | Open | Scheduled in next sprint |
6. Trend Analysis
| Period | Critical | High | Medium | Low | Total | Closed | Remediation Rate |
|---|---|---|---|---|---|---|---|
| 2026-03 | 5 | 18 | 62 | 110 | 195 | 160 | 82% |
| 2026-04 | 4 | 15 | 55 | 98 | 172 | 145 | 84% |
| 2026-05 | 2 | 14 | 50 | 92 | 158 | 130 | 82% |
| 2026-06 | 3 | 12 | 47 | 89 | 151 | 120 | 78% |
Explanation
The report converts raw scanner output into a structured story: what was scanned, what was found, who is fixing it, and how fast. The executive summary gives leadership a quick view, while the detailed tables give engineers useful items. Trend analysis shows whether the security program is improving or falling behind.
Variants
- Executive dashboard report: One-page summary with charts and risk posture.
- Technical scan report: Full finding details with CVSS, affected packages, and remediation commands.
- Cloud configuration report: Focuses on cloud misconfigurations from tools like Prowler or CloudSploit.
- DAST report: Web application-specific findings from live scanners.
- Monthly compliance report: Maps findings to control frameworks and tracks SLA compliance.
What Works
- Include asset coverage so stakeholders know what was not scanned.
- Prioritize by exploitability and business impact, not just CVSS.
- Assign every finding to a named owner with a due date.
- Track remediation status weekly until closure.
- Retest after remediation to confirm the fix.
- Compare trends across periods to measure improvement.
- Document accepted risks with justification and expiration.
Common Mistakes
- Reporting only vulnerability counts without context.
- Not tracking which assets were unreachable or unscanned.
- Assigning findings to teams without capacity or context.
- Closing findings without verifying the fix.
- Ignoring medium and low findings until they accumulate.
- Not including trend data or historical comparison.
Advanced Solutions
Automated vulnerability scanning pipeline with Trivy and GitHub Actions
Schedule automated container image scans and generate structured reports:
# .github/workflows/vuln-scan.yml
name: Vulnerability Scan Report
on:
schedule:
- cron: "0 2 * * 1" # Weekly Monday 2am
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy scan
uses: aquasecurity/trivy-action@master
with:
image-ref: "myregistry/app:latest"
format: "json"
output: "trivy-report.json"
severity: "CRITICAL,HIGH,MEDIUM"
exit-code: "0" # Don't fail the workflow
- name: Parse and summarize findings
run: |
python scripts/parse_vuln_report.py \
--input trivy-report.json \
--output vuln-summary.md \
--format markdown
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: vulnerability-report
path: |
trivy-report.json
vuln-summary.md
# scripts/parse_vuln_report.py
import json
import argparse
from collections import Counter
from datetime import datetime
def parse_trivy_report(input_file: str, output_file: str) -> None:
"""Parse Trivy JSON report and generate a markdown summary."""
with open(input_file) as f:
data = json.load(f)
severity_counts = Counter()
findings_by_severity = {"CRITICAL": [], "HIGH": [], "MEDIUM": [], "LOW": []}
for result in data.get("Results", []):
target = result.get("Target", "unknown")
for vuln in result.get("Vulnerabilities", []):
severity = vuln.get("Severity", "UNKNOWN")
severity_counts[severity] += 1
if severity in findings_by_severity:
findings_by_severity[severity].append({
"target": target,
"cve": vuln.get("VulnerabilityID", "N/A"),
"package": vuln.get("PkgName", "N/A"),
"installed": vuln.get("InstalledVersion", "N/A"),
"fixed": vuln.get("FixedVersion", "N/A"),
})
with open(output_file, "w") as f:
f.write(f"# Vulnerability Scan Report - {datetime.now().strftime('%Y-%m-%d')}\n\n")
f.write("## Executive Summary\n\n")
f.write(f"| Metric | Value |\n|--------|-------|\n")
f.write(f"| Total findings | {sum(severity_counts.values())} |\n")
f.write(f"| Critical | {severity_counts.get('CRITICAL', 0)} |\n")
f.write(f"| High | {severity_counts.get('HIGH', 0)} |\n")
f.write(f"| Medium | {severity_counts.get('MEDIUM', 0)} |\n")
f.write(f"| Low | {severity_counts.get('LOW', 0)} |\n\n")
for severity in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
findings = findings_by_severity[severity]
if not findings:
continue
f.write(f"## {severity.title()} Findings ({len(findings)})\n\n")
f.write("| Target | CVE | Package | Installed | Fixed |\n")
f.write("|--------|-----|---------|-----------|-------|\n")
for finding in findings[:20]: # Top 20 per severity
f.write(
f"| {finding['target']} | {finding['cve']} | "
f"{finding['package']} | {finding['installed']} | "
f"{finding['fixed']} |\n"
)
if len(findings) > 20:
f.write(f"\n*...and {len(findings) - 20} more*\n")
f.write("\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--input", required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()
parse_trivy_report(args.input, args.output)
Prowler for AWS configuration scanning
Scan AWS environments for misconfigurations and generate a compliance-aligned report:
#!/bin/bash
set -euo pipefail
# Run Prowler against AWS account
prowler aws \
--output-formats json html \
--output-directory ./prowler-reports \
--quiet
# Extract critical findings
jq '[.Controls[] | select(.Status == "FAIL" and .Severity == "critical")] | length' \
prowler-reports/*.json
# Generate summary by service
jq -r '.Controls[] | select(.Status == "FAIL") | .ControlID + " - " + .CheckTitle' \
prowler-reports/*.json > aws-findings-summary.txt
echo "=== AWS Misconfiguration Summary ==="
echo "Critical failures: $(jq '[.Controls[] | select(.Status == "FAIL" and .Severity == "critical")] | length' prowler-reports/*.json)"
echo "High failures: $(jq '[.Controls[] | select(.Status == "FAIL" and .Severity == "high")] | length' prowler-reports/*.json)"
echo "Medium failures: $(jq '[.Controls[] | select(.Status == "FAIL" and .Severity == "medium")] | length' prowler-reports/*.json)"
Risk-based prioritization with EPSS scoring
Combine CVSS with Exploit Prediction Scoring System (EPSS) to prioritize remediation:
import requests
from dataclasses import dataclass
from typing import List
@dataclass
class VulnerabilityPriority:
cve: str
cvss_score: float
epss_score: float
is_exploited: bool
is_internet_facing: bool
has_sensitive_data: bool
@property
def priority_score(self) -> float:
"""Calculate composite priority score."""
base = self.cvss_score * 10 # Scale CVSS to 0-100
epss_boost = self.epss_score * 30 # EPSS probability weight
exploited_boost = 25 if self.is_exploited else 0
exposure_boost = 15 if self.is_internet_facing else 0
data_boost = 10 if self.has_sensitive_data else 0
return round(base + epss_boost + exploited_boost + exposure_boost + data_boost, 1)
@property
def priority_label(self) -> str:
if self.priority_score >= 80:
return "P0 - Immediate"
elif self.priority_score >= 60:
return "P1 - Urgent"
elif self.priority_score >= 40:
return "P2 - High"
elif self.priority_score >= 20:
return "P3 - Medium"
else:
return "P4 - Low"
def get_epss_score(cve_id: str) -> float:
"""Fetch EPSS score from FIRST.org API."""
try:
resp = requests.get(
f"https://api.first.org/data/v1/epss?cve={cve_id}",
timeout=10,
)
data = resp.json()
if data.get("data"):
return float(data["data"][0]["epss"])
except Exception:
pass
return 0.0
# Example: Prioritize a list of vulnerabilities
vulns = [
VulnerabilityPriority("CVE-2026-1234", 9.8, get_epss_score("CVE-2026-1234"), True, True, True),
VulnerabilityPriority("CVE-2026-5678", 7.5, get_epss_score("CVE-2026-5678"), False, False, False),
VulnerabilityPriority("CVE-2026-9012", 6.5, get_epss_score("CVE-2026-9012"), False, True, True),
]
vulns.sort(key=lambda v: v.priority_score, reverse=True)
for v in vulns:
print(f"{v.cve}: {v.priority_label} (score: {v.priority_score})") Frequently Asked Questions
- What should be considered critical beyond CVSS 9.0?
- A critical finding should also consider exploitability, exposure to the internet, data sensitivity, and whether a public exploit exists. An internal CVSS 7 vulnerability in a public service may be...
- How do we handle findings that cannot be patched?
- Document a compensating control, accept the risk with an expiration date, and monitor for changes. Examples include WAF rules, network isolation, or additional monitoring.
- Who should receive this report?
- Security team, engineering managers, CISO, compliance officer, and asset owners. The executive summary is useful for leadership; the detailed tables are for remediation teams.
Related Resources
Dependency Vulnerability Report Template
A template for documenting security findings in dependencies, including severity, impact, and remediation steps for engineering teams.
DocCompliance Gap Analysis Template
A template for mapping current security controls to compliance frameworks like SOC 2, ISO 27001, and PCI-DSS.
DocPenetration Test Scope Template
A template for defining the boundaries, targets, rules, and deliverables for a penetration testing engagement.