4 Commits

Author SHA1 Message Date
OfficeCom Codex
072a4257ed Report ransomware sensor coverage in beta
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 22s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 51s
2026-08-01 01:41:57 +02:00
OfficeCom Codex
47fe9448b7 Publish beta 1.5.0-beta.4 manifest
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 47s
2026-08-01 01:39:17 +02:00
OfficeCom Codex
fd990b698f Enable passive ransomware detection by default for beta
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 50s
2026-08-01 01:35:58 +02:00
OfficeCom Codex
d1bbd8838f Publish beta 1.5.0-beta.3 manifest
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 48s
2026-08-01 01:32:55 +02:00
10 changed files with 145 additions and 24 deletions

View File

@@ -10,7 +10,7 @@
"criticalSprayAccountCount": 10, "criticalSprayAccountCount": 10,
"correlationWarningCveThreshold": 1, "correlationWarningCveThreshold": 1,
"correlationCriticalCveThreshold": 1, "correlationCriticalCveThreshold": 1,
"ransomwareBetaEnabled": false, "ransomwareBetaEnabled": true,
"ransomwareBetaAlertingEnabled": false, "ransomwareBetaAlertingEnabled": false,
"ransomwareLookbackMinutes": 15, "ransomwareLookbackMinutes": 15,
"ransomwareWarningSignalCount": 2, "ransomwareWarningSignalCount": 2,

View File

@@ -19,12 +19,19 @@ Die Stable-Aufgabe verwendet keinen Kanalwert oder den Wert `stable`.
## Passive Ransomware-Beta ## Passive Ransomware-Beta
Die Beta ist nach der Installation weiterhin deaktiviert. Auf einem Die Ransomware-Beta ist im Beta-Kanal standardmaessig aktiviert. Die Auswertung
Pilotgeraet wird in `C:\Program Files\OCSentinel\config\ocsentinel-settings.json` bleibt passiv, solange `ransomwareBetaAlertingEnabled` auf `false` steht:
der Wert `ransomwareBetaEnabled` auf `true` gesetzt. Die Auswertung bleibt Hinweise, Warnungen und kritische Beta-Signale erscheinen im JSON-Report und
passiv, solange `ransomwareBetaAlertingEnabled` auf `false` steht: Hinweise, Dashboard, veraendern aber keine NinjaOne-Alarmfelder. Fuer eine lokale
Warnungen und kritische Beta-Signale erscheinen im JSON-Report und Dashboard, Ausnahme kann `ransomwareBetaEnabled` in
veraendern aber keine NinjaOne-Alarmfelder. `C:\Program Files\OCSentinel\config\ocsentinel-settings.json` auf `false`
gesetzt werden.
Jeder Beta-Report enthaelt zudem eine datensparsame Sensorabdeckung fuer
Security-Ereignis 4688, PowerShell 4104, Sysmon 1 und das optionale
Datei-Auditing 4663. Damit bedeutet ein Status `ok` nicht mehr stillschweigend,
dass alle Datenquellen vorhanden waren. Es werden nur Verfuegbarkeit,
technischer Zustand und gezaehlte Ereignisse uebertragen.
Der optionale Datei-Churn-Sensor wird nur mit Der optionale Datei-Churn-Sensor wird nur mit
`ransomwareFileChurnEnabled: true` aktiviert. Er wertet ausschliesslich bereits `ransomwareFileChurnEnabled: true` aktiviert. Er wertet ausschliesslich bereits

View File

@@ -126,6 +126,8 @@ Ein Pilot wird pausiert und zurueckgesetzt, wenn eines dieser Kriterien eintritt
- Snapshot der SMB-Sitzungen und des Incident-Kontexts erst bei einer - Snapshot der SMB-Sitzungen und des Incident-Kontexts erst bei einer
Auffaelligkeit. Auffaelligkeit.
- Verdichtetes Ransomware-Incident-Protokoll fuer n8n und die Uebersicht. - Verdichtetes Ransomware-Incident-Protokoll fuer n8n und die Uebersicht.
- Strukturierte Sensorabdeckung im Report, damit fehlende Audit- oder Sysmon-
Quellen von einem echten unauffaelligen Zeitraum unterscheidbar sind.
**Bewertung** **Bewertung**

View File

