template restructure

This commit is contained in:
2025-10-21 21:00:48 -05:00
parent 583cbffeca
commit f394e268da
11 changed files with 512 additions and 126 deletions

View File

@@ -3,8 +3,6 @@ import logging
logging.basicConfig(level=logging.INFO)
# TODO:
# LOGGING
# REPORT - use the scan config names for the names of the report and file
# REPORT - make darkmode
# TLS SCANNING
# TLS Version PROBE
@@ -12,13 +10,14 @@ logging.basicConfig(level=logging.INFO)
import time
from pathlib import Path
from typing import Dict, List, Set
from dataclasses import asdict
from ipaddress import ip_address
from typing import Any, Dict, List, Set
from utils.scan_config_loader import ScanConfigRepository, ScanConfigFile
from utils.schedule_manager import ScanScheduler
from utils.scanner import nmap_scanner
from utils.models import HostResult
from utils.models import HostResult, HostReport, GroupedReports
from reporting_jinja import write_html_report_jinja
from utils.settings import get_settings
@@ -29,8 +28,6 @@ logger = logging.getLogger(__file__)
utils = get_common_utils()
settings = get_settings()
HTML_REPORT_FILE = Path() / "data" / "report.html"
def results_to_open_sets(
results: List[HostResult],
count_as_open: Set[str] = frozenset({"open", "open|filtered"})) -> Dict[str, Dict[str, Set[int]]]:
@@ -125,16 +122,35 @@ def build_reports(
def run_repo_scan(scan_config:ScanConfigFile):
logger.info(f"Starting scan for {scan_config.name}")
logger.info("Options: udp=%s tls_sec=%s tls_exp=%s",
scan_config.scan_options.udp_scan,
scan_config.scan_options.tls_security_scan,
scan_config.scan_options.tls_exp_check)
logger.info(f"Options: udp={scan_config.scan_options.udp_scan} tls_sec={scan_config.scan_options.tls_security_scan} tls_exp={scan_config.scan_options.tls_exp_check}",)
logger.info(f"Reporting Options: Dark Mode: {scan_config.reporting.dark_mode}")
logger.info("Targets: %d hosts", len(scan_config.scan_targets))
# tack the filename on the end of our data path
file_out_path = Path() / "data" / "output" / scan_config.reporting.report_filename
LIGHT_TEMPLATE = "report_light.html.j2"
DARK_TEMPLATE = "report_dark.html.j2"
template = LIGHT_TEMPLATE
if scan_config.reporting.dark_mode:
template = DARK_TEMPLATE
logger.info(f"Reporting Template Set to: {template}")
scanner = nmap_scanner(scan_config)
scan_results = scanner.scan_targets()
discovered_sets = results_to_open_sets(scan_results, count_as_open={"open", "open|filtered"})
reports = build_reports(scan_config, discovered_sets)
write_html_report_jinja(reports=reports,host_results=scan_results,out_path=HTML_REPORT_FILE,title="Compliance Report",only_issues=True)
# build the HTML report
write_html_report_jinja(reports=reports,
host_results=scan_results,
out_path=file_out_path,
title=scan_config.reporting.report_name,
template_name=template,
only_issues=True)
scanner.cleanup()
def main():

View File

@@ -78,7 +78,6 @@ def render_html_report_jinja(
)
return html
def write_html_report_jinja(
reports: Dict[str, Dict[str, List[int]]],
host_results: List[HostResult],

View File

@@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
{# Dark mode variant of compliance report. Filename: report_dark.html.j2 #}
</head>
<body style="margin:0;padding:16px;background:#0b1020;color:#e5e7eb">
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
{# ===== Title Card ===== #}
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
<tr>
<td style="padding:14px 16px;background:#1e293b;border:1px solid #334155;border-radius:10px;color:#f1f5f9">
<div style="font-size:20px;font-weight:700;margin-bottom:4px">{{ title }}</div>
<div style="font-size:12px;color:#94a3b8">Generated: {{ generated }}</div>
</td>
</tr>
</table>
{# ===== Summary Bar ===== #}
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
<tr>
<td style="
padding:12px 16px;
border:1px solid #334155;
border-radius:8px;
background:#1e293b;
color:#f1f5f9;
font-size:14px;
">
Total hosts:
<strong style="color:#f8fafc">{{ total_hosts }}</strong>&nbsp;&nbsp;
Matching expected:
<strong style="color:#4ade80">{{ ok_hosts }}</strong>&nbsp;&nbsp;
With issues:
<strong style="color:#f87171">{{ hosts_with_issues|length }}</strong>
</td>
</tr>
</table>
{% if not only_issues and ok_hosts == total_hosts and total_hosts > 0 %}
<div style="margin:6px 0 12px 0;font-size:12px;color:#9ca3af">
All hosts matched expected ports.
</div>
{% endif %}
{% if only_issues and hosts_with_issues|length == 0 %}
<div style="margin:8px 0;color:#9ca3af">
No hosts with issues found. ✅
</div>
{% endif %}
{# ===== Host Sections ===== #}
{% for ip_key, r in reports|dictsort %}
{% set has_issues = r.unexpected_tcp or r.missing_tcp or r.unexpected_udp or r.missing_udp %}
{% set hr = host_results_by_ip.get(ip_key) %}
{% set header_title = ip_key ~ ((' (' ~ hr.host ~ ')') if hr and hr.host else '') %}
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
<tr>
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
{{ header_title }}
{% if has_issues %}
{{ badge('ISSUES', '#ef4444') }}
{% else %}
{{ badge('OK', '#16a34a') }}
{% endif %}
</td>
</tr>
{% if has_issues %}
<tr>
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
{{ badge('ISSUES', '#ef4444') }}
</td>
</tr>
{% macro delta_row(label, ports) -%}
<tr>
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
<strong style="color:#e5e7eb">{{ label }}:</strong>
<span style="color:#cbd5e1">{{ fmt_ports(ports) }}</span>
</td>
</tr>
{%- endmacro %}
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
<tr>
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
<div style="font-weight:600;margin:8px 0;color:#e5e7eb">Discovered Ports</div>
</td>
</tr>
<tr>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Protocol</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Port</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">State</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Service</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
</tr>
{% if hr and hr.ports %}
{# Track visible rows after filtering (e.g., skip closed UDP) #}
{% set ns = namespace(visible_rows=0) %}
{% for p in hr.ports %}
{# --- Normalize helpers --- #}
{% set proto = (p.protocol|string|lower) %}
{% set state = (p.state|string|lower) %}
{# --- Skip rule: hide UDP rows that are "closed" (substring match) --- #}
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
{# --- Expectation labeling --- #}
{% set is_tcp = (proto == 'tcp') %}
{% set is_udp = (proto == 'udp') %}
{% set is_open = ('open' in state) %}
{% set is_issue = (is_open and (
(is_tcp and (p.port in (r.unexpected_tcp or []))) or
(is_udp and (p.port in (r.unexpected_udp or [])))
)) %}
{% set expectation_badge = (
badge('Issue', '#ef4444') if is_issue
else (badge('Expected', '#16a34a') if is_open else '<span style="color:#9ca3af">—</span>')
) %}
{% if not skip_row %}
{% set ns.visible_rows = ns.visible_rows + 1 %}
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_proto(p.protocol) }}</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">{{ p.port }}</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ pill_state(p.state) }}</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">{{ p.service or '-' }}</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020">{{ expectation_badge | safe }}</td>
</tr>
{% endif %}
{% endfor %}
{% if ns.visible_rows == 0 %}
<tr>
<td colspan="5" style="padding:8px 10px;border:1px solid #1f2937;background:#0b1020;color:#9ca3af">
No per-port details to display after filtering closed UDP results.
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan="5" style="padding:8px 10px;border:1px solid #1f2937;background:#0b1020;color:#9ca3af">
No per-port details available for this host.
</td>
</tr>
{% endif %}
{% else %}
{# Host has no issues #}
<tr>
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
{{ badge('OK', '#16a34a') }} &nbsp; <span style="color:#cbd5e1">Matches expected ports.</span>
</td>
</tr>
{% endif %}
</table>
{% endfor %}
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
Report generated by mass-scan-v2 • {{ generated }}
</div>
</div>
</body>
</html>

View File

@@ -81,20 +81,57 @@
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Port</td>
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">State</td>
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Service</td>
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
</tr>
{% if hr and hr.ports %}
{# Track whether we rendered any visible rows after filtering #}
{% set ns = namespace(visible_rows=0) %}
{% for p in hr.ports %}
{# Normalize helpers #}
{% set proto = (p.protocol|string|lower) %}
{% set state = (p.state|string|lower) %}
{# Skip rule: hide UDP rows that are 'closed' #}
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
{# Compute expectation labeling (only matters for displayed rows) #}
{% set is_tcp = (proto == 'tcp') %}
{% set is_udp = (proto == 'udp') %}
{% set is_open = ('open' in state) %}
{% set is_issue = (is_open and (
(is_tcp and (p.port in (r.unexpected_tcp or []))) or
(is_udp and (p.port in (r.unexpected_udp or [])))
)) %}
{% set expectation_badge = (
badge('Issue', '#ef4444') if is_issue
else (badge('Expected', '#16a34a') if is_open else '<span style="color:#64748b">—</span>')
) %}
{% if not skip_row %}
{% set ns.visible_rows = ns.visible_rows + 1 %}
<tr>
<td style="padding:6px 10px;border:1px solid #e5e7eb">{{ pill_proto(p.protocol) }}</td>
<td style="padding:6px 10px;border:1px solid #e5e7eb">{{ p.port }}</td>
<td style="padding:6px 10px;border:1px solid #e5e7eb">{{ pill_state(p.state) }}</td>
<td style="padding:6px 10px;border:1px solid #e5e7eb">{{ p.service or '-' }}</td>
<td style="padding:6px 10px;border:1px solid #e5e7eb">{{ expectation_badge | safe }}</td>
</tr>
{% endif %}
{% endfor %}
{# If everything was filtered out, show a friendly note #}
{% if ns.visible_rows == 0 %}
<tr>
<td colspan="5" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
No per-port details to display after filtering closed UDP results.
</td>
</tr>
{% endif %}
{% else %}
<tr>
<td colspan="4" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
<td colspan="5" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
No per-port details available for this host.
</td>
</tr>

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Set, Optional, Any
from ipaddress import ip_address
@dataclass
class PortFinding:
@@ -16,7 +16,6 @@ class PortFinding:
state: str
service: Optional[str] = None
@dataclass
class HostResult:
"""
@@ -28,3 +27,68 @@ class HostResult:
address: str
host: Optional[str] = None
ports: List[PortFinding] = field(default_factory=list)
@dataclass
class HostReport:
"""
Delta result for a single host.
"""
ip: str
unexpected_tcp: List[int]
missing_tcp: List[int]
unexpected_udp: List[int]
missing_udp: List[int]
def has_issues(self) -> bool:
"""
Returns True if any delta list is non-empty.
"""
if self.unexpected_tcp:
return True
if self.missing_tcp:
return True
if self.unexpected_udp:
return True
if self.missing_udp:
return True
return False
def to_dict(self) -> Dict[str, Any]:
"""
Convert to a plain dict for JSON/Jinja contexts.
"""
return asdict(self)
@dataclass
class GroupedReports:
"""
Final, template-friendly structure:
- issues: list of HostReport with any deltas (sorted by IP)
- expected: list of HostReport with no deltas (sorted by IP)
- by_ip: mapping for random access if needed
"""
issues: List[HostReport]
expected: List[HostReport]
by_ip: Dict[str, HostReport]
def to_context(self) -> Dict[str, Any]:
"""
Produce plain-dict context for Jinja render() if you prefer dicts.
"""
issues_dicts: List[Dict[str, Any]] = []
for hr in self.issues:
issues_dicts.append(hr.to_dict())
expected_dicts: List[Dict[str, Any]] = []
for hr in self.expected:
expected_dicts.append(hr.to_dict())
by_ip_dict: Dict[str, Dict[str, Any]] = {}
for ip, hr in self.by_ip.items():
by_ip_dict[ip] = hr.to_dict()
return {
"issues": issues_dicts,
"expected": expected_dicts,
"by_ip": by_ip_dict,
}

View File

@@ -41,6 +41,7 @@ class Reporting:
report_name: str = "Scan Report"
report_filename: str = "report.html"
full_details: bool = False
dark_mode: bool = True
email_to: List[str] = field(default_factory=list)
email_cc: List[str] = field(default_factory=list)

View File

@@ -1,5 +0,0 @@
[
{"ip": "10.10.99.6", "expected_tcp": [22,222,3000], "expected_udp": []},
{"ip": "10.10.99.2", "expected_tcp": [22,222,3000], "expected_udp": []},
{"ip": "10.10.99.10", "expected_tcp": [22,80,443], "expected_udp": []}
]

190
data/output/corp-wan.html Normal file
View File

@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Corporate WAN Perimeter</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body style="margin:0;padding:16px;background:#0b1020;color:#e5e7eb">
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
<tr>
<td style="padding:14px 16px;background:#1e293b;border:1px solid #334155;border-radius:10px;color:#f1f5f9">
<div style="font-size:20px;font-weight:700;margin-bottom:4px">Corporate WAN Perimeter</div>
<div style="font-size:12px;color:#94a3b8">Generated: 2025-10-22 01:45:32</div>
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
<tr>
<td style="
padding:12px 16px;
border:1px solid #334155;
border-radius:8px;
background:#1e293b;
color:#f1f5f9;
font-size:14px;
">
Total hosts:
<strong style="color:#f8fafc">3</strong>&nbsp;&nbsp;
Matching expected:
<strong style="color:#4ade80">2</strong>&nbsp;&nbsp;
With issues:
<strong style="color:#f87171">1</strong>
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
<tr>
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
10.10.20.12
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">ISSUES</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">ISSUES</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
<strong style="color:#e5e7eb">Unexpected TCP open ports:</strong>
<span style="color:#cbd5e1">3128</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
<strong style="color:#e5e7eb">Expected TCP ports not seen:</strong>
<span style="color:#cbd5e1">none</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
<strong style="color:#e5e7eb">Unexpected UDP open ports:</strong>
<span style="color:#cbd5e1">none</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
<strong style="color:#e5e7eb">Expected UDP ports not seen:</strong>
<span style="color:#cbd5e1">none</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
<div style="font-weight:600;margin:8px 0;color:#e5e7eb">Discovered Ports</div>
</td>
</tr>
<tr>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Protocol</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Port</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">State</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Service</td>
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
</tr>
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">22</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">ssh</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
</tr>
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">111</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">rpcbind</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
</tr>
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">3128</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">squid-http</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Issue</span></td>
</tr>
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">8006</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">wpl-analytics</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
</tr>
<tr>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">udp</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">111</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">rpcbind</td>
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
<tr>
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
10.10.20.4
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> &nbsp; <span style="color:#cbd5e1">Matches expected ports.</span>
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
<tr>
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
10.10.20.5
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
</td>
</tr>
<tr>
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> &nbsp; <span style="color:#cbd5e1">Matches expected ports.</span>
</td>
</tr>
</table>
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
Report generated by mass-scan-v2 • 2025-10-22 01:45:32
</div>
</div>
</body>
</html>

View File

@@ -1,64 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Compliance Report</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body style="margin:0;padding:16px;background:#ffffff">
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
<tr>
<td style="padding:10px;background:#0f172a;border-radius:10px;color:#e2e8f0">
<div style="font-size:18px;font-weight:700;margin-bottom:4px">Compliance Report</div>
<div style="font-size:12px;color:#94a3b8">Generated: 2025-10-17 21:42:08</div>
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
<tr>
<td style="padding:10px;border:1px solid #e5e7eb;border-radius:8px">
Total hosts: <strong>2</strong>&nbsp;
Matching expected: <strong>2</strong>&nbsp;
With issues: <strong>0</strong>
</td>
</tr>
</table>
<div style="margin:8px 0;color:#64748b">
No hosts with issues found. ✅
</div>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
<tr>
<td colspan="4" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
10.10.20.4 <span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> </td>
</tr>
<tr>
<td colspan="4" style="padding:10px 10px 8px 10px;font-size:13px">
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> &nbsp; Matches expected ports.
</td>
</tr>
</table>
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
<tr>
<td colspan="4" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
10.10.20.5 <span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> </td>
</tr>
<tr>
<td colspan="4" style="padding:10px 10px 8px 10px;font-size:13px">
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> &nbsp; Matches expected ports.
</td>
</tr>
</table>
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
Report generated by mass-scan-v2 • 2025-10-17 21:42:08
</div>

View File

@@ -1,34 +0,0 @@
[
{"ip": "81.246.102.192", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.193", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.194", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.195", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.196", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.197", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.198", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.199", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.200", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.201", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.202", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.203", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.204", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.205", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.206", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.207", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.208", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.209", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.210", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.211", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.212", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.213", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.214", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.215", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.216", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.217", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.218", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.219", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.220", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.221", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.222", "expected_tcp": [], "expected_udp": []},
{"ip": "81.246.102.223", "expected_tcp": [], "expected_udp": []}
]

View File

@@ -8,6 +8,7 @@ scan_options:
reporting:
report_name: Corporate WAN Perimeter
report_filename: corp-wan.html
dark_mode: true
full_details: true
email_to: soc@example.com # single string is fine; or a list
email_cc: [] # explicitly none
@@ -20,3 +21,7 @@ scan_targets:
- ip: 10.10.20.5
expected_tcp: [22, 53, 80]
expected_udp: [53]
- ip: 10.10.20.12
expected_tcp: [22, 111, 8006]
expected_udp: [111]