diff --git a/SysManager/SysManager/Services/AppBlockerService.cs b/SysManager/SysManager/Services/AppBlockerService.cs index eb6bf610..d839f87d 100644 --- a/SysManager/SysManager/Services/AppBlockerService.cs +++ b/SysManager/SysManager/Services/AppBlockerService.cs @@ -17,7 +17,7 @@ namespace SysManager.Services; /// effectively preventing it from running. Fully reversible by removing the key. /// Requires administrator privileges. /// -public sealed class AppBlockerService +public sealed partial class AppBlockerService { private const string IfeoPath = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options"; private const string BlockerDebugger = @"C:\Windows\System32\SysManager_Blocked.exe"; @@ -33,7 +33,7 @@ public static bool BlockApp(string exeName) exeName += ".exe"; // SEC-004: reject path separators and invalid chars to prevent registry path injection - if (!System.Text.RegularExpressions.Regex.IsMatch(exeName, @"^[A-Za-z0-9_\-. ]+\.exe$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)) + if (!ExeNamePattern().IsMatch(exeName)) { Log.Warning("Rejected invalid exeName: {ExeName}", exeName); return false; @@ -78,7 +78,7 @@ public static bool UnblockApp(string exeName) exeName += ".exe"; // SEC-004: apply same validation as BlockApp to prevent registry path injection - if (!System.Text.RegularExpressions.Regex.IsMatch(exeName, @"^[A-Za-z0-9_\-. ]+\.exe$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)) + if (!ExeNamePattern().IsMatch(exeName)) { Log.Warning("Rejected invalid exeName for unblock: {ExeName}", exeName); return false; @@ -190,4 +190,7 @@ public static IReadOnlyList GetBlockedApps() return blocked; } + + [System.Text.RegularExpressions.GeneratedRegex(@"^[A-Za-z0-9_\-. ]+\.exe$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)] + private static partial System.Text.RegularExpressions.Regex ExeNamePattern(); } diff --git a/SysManager/SysManager/Services/NetworkRepairService.cs b/SysManager/SysManager/Services/NetworkRepairService.cs index 17324a2d..45e9b909 100644 --- a/SysManager/SysManager/Services/NetworkRepairService.cs +++ b/SysManager/SysManager/Services/NetworkRepairService.cs @@ -10,7 +10,7 @@ namespace SysManager.Services; /// Runs common network repair commands: DNS flush, Winsock reset, TCP/IP reset. /// Each method captures stdout/stderr and returns a . /// -public class NetworkRepairService +public sealed class NetworkRepairService { private readonly PowerShellRunner _ps; diff --git a/SysManager/SysManager/Services/PerformanceService.cs b/SysManager/SysManager/Services/PerformanceService.cs index 1547c937..f05ada36 100644 --- a/SysManager/SysManager/Services/PerformanceService.cs +++ b/SysManager/SysManager/Services/PerformanceService.cs @@ -24,7 +24,7 @@ namespace SysManager.Services; /// • NVIDIA GPU subkey is auto-detected (not hardcoded to 0000). /// • Visual effects use SystemParametersInfo (instant), not registry-only. /// -public class PerformanceService +public sealed class PerformanceService { private readonly PowerShellRunner _ps; diff --git a/SysManager/SysManager/Services/PowerShellRunner.cs b/SysManager/SysManager/Services/PowerShellRunner.cs index 29bd6f30..263d9b3b 100644 --- a/SysManager/SysManager/Services/PowerShellRunner.cs +++ b/SysManager/SysManager/Services/PowerShellRunner.cs @@ -19,7 +19,7 @@ namespace SysManager.Services; /// same reason. Callers that use RunAsync or RunScriptViaPwshAsync must only pass /// hard-coded script strings; never pass user input directly as a script. /// -public class PowerShellRunner +public sealed class PowerShellRunner { public event Action? LineReceived; public event Action? ProgressChanged; // 0-100 diff --git a/SysManager/SysManager/Services/ServiceManagerService.cs b/SysManager/SysManager/Services/ServiceManagerService.cs index 97e9c4c0..946e167d 100644 --- a/SysManager/SysManager/Services/ServiceManagerService.cs +++ b/SysManager/SysManager/Services/ServiceManagerService.cs @@ -11,7 +11,7 @@ namespace SysManager.Services; /// Enumerates Windows services, provides gaming recommendations, and /// allows starting/stopping/changing startup type. All mutations require admin. /// -public class ServiceManagerService +public sealed partial class ServiceManagerService { /// /// Gaming-oriented recommendations for common Windows services. @@ -128,7 +128,7 @@ public static async Task SetStartupTypeAsync(string serviceName, string startTyp // hyphens, underscores, dots, and dollar signs only (covers all valid // Windows service names including instance names like MSSQL$INSTANCE). if (string.IsNullOrWhiteSpace(serviceName) || - !System.Text.RegularExpressions.Regex.IsMatch(serviceName, @"^[\w \-.$]+$")) + !ServiceNamePattern().IsMatch(serviceName)) throw new ArgumentException("Invalid service name.", nameof(serviceName)); var allowedTypes = new[] { "auto", "delayed-auto", "demand", "disabled" }; @@ -162,4 +162,7 @@ private static string GetServiceDescription(ServiceController sc) catch (System.Security.SecurityException) { return ""; } catch (UnauthorizedAccessException) { return ""; } } + + [System.Text.RegularExpressions.GeneratedRegex(@"^[\w \-.$]+$")] + private static partial System.Text.RegularExpressions.Regex ServiceNamePattern(); } diff --git a/SysManager/SysManager/Services/SystemInfoService.cs b/SysManager/SysManager/Services/SystemInfoService.cs index 68592c2f..da044320 100644 --- a/SysManager/SysManager/Services/SystemInfoService.cs +++ b/SysManager/SysManager/Services/SystemInfoService.cs @@ -10,7 +10,7 @@ namespace SysManager.Services; /// /// Collects system information via WMI / CIM (no PowerShell spawn needed). /// -public class SystemInfoService +public sealed class SystemInfoService { public Task CaptureAsync(CancellationToken ct = default) => Task.Run(() => Capture(), ct);