@@ -1,8 +1,8 @@
{ {
"channel": "beta", "channel": "beta",
"version": "1.5.0-beta.2", "version": "1.5.0-beta.4",
"publishedAtUtc": "2026-07-31T23:18:06.4787811Z", "publishedAtUtc": "2026-07-31T23:38:20.1674646Z",
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.5.0-beta.2/OCSentinelClient-win-x64.zip", "artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.5.0-beta.4/OCSentinelClient-win-x64.zip",
"sha256": "31dca46b50f95c1175293919177fed4370a58b7d279696d0d763a6b27b343684", "sha256": "f8e8714a4ea1426674bca27a403c33e166a31b38f77f0c638cb4a6a51b8f3343",
"minUpdaterVersion": "1.0.0" "minUpdaterVersion": "1.0.0"
} }

View File

@@ -112,6 +112,30 @@ function Get-OCSentinelArtifact {
} }
} }
function Enable-OCSentinelBetaDefaults {
param([Parameter(Mandatory)][string]$SettingsPath)
if (-not (Test-Path -LiteralPath $SettingsPath)) {
return
}
$settings = Get-Content -LiteralPath $SettingsPath -Raw | ConvertFrom-Json
if ($null -ne $settings.PSObject.Properties["ransomwareBetaDefaultApplied"]) {
return
}
if ($null -eq $settings.PSObject.Properties["ransomwareBetaEnabled"]) {
$settings | Add-Member -NotePropertyName "ransomwareBetaEnabled" -NotePropertyValue $true
}
else {
$settings.ransomwareBetaEnabled = $true
}
$settings | Add-Member -NotePropertyName "ransomwareBetaDefaultApplied" -NotePropertyValue "1.5.0-beta.4"
$settings | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $SettingsPath -Encoding UTF8
Write-Host "Enabled passive ransomware beta defaults."
}
Initialize-OCSentinelTls Initialize-OCSentinelTls
if ($ReleaseChannel -eq "stable" -and -not [string]::IsNullOrWhiteSpace($env:ReleaseChannel)) { if ($ReleaseChannel -eq "stable" -and -not [string]::IsNullOrWhiteSpace($env:ReleaseChannel)) {
@@ -133,6 +157,7 @@ $updaterPath = Join-Path $installRoot "scripts\update-ocsentinel.ps1"
$monitorPath = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1" $monitorPath = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
$appPath = Join-Path $installRoot "app\OCSentinelCli.exe" $appPath = Join-Path $installRoot "app\OCSentinelCli.exe"
$clientConfigPath = Join-Path $installRoot "config\ocsentinel-client.json" $clientConfigPath = Join-Path $installRoot "config\ocsentinel-client.json"
$settingsPath = Join-Path $installRoot "config\ocsentinel-settings.json"
$secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1" $secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat" $secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
@@ -214,6 +239,10 @@ if (-not (Test-Path -LiteralPath $appPath)) {
throw "OCSentinel installation completed, but the client executable was not found." throw "OCSentinel installation completed, but the client executable was not found."
} }
if ($ReleaseChannel -eq "beta") {
Enable-OCSentinelBetaDefaults -SettingsPath $settingsPath
}
if (-not [string]::IsNullOrWhiteSpace($WebhookUrl)) { if (-not [string]::IsNullOrWhiteSpace($WebhookUrl)) {
if (-not (Test-Path -LiteralPath $clientConfigPath)) { if (-not (Test-Path -LiteralPath $clientConfigPath)) {
throw "OCSentinel client configuration was not found: $clientConfigPath" throw "OCSentinel client configuration was not found: $clientConfigPath"

View File

@@ -26,7 +26,7 @@ internal sealed record ScannerConfiguration
public int CorrelationCriticalCveThreshold { get; init; } = 1; public int CorrelationCriticalCveThreshold { get; init; } = 1;
public bool RansomwareBetaEnabled { get; init; } public bool RansomwareBetaEnabled { get; init; } = true;
public bool RansomwareBetaAlertingEnabled { get; init; } public bool RansomwareBetaAlertingEnabled { get; init; }

View File

@@ -129,11 +129,26 @@ internal sealed record RansomwareBetaSummary
public List<RansomwareSignal> Signals { get; init; } = []; public List<RansomwareSignal> Signals { get; init; } = [];
public List<RansomwareSensorStatus> Sensors { get; init; } = [];
public List<RansomwareSmbSession> SmbSessions { get; init; } = []; public List<RansomwareSmbSession> SmbSessions { get; init; } = [];
public RansomwareFileChurnSummary FileChurn { get; init; } = new(); public RansomwareFileChurnSummary FileChurn { get; init; } = new();
} }
internal sealed record RansomwareSensorStatus
{
public string Name { get; init; } = string.Empty;
public bool Enabled { get; init; }
public bool Available { get; init; }
public string State { get; init; } = "unknown";
public int EventCount { get; init; }
}
internal sealed record RansomwareFileChurnSummary internal sealed record RansomwareFileChurnSummary
{ {
public bool Enabled { get; init; } public bool Enabled { get; init; }

View File

@@ -9,10 +9,10 @@
<RootNamespace>OCSentinelCli</RootNamespace> <RootNamespace>OCSentinelCli</RootNamespace>
<Product>OfficeCom Sentinel</Product> <Product>OfficeCom Sentinel</Product>
<Company>OfficeCom</Company> <Company>OfficeCom</Company>
<Version>1.5.0-beta.3</Version> <Version>1.5.0-beta.5</Version>
<AssemblyVersion>1.5.0.0</AssemblyVersion> <AssemblyVersion>1.5.0.0</AssemblyVersion>
<FileVersion>1.5.0.0</FileVersion> <FileVersion>1.5.0.0</FileVersion>
<InformationalVersion>1.5.0-beta.3</InformationalVersion> <InformationalVersion>1.5.0-beta.5</InformationalVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -18,11 +18,22 @@ internal static class RansomwareBetaDetector
int lookbackMinutes = Math.Clamp(configuration.RansomwareLookbackMinutes, 1, 60); int lookbackMinutes = Math.Clamp(configuration.RansomwareLookbackMinutes, 1, 60);
DateTimeOffset since = DateTimeOffset.UtcNow.AddMinutes(-lookbackMinutes); DateTimeOffset since = DateTimeOffset.UtcNow.AddMinutes(-lookbackMinutes);
var signals = new List<RansomwareSignal>(); var signals = new List<RansomwareSignal>();
var sensors = new List<RansomwareSensorStatus>
{
ScanSecurityProcesses(signals, errors, since, configuration),
ScanPowerShellScriptBlocks(signals, errors, since, configuration),
ScanSysmonProcesses(signals, errors, since, configuration)
};
ScanSecurityProcesses(signals, errors, since, configuration);
ScanPowerShellScriptBlocks(signals, errors, since, configuration);
ScanSysmonProcesses(signals, errors, since, configuration);
RansomwareFileChurnSummary fileChurn = RansomwareFileChurnDetector.Scan(configuration, errors); RansomwareFileChurnSummary fileChurn = RansomwareFileChurnDetector.Scan(configuration, errors);
sensors.Add(new RansomwareSensorStatus
{
Name = "security-file-audit-4663",
Enabled = fileChurn.Enabled,
Available = fileChurn.DataAvailable,
State = !fileChurn.Enabled ? "disabled" : fileChurn.IsTruncated ? "truncated" : fileChurn.DataAvailable ? "available" : "unavailable",
EventCount = fileChurn.FileOperationCount
});
RansomwareSignal? fileChurnSignal = RansomwareFileChurnDetector.CreateSignal(fileChurn, configuration); RansomwareSignal? fileChurnSignal = RansomwareFileChurnDetector.CreateSignal(fileChurn, configuration);
if (fileChurnSignal is not null) if (fileChurnSignal is not null)
{ {
@@ -61,24 +72,25 @@ internal static class RansomwareBetaDetector
Reason = reason, Reason = reason,
LookbackMinutes = lookbackMinutes, LookbackMinutes = lookbackMinutes,
Signals = distinctSignals, Signals = distinctSignals,
Sensors = sensors,
SmbSessions = smbSessions, SmbSessions = smbSessions,
FileChurn = fileChurn FileChurn = fileChurn
}; };
} }
private static void ScanSecurityProcesses(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration) private static RansomwareSensorStatus ScanSecurityProcesses(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration)
{ {
TryScan("Security", 4688, since, errors, record => AddSignal(signals, record, ReadProperty(record, 5), ReadProperty(record, 8), "Security", configuration)); return TryScan("security-process-4688", "Security", 4688, since, errors, record => AddSignal(signals, record, ReadProperty(record, 5), ReadProperty(record, 8), "Security", configuration));
} }
private static void ScanPowerShellScriptBlocks(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration) private static RansomwareSensorStatus ScanPowerShellScriptBlocks(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration)
{ {
TryScan("Microsoft-Windows-PowerShell/Operational", 4104, since, errors, record => AddSignal(signals, record, "powershell", FormatDescription(record), "PowerShell", configuration)); return TryScan("powershell-script-block-4104", "Microsoft-Windows-PowerShell/Operational", 4104, since, errors, record => AddSignal(signals, record, "powershell", FormatDescription(record), "PowerShell", configuration));
} }
private static void ScanSysmonProcesses(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration) private static RansomwareSensorStatus ScanSysmonProcesses(List<RansomwareSignal> signals, List<string> errors, DateTimeOffset since, ScannerConfiguration configuration)
{ {
TryScan("Microsoft-Windows-Sysmon/Operational", 1, since, errors, record => AddSignal(signals, record, "sysmon-process", FormatDescription(record), "Sysmon", configuration)); return TryScan("sysmon-process-1", "Microsoft-Windows-Sysmon/Operational", 1, since, errors, record => AddSignal(signals, record, "sysmon-process", FormatDescription(record), "Sysmon", configuration));
} }
private static void AddSignal(List<RansomwareSignal> signals, EventRecord record, string process, string commandLine, string source, ScannerConfiguration configuration) private static void AddSignal(List<RansomwareSignal> signals, EventRecord record, string process, string commandLine, string source, ScannerConfiguration configuration)
@@ -146,8 +158,9 @@ internal static class RansomwareBetaDetector
private static bool ContainsAll(string value, params string[] needles) => needles.All(needle => value.Contains(needle, StringComparison.Ordinal)); private static bool ContainsAll(string value, params string[] needles) => needles.All(needle => value.Contains(needle, StringComparison.Ordinal));
private static void TryScan(string logName, int eventId, DateTimeOffset since, List<string> errors, Action<EventRecord> processRecord) private static RansomwareSensorStatus TryScan(string sensorName, string logName, int eventId, DateTimeOffset since, List<string> errors, Action<EventRecord> processRecord)
{ {
int eventCount = 0;
try try
{ {
long milliseconds = Math.Max(1, (long)(DateTimeOffset.UtcNow - since).TotalMilliseconds); long milliseconds = Math.Max(1, (long)(DateTimeOffset.UtcNow - since).TotalMilliseconds);
@@ -157,16 +170,39 @@ internal static class RansomwareBetaDetector
{ {
using (record) using (record)
{ {
eventCount++;
processRecord(record); processRecord(record);
} }
} }
return new RansomwareSensorStatus
{
Name = sensorName,
Enabled = true,
Available = true,
State = "available",
EventCount = eventCount
};
} }
catch (EventLogNotFoundException) catch (EventLogNotFoundException)
{ {
return new RansomwareSensorStatus
{
Name = sensorName,
Enabled = true,
State = "not-installed"
};
} }
catch (Exception exception) catch (Exception exception)
{ {
errors.Add($"Ransomware beta query failed for {logName}: {exception.Message}"); errors.Add($"Ransomware beta query failed for {logName}: {exception.Message}");
return new RansomwareSensorStatus
{
Name = sensorName,
Enabled = true,
State = "query-failed",
EventCount = eventCount
};
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text.Json;
using Xunit; using Xunit;
namespace OCSentinelCli.Tests; namespace OCSentinelCli.Tests;
@@ -6,6 +7,37 @@ namespace OCSentinelCli.Tests;
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public sealed class RansomwareBetaTests public sealed class RansomwareBetaTests
{ {
[Fact]
public void RansomwareBetaIsEnabledByDefault()
{
Assert.True(new ScannerConfiguration().RansomwareBetaEnabled);
Assert.False(new ScannerConfiguration().RansomwareBetaAlertingEnabled);
}
[Fact]
public void DisabledRansomwareBetaDoesNotReportSensorCoverage()
{
var summary = RansomwareBetaDetector.Scan(new ScannerConfiguration { RansomwareBetaEnabled = false }, []);
Assert.False(summary.Enabled);
Assert.Empty(summary.Sensors);
}
[Fact]
public void SensorCoverageIsIncludedInTheJsonReport()
{
var summary = new RansomwareBetaSummary
{
Enabled = true,
Sensors = [new RansomwareSensorStatus { Name = "security-process-4688", Enabled = true, Available = true, State = "available", EventCount = 4 }]
};
string json = JsonSerializer.Serialize(summary, JsonOptions.Default);
Assert.Contains("\"Sensors\"", json);
Assert.Contains("security-process-4688", json);
}
[Fact] [Fact]
public void FileChurnBelowBothThresholdsDoesNotCreateSignal() public void FileChurnBelowBothThresholdsDoesNotCreateSignal()
{ {