intermediate By Mathias Paulenko

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

FieldValue
Reporting period2026-06-01 to 2026-06-30
Total assets scanned142
Assets with findings38
Critical findings3
High findings12
Medium findings47
Low findings89
Remediation rate78% of findings closed from prior period
Aggregate trendImproving

2. Scan Scope and Coverage

Asset GroupAssets ScannedCoverage %Scan TypeOwner
Production servers54100%Authenticated network scanPlatform team
Cloud workloads3695%Agent-based scanCloud team
Web applications18100%DASTApplication security
Containers2480%Registry + runtime scanDevOps team
Databases10100%Configuration scanDBA team

3. Findings by Severity

SeverityCountOpenIn ProgressClosedAvg Days to Remediate
Critical31112
High124357
Medium4715122021
Low8930253445

4. Top Critical Findings

FindingCVEAffected AssetsSeverityExploit AvailableRemediationOwnerDue Date
Unpatched OpenSSLCVE-2026-XXXXapi-01, api-02CriticalYesUpgrade to 3.0.9Backend team2026-07-02
Exposed RDP serviceN/Ajump-host-legacyCriticalYesDisable RDP, use bastionNetwork team2026-07-01
Default admin accountN/Astaging-dbCriticalNoRemove or rename accountDBA team2026-07-03

5. Remediation Tracking

Finding IDTitleSeverityOwnerOpenedDue DateStatusNotes
VULN-042Unpatched OpenSSLCriticalBackend team2026-06-152026-07-02In progressPatch staged for release
VULN-043Exposed SMB portHighNetwork team2026-06-202026-07-05OpenFirewall rule pending approval
VULN-044Outdated TLS versionMediumPlatform team2026-06-102026-07-10In progressConfig tested in staging
VULN-045Missing security headerLowFrontend team2026-06-252026-08-01OpenScheduled in next sprint

6. Trend Analysis

PeriodCriticalHighMediumLowTotalClosedRemediation Rate
2026-035186211019516082%
2026-04415559817214584%
2026-05214509215813082%
2026-06312478915112078%

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.