Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/NetworkOptimizer.Reports/MarkdownReportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,51 @@ private void ComposeThreatSummary(StringBuilder sb, ReportData data)
sb.AppendLine();
sb.AppendLine("| IP Address | Country | ASN | Events |");
sb.AppendLine("|-----------|---------|-----|--------|");
foreach (var source in threat.TopSources.Take(5))
foreach (var source in threat.TopSources)
{
sb.AppendLine($"| {source.Ip} | {source.CountryCode ?? "-"} | {source.AsnOrg ?? "-"} | {source.EventCount:N0} |");
}
sb.AppendLine();

// Suppressed-count footnote
if (threat.SuppressedEventCount > 0)
{
var infraCount = threat.InfrastructureSources.Sum(s => s.EventCount);
var trustedCount = threat.TrustedUserSources.Sum(s => s.EventCount);
var parts = new List<string>();
if (infraCount > 0) parts.Add($"{infraCount:N0} from known infrastructure");
if (trustedCount > 0) parts.Add($"{trustedCount:N0} from trusted user devices");
sb.AppendLine($"*Excludes {string.Join(" and ", parts)} (see categorized tables below).*");
sb.AppendLine();
}
}

// Known Infrastructure Activity
if (threat.InfrastructureSources.Any())
{
sb.AppendLine("### Known Infrastructure Activity");
sb.AppendLine();
sb.AppendLine("| IP Address | Label | Country | Events |");
sb.AppendLine("|-----------|-------|---------|--------|");
foreach (var source in threat.InfrastructureSources)
{
sb.AppendLine($"| {source.Ip} | {source.Label ?? "-"} | {source.CountryCode ?? "-"} | {source.EventCount:N0} |");
}
sb.AppendLine();
}

// Trusted User Activity
if (threat.TrustedUserSources.Any())
{
sb.AppendLine("### Trusted User Activity");
sb.AppendLine();
sb.AppendLine("| IP Address | Label | Country | Events |");
sb.AppendLine("|-----------|-------|---------|--------|");
foreach (var source in threat.TrustedUserSources)
{
sb.AppendLine($"| {source.Ip} | {source.Label ?? "-"} | {source.CountryCode ?? "-"} | {source.EventCount:N0} |");
}
sb.AppendLine();
}

// Exposed services
Expand Down
100 changes: 99 additions & 1 deletion src/NetworkOptimizer.Reports/PdfReportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,7 @@ private void ComposeThreatSummary(IContainer container, ReportData data)
.Text("Events").Bold().FontSize(8);
});

foreach (var source in threat.TopSources.Take(5))
foreach (var source in threat.TopSources)
{
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(4)
.Text(source.Ip).FontSize(8);
Expand All @@ -1217,6 +1217,48 @@ private void ComposeThreatSummary(IContainer container, ReportData data)
.Text(source.EventCount.ToString("N0")).FontSize(8);
}
});

// Suppressed-count footnote: tells the reader the table above excludes
// events from internal infrastructure and trusted user devices, with
// the actual numbers shown in the sub-tables that follow.
if (threat.SuppressedEventCount > 0)
{
var infraCount = threat.InfrastructureSources.Sum(s => s.EventCount);
var trustedCount = threat.TrustedUserSources.Sum(s => s.EventCount);
var parts = new List<string>();
if (infraCount > 0) parts.Add($"{infraCount:N0} from known infrastructure");
if (trustedCount > 0) parts.Add($"{trustedCount:N0} from trusted user devices");
var footnote = $"Excludes {string.Join(" and ", parts)} (see categorized tables below).";

column.Item()
.PaddingTop(4)
.Text(footnote)
.FontSize(8)
.Italic()
.FontColor(Colors.Grey.Medium);
}
}

// Known Infrastructure Activity
if (threat.InfrastructureSources.Any())
{
ComposeCategorizedSourceTable(
column,
"Known Infrastructure Activity",
threat.InfrastructureSources,
primaryColor,
lightGray);
}

