Implement reversible Sentinel beta foundation
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 51s

This commit is contained in:
OfficeCom Codex
2026-07-30 01:05:30 +02:00
parent 58ad77242f
commit c3ca95dfa5
21 changed files with 464 additions and 18 deletions

View File

@@ -34,6 +34,7 @@ internal sealed class AttackScanner
ScanExchangeLogons(attacks, errors, since);
ScanIisFtpLogs(attacks, errors, since, configuration);
ScanFileZillaLogs(attacks, errors, since, configuration);
RansomwareBetaSummary ransomwareBeta = RansomwareBetaDetector.Scan(configuration, errors);
if (configuration.ExcludedIps.Count > 0)
{
@@ -53,7 +54,7 @@ internal sealed class AttackScanner
.ToList();
int uniqueIpCount = attacks.Select(static attack => attack.SourceIp).Distinct(StringComparer.OrdinalIgnoreCase).Count();
AlertAssessment baseAssessment = AssessAttackActivity(attacks, uniqueIpCount, configuration);
AlertAssessment baseAssessment = MergeRansomwareAssessment(AssessAttackActivity(attacks, uniqueIpCount, configuration), ransomwareBeta);
string baseAlertState = baseAssessment.State;
string baseAlertReason = baseAssessment.Reason;
VulnerabilityCorrelationSummary vulnerabilityCorrelation = string.IsNullOrWhiteSpace(options.VulnerabilityCsvPath)
@@ -79,6 +80,7 @@ internal sealed class AttackScanner
BaseAlertState = baseAlertState,
BaseAlertReason = baseAlertReason,
VulnerabilityCorrelation = vulnerabilityCorrelation,
RansomwareBeta = ransomwareBeta,
Runtime = new ScanRuntimeMetadata
{
StartedAtUtc = startedAtUtc,
@@ -517,6 +519,27 @@ internal sealed class AttackScanner
return new AlertAssessment("ok", $"Low-volume login errors observed: events={attacks.Count}, unique IPs={uniqueIpCount}; no burst or password-spraying pattern detected.");
}
private static AlertAssessment MergeRansomwareAssessment(AlertAssessment loginAssessment, RansomwareBetaSummary ransomwareBeta)
{
if (ransomwareBeta.State is not ("warning" or "critical"))
{
return loginAssessment;
}
int loginPriority = AlertPriority(loginAssessment.State);
int ransomwarePriority = AlertPriority(ransomwareBeta.State);
string state = ransomwarePriority > loginPriority ? ransomwareBeta.State : loginAssessment.State;
string reason = $"{loginAssessment.Reason} {ransomwareBeta.Reason}";
return new AlertAssessment(state, reason);
}
private static int AlertPriority(string state) => state switch
{
"critical" => 2,
"warning" => 1,
_ => 0
};
private static int GetPeakEventCount(IReadOnlyList<AttackEvent> events, TimeSpan window)
{
int start = 0;