// Trusted User Activity
if (threat.TrustedUserSources.Any())
{
ComposeCategorizedSourceTable(
column,
"Trusted User Activity",
threat.TrustedUserSources,
primaryColor,
lightGray);
}

// Exposed services
Expand Down Expand Up @@ -1273,6 +1315,62 @@ private void ComposeThreatSummary(IContainer container, ReportData data)
});
}

/// <summary>
/// Renders a small sub-table beneath the main "Top Threat Sources" table for
/// a categorized group (Known Infrastructure Activity or Trusted User Activity).
/// Adds a Label column so the reader can see what each source actually is.
/// </summary>
private void ComposeCategorizedSourceTable(
ColumnDescriptor column,
string title,
List<ThreatSourceEntry> sources,
string primaryColor,
string lightGray)
{
column.Item()
.PaddingTop(10)
.PaddingBottom(4)
.Text(title)
.FontSize(10)
.Bold()
.FontColor(primaryColor);

column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(2f); // IP
columns.RelativeColumn(2.5f); // Label
columns.RelativeColumn(1f); // Country
columns.RelativeColumn(1f); // Events
});

table.Header(header =>
{
header.Cell().Background(lightGray).Padding(4)
.Text("IP Address").Bold().FontSize(8);
header.Cell().Background(lightGray).Padding(4)
.Text("Label").Bold().FontSize(8);
header.Cell().Background(lightGray).Padding(4)
.Text("Country").Bold().FontSize(8);
header.Cell().Background(lightGray).Padding(4)
.Text("Events").Bold().FontSize(8);
});

foreach (var source in sources)
{
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(4)
.Text(source.Ip).FontSize(8);
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(4)
.Text(source.Label ?? "-").FontSize(8);
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(4)
.Text(source.CountryCode ?? "-").FontSize(8);
table.Cell().Border(0.5f).BorderColor(Colors.Grey.Lighten2).Padding(4)
.Text(source.EventCount.ToString("N0")).FontSize(8);
}
});
}

private void ComposePortSecuritySummary(IContainer container, ReportData data)
{
var primaryColor = GetColor(_branding.Colors.Primary);
Expand Down
27 changes: 27 additions & 0 deletions src/NetworkOptimizer.Reports/ReportData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,26 @@ public class ThreatSummaryData
public Dictionary<string, int> ByKillChain { get; set; } = new();
public List<ThreatSourceEntry> TopSources { get; set; } = new();
public List<ExposedServiceEntry> ExposedServices { get; set; } = new();

/// <summary>
/// Source IPs matching enabled Infrastructure-category noise filters. Shown
/// in a separate "Known Infrastructure Activity" sub-table so the user can
/// see what was excluded from the main Top Threat Sources table.
/// </summary>
public List<ThreatSourceEntry> InfrastructureSources { get; set; } = new();

/// <summary>
/// Source IPs matching enabled TrustedUser-category noise filters. Shown
/// in a separate "Trusted User Activity" sub-table.
/// </summary>
public List<ThreatSourceEntry> TrustedUserSources { get; set; } = new();

/// <summary>
/// Aggregate event count for events suppressed from the top threat sources
/// table because they matched Infrastructure or TrustedUser filters. Used
/// for the "X events suppressed" header line.
/// </summary>
public int SuppressedEventCount { get; set; }
}

public class ThreatSourceEntry
Expand All @@ -567,6 +587,13 @@ public class ThreatSourceEntry
public string? CountryCode { get; set; }
public string? AsnOrg { get; set; }
public int EventCount { get; set; }

/// <summary>
/// Human-readable label from the matching noise filter for categorized
/// sub-tables (e.g., "Network Optimizer (self)"). Null for the main
/// Top Threat Sources table.
/// </summary>
public string? Label { get; set; }
}

public class ExposedServiceEntry
Expand Down
Loading