github aelassas/servy v8.5
Servy 8.5

7 hours ago

Servy 8.5 focuses on stability, security, and maintainability, with a large number of fixes across all components and full native support for ARM64. The full changelog is available below.

Full Changelog

Click to expand release notes!
  • feat: provide ARM64 binaries (#2243)
  • fix: Wrapper executable not found (#4134)
  • fix: Cannot remove service with status "Not Installed" - Uninstall button greyed out (#2904)
  • fix(core): XmlServiceValidator.TryValidate / JsonServiceValidator.TryValidate - Compares string.Length (chars) against MaxConfigFileSizeBytes (bytes), allowing 2-3x oversized payloads with multibyte UTF-8 content (#1441)
  • fix(core): AppConfig.cs - Doc comment references non-existent constant 'MaxBulkOperationParallelism' (actual is 'DefaultMaxBulkOperationParallelism') (#1445)
  • fix(core): AppConfig.cs - MaxPreLaunchRetryAttempts = int.MaxValue is inconsistent with bounded MaxMaxRestartAttempts (100_000); allows unbounded retry loops (#1446)
  • fix(core): AppConfig.cs - Confusingly similar names: DefaultStopTimeout (5s) vs DefaultServiceStopTimeoutSeconds (60s) (#1448)
  • fix(core): ProcessKiller.cs - parentProcess.WaitForExit return value ignored; inconsistent with KillProcessTree/WalkAndKillChildren which log on timeout (#1449)
  • fix(core): ResourceHelper.cs - ShouldCopyResource XML doc lists 'targetFileName' as out parameter, but the actual signature has no such parameter (#1453)
  • fix(core): Helper.IsServiceNameValid - missing length check inconsistent with ServiceValidationRules.Validate (allows long names through start/stop/uninstall paths) (#1457)
  • fix(core): Logger.FormatException - 'truncation to LoggerMaxFormattedExceptionLength' overshoots by 14+ chars (writes '... [truncated]' AFTER setting Length) (#1458)
  • fix(core): AppConfig.cs - Static constructor invariant check between two const ints is dead code (compile-time folded) (#1460)
  • fix(core): ServiceHelper.CalculateStopTimeout - Math.Max(total, floor) is always 'total' (dead floor guard) (#1462)
  • fix(core): Logger.Log() - log entry timestamps reuse '_useLocalTimeForRotation' flag, conflating rotation policy with log formatting (#1464)
  • fix(core): Helper.Quote() - whitespace-only inputs are coerced to empty quotes; literal-whitespace arguments cannot be passed (#1465)
  • fix(core): SecureData.Encrypt - plainBytes allocated outside try, not zeroed on early failure (Aes.Create / aes.Key throws) (#1488)
  • fix(core): StringHelper.cs - unused 'using System.Text.RegularExpressions' import (#1490)
  • fix(core): ProtectedKeyProvider.cs - defensive 'encrypted is null' branch throws FileNotFoundException with a 'failed to read' message (#1501)
  • fix(core): ServiceHelper.StartServices - Math.Max(startTimeout, DefaultServiceStartTimeoutSeconds) is dead floor (same family as #1462) (#1502)
  • fix(core): SecureData.Decrypt - 'Returning original input' log message contradicts the immediately-thrown SecureDataIntegrityException (#1509)
  • fix(core): ProtectedKeyProvider.GetOrGenerate - inconsistent retry backoff between IOException (exponential) and UnauthorizedAccessException (fixed 100ms) (#1512)
  • fix(core): AppConfig.AllowLegacyV1Decryption - documented as a runtime migration toggle but is a static-readonly hard-coded false (no runtime override) (#1517)
  • fix(core): AppConfig.ResolveExe - XML doc claims it throws FileNotFoundException but the implementation never throws (#1518)
  • fix(core): Helper.WriteFileAtomic / WriteFileAtomicCore - duplicated 'retries = 3' and '100ms backoff' magic numbers across sync and async (#1522)
  • fix(core): AppConfig.cs - TargetFramework field throws unconditionally even though it is only consumed inside #if DEBUG blocks (#1525)
  • fix(core): SecureData.Decrypt - legacy fallback path emits two redundant 'returning as plaintext' warnings on a single decrypt failure (#1526)
  • fix(core): ProcessKiller.KillParentProcesses - parent Process handle opened twice (once for StartTime, then re-opened to Kill) (#1527)
  • fix(core): ProcessKiller.WalkAndKillChildren / KillParentProcesses - generic Logger.Warn drops the Exception (no stack), inconsistent with Logger.Error sites in the same file (#1528)
  • fix(core): StringHelper.cs - XML doc cref points to wrong namespace 'Servy.Core.Security.EnvironmentVariableParser' (#1534)
  • fix(core): ServiceValidationRules.Validate - service-dependency errors are joined into one Errors entry while every other check adds individual strings (#1547)
  • fix(core): RotatingStreamWriter.EnforceMaxRotations - 'consecutiveDeletionFailures >= 10' threshold duplicated as a magic literal across the enumerate-failure and delete-failure paths (#1555)
  • fix(core): RotatingStreamWriter.TripCircuitBreaker - 'CooldownMs / 60000' inline magic number for ms->minutes conversion in error message (same family as #1549) (#1556)
  • fix(core): Logger.cs fallback log entries - inconsistent timestamp format vs. main log (no TZ marker) (#1559)
  • fix(core): NativeMethods.ValidateCredentials - 'if (username.Contains("\"))' is dead-conditional after regex pre-check, plus dead initialisations of 'domain'/'user' (#1563)
  • fix(core): NativeMethods.ValidateCredentials - generic catch wraps Exception in SecurityException without preserving inner exception (#1575)
  • fix(core): ProcessHelper.ResolvePath - throws InvalidOperationException but IProcessHelper XML doc declares ArgumentException (#1583)
  • fix(core): Servy.Core Helper.IsValidPath / Helper.CreateParentDirectory - bare 'catch { return false; }' silently swallows IO/Access errors with no telemetry (same family as #1519) (#1586)
  • fix(core): ServiceDependenciesValidator.Validate - error message '[ServiceDependencies] Invalid service name...' hardcoded in English; inconsistent with localized Strings.Msg_* in sibling validators (#1592)
  • fix(core): XmlServiceValidator vs JsonServiceValidator - asymmetric parse-failure logging and inconsistent error wording across symmetric API pair (#1595)
  • fix(core): ServiceDto.cs - ShouldSerialize methods redundant for properties already marked [JsonIgnore] + [XmlIgnore] (#1597)
  • fix(core): SecureData ctor - second HKDF.DeriveKey throw leaves _v2EncryptionKey unzeroed in half-constructed instance (#1599)
  • fix(core): ServiceValidationRules.Validate - Description-too-long uses Warnings while every other length check uses Errors (#1600)
  • fix(core): ServiceValidationRules.Validate - paramFields length error doesn't identify which of 6 parameters exceeded the limit (#1601)
  • fix(core): AppConfig.RecoverySchedulingDelay - missing 'Ms' suffix on a milliseconds constant, inconsistent with every other Ms-valued constant (#1603)
  • fix(core): ServiceManager.InstallServiceAsync - Math.Max(total, floor) dead floor on totalWaitTime (same family as #1462) (#1604)
  • fix(core): ServiceManager.StartServiceAsync - Math.Max(timeout, floor) dead floor (same family as #1462) (#1605)
  • fix(core): ServiceManager.InstallServiceAsync - DeleteService rollback return value ignored on pre-shutdown/delayed-autostart failure (#1606)
  • fix(core): ServiceManager.UninstallServiceAsync - ControlService(STOP) return value ignored, masking SCM stop-command failures (#1607)
  • fix(core): EventLogLogger.SetIsEventLogEnabled - non-atomic _eventLog.Dispose()+null sequence racing with concurrent WriteEntry causes ObjectDisposedException (#1615)
  • fix(core): ResourceHelper.CopyEmbeddedResource - returns success even when StartServices throws in finally; caller cannot detect stopped-and-not-restarted services (#1616)
  • fix(core): Servy.psm1 / AppConfig.cs - 'SERVY_PASSWORD' env var name duplicated as a string literal in PowerShell and C# (same family as #1396) (#1631)
  • fix(core): HandleHelper.GetProcessesUsingFile - unbounded WaitForExit() after a failed Kill can hang the caller indefinitely (#1634)
  • fix(core): EventLogReader.MapToDto - garbled XML doc comment 'as as local on this OS' (duplicate 'as') (#1635)
  • fix(core): EventLogReader.ParseLevel - Critical (1) silently collapsed to Error with no diagnostic (#1636)
  • fix(core): NativeMethods.AtomicSecureMove - ERROR_NOT_SAME_DEVICE (0x11) inline magic number; Errors.cs is the central location (#1637)
  • fix(core): NativeMethods.GetFileIdentity - 4KB prefix-digest false-negatives on files with identical headers (e.g. rotated logs with same log prologue) (#1638)
  • fix(core): ProcessKiller.KillChildren - bypasses ancestor protection; descendants matching the Servy process chain can be killed (#1639)
  • fix(core): ProcessKiller - '.exe' stripping logic duplicated three times with subtle skip in KillProcessesUsingFile (#1640)
  • fix(core): EnvironmentVariableParser / EnvironmentVariablesValidator - delimiter array { ';', '\r', '\n' } duplicated between parser and validator (#1642)
  • fix(core): ServiceControllerWrapper.BuildDependencyTree - ServicesDependedOn[] disposed twice on happy path (loop + finally) (#1643)
  • fix(core): mlServiceValidator.TryValidate / JsonServiceValidator.TryValidate - 90% identical logic; only parser differs (#1645)
  • fix(core): ProtectedKeyProvider - 'try { EventLog.WriteEntry... } catch (Exception) { Logger.Debug... }' block duplicated 3 times (#1646)
  • fix(core): LoggerConfigurator.ConfigureFromAppSettings - inconsistent warning logging: LogLevel parse failure warns, every other field silently falls back (#1650)
  • fix(core): Helper.IsRunningUnderXunit - public static method with zero callers in src/ and tests/ (dead code) (#1655)
  • fix(core): ProcessKiller.KillParentProcesses - hardcoded '2' for PID-reuse tolerance instead of AppConfig.PidReuseToleranceSeconds (inconsistent with line 122 in same file) (#1659)
  • fix(core): ResourceHelper.CopyEmbeddedResource - currentResourceName / currentTargetPath locals assigned but never read (dead variables) (#1660)
  • fix(core): HandleHelper.GetProcessesUsingFile - inline magic 2000ms drain timeout inconsistent with AppConfig.HandleExeTimeoutMs/HandleExeRegexTimeout used in the same method (#1662)
  • fix(core): ServiceDtoHelper.ApplyDefaults - name and XML doc hide that RunAsLocalSystem/UserAccount/Password are unconditionally overwritten (hidden side-effect) (#1663)
  • fix(core): ValidationResult.IsValid - contradicts its own XML doc: returns false on any Warning even though docs state warnings 'may not block the operation' (#1664)
  • fix(core): Logger.cs - dead null-conditional access on message after earlier null-empty guard (#1665)
  • fix(core): ProtectedKeyProvider.cs - Migration warning logs use inconsistent EventIds across file log and Windows Event Log (#1666)
  • fix(core): AppConfig.cs - MaxConfigFileSizeBytes recomputes the MB-to-bytes factor instead of using BytesInMegabyte (#1667)
  • fix(core): AppConfig.cs - leftover '// MaxConsoleMaxLines already exists in AppConfig' placeholder comment (#1668)
  • fix(core): ServiceManager.cs - delayed auto-start rollback doesn't check DeleteService return value (inconsistent with pre-shutdown rollback) (#1685)
  • fix(core): Helper.cs - WriteFileAtomicCore async retry loop missing CancellationToken check that the sync version has (#1695)
  • fix(core): ProtectedKeyProvider.cs - multi-process race during first run can produce divergent keys (#1701)
  • fix(core): ProcessKiller.cs - KillProcessTreeAndParents(string) aborts all kills if any Process.ProcessName access throws Win32Exception (#1705)
  • fix(core): ProcessKiller.cs - KillChildren skips PID-reuse identity check on first-level children (parentStartTime = DateTime.MinValue) (#1706)
  • fix(core): NativeMethods.cs - class-wide [ExcludeFromCodeCoverage] hides coverage for ValidateCredentials/AtomicSecureMove/GetFileIdentity logic (#1710)
  • fix(core): Helper.cs - EscapeArgs and EscapeBackslashes are ~90% duplicate; extract a shared core (#1712)
  • fix(core): Logger.cs - FormatException truncation skips closing brackets, leaving unmatched [Inner -> markers (#1716)
  • fix(core): Logger.cs - _initFallbackWriteCount / _logFallbackWriteCount never reset; long-running services go silent forever after 10 lifetime failures (#1717)
  • fix(core): SecureData.cs - DecryptV2 HMAC failures bypass Logger.Error in catch filter (#1723)
  • fix(core): HandleHelper.cs - GetProcessesUsingFile ignores handle.exe ExitCode; non-zero exits silently yield an empty list (#1726)
  • fix(core): AppConfig.cs - hardcoded Version string '8.5' duplicates Servy.Core.csproj Version/AssemblyVersion and will drift after release bumps (#1732)
  • fix(core): Logger.cs - log timestamp format 'yyyy-MM-dd HH:mm:ss' lacks milliseconds; events within same second cannot be ordered (#1736)
  • fix(core): ServiceValidationRules.cs - duplicate Name-length check is unreachable dead code (#1737)
  • fix(core): AppConfig.cs - static-ctor invariant check throws TypeInitializationException; better expressed as a unit test (#1739)
  • fix(core): Helper.cs - NormalizePath trims drive-root backslash ('C:' → 'C:'), changing semantics, and ignores AltDirectorySeparatorChar (#1744)
  • fix(core): HandleHelper.cs - RegexMatchTimeoutException is swallowed, returning silent partial process list (#1745)
  • fix(core): Helpers/ServiceHelper.cs - GetRunningServyServices enumerates SCM and registry twice (#1751)
  • fix(core): ProcessKiller.cs - KillProcessTreeAndParents(string) duplicates work by calling Process.GetProcesses() after already building a Toolhelp32 snapshot (#1753)
  • fix(core): EventLogService.cs / EventLogReader.cs - Error filter silently includes Critical events while comment claims they are collapsed (#1754)
  • fix(core): [wiki] Security - Subdirectory list mentions a 'recovery' folder that the code never creates (#1755)
  • fix(core): ResourceHelper.cs - GetHostProcessLastWriteTimeUTC logs Debug on dual probe failure, silently disabling resource re-extraction (#1759)
  • fix(core): ServiceHelper.cs - StartServices timeout formula is inconsistent with CalculateStopTimeout; should be extracted into a symmetric helper (#1763)
  • fix(core): ProcessHelper.cs - GetProcessTree has no parent-PID start-time check; metrics can include unrelated descendants of recycled PIDs (#1770)
  • fix(core): EventLogLogger.cs - SourceExists matches ALL logs; existing 'Servy' source in a different log silently redirects events away from Application (#1773)
  • fix(core): ServiceDtoImportValidator.cs - TException=Exception in both JSON/XML subclasses makes the catch-all unreachable and erases the intended parser-vs-unexpected error split (#1784)
  • fix(core): Helper.cs / SecurityHelper.cs - duplicate, divergent reserved Windows device-name lists (#1798)
  • fix(core): Helper.cs - HasAncestorReparsePoint throws NullReferenceException when passed a root-drive path (#1799)
  • fix(core): NativeMethodsHelpers.cs - PrefixDigest embeds fs.Length, causing false-positive rotation detection on every poll for actively growing logs on FAT32/SMB (#1806)
  • fix(core): ProtectedKeyProvider.cs - AbandonedMutexException is not caught; service fails to start after any process is killed while holding the key-generation mutex (#1808)
  • fix(core): ProcessHelper.cs - GetProcessTree leaks Win32Exception from Process.StartTime, crashing metrics collection on permission-denied process queries (#1813)
  • fix(core): ResourceHelper.cs - CopyEmbeddedResource logs 'successfully copied' in the restart-failure branch even when the copy itself was aborted by TerminateBlockingProcesses (#1817)
  • fix(core): Service.cs - Stop is the only virtual public method; Start/Restart/Install/Uninstall asymmetrically sealed (#1818)
  • fix(core): Helper.cs - IsServiceNameValid uses Msg_ServiceNameContainsTrailingWhitespace for both leading AND trailing whitespace (#1819)
  • fix(core): ServiceManager.cs - InstallServiceAsync calls GetByNameAsync with decrypt:true but only reads Pid and PreviousStopTimeout; credentials are never used (preserveExistingCredentials:false) (#1820)
  • fix(core): Helper.cs - WriteFileAtomic / WriteFileAtomicCore swallow retry exceptions silently; root cause is lost when retries are exhausted (#1821)
  • fix(core): ServiceManager.cs - InstallServiceAsync leaves a partially-installed SCM service if SetServiceDescription or UpsertAsync throws after CreateService succeeds (no rollback) (#1823)
  • fix(core): InstallServiceOptions.cs - three booleans typed as bool? (EnableConsoleUI, RecoveryOnCleanExit, PreStopLogAsError) while the other six are bool, with no documented reason (#1824)
  • fix(core): IServiceRepository.cs - UpsertBatchAsync xmldoc claims 'NOT executed within an atomic transaction', but implementation wraps the batch in BeginTransaction/Commit (#1826)
  • fix(core): ServiceControllerWrapper.cs - '(Unavailable)' / '(Access Denied)' display-name suffixes are hardcoded English, shown directly in the Manager dependency tree (#1828)
  • fix(core): Helper.cs - HasAncestorReparsePoint silently returns false when target's immediate parent doesn't exist, bypassing reparse-point security guard (#1829)
  • fix(core): ImportServiceCommand.cs - ?\UNC\ resolved-path prefix bypasses the post-handle UNC guard (#1837)
  • fix(core): ExportServiceCommand.cs - UNC exfiltration guards do not cover subst / DefineDosDevice mappings to remote shares (#1838)
  • fix(core): ServiceHelper.cs + ServiceManager.cs - three batch-loop call sites pay for unnecessary GetByNameAsync decryption (#1839)
  • fix(core): Helper.cs - IsServiceNameValid reserved-name check is bypassed when service name starts with '.' (#1840)
  • fix(core): StringHelper.cs - FormatServiceDependencies returns null while sibling FormatEnvironmentVariables always returns non-null, forcing every caller to defend with '?? string.Empty' (#1842)
  • fix(core): Helper.cs - IsValidPath uses Path.IsPathRooted, accepts drive-relative paths like '\foo\log.txt' as 'absolute' (#1849)
  • fix(core): Helper.cs - GetUniqueTempPath appends 37 chars; near-MAX_PATH destinations throw PathTooLongException on first write (#1850)
  • fix(core): ResourceHelper.cs - TerminateBlockingProcesses kills locking processes (and stops Servy services) BEFORE checking the embedded resource exists (#1851)
  • fix(core): ProtectedKeyProvider.cs - Global\ Mutex created without explicit DACL; SYSTEM service and user Manager fall into separate Local\ mutexes and can race key generation (#1854)
  • fix(core): NativeMethodsHelpers.cs - ValidateCredentials rejects valid service accounts via LOGON32_LOGON_NETWORK when network logon is policy-denied (#1855)
  • fix(core): EventLogLogger.cs - ScopedEventLogLogger.CreateScoped loses intermediate scope's prefix when scopes are nested (#1859)
  • fix(core): RotatingStreamWriter.cs - disabled-rotation oversize warning is emitted on EVERY write while the circuit breaker is tripped (#1865)
  • fix(core): LogTailer.cs - ReadLineAsync called without CancellationToken; tailer can't be cancelled while a slow-network/locked-file read is in flight (#1867)
  • fix(core): ProcessKiller.cs - KillParentProcesses recurses UP before verifying the parent's identity; a PID-recycled immediate parent leaks the walk into unrelated ancestors (#1870)
  • fix(core): Helper.cs - EscapeArgs xmldoc claims null-or-whitespace input returns empty string, but code only checks IsNullOrEmpty (#1872)
  • fix(core): AppConfig.cs / HelpService.cs - Version uses ToString(2), causes false 'update available' prompt when assembly has a Build component (#1874)
  • fix(core): ProcessKiller.cs - KillProcessTree reads process.StartTime unguarded; the entire tree kill is silently aborted on access-denied (#1877)
  • fix(core): ServiceManager.cs - UpdateServiceConfig always returns true or throws; bool return type hides the contract, caller's '!updated' branch is dead code (#1880)
  • fix(core): SecureData.cs - Decrypt xmldoc says 'returns the original cipherText' on failure, but actually throws SecureDataIntegrityException for marked payloads and raw-Base64 with legacy disabled (#1882)
  • fix(core): EventLogReader.cs - ParseLevel(0) logs a spurious 'Unknown event log level' warning; level 0 is the documented LogAlways severity (#1890)
  • fix(core): IProcessKiller.cs - KillProcessesUsingFile xmldoc states a hardcoded 'C:\Program Files\Sysinternals\handle64.exe' path that no longer matches the implementation (#1891)
  • fix(core): EventLogService.cs - provider heuristic filter is dead in wildcard mode; redundant in normal mode (#1893)
  • fix(core): ProcessMetrics.cs - RamUsage xmldoc claims 'Private Working Set' but ProcessHelper reads PrivateMemorySize64 (private bytes / commit) (#1894)
  • fix(core): ProtectedKeyProvider.cs - SaveProtected compares the current user's SID against BuiltinAdministratorsSid (a group SID), so the guard never matches and the user ACL is always added (#1897)
  • fix(core): ResourceHelper.cs - GetHostProcessLastWriteTimeUTC silently returns DateTime.MinValue when MainModule is null (no exception), skipping AppDomain fallback (#1903)
  • fix(core): StringHelper.cs - FormatEnvironmentVariables does not escape literal CR/LF in values, breaking round-trip through Parse (#1902)
  • fix(core): rotectedKeyProvider.cs - 'Exponential backoff: 100ms, 200ms, 400ms' comment is misleading; the 400ms sleep never executes (#1904)
  • fix(core): NativeMethodsHelpers.cs - ValidateCredentials trims username but not domain; whitespace in DOMAIN\User splits leak into LogonUser (#1905)
  • fix(core): ProcessHelper.cs - GetProcessMetrics evicts CPU sample on ArgumentException but not on InvalidOperationException (process-exited mid-call), causing stale baseline after PID reuse (#1914)
  • fix(core): Servy.Core/Helpers/StringHelper.cs vs EscapedTokenizer.cs - Escape emits letter sequences \r/\n, but Unescape only recognizes literal CR/LF bytes; CR/LF in env-var values is lost on round-trip (#1920)
  • fix(core): RotatingStreamWriter.cs - CloseWriter abandons _writer/_baseStream if Flush throws, leaking handles and wedging future writes/Dispose (#1928)
  • fix(core): HandleHelper.cs - 'No matching handles found' English string match is fragile; locale or future Sysinternals rewording silently fails the operation (#1931)
  • fix(core): ServiceExporter.cs - ExportJson(file) buffers the full JSON to a string before writing, while ExportXml(file) streams directly (#1932)
  • fix(core): StringHelper.cs - NormalizeString silently merges adjacent env vars when a value ends with an unescaped backslash (#1934)
  • fix(core): ResourceHelper.cs - CopyEmbeddedResource leaks the embedded resource Stream if the pre-using service-enumeration step throws (#1935)
  • fix(core): Helper.cs - IsServiceNameValid calls segment.ToUpperInvariant() before lookup in a case-insensitive HashSet (#1938)
  • fix(core): ProcessKiller.cs - WalkAndKillChildren reads child.StartTime without SafeStartTime guard; access-denied throws abort the whole descendant subtree (#1945)
  • fix(core): ServiceDto.cs - ShouldSerializeDisplayName missing; null DisplayName appears in exports while sibling Description is suppressed (#1949)
  • fix(core): HandleHelper.cs - HandleOutputRegex.IsMatch is not wrapped in try/catch for RegexMatchTimeoutException, while Matches() is (#1953)
  • fix(core): AppFoldersHelper.cs - EnsureFolders self-skip compares normalized folder against raw AppConfig.ProgramDataPath (#1955)
  • fix(core): EventLogReader.cs - ParseLevel xmldoc claims it 'prevents information loss' but body folds Critical (level 1) into Error (#1956)
  • fix(core): ServiceHelper.cs - malformed 'bypasses prevents' xmldoc + asymmetric catch-all error wording between StartServices/StopServices (#1957)
  • fix(core): Logger.cs - FormatException leaves unbalanced brackets on truncation (#1959)
  • fix(core): RotatingStreamWriter.cs - EnforceMaxRotations orders by local time, ignoring useLocalTimeForRotation (#1960)
  • fix(core): RotatingStreamWriter.cs - _consecutiveDeletionFailures incremented/reset outside the lock (#1961)
  • fix(core): SecureData.cs - HKDF info parameters allocated per constructor call (#1965)
  • fix(core): ProtectedKeyProvider.cs - GenerateRandomBytes can use static RandomNumberGenerator.Fill (#1966)
  • fix(core): EscapedTokenizer.cs - O(n^2) backward scan on every delimiter check (#1970)
  • fix(core): StringHelper.NormalizeString - round-trip data loss for env-var values ending in backslash or containing newlines (#1972)
  • fix(core): ImportGuard.ValidatePathSecurity - TOCTOU between validation handle-open and caller's File.ReadAllTextAsync (#1976)
  • fix(core): Helper.WriteFileAtomic - synchronous retry path uses Thread.Sleep that ignores the CancellationToken (#1980)
  • fix(core): AppConfig.cs - misleading remarks claim 'static readonly' but fields are 'const'; runtime invariant check is non-existent (#1981)
  • fix(core): Helper.cs - Canonicalise and NormalizePath diverge on drive-root semantics, risking comparison bugs (#1983)
  • fix(core): ProcessKiller.cs - '.exe' suffix stripping is duplicated in three places (#1984)
  • fix(core): ServiceManager.cs - GetServiceDescription and GetServiceUser silently return null on Win32 errors other than ERROR_INSUFFICIENT_BUFFER (#1985)
  • fix(core): RotatingStreamWriter.cs - Monitor.Wait inside WriteInternal has no timeout; a stuck rotation hangs all writers indefinitely (#1988)
  • fix(core): Magic Win32 error codes 31, 233, 1326 used inline instead of Errors.cs constants (#1990)
  • fix(core): Logger.cs - FormatException sb.Length truncation can split UTF-16 surrogate pairs and corrupt log output (#1993)
  • fix(core): Logger.cs - second Initialize() overload silently drops settings if called after Shutdown() (#1999)
  • fix(core): EventLogLogger.cs - nested CreateScoped produces double-bracketed prefix (e.g. '[[A] [B]] message') (#2000)
  • fix(core): ImportGuard.ValidatePathSecurity - Reserved-device-name check rejects valid filenames where 'CON'/'NUL'/etc. appears as a non-leading segment (e.g. 'prefix.NUL.json') (#2006)
  • fix(core): Helper.cs - WriteFileAtomicAsync never propagates the CancellationToken into the writer delegate (#2011)
  • fix(core): SecurityHelper.ApplySecurityRules - PurgeAccessRules also removes Deny rules for Users/Authenticated Users/Everyone (#2012)
  • fix(core): ProcessKiller.cs - PID-reuse temporal check is silently bypassed when StartTime cannot be queried (#2013)
  • fix(core): ConfigParser.ParseEnum - Enum.IsDefined rejects valid combined values, silently breaking any future [Flags] enum (#2014)
  • fix(core): ServiceManager.InstallServiceAsync - existing-service update path returns Failure on EnablePreShutdown but the description, start-type, and delayed-auto-start have already been mutated (#2054)
  • fix(core): Logger - RotatingStreamWriter - re-entrant rotation deadlock: Logger.Warn inside PerformPhysicalRotation re-enters the same writer and blocks forever on Monitor.Wait (#2055)
  • fix(core): AppFoldersHelper.EnsureFolders - XML doc lists 'logs' subfolder but code secures 'recovery' instead (#2060)
  • fix(core): ProcessHelper.GetProcessTree - strict child-start-time check has no PidReuseToleranceSeconds, inconsistent with ProcessKiller/ProcessExtensions (#2065)
  • fix(core): Helper.WriteFileAtomic / WriteFileAtomicCore - Flush() does not force a physical disk write, comment overstates the durability guarantee (#2066)
  • fix(core): ServiceManager.InstallServiceAsync - OperationCanceledException logged as Error, inconsistent with Uninstall/Start/Stop which log it as Info (#2071)
  • fix(core): ServiceManager.GetAllServices - LogOnAs defaults to LocalSystem before PopulateNativeDetails runs, so timed-out / faulted queries silently report the wrong account (#2072)
  • fix(core): Helper.WriteFileAtomic / WriteFileAtomicCore - PrepareDestinationForMove runs outside the retry loop, so AV/EDR-induced AccessDenied on the destination file bypasses the documented retry policy (#2073)
  • fix(core): HandleHelper.GetProcessesUsingFile - RegexMatchTimeoutException escapes the try/catch because MatchCollection enumerates lazily (#2079)
  • fix(core): ProcessHelper.ResolvePath - legitimate paths containing literal '%X%' segments are rejected as 'unexpanded variable' when the file does not yet exist on disk (#2082)
  • fix(core): NativeMethodsHelpers.ValidateCredentials - username regex allows newline / tab / NBSP via \s, producing misleading downstream errors (#2089)
  • fix(core): ServiceHelper.CalculateStopTimeout - uncapped previousStopTimeout lets one abnormal stop poison every subsequent stop (#2091)
  • fix(core): Helper.IsValidPath - uses Path.GetInvalidPathChars(), accepting filename-invalid chars like '<', '>', '|', '*', '?' that cause downstream open failures (#2106)
  • fix(core): ServiceHelper.StopServices - no InvalidOperationException fallback like StartServices, so a benign race (service already stopped by another caller) is reported as an error (#2111)
  • fix(core): ServiceManager.UninstallServiceAsync - pre-uninstall ChangeServiceConfig(DEMAND_START) is not rolled back when stop/delete fails, leaving the service silently switched to Manual (#2115)
  • fix(core): ServiceManager.InstallServiceAsync - CREATE path applies PreShutdown then DelayedStart; UPDATE path applies them in opposite order (#2121)
  • fix(core): NativeMethodsHelpers.GetFileIdentity - PrefixDigest never incorporates fs.Length, contradicting the inline comment; two rotated logs with identical 4096-byte prefixes hash to the same digest and rotation is missed (#2130)
  • fix(core): EventLogService.SearchAsync - startDate/endDate with DateTimeKind.Unspecified silently shifts the filter window by the local UTC offset (#2131)
  • fix(core): Logger.cs Log - sanitized message keeps '[' and ']', allowing log-line forgery via service name / process output (CWE-117) (#2132)
  • fix(core): ImportGuard / ExportServiceCommand - reserved-device check misses trailing-space variants (sibling of #2069) (#2141)
  • fix(core): ProtectedKeyProvider.SaveProtected - File.Move lacks AV/EDR retry that Helper.WriteFileAtomic has (#2152)
  • fix(core): ServiceControllerWrapper.BuildDependencyTree - currentPath cycle check is O(n) per node → O(n^2) for deep dependency chains (#2163)
  • fix(core): EventLogReader.MapToDto - ArgumentOutOfRangeException from 'new DateTimeOffset(evt.TimeCreated.Value)' is not caught; the in-code comment claims it is, but the fallback only fires when TimeCreated has NO value (#2170)
  • fix(core): Helper.IsServiceNameValid - char.IsControl misses Unicode format category (ZWSP, RLO, LRM), allowing invisible/bidi chars in service names (#2171)
  • fix(core): Logger.Log - single-line sanitization does not strip Unicode line separators (U+2028, U+2029), partially defeating the scannability guarantee (#2172)
  • fix(core): IServiceManager - IsServiceInstalled and GetDependencies lack CancellationToken, asymmetric with sibling interface methods (#2176)
  • fix(core): ScopedEventLogLogger.SetIsEventLogEnabled(true) silently no-ops if parent's _eventLog was never initialized (#2182)
  • fix(core): Logger.FormatException - only walks AggregateException.InnerException (first); siblings via InnerExceptions are silently dropped (#2183)
  • fix(core): ServiceDtoHelper.ApplyDefaultsAndResetIdentity silently discards UserAccount/Password from XML/JSON imports with no operator-visible warning (#2184)
  • fix(core): ProcessKiller.CriticalSystemProcesses - missing Windows 10+ pseudo-system processes (Registry, MemCompression) (#2187)
  • fix(core): SecureData - v1 legacy keying material (_v1MasterKey, _v1StaticIv) is loaded, cloned, and zeroed on every instance even though AllowLegacyV1Decryption is hard-coded false (#2193)
  • fix(core): ImportGuard.ValidatePathSecurity - handle-resolution validation block is silently skipped when GetFinalPathNameByHandle returns 0, bypassing the UNC re-check (#2196)
  • fix(core): LogonAsServiceGrant.GrantLogonAsService - inconsistent handling of LsaClose return value vs HasLogonAsService (#2020)
  • fix(core): ServiceHelper.StartServices - StopPending/PausePending services skip Start()/Continue() and then time out polling for Running (#2025)
  • fix(core): WindowsServiceInfo.cs - XML doc cref references non-existent IWindowsServiceProvider; should be IWindowsServiceApi (#2030)
  • fix(core): HandleHelper.GetProcessesUsingFile - timeout path's TimeoutException reports possibly-truncated stderr because async readers are not drained before throwing (#2043)
  • fix(core): ServiceManager.MapStartupType - log message says 'Falling back to Manual' but the code returns ServiceStartType.Unknown (#2050)
  • fix(core): AppConfig.cs - FindRepoRoot XML doc says 'only available in DEBUG builds' but the method is compiled and called in Release tests (#2084)
  • fix(core): AppFoldersHelper.EnsureFolders - XML doc promises InvalidOperationException for malformed connection strings, but DbConnectionStringBuilder throws ArgumentException (#2090)
  • fix(core): ServiceManager.RestartServiceAsync - Stop failure reason discarded; caller sees generic 'Failed to restart' instead of the real cause (#2122)
  • fix(core): ResourceHelper.CopyEmbeddedResource - restart failure after successful copy returns false, so caller logs 'failed to extract embedded resource' for a copy that actually succeeded (#2124)
  • fix(core): InstallServiceOptions - five nullable int?/bool? fields are asymmetric with non-nullable siblings; null leaks downstream because two of three call sites forget to apply '?? Default' (#2126)
  • fix(core): ServiceHelper.cs StartServices - polling loop ignores Stopped state, mis-reports crashed-during-start as 'timed out waiting' (#2133)
  • fix(core): ProcessHelper.GetProcessTree - inline parentToChildren snapshot duplicates Toolhelp32Snapshot.BuildSnapshotAndChildMap (#2146)
  • fix(core): ServiceAccounts / GetLogOnAsDisplayName - LocalSystem has a 4-alias set but LocalService and NetworkService only match the canonical 'NT AUTHORITY\…' form (#2147)
  • fix(core): ProcessKiller.KillProcessesUsingFile - invokes KillProcessTreeAndParents with default killParents=true, terminating unrelated parent processes that are not holding the file lock (#2048)
  • fix(core): ServiceManager.InstallServiceAsync - cancellation between CreateService and the try/catch rollback block leaves an orphan SCM entry with no DB record (#2049)
  • fix(core): EventLogReader.MapToDto - only FormatDescription is guarded; Id/Level/ProviderName/TimeCreated can throw EventLogException and abort the entire enumeration (#2061)
  • fix(core): EventLogReader.ReadEvents - for-loop fetches one extra EventRecord past maxReadCount; a throw on that read aborts the whole batch the caller had already received (#2139)
  • fix(core): StringHelper.FormatServiceDependencies - XML doc and nullable return type both promise null, but method never returns null (#2157)
  • fix(core): StringHelper.Escape / EscapedTokenizer.Unescape - round-trip is broken for \r and \n in env-var values (#2158)
  • fix(core): EnvironmentVariableParser.Parse - value trim-then-unescape is asymmetric with key unescape-then-trim, drops escaped trailing whitespace in values (#2159)
  • fix(core): EventLogService.SearchAsync - IndexOf bracket check uses StringComparison.OrdinalIgnoreCase which is meaningless for '[' and ']' (#2160)
  • fix(core): EventLogService.SearchAsync - comment claims 'records is a fully-materialized DTO sequence' but ReadEvents is a lazy yield-based iterator (#2161)
  • fix(core): ConfigParser.ParseBool - only accepts 'True'/'False', rejects common config-file boolean variants ('1', '0', 'yes', 'no', 'on', 'off') (#2162)
  • fix(core): EnvironmentVariablesValidator.Validate - short-circuits on first error, asymmetric with sibling ServiceDependenciesValidator which collects all errors (#2164)
  • fix(core): ProcessHelper.cs - UnexpandedEnvVarRegex static field declared but never referenced (dead code) (#2165)
  • fix(core): EventLogLogger.ScopedEventLogLogger.CreateScoped - nested-prefix bracket-balancing trick ("{Prefix}] [{prefix}") silently breaks when a prefix contains '[' or ']' (#2166)
  • fix(core): Domain/Service.cs - XML doc on Start/Stop/Restart/Install/Uninstall describe a bool result but methods return OperationResult (#2168)
  • fix(core): Logger.FormatException - truncation path emits one unclosed '[' bracket when the size limit triggers on inner exception N (off-by-one) (#2169)
  • fix(core): Domain/Service.cs - IsInstalled() and GetServiceStartupType() do not accept CancellationToken, breaking propagation from callers (#2177)
  • fix(core): Logger.cs - now/tzMarker timestamp formatting duplicated across three sites (DRY) (#2185)
  • fix(core): NativeMethods.PROCESS_BASIC_INFORMATION has 3 Reserved2_* fields where Win32 defines only Reserved2[2]; layout is 1 IntPtr larger than the OS struct, so UniqueProcessId would read garbage if NtQueryInformationProcess is ever wired up (#2198)
  • fix(core): ServiceStartType.cs - XML doc cref references nonexistent 'AutoStart' member (should be 'Automatic') (#2200)
  • fix(core): nstallServiceOptions.cs - XML doc / HelpText for Pre-Stop / Post-Launch / Post-Stop StartupDir contradict the actual fallback in Service.cs (#2208)
  • fix(core): ServiceHelper.CalculateStartTimeout - does not account for PreLaunchRetryAttempts, causing 'Timed out waiting to start' for services with configured pre-launch retries (#2211)
  • fix(core): ProtectedKeyProvider.SaveProtected - crash between WriteAllBytes and File.Move leaves encrypted .tmp files orphaned with elevated ACLs; subsequent runs never clean them up (#2213)
  • fix(core): ProcessKiller.KillParentProcesses - recursive call passes stale parentStartTime (possibly MinValue) instead of verified exactStartTime, silently truncating the upward walk one level (#2214)
  • fix(core): ImportGuard.ValidatePathSecurity - ReadToEnd has no size guard and runs before every caller's size check, so a giant import file OOMs the process before MaxConfigFileSizeMB rejects it (#2220)
  • fix(core): SecurityHelper.CreateSecureDirectory - race-window comment is misleading; ACE purge does not mitigate handle-retention or squat attacks during the TOCTOU window (#2224)
  • fix(core): XmlServiceSerializer vs ServiceExporter - divergent XmlSerializer caching and BOM encoding for identical ServiceDto XML pipeline (#2227)
  • fix(core): ServiceDto.EnableDebugLogs XML doc claims data goes to Windows Event Log; actual destination is the local Servy.Service.log file (#2228)
  • fix(core): DateRotationType.cs - XML doc claims 'per calendar day/week/month (local)' but RotatingStreamWriter defaults to UTC (useLocalTimeForRotation=false) (#2231)
  • fix(core): SecurityHelper.ApplySecurityRules - DACL purge only removes Allow ACEs for Users/AuthenticatedUsers/Everyone; pre-squatted directory keeps attacker-added explicit Allow ACE (local privilege escalation) (#2235)
  • fix(core): EnvironmentVariablesValidator.Validate accepts env-var values that EnvironmentVariableParser.Parse later rejects with FormatException (missing newline-validation parity) (#2238)
  • fix(core): ReservedNames.cs - COM0/LPT0 are not Windows reserved device names; over-restriction rejects valid names and XML doc 'ports (0-9)' is wrong (#2240)
  • fix(core): ServiceControllerWrapper.BuildDependencyTree - fullyExpanded memoization reuses path-dependent cycle placeholders, mislabeling shared/diamond dependencies as cyclic (#2241)
  • fix(core): SecureData.cs DecryptV2 - corrupted v2 Base64 throws FormatException, not the documented SecureDataIntegrityException (#2242)
  • fix(core): EventLogLevel.Verbose is preserved by ParseLevel but excluded from the Logs filter - asymmetric with Critical (folded into Error) (#2246)
  • fix(core): ServiceDependenciesParser.Parse - nullable return type string? but the method never returns null (#2254)
  • fix(core): SecurityHelper.CreateSecureDirectory - redundant SetAccessRuleProtection(true, false) call; ApplySecurityRules already sets protection unconditionally (#2256)
  • fix(core): mportGuard.ValidatePathSecurityAndSize - 'content' out-param XML doc claims it outputs the validated path, but it returns the file contents (#2258)
  • fix(core): EventLogReader.ParseLevel - XML doc says 'Verbose is preserved' but Verbose (level 5) is folded into Information (#2259)
  • fix(core): NativeMethodsHelpers.GetFileIdentity - comment says '4-byte-len' but BitConverter.GetBytes(fs.Length) emits an 8-byte long (#2261)
  • fix(core): RotatingStreamWriter.WriteInternal - lock-timeout log says 'drop current line' but the line is actually written (#2269)
  • fix(core): ProtectedKeyProvider.GetCachedOrGenerate - XML doc promises 'a clone' but the generate path returns the original array (only the fast path clones) (#2270)
  • fix(core): SecurityHelper.ApplySecurityRules - XML doc claims current user is skipped when in 'System or Admin groups' but code only excludes LocalSystem (#2271)
  • fix(core): ServiceManager.GetServiceStartupType - inlines delayed-auto-start query that IsDelayedStart already encapsulates (DRY) (#2275)
  • fix(core): ProtectedKeyProvider.SaveProtected - fixed staging file assumes mutex protection, but the v7.8 migration re-save runs outside the mutex (#2276)
  • fix(core): ProcessKiller.cs - reserved system-PID threshold '4' hardcoded inline at four guard sites instead of a named constant (#2277)
  • fix(core): Servy.Core ServiceHelper.CalculateStartTimeout - preLaunchRetryAttempts parameter is undocumented (missing tag) (#2281)
  • fix(core): ConfigParser.ParseEnum(string) - Flags parity check rejects valid canonical comma-separated combinations (asymmetric with int overload) (#2282)
  • fix(core): ServiceManager.cs - AutomaticDelayedStart→Automatic start-type coercion duplicated in CreateService and UpdateServiceConfig (#2288)
  • fix(core): EventLogService.cs - SearchAsync truncates to MaxResults before ordering; with default oldest-first reads the most recent events are never returned (#2295)
  • fix(core): ResourceHelper.ShouldCopyResource - 'embeddedResourceTime' variable and debug log actually report the host executable's timestamp, not the embedded resource's (#2301)
  • fix(core): Logger.cs - FormatException emits an unmatched trailing ']' for every formatted exception (off-by-one in structural-bracket balancing) (#2307)
  • fix(core): EventLogReader.ReadEvents - the (maxReadCount+1)th EventRecord is read by the for-loop increment but never disposed, leaking a native handle (#2310)
  • fix(core): ServiceDependenciesValidator.cs - XML doc summary omits the dollar sign '$' that the regex and inline comment allow (#2317)
  • fix(core): IServiceManager.cs - GetDependencies XML doc claims reverse dependencies are returned, but implementation only resolves ServicesDependedOn (#2327)
  • fix(core): IServiceManager.cs / ServiceManager.cs - InstallServiceAsync documents ArgumentNullException but actually throws ArgumentException for missing names/paths (#2328)
  • fix(core): InstallServiceOptions.cs - EnableDateRotation doc uses referencing a nonexistent parameter (#2329)
  • fix(core): ServiceManager.cs - StartServiceAsync/StopServiceAsync repository-null guard throws a misleading "Cannot install service" message (copy-paste) (#2330)
  • fix(core): ServiceManager.cs - IsServiceInstalled/GetServiceStartupType throw ArgumentNullException for non-null whitespace names (inconsistent with sibling methods) (#2331)
  • fix(core): XmlServiceSerializer.cs / XmlServiceValidator.cs - duplicated XXE-hardening XmlReaderSettings can drift between the two XML entry points (#2334)
  • fix(core,service): PID-reuse start-time tolerance differs: ProcessKiller uses AddSeconds(-2), ProcessExtensions uses AddSeconds(-1) (#1571)
  • fix(core,service): ProcessExtensions.BuildParentChildMapNative / ProcessKiller.BuildSnapshotAndChildMapNative - Toolhelp32 process-map building duplicated across Servy.Service and Servy.Core (#1641)
  • fix(core,service): EnableDebugLogs XML doc (Domain/Service.cs, StartOptions.cs) wrongly claims env vars/params go to the Windows Event Log - they go to the local log file only (#2272)
  • fix(infra): ServiceRepository.PatchRuntimeStateAsync/PatchRuntimeState - sync and async siblings duplicated verbatim (drift risk) (#1608)
  • fix(infra): ServiceRepository.DecryptDto - mid-loop decryption failure leaves DTO half-decrypted, mixed plaintext + ciphertext returned to caller (#1609)
  • fix(infra): ServiceRepository.UpsertBatchAsync - encrypted upsert + plaintext ID-resolution SELECT not wrapped in transaction; concurrent inserts/deletes can yield mismatched/missing IDs (#1610)
  • fix(infra): SQLiteDbInitializer ApplyVersion2/3/5 - 'add column if missing' migration boilerplate duplicated three times with copy-paste try/catch/log (#1611)
  • fix(infra): SQLiteDbInitializer.ReconcileSchema - 'self-healing' only adds missing columns; cannot detect renamed/removed/type-changed columns, silently masking real drift (#1612)
  • fix(infra): DapperExecutor.CalculateBackoff - maxBackoffMs default 5000 is an inline magic number; sibling DB tuning lives in AppConfig (same family as #1556) (#1654)
  • fix(infra): SQLiteDbInitializer.ReconcileSchema - XML doc summary and blocks duplicated verbatim (two identical doc blocks above one method) (#1661)
  • fix(infra): DapperExecutor.cs - WrappedDbTransaction.Dispose leaks Connection if Transaction.Dispose throws (#1687)
  • fix(infra): SQLiteDbInitializer.cs - ApplyVersion4 silently drops orphan columns, masking missed rename migrations (#1708)
  • fix(infra): ServiceRepository.cs - UpsertBatchAsync ID sync uses C# ToLowerInvariant against SQLite ASCII LOWER, missing non-ASCII service names (#1714)
  • fix(infra): ServiceRepository.cs - ApplyRuntimeState preserves only 3 fields; importing a config silently wipes Password/UserAccount/RunAsLocalSystem/PreviousStopTimeout (#1765)
  • fix(infra): SQLiteDbInitializer.cs - first-init schema migration is not atomic across processes; second Servy starter crashes with 'table Services already exists' (#1788)
  • fix(infra): SQLiteDbInitializer.cs - AddColumnIfMissing/RenameColumnIfExists logs 'successfully migrated' even when no schema change occurred (#1797)
  • fix(infra): DapperExecutor.cs - BeginTransaction leaks the connection if Open() or BeginTransaction() throws (#1805)
  • fix(infra): SQLiteDbInitializer.cs - ReconcileSchema type-mismatch check assumes [SqlColumn] never contains anything other than 'TYPE NOT NULL', causing false positives if a DEFAULT/CHECK/COLLATE clause is ever added (#1810)
  • fix(infra): SQLiteDbInitializer.cs - ApplyVersion3/5 hardcode column SQL types, decoupling migration path from [SqlColumn] SSoT on ServiceDto (#1835)
  • fix(infra): DapperExecutor.cs - QueryFirstOrDefaultAsync is the only async query without a CommandDefinition overload; duplicates retry/rebind logic (#1907)
  • fix(infra): SQLiteDbInitializer.cs - UpgradeLegacyDatabaseToVersion1 creates UNIQUE index without dedup, breaks migration if legacy DB has duplicate LOWER(Name) rows (#1944)
  • fix(infra): SQLiteDbInitializer.cs - DELETE uses string interpolation for ID list instead of parameters (#1964)
  • fix(infra): SQLiteDbInitializer.cs - UpgradeLegacyDatabaseToVersion1 relies on non-deterministic GROUP_CONCAT order to pick which duplicate to keep (#1973)
  • fix(infra): ServiceRepository.cs - One corrupt service breaks GetAllAsync/SearchAsync for every other service (#1975)
  • fix(infra): SQLiteDbInitializer.cs - ApplyVersion4 only logs orphan columns before dropping the table, silently losing their data (#1979)
  • fix(infra): DapperExecutor.cs - Duplicate 'Retry loop exited' guard with identical message in sync and async paths (#2010)
  • fix(infra): ServiceRepository.UpsertBatchAsync - does not preserve runtime state, clobbers Pid/ActiveStdoutPath/ActiveStderrPath/Password on batch import (#2051)
  • fix(infra): DapperExecutor - no BeginTransactionAsync; ServiceRepository.UpsertBatchAsync blocks on synchronous connection.Open inside an async path (#2125)
  • fix(infra): IServiceRepository.UpsertAsync - return-value contract says 'affected records' but impl returns service ID, making consumer 'affected <= 0' checks dead code (#2154)
  • fix(infra): SQLiteDbInitializer.ReconcileSchema - detects type/nullability mismatches but only warns; 'Self-Healing' claim only covers missing columns (#2180)
  • fix(infra): SQLiteDbInitializer.ApplyVersion4 - table rebuild silently loses every index/trigger/view on Services except idx_services_name_lower (#2181)
  • fix(infra): DapperExecutor - sync methods recompute Unwrap(transaction) inside the retry lambda; async methods correctly hoist it outside (#2233)
  • fix(infra): ServiceRepository.HandleCorruptServiceDecryption - duplicated word 'payload payload' in user-facing description text (#2244)
  • fix(infra): ServiceRepository - GetByIdAsync/GetByNameAsync/GetByName decrypt without the HandleCorruptServiceDecryption guard used by GetAllAsync/SearchAsync (#2245)
  • fix(infra): ServiceRepository.UpsertBatchAsync - PatchRuntimeState reads run outside the batch transaction (separate connection), breaking the documented snapshot isolation (#2257)
  • fix(infra): ServiceRepository.cs - decrypt-then-handle-corruption block duplicated across 5 read methods (DRY) (#2293)
  • fix(infra): SQLiteDbInitializer.cs - AddColumnIfMissing / UpgradeLegacyDatabaseToVersion1 emit ALTER ADD COLUMN without the NOT-NULL/DEFAULT guard that ReconcileSchema already has (#2297)
  • fix(infra): ServiceRepository.cs - UpsertBatchAsync ID-sync chunk size (900) is a hardcoded magic number; SQLite parameter cap lives in AppConfig elsewhere (#2324)
  • fix(service): ServySecurity.ps1 / Servy.Service ServiceHelper.cs - SensitiveKeyWords list duplicated across PowerShell and C# (must be hand-synced) (#1396)
  • fix(service): ProcessLauncher.cs - ApplyLanguageFixes .jar branch is dead; UseShellExecute=false cannot directly launch .jar (#1444)
  • fix(service): Service.cs - LogIssue local functions drop Exception parameter when failures are ignored (Pre-Launch and Pre-Stop) (#1484)
  • fix(service): Service.cs OnCustomCommand fallback - logs 'Pre-Shutdown handling complete' BEFORE ExecuteTeardown actually runs (#1510)
  • fix(service): Service.cs HandleStopResult - XML doc and 'canceled' wording mismatch the underlying TryStopGracefullyOrKill semantics (#1514)
  • fix(service): ConsoleAppDetector.cs - generic 'catch { return false }' silently swallows IO/Access errors with no telemetry (#1519)
  • fix(service): Service.cs - dead health check timer disposal in Cleanup() finally block (already disposed in ExecuteTeardown) (#1523)
  • fix(service): ProcessLauncher.Start - subscribes to UnderlyingProcess events directly, bypassing IProcessWrapper abstraction (#1530)
  • fix(service): ProcessLauncher.Start - drain timeout branch is unreachable; warning at line 271 is misleading dead code (#1539)
  • fix(service): ServiceHelper.RestartService - magic numbers '60_000' and '1000' inline for ms->minutes/seconds display conversion (#1549)
  • fix(service): Service.cs ConditionalResetRestartAttemptsAsync - try/catch around Environment.TickCount64 arithmetic is dead error-handling (#1598)
  • fix(service): ServiceHelper.LogStartupArguments - inconsistent 'InSeconds' suffix for timeout fields (#1602)
  • fix(service): Service.cs _cancellationSource may leak if Cancel() throws an exception other than ObjectDisposedException (#1672)
  • fix(service): Service.cs - post-launch Task.Run captures _childProcess by closure; NullReferenceException if Cleanup nulls it during the wait (#1679)
  • fix(service): Service.cs - RunSynchronousPreLaunch leaks IProcessWrapper on every retry (#1680)
  • fix(service): Service.cs - background ConditionalReset task uses CancellationToken.None, can race with semaphore disposal at teardown (#1681)
  • fix(service): Service.cs - SafeKillProcess timeout path logs misleading 'already exited' message (#1684)
  • fix(service): ServiceHelper.cs - RestartProcess skips startProcess if stop throws, silently aborting recovery (#1702)
  • fix(service): Service.cs - SafeKillProcess IsFaulted/IsCanceled branches are unreachable; faults propagate through Wait() and reach outer catch instead (#1704)
  • fix(service): Service.cs - OnCustomCommand IsFaulted branch unreachable; AggregateException from Wait() escapes the SCM handler (#1713)
  • fix(service): ProcessWrapper.cs - WaitAndCheckStillRunningAsync uses DateTime.UtcNow for timeout, vulnerable to clock changes (#1718)
  • fix(service): Service.cs - HandleStopResult XML doc contradicts implementation for true/false/null cases (#1722)
  • fix(service): ServiceHelper.cs - RestartProcess never disposes the old IProcessWrapper; every RestartProcess recovery leaks a Process handle (#1725)
  • fix(service): ProcessLauncher.cs - ApplyLanguageFixes Java branch misses .jar despite docs promising it (#1740)
  • fix(service): Service.cs - OnShutdown / OnCustomCommand reboot bypass skips FlushAndShutdownLogger, losing recent logs (#1741)
  • fix(service): Service.cs - ConditionalReset background task reads _preLaunchEnabled before StartPreLaunchProcess sets it (#1743)
  • fix(service): ServiceHelper.cs - RestartProcess skips descendant cleanup when parent has already exited (the common case) (#1746)
  • fix(service): ProcessLauncher.cs - ApplyLanguageFixes misses py.exe (Python launcher) and python2; services lose UTF-8 fixes (#1747)
  • fix(service): ProcessLauncher.cs - Helper.EnsureDirectoryExists is outside the lazy-init try/catch; persistent directory failures cause unbounded log spam (#1769)
  • fix(service): StartOptionsParser.cs - EnvironmentVariableParser.Parse FormatException aborts every service start when stored env vars are malformed; no graceful fallback (#1782)
  • fix(service): ServiceHelper.cs / IServiceHelper.cs - LogStartupArguments 'string[] args' parameter is dead; never referenced in the body (#1792)
  • fix(service): EnvironmentVariableHelper.cs - ProtectedVariables list omits APPDATA/LOCALAPPDATA/HOMEPATH and other profile-redirection vectors (#1802)
  • fix(service): Service.cs - OnOutputDataReceived/OnErrorDataReceived have no try/catch; writer exceptions can crash the service (#1807)
  • fix(service): ProcessLauncher.cs - ApplyLanguageFixes lets RegexMatchTimeoutException propagate, crashing every Java service start when input regex times out (#1809)
  • fix(service): ProcessLauncher.cs - if stdoutWriter.Dispose() throws in finally, orphaned child process is never killed (skips the !returnedOwnership cleanup branch) (#1816)
  • fix(service): ervice.cs - HandleLogWriters compares StdOutPath/StdErrPath with raw OrdinalIgnoreCase, missing same-file detection for normalised variants (#1833)
  • fix(service): Service.cs - OnOutputDataReceived/OnErrorDataReceived race with Cleanup can call WriteLine on disposed RotatingStreamWriter (#1834)
  • fix(service): EnvironmentVariableHelper.cs - ProtectedVariables omits PROGRAMFILES / PROGRAMFILES(x86) / COMMONPROGRAMFILES; user-supplied env vars can redirect %ProgramFiles% to a malicious path (#1843)
  • fix(service): ProcessLauncher.cs - ApplyLanguageFixes Python detection uses StartsWith and matches third-party tools (pythonista, python_wrapper, etc.) (#1844)
  • fix(service): Service.cs - OnErrorDataReceived xmldoc says 'logs errors' but body only writes to stderr writer (#1847)
  • fix(service): Service.cs - CleanupTrackedHooks re-acquires _trackedHooks lock its only caller is already holding (#1848)
  • fix(service): ProcessWrapper.cs - CloseMainWindow Win32Exception escapes TryStopGracefullyOrKill catch, crashing graceful-stop attempts (#1852)
  • fix(service): Service.cs - _isRebooting flag is never reset on RestartComputer failure, leaving the service permanently deaf to health checks and exits (#1863)
  • fix(service): Service.cs - StartProcess briefly nulls _cancellationSource between Interlocked.Exchange and reassignment, breaking shutdown cancellation for concurrent readers (#1878)
  • fix(service): Service.cs - three 'if (process == null)' branches after ProcessLauncher.Start are unreachable; Start always returns non-null or throws (#1883)
  • fix(service): EnvironmentVariableHelper.cs - inline-expansion overflow warning logs first 40 chars of raw env-var value, potentially leaking secrets to disk (#1884)
  • fix(service): Service.cs - PersistProcessState calls GetByName with default decrypt:true but only mutates runtime-state fields (#1885)
  • fix(service): IServiceHelper.cs - ValidateAndLog and EnsureValidWorkingDirectory xmldocs are out of sync with the actual signatures (#1895)
  • fix(service): ProcessExtensions.cs - GetChildren duplicates the Toolhelp32 snapshot walk that GetAllDescendants already centralizes via Toolhelp32Snapshot.BuildSnapshotAndChildMap (#1911)
  • fix(service): Servy.Service/Service.cs - ConditionalResetRestartAttemptsAsync adds PreLaunchTimeoutInSeconds AFTER the 1-hour cap, silently breaking the stated ceiling (#1916)
  • fix(service): Servy.Service/Service.cs - OnStart's background restart-attempts reset task captures CancellationToken.None because _cancellationSource is still null at that point (#1917)
  • fix(service): Servy.Service/Service.cs - production constructor xmldoc is missing entries for processHelper and processKiller (#1918)
  • fix(service): Service.cs - SafeKillProcess's totalTimeoutMs underestimates actual stop budget; each ProcessWrapper.Stop can consume up to 2*timeoutMs (graceful wait + post-kill wait) (#1919)
  • fix(service): Service.cs - RunSynchronousPreLaunch retries with no delay/backoff between attempts (#1921)
  • fix(service): Service.cs - FlushAndShutdownLogger leaves an orphan Task that mutates _logger after the 1.5s timeout returns (#1922)
  • fix(service): Service.cs - StartProcess logs unexpanded placeholders in args twice (ExpandAndAudit + explicit call) (#1930)
  • fix(service): Service.cs - RunSynchronousPreLaunch hardcodes waitChunkMs=250, ignoring the configurable _waitChunkMs field (#1936)
  • fix(service): Service.cs - ConditionalResetRestartAttemptsAsync hardcodes 3600s cap instead of an AppConfig constant (#1937)
  • fix(service): Service.cs - SafeKillProcess reports already-exited child as 'stopped gracefully' instead of 'already exited' (mainExitedGracefully default is true, not null) (#1947)
  • fix(service): Service.cs - StartPreLaunchProcess asymmetric fallback: args expanded with main env vars, but process gets none (#1952)
  • fix(service): Service.cs - RunFailureProgram's ExpandAndAudit call omits the context prefix used by every sibling hook (#1954)
  • fix(service): EnvironmentVariableHelper.cs - ExpandWithDictionary uses both 'IndexOf > -1' and 'IndexOf >= 0' six lines apart (#1958)
  • fix(service): Service.cs - Unnecessary (Func<Task?>) cast on Task.Run lambda (#1962)
  • fix(service): Service.cs - Dead null-conditional on readonly semaphore fields (#1963)
  • fix(service): ProcessLauncher.cs - Finally block calls process.HasExited even when Start() never succeeded (#1968)
  • fix(service): ProcessWrapper.cs - Stop(timeoutMs) uses the same value for graceful timeout and post-kill wait (#1971)
  • fix(service): ProcessLauncher.cs - Unused 'extension' local in ApplyLanguageFixes (#1974)
  • fix(service): Service.cs - Dead File.Exists check in ConditionalResetRestartAttemptsAsync (#1982)
  • fix(service): EnvironmentVariableHelper - self-referential custom variables silently lose their declared value (#1987)
  • fix(service): ProcessLauncher.ApplyLanguageFixes - Inconsistent regex-timeout handling between Python (silent) and Java (logged) (#2015)
  • fix(service): ServiceHelper.RestartComputer - relies on PATH resolution for 'shutdown' executable (#2016)
  • fix(service): Environment-Variables.md - direct circular reference (A=%B%, B=%A%) is silently kept, not detected or logged (#2024)
  • fix(service): Service.cs RunSynchronousPreLaunch - retry back-off Thread.Sleep loop ignores teardown/cancellation, blocking SCM Stop during pre-launch (#2035)
  • fix(service): EnvironmentVariableHelper.ProtectedVariables - missing DOTNET_DiagnosticPorts and other CLR/.NET diagnostic injection vectors (#2037)
  • fix(service): Service.cs HandleStopResult - XML doc claims null means 'timed out' but timeout no longer routes through HandleStopResult (#2046)
  • fix(service): Service.cs StartProcess - assigns caller-supplied environmentVariables to _environmentVariables field without null guard, can NRE on RestartProcess (#2047)
  • fix(service): Service.cs OnCustomCommand - fallback path calls Environment.Exit without flushing the logger, losing the diagnostic messages it just wrote (#2052)
  • fix(service): Service.cs hook framework - only Pre-Launch supports dedicated environment variables; Pre-Stop / Post-Launch / Post-Stop / Failure Program reuse the main service envs (#2053)
  • fix(service): Service.cs HandleLogWriters - stderr path is passed to Helper.Canonicalise before _pathValidator.IsValidPath runs, so a malformed StdErrPath throws and crashes OnStart (#2068)
  • fix(service): Service.cs MakeFilenameSafe - does not strip trailing spaces/periods, allowing two distinct service names to collide on the same restartAttempts file (#2069)
  • fix(service): Service.cs PersistProcessState - read-modify-write of full DTO with preserveExistingCredentials:false overwrites concurrent password/parameter updates from Servy.Manager (#2070)
  • fix(service): ProcessLauncher.Start - return value of process.Start() is ignored, masking 'no new process started' for pre-launch / post-launch / pre-stop / post-stop / failure hooks (#2074)
  • fix(service): Environment-Variables.md - Tips section warns against wrapping a literal '%' in another '%' but never documents how to actually escape a percent sign (#2075)
  • fix(service): Service.cs MakeFilenameSafe - multi-extension reserved-device names (e.g. CON.log.gz) bypass the guard and collide with the actual DOS device (#2080)
  • fix(service): Service.cs InitiateRecoveryAsync - _healthCheckSemaphore.WaitAsync uses no CancellationToken, throws ObjectDisposedException if teardown disposes the semaphore mid-wait (#2097)
  • fix(service): Service.cs CleanupTrackedHooks - single try/catch wraps the whole foreach, so one hook.Dispose() throw leaks every remaining hook (#2098)
  • fix(service): Service.cs ExecuteRecoveryAction - XML doc summary describes InitiateRecoveryAsync's responsibilities, not its own (#2099)
  • fix(service): Service.cs StartPreLaunchProcess - XML doc references unresolved cref MinPreLaunchTimeoutSeconds (#2100)
  • fix(service): IServiceHelper / ServiceHelper.ValidateAndLog - fullArgs parameter declared on interface and implementation but never used (#2101)
  • fix(service): IServiceHelper.RestartProcess - XML doc references nonexistent terminateJobObject parameter; doc step 2 claims behavior that no longer exists (#2102)
  • fix(service): Service.cs Cleanup - stdout/stderr writer Dispose exception leaks the other writer and leaves fields non-null (#2104)
  • fix(service): Service.cs OnProcessExited - unconditional Warn 'Child process exit detected' fires even on configured clean exits (#2105)
  • fix(service): Service.cs MakeFilenameSafe - reserved-name '' prefix causes collision with user-supplied '' service (#2118)
  • fix(service): Service.cs ConditionalResetRestartAttemptsAsync - Math.Max/Min clamp silently bypasses the 1-hour cap when detection window exceeds it (#2119)
  • fix(service): Service.cs OnCustomCommand - ExecuteTeardown return value discarded; SERVICE_STOPPED reported to SCM even when cleanup actually failed (#2120)
  • fix(service): ProcessWrapper.TryStopGracefullyOrKill - CloseMainWindow exception returns null (interpreted as 'already exited'), skipping the force-kill path even when the process is still running (#2129)
  • fix(service): EnvironmentVariableHelper.ProtectedVariables - missing Windows AppCompat / debugger injection vectors (__COMPAT_LAYER, SHIM_FILE_LOG, _NT_SYMBOL_PATH, …) (#2134)
  • fix(service): EnvironmentVariableHelper.ProtectedVariables - missing PowerShell language-mode / execution-policy bypass vectors (__PSLockDownPolicy, PSExecutionPolicyPreference) (#2135)
  • fix(service): Service.cs OnProcessExited / CheckHealth - _healthCheckSemaphore.WaitAsync uses no CancellationToken (siblings of #2097) (#2140)
  • fix(service): ServiceHelper.RestartComputer - Process.Start null return is silently swallowed; failed reboot leaves no log entry (#2148)
  • fix(service): Service.cs ExecuteTeardown - finally-block disposal cascade is not exception-safe (sibling of #2109/#2136) (#2151)
  • fix(service): ProcessLauncher.ApplyLanguageFixes - Windows Python Launcher (py.exe / pyw.exe) not detected, UTF-8 / unbuffered env vars are not applied (#2153)
  • fix(service): EnvironmentVariableHelper.ProtectedVariables - missing JAVA_OPTS / MAVEN_OPTS / CATALINA_OPTS and other JVM-launcher injection vectors (#2174)
  • fix(service): Service.cs MakeFilenameSafe - XML doc block is duplicated (two identical /// summary/param/returns blocks back-to-back) (#2179)
  • fix(service): StartOptionsParser.SafeResolvePath - only catches InvalidOperationException/ArgumentException; SecurityException, NotSupportedException, PathTooLongException from Path.GetFullPath propagate and crash OnStart (#2188)
  • fix(service): Service.cs MakeFilenameSafe - Contains(seg, StringComparer.OrdinalIgnoreCase) degrades ImmutableHashSet O(1) lookup to O(N) LINQ scan (#2190)
  • fix(service): Service.cs hook launchers - ExpandAndAudit args/env then ProcessLauncher.Start expands them a second time (double expansion, env audit work wasted) (#2191)
  • fix(service): Service.cs InitiateRecoveryAsync - gate stays closed forever if RestartService/RestartComputer throws, silently dropping all subsequent recovery (#2062)
  • fix(service): Service.cs ScheduleRecoveryAsync - SAFETY RESET writes _isRecovering outside _healthCheckSemaphore, racing with health-check evaluations (#2067)
  • fix(service): ProcessWrapper.SendCtrlC - GenerateConsoleCtrlEvent return value discarded; method reports success on signal failure (#2212)
  • fix(service): Service.cs StartProcess - Task.Run lambda reads capturedCts.Token after Interlocked.Exchange disposes it on rapid recovery cycles (#2222)
  • fix(service): ProcessExtensions.Format - asymmetric fallback strings between InvalidOperationException and Win32Exception catches (#2230)
  • fix(service): EnvironmentVariableHelper.cs - %% escape token decoded too early; literal %VAR% in custom values gets OS-re-expanded by the string overload (#2239)
  • fix(service): ServySecurity.ps1 vs ServiceHelper.cs - MaskingRegex Branch B diverges (multi-word in PS vs single-word in C#), breaking the 'strict parity' contract (#2218)
  • fix(service): EnvironmentVariableHelper.cs - memory-guard Substring truncation can split the PercentEscapeToken, leaking SERVY_ESC_PERCENT fragments into the final env-var value (#2255)
  • fix(service): Service.cs - Redundant nested 'if (!recoveryTriggered)' in ScheduleRecoveryAsync (dead guard) (#2263)
  • fix(service): Service.cs - ExecuteRecoveryAction swallows all exceptions, making InitiateRecoveryAsync's failure handling dead code (recovery gate never reopens on failed terminal action) (#2265)
  • fix(service): Service.cs OnStart - health-monitoring fields (_maxFailedChecks, _recoveryAction, _heartbeatIntervalSeconds) are initialized in SetupHealthMonitoring AFTER the child process and its Exited handler are wired up, creating a startup race (#2266)
  • fix(service): EnvironmentVariableHelper.ExpandWithDictionary - inline length-guard truncation can split a PercentEscapeToken, leaving marker fragments in the final expanded value (#2267)
  • fix(service): EnvironmentVariableHelper.ExpandEnvironmentVariables - outer truncation guard corrupts an INTACT PercentEscapeToken at the boundary (distinct from #2267) (#2273)
  • fix(service): Service.cs ExecuteRecoveryAction - attempt counter logged as '(0/unlimited)' on every recovery in unlimited mode (#2274)
  • fix(service): ProcessLauncher.Start - process wrapper not disposed when process.Start() throws/returns false (dispose gated on processStarted) (#2278)
  • fix(service): Service.cs RunFailureProgram - XML doc claims it runs 'when the main child process fails to start', but start failures never invoke it (#2280)
  • fix(service): Service.cs - health-monitoring enable predicate duplicated in OnStart (_recoveryActionEnabled) and SetupHealthMonitoring (#2287)
  • fix(service): StartOptionsParser.cs - SafeResolvePath XML summary claims it falls back to 'empty values' but actually returns the original raw path (#2299)
  • fix(service): EnvironmentVariableHelper.cs - dictionary-builder OS expansion lacks the injected-% protection the string overload uses, re-expanding literal percent content (#2300)
  • fix(service): Service.cs - RunFailureProgram has a truncated comment "// 1. Prepare Environment and" (#2302)
  • fix(service): Service.cs - PRESHUTDOWN failure path sets ExitCode=1066 but UpdateServiceStatus hardcodes dwWin32ExitCode/dwServiceSpecificExitCode to 0, so SCM records a clean stop (#2305)
  • fix(service): Service.cs - Dispose(bool) has two '// 2.' numbered comment steps (base-class call should be '// 3.') (#2306)
  • fix(service): IServiceHelper.cs - RequestAdditionalTime XML doc uses unresolvable to OnStart/OnStop/OnPause/OnContinue (#2340)
  • fix(service): Service.cs - StartPostLaunchProcess / RunFailureProgram / StartPostStopProcess are near-duplicate fire-and-forget hook launchers (DRY) (#2341)
  • fix(service): IServiceHelper.cs - RestartService XML doc omits the 'serviceName' parameter (#2349)
  • fix(service): IProcessWrapper.cs - Format() has an empty doc tag (#2350)
  • fix(service): IStreamWriter.cs - Write(string text) is missing its doc (#2351)
  • fix(service): StreamWriterFactory.cs - Create return type drops the nullable annotation declared on IStreamWriterFactory (#2352)
  • fix(service): Service.cs - OnCustomCommand hardcodes pre-shutdown pulse interval (2000) instead of an AppConfig constant (#2359)
  • fix(restarter): Program.Main - 'reloadOnChange: true' is wasted on a one-shot console process; spins up a FileSystemWatcher that never reloads (#1565)
  • fix(restarter): Program.Main - resource disposal order does not match reverse-of-construction (dbContext disposed AFTER scopedLogger/rootLogger) - same family as #1498 (#1589)
  • fix(restarter): Program.cs - DefaultRestartTimeoutSeconds (120) defined as public const in Program.cs instead of AppConfig (#1567)
  • fix(restarter): Program.cs - early returns skip Logger.Shutdown() and may lose recent log entries (#1750)
  • fix(restarter): ServiceRestarter.cs - HandleTransitionalError can sleep past the budget; remaining-time check happens before WaitForStatus but not before Thread.Sleep (#1822)
  • fix(restarter): Program.cs - GetByName decrypts 8 sensitive fields just to check service existence (#1836)
  • fix(restarter): Servy.Restarter/Program.cs - duplicates EnableEventLog parsing + SetIsEventLogEnabled that LoggerConfigurator already performed three lines above (#1915)
  • fix(restarter): ServiceRestarter.cs - RestartService's pending-state settle loop also sleeps past the budget (follow-up to #1822) (#1942)
  • fix(restarter): ServiceRestarter.RestartService - throws System.TimeoutException internally but lets System.ServiceProcess.TimeoutException propagate from WaitForStatus, two unrelated exception types for the same logical timeout (#2083)
  • fix(restarter): Program.cs Main - finally block disposal cascade is not exception-safe; one Dispose() throw skips Logger.Shutdown() and remaining cleanup (#2109)
  • fix(restarter): ServiceRestarter.RestartService - pending-state settle loop is not protected against InvalidOperationException/Win32Exception from a deleted-mid-flight service, inconsistent with Stop/Start phases (#2110)
  • fix(restarter): ServiceRestarter.RestartService - start-phase controller.Status read (line 90) is unguarded, unlike the settle phase, so a service uninstalled between stop and start throws an unhandled exception (#2298)
  • fix(restarter): Program.cs - step '3. Configure the GLOBAL logging' comment sits above the restart-timeout parse, not the logging setup (#2347)
  • fix(restarter): IServiceRestarter.cs - RestartService XML doc omits the 'timeout' parameter (impl uses inheritdoc, so the gap propagates) (#2348)
  • fix(restarter): Servy.Restarter/publish.ps1 - comment-based help documents a -Version parameter that the param() block does not declare (#2358)
  • fix(ui): AppBootstrapper.cs - resource disposal order does not match reverse-of-construction (same family as #1202) (#1498)
  • fix(ui): AppBootstrapper.OnExit - 'TryDispose' helper is misused for a Cancel() call (misleading name) (#1505)
  • fix(ui): AppBootstrapper.InitializeAppAsync - splash visibility threshold (1000ms) and minimum-display delay (500ms) are inline magic numbers (#1562)
  • fix(ui): BulkObservableCollection - duplicated reset-notification block in AddRange and TrimToSize (#1568)
  • fix(ui): AppBootstrapper.InitializeAppAsync - bool.TryParse(out showSplash) resets default 'true' to 'false' on malformed first arg (#1573)
  • fix(ui): AppBootstrapper.OnStartup - 'FATAL: AppDomain Unhandled Exception.Process is terminating' missing space between sentences (#1574)
  • fix(ui): Servy.UI HelpService - duplicated Process.Start+dispose 'open external URL' block in OpenDocumentation and CheckUpdates (#1585)
  • fix(ui): WpfUiDispatcher.InvokeAsync(Action, DispatcherPriority) - 'if (_dispatcher == null) return;' is dead code; field is set in ctor via 'Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher' (#1588)
  • fix(ui): ICursorService.SetWaitCursor returns IDisposable that two callers discard, then manually call ResetCursor in finally (API misuse / contract drift) (#1593)
  • fix(ui): AppBootstrapper.cs - direct CLI invocation 'Servy.exe MyService' silently drops the service name when first positional arg isn't 'true'/'false' (#1719)
  • fix(ui): App.xaml.cs - ServiceProvider singletons never disposed on OnExit (#1749)
  • fix(ui): AppBootstrapper.cs -DispatcherUnhandledException handler shows MessageBox unconditionally; recurring background-tick faults cause modal-dialog spam (#1762)
  • fix(ui): ServiceConfigurationValidator.cs (Servy & Servy.Manager) - XML doc references warnings, but ValidationResult has no Warnings and the code only shows errors (#1775)
  • fix(ui): AppBootstrapper.cs - InitializeAppAsync catch block calls app.Shutdown() with no exit code; critical startup failures exit 0 and look green to CI/service wrappers (#1787)
  • fix(ui): ImportGuard.cs - ValidateFileSizeAsync silently returns false when Path.GetFullPath throws, leaving the user with no feedback (#1803)
  • fix(ui): AppBootstrapper.cs - FileSystemWatcher.Error handler logs a warning without capturing the underlying exception (#1841)
  • fix(ui): AppBootstrapper.cs - InitializeAppAsync discards CopyEmbeddedResource's success bool; UI starts normally even when Servy.Service.exe / handle.exe extraction failed (#1858)
  • fix(ui): ServiceCommands.cs - GUI ImportConfigAsync lacks UNC/reparse/protected-folder guards that CLI ImportServiceCommand enforces (#1861)
  • fix(ui): AppBootstrapper.cs - hardcoded English MessageBox for missing handle.exe, while sibling failures use Strings.resx (#1923)
  • fix(ui,core): App.xaml.cs (Servy, Servy.Manager) and ProcessHelper.cs - three more sites use Path.IsPathRooted as an 'is absolute' check (follow-up to #1849) (#1866)
  • fix(ui): ImportGuard.cs and ExportServiceCommand.cs - same Contains("\UNC") false-positive as #1875, but in two more files (#1879)
  • fix(ui): AppBootstrapper.cs - OnExit calls CleanupAvailabilityWatcher() outside TryRun, breaking deterministic cleanup if Dispose throws (#1886)
  • fix(ui): AppBootstrapper.cs - OnStartup re-checks _options for null even though the readonly field cannot be null after construction (#1943)
  • fix(ui): ImportGuard.cs - reserved-device-name guard only checks first filename segment (mirror of #1939 in the UI security pipeline) (#1951)
  • fix(ui): ServiceCommands.cs - Synchronous IsServiceInstalled and GetServiceStartupType run on the UI thread inside async methods (#1978)
  • fix(ui): AppBootstrapper.cs - Dead null-conditional on readonly _appLifetimeCts field (#2007)
  • fix(ui): AppBootstrapper.cs - Stale comment claims 'async void monitor' but method is async Task (#2008)
  • fix(ui): AppBootstrapper.cs - FileSystemWatcher.Error handler is anonymous and never detached on cleanup (#2009)
  • fix(ui): MainWindow.OnClosed calls Logger.Shutdown() before App.OnExit, silently dropping disposal warnings (#2027)
  • fix(ui): AppBootstrapper.cs - FileSystemWatcher.Error in availability monitor only logs; does not refresh state, leaving UI stale after a buffer overflow (#2036)
  • fix(ui): HelpService.CheckUpdates - HttpResponseMessage from GetAsync is never disposed, leaking the response object and pinning pooled connections longer than necessary (#2045)
  • fix(ui): DesignTimeMocks.cs - DesignTimeServiceRepository declares duplicate GetByName overload that isn't on IServiceRepository (#2085)
  • fix(ui): AsyncCommand.Execute - OperationCanceledException logged as Error, inconsistent with ServiceManager and noisy on user cancel (#2094)
  • fix(ui): BulkObservableCollection.AddRange - partial mutation when source enumeration throws midway, leaves UI bindings out of sync (#2095)
  • fix(ui): HelpService.CheckUpdates - non-numeric latest tag (e.g. 'v1.0.0-rc.1') silently parses to 0.0.0.0 and tells users 'no updates available' (#2128)
  • fix(ui): DesignTimeMocks.cs DesignTimeHelpService - three orphan method overloads not on IHelpService (sibling of #2085) (#2145)
  • fix(ui): InverseBooleanConverter.cs - ConvertBack XML doc claims 'otherwise true' but the method returns Binding.DoNothing (#2236)
  • fix(ui): IHelpService.cs - OpenDocumentation 'caption' param doc copy-pasted from CheckUpdates (says 'during the update check') (#2342)
  • fix(ui): BulkObservableCollection.cs - TrimToSize XML doc references ".NET Framework 4.8" but the project targets net10.0-windows (#2251)
  • fix(ui): DesignTimeMocks.cs (Servy.UI) - DesignTimeCursorService.SetWaitCursor doc says 'Returns a dummy disposable' but the method returns void (#2354)
  • fix(ui): MessageBoxService.cs - Application.Current.Dispatcher dereferenced without null guard (sibling WpfUiDispatcher guards it) (#2361)
  • fix(desktop,manager): SplashWindow.xaml.cs (Servy + Servy.Manager) - leftover French XML doc comment 'Logique d'interaction pour…' inconsistent with English docs (#2339)
  • fix(desktop): ServiceCommands.cs - rotationSizeValue ternary has dead null-coalescing branch (dto.RotationSize already non-null when > 0 is true) (#1478)
  • fix(desktop): ServiceCommands.cs - XmlServiceSerializer/JsonServiceSerializer instantiated per-call instead of injected (#1491)
  • fix(desktop): MainViewModel.cs - StopTimeout XML doc says 'start timeout' (copy-paste error from StartTimeout) (#1493)
  • fix(desktop): MainViewModel.cs - BrowseFailureProgramPath XML doc references nonexistent property 'FailureProgramExecutablePath' (#1494)
  • fix(desktop): MainViewModel.cs - inconsistent IsBusy reset (ResetIsBusy() helper vs. inline 'IsBusy = false') (#1495)
  • fix(desktop): MainViewModel.cs - ResetToDefaults hardcodes RunAsLocalSystem/PreStopLogAsError instead of using AppConfig.Default* constants (#1709)
  • fix(desktop): ServiceCommands.cs - Start/Stop/RestartService show generic 'Unexpected error' on UnauthorizedAccessException, unlike Install/Uninstall which show 'Admin rights required' (#1772)
  • fix(desktop): ServiceCommands.cs - OperationResult.ErrorMessage is discarded on failure; users always see generic 'Unexpected error' even when the SCM returned a specific reason (#1815)
  • fix(desktop): MainViewModel.cs - 50 property setters fire PropertyChanged unconditionally; only ServiceName/ServiceDisplayName check for actual change (#1950)
  • fix(desktop): MainViewModel.cs - Two identical '#region Public Methods' regions in same class (#1995)
  • fix(desktop): ServiceCommands.cs - same synchronous IsServiceInstalled/GetServiceStartupType freeze as #1978, this time in the desktop app (#1996)
  • fix(desktop): ServiceCommands.cs - OpenManager swallows Process.Start exceptions and gives the user no feedback on failure (#1997)
  • fix(desktop): ServiceConfigurationValidator depends on concrete ServiceValidationRules while Servy.Manager/Validators uses IServiceValidationRules - inconsistent coupling (#2044)
  • fix(desktop): DateRotationTypeItem.cs - class summary copy-pasted from StartupTypeItem (says 'startup type' instead of date rotation type) (#2355)
  • fix(desktop): MainViewModel.cs - SelectedDateRotationType / DateRotationTypes XML docs say 'startup type' (copy-pasted from startup-type members) (#2356)
  • fix(desktop): ServiceCommands.cs - InstallService/UninstallService call IsServiceInstalled outside try/catch (asymmetric with Start/Stop/Restart) (#2360)
  • fix(desktop): MainViewModel.cs - ServiceName XML doc claims it updates a 'ServiceControllerWrapper' instance that does not exist (#2363)
  • fix(manager): ConsoleViewModel.cs - unused 'using Newtonsoft.Json.Linq' import (#1472)
  • fix(manager): PerformanceViewModel.cs - garbled XML doc: 'buffdouble stepX ers' (corrupted 'buffers') (#1482)
  • fix(manager): ConsoleViewModel.cs - 'SECURITY CHECK' label on session-id race guard is misleading (#1500)
  • fix(manager): Mappers/ServiceMapper.cs - namespace 'Servy.Manager' doesn't match folder, inconsistent with Servy.Core.Mappers convention (#1537)
  • fix(manager): LogTailer.RunFromPosition - GetFileIdentity called without IServyLogger; rotation/probe failures are silently swallowed (#1542)
  • fix(manager): CopyPidCommand declared as { get; set; } in three ViewModels; all other IAsyncCommand properties (incl. ServiceRowViewModel.CopyPidCommand) use { get; } (#1543)
  • fix(manager): erformanceViewModel - CopyPidCommand uses nameof(CopyPidAsync) (method) for AsyncCommand name; sibling ViewModels use nameof(CopyPidCommand) (property) (#1544)
  • fix(manager): App.xaml.cs - Min/Max bounds for config values hardcoded as magic numbers; AppConfig defines Default* but no Min*/Max* siblings (#1545)
  • fix(manager): DependenciesViewModel.LoadDependencyTreeAsync - redundant OnPropertyChanged(nameof(DependencyTree)) after ObservableCollection mutation (#1550)
  • fix(manager): ServiceCommands.CopyPid - 'maxRetries = 5' hardcoded as local const while the matching delay lives in AppConfig.ClipboardComRetryDelayMs (#1552)
  • fix(manager): MainWindow.xaml.cs - inconsistent parameter casing across HandleXxxTabSelected helpers ('consoleVM' / 'dependenciesVM' vs 'perfVm' / 'logsVm') (#1561)
  • fix(manager): MainViewModel.cs - 'if (ServiceCommands == null) throw' guard duplicated across 6 command handlers; ServiceCommands is already null-guarded in ctor (#1576)
  • fix(manager): MainViewModel.StopRefreshTimer - dead 'if (_appConfig != null)' check; _appConfig is set via constructor null-throw (#1577)
  • fix(manager): App.xaml.cs - malformed LogLevel config value falls back silently; inconsistent with GetConfigInt which logs a warning (#1578)
  • fix(manager): PerformanceViewModel.OnTickAsync - clears 'SelectedService.Pid' instead of snapshotted 'currentSelection.Pid'; can wipe PID of a different service if user changes selection mid-tick (#1579)
  • fix(manager): PerformanceViewModel.CopyPidAsync - dead 'ServiceCommands == null' guard; field is set in ctor via '?? throw' (#1580)
  • fix(manager): LogsViewModel.Cleanup - aliased to Dispose() makes the VM single-use; subsequent tab-switches leak CancellationTokenSource instances (#1581)
  • fix(manager): DependenciesViewModel.OnTickAsync - same stale-selection PID bug as PerformanceViewModel (#1579) (#1582)
  • fix(manager): CpuUsageConverter.Convert - XML doc says The PID. but the parameter is a double CPU usage value (#1584)
  • fix(manager): Servy.Manager Service.CpuUsage - XML doc says 'Values can exceed 100.0 if the tree utilizes multiple cores' but producer ProcessHelper.GetProcessTreeMetrics clamps to 100.0 (#1587)
  • fix(manager): MonitoringViewModelBase.OnTick - 'Interlocked.CompareExchange(ref _isMonitoringFlag, 1, 1)' used purely as a read; should be Volatile.Read (#1590)
  • fix(manager): Servy.Manager LogEntryModel.LevelIcon - stale '// LOG: ... using .NET 4.8 switch syntax' comment; project targets modern .NET (#1591)
  • fix(manager): ServiceSearchViewModelBase ctor - missing null checks for cursorService and uiDispatcher; inconsistent with rest of codebase (#1594)
  • fix(manager): LogTailer.Dispose - non-atomic _isDisposed bool check; concurrent Dispose calls can double-cancel and double-dispose _disposeCts (#1614)
  • fix(manager): MainViewModel.cs - Dispose leaks PerformanceVM/ConsoleVM/DependenciesVM and disposes ServiceCommands while child VMs still reference it (#1711)
  • fix(manager): LogTailer.cs - batch.Clear() races with async consumer; high-volume batches arrive empty to UI (#1721)
  • fix(manager): MainWindow.xaml.cs - RunAsync xmldoc claims async void but method is async Task (#1724)
  • fix(manager): ServiceCommands.cs - ImportConfigAsync bypasses ExecuteLockedAsync, can race with Start/Stop/Install/Uninstall/Remove on the same service (#1727)
  • fix(manager): LogsViewModel.cs - uses ObservableCollection.Add in a loop; should use BulkObservableCollection for large result sets (#1730)
  • fix(manager): LogTailer.cs - unhandled exceptions trigger 1-second retry loop without circuit-breaker, spamming Logger.Error indefinitely (#1731)
  • fix(manager): CpuUsageConverter.cs / RamUsageConverter.cs - design-mode check 'GetIsInDesignMode(new DependencyObject())' never returns true; designer crashes when opening MainWindow.xaml (#1733)
  • fix(manager): ViewModels - OnTickAsync error logging is inconsistent across Console/Performance/Dependencies VMs (#1735)
  • fix(manager): DependenciesViewModel.cs / ConsoleViewModel.cs - copy-pasted 'so we don't clear the zeros we just added' comment refers to graph zeros that don't exist here (#1761)
  • fix(manager): ServiceCommands.cs - CopyPid silently fails after retries; user sees confirmation on success but nothing on failure (#1764)
  • fix(manager): ViewModels/ServiceSearchViewModelBase.cs - Services collection uses plain ObservableCollection.Add in a loop; should use BulkObservableCollection like MainViewModel (#1776)
  • fix(manager): MainViewModel.cs - Dispose doesn't drain _services row VMs; final snapshot leaks N ServiceRowViewModels with attached PropertyChanged handlers (#1777)
  • fix(manager): LogEntryModel.cs - LevelIcon switch has no case for EventLogLevel.Critical (or Verbose); Critical events get the Info icon in LogsView (#1793)
  • fix(manager): ServiceSearchViewModelBase.cs - SearchServicesAsync mutates Services without re-checking the token after await; an older search can overwrite a newer one's results (#1796)
  • fix(manager): ConsoleView.xaml.cs - CopyMenuItem_Click uses raw Clipboard.SetText with no retry or COMException handling; transient clipboard locks crash the Manager UI (#1804)
  • fix(manager): ServiceMapper.cs - GetLogOnAsDisplayName only handles LocalSystem; LocalService and NetworkService leak raw 'NT AUTHORITY\…' strings into the UI (#1825)
  • fix(manager): ServiceCommands.cs - Install/Uninstall/Start/Stop discard res.ErrorMessage, always show generic Msg_UnexpectedError (#1832)
  • fix(manager): ConsoleViewModel.cs - OnTickAsync mutates stale 'currentSelection' after await, can update the previous service when SelectedService changes during the DB call (#1856)
  • fix(manager): PerformanceViewModel.cs - OnTickAsync graph data from previous service leaks into current view when selection changes during await (#1857)
  • fix(manager): MainViewModel.cs - ExecuteBulkOperationAsync does not await the MessageBox; IsBusy/cursor reset before the user closes the dialog (#1864)
  • fix(manager): LogTailer.cs - LoadHistory inline comment says '1 tick less' but code spaces synthetic timestamps by 1 millisecond (#1871)
  • fix(manager): DependenciesViewModel.cs - OnTickAsync mutates stale 'currentSelection' after GetServicePidAsync; previous service's PID can overwrite the current view (#1892)
  • fix(manager): LogLine.cs - Timestamp property xmldoc claims 'local time' but the value is always stored in UTC (#1898)
  • fix(manager): LogTailer.cs - RunFromPosition's metadata-fallback rotation branch is dead code (covered by outer condition) (#1906)
  • fix(manager): LogsViewModel.cs - FromDate/ToDate setters fire PropertyChanged twice for ToDateMinDate/FromDateMaxDate (#1908)
  • fix(manager): LogsViewModel.cs - GetLogLevels excludes Critical/Verbose but xmldoc claims 'all available log levels' (#1909)
  • fix(manager): MonitoringViewModelBase.cs - StartMonitoring is virtual but StopMonitoring is sealed; asymmetric override surface (mirror of #1818) (#1910)
  • fix(manager): ConsoleViewModel.cs - ResetConsole's 'resetLabels' parameter is dead; every caller passes true (#1948)
  • fix(manager): ServiceCommands.cs - Accidental unused 'using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window' (#1969)
  • fix(manager): ServiceCommands.cs - SemaphoreSlim leak when ConcurrentDictionary.GetOrAdd factory races (#1989)
  • fix(manager): MainViewModel.cs - DTO startup type clobbers freshly-fetched OS value on first refresh (#1994)
  • fix(manager): ConsoleViewModel.cs - RequestScroll and RequestStatePreservation event delegates never unsubscribed on Dispose, retaining the View (#1998)
  • fix(manager): ConsoleViewModel.cs - RequestStatePreservation event raised but never subscribed by any view; the 'preserve UI selection during log updates' contract is silently broken (#2001)
  • fix(manager): ServiceMapper.cs - Stale 'future-proofing' comment promises checks for LocalService/NetworkService that are already implemented two lines above (#2004)
  • fix(manager): ServiceCommands.RemoveServiceAsync - wraps async DeleteAsync in Task.Run, dropping the outer CancellationToken (#2017)
  • fix(manager): ServiceCommands.Dispose - disposes SemaphoreSlim instances without guarding against concurrent in-flight operations (#2018)
  • fix(manager): ServiceCommands.GetServiceDomain - drops CancellationToken on the repository fetch, blocking cancel during DB I/O (#2028)
  • fix(manager): LogTailer.cs - Comment claims 'EXPONENTIAL BACKOFF' but the formula is linear (baseDelay * attempts) (#2029)
  • fix(manager): IServiceCommands.cs - <param name="showMessageBox""> has a stray double-quote in three places (#2034)
  • fix(manager): DependenciesViewModel.LoadDependencyTreeAsync - DependencyTree.Clear() runs before SelectedService null-check, causing UI flash when no service is selected (#2041)
  • fix(manager): MainViewModel / LogsViewModel / DependenciesViewModel - Interlocked CTS-swap pattern races between Dispose and newCts.Token read, can throw ObjectDisposedException (#2042)
  • fix(manager): LogTailer.RunFromPosition - position tracker uses reader.CurrentEncoding.GetByteCount, mis-counts on non-UTF8 BOM-less files and can drop bytes on transient error (#2064)
  • fix(manager): PerformanceViewModel.cs - Task.Run drops CancellationToken in OnTickAsync, asymmetric with Servy.Manager ServiceMapper.ToModelAsync (#2081)
  • fix(manager): ServiceMapper.GetLogOnAsDisplayName - does not recognize NT AUTHORITY\SYSTEM / .\LocalSystem aliases for the LocalSystem account, leaks raw SCM string into the UI (#2087)
  • fix(manager): MainWindow.xaml.cs RunAsync / MainTabControl_SelectionChangedAsync - OperationCanceledException logged as Error on every tab switch / window close (#2096)
  • fix(manager): LogsViewModel.Search - oldCts.Cancel() / Dispose() lack try/catch and finally; inconsistent with Cleanup() and leaks the CTS if Cancel throws (#2103)
  • fix(manager): MonitoringViewModelBase / PerformanceViewModel / ConsoleViewModel - same Interlocked CTS-swap race as #2042 in shared base + two un-listed ViewModels (#2114)
  • fix(manager): ServiceManager.GetAllServices - silent partial results when cancellation fires mid-Parallel.ForEach (#2123)
  • fix(manager): ServiceSearchViewModelBase - same Interlocked CTS-swap race as #2042/#2114; concurrent SearchCommand vs. Dispose leaks the new CTS (#2127)
  • fix(manager): oldCts.Cancel()/Dispose() lack try/catch in 11 additional ViewModel sites (sibling of #2103) (#2137)
  • fix(manager): ServiceRowViewModel - StartCommand is enabled in StartPending/StopPending/Paused/None states, causing SCM errors on click (#2138)
  • fix(manager): ConsoleViewModel.SwitchServiceAsync - List.Sort by Timestamp is unstable, so stdout/stderr lines with tied synthetic timestamps interleave non-deterministically in history (#2143)
  • fix(manager): CpuUsageConverter and RamUsageConverter - near-identical scaffolding can be extracted into a shared base or generic (#2178)
  • fix(manager): LogTailer.Dispose - _disposeCts.Cancel() / Dispose() lack try/catch; CTS leaks if Cancel throws (sibling of #2103 / #2137) (#2205)
  • fix(manager): ConsoleView.xaml.cs CopyMenuItem_Click - Thread.Sleep retry loop runs on the WPF UI thread; freezes UI on clipboard contention (asymmetric with ServiceCommands.CopyPid which uses async Task.Delay) (#2229)
  • fix(manager): ServiceSearchViewModelBase.SearchServicesAsync - stale superseded search clobbers the newer search's IsBusy/cursor/button state in finally (#2247)
  • fix(manager): DependenciesViewModel.LoadDependencyTreeAsync - superseded load's finally resets IsBusy while a newer load is still running (#2248)
  • fix(manager): LogsViewModel.Search - superseded search's finally resets IsBusy/cursor/SearchButtonText while a newer search is still running (#2249)
  • fix(manager): LogsViewModel.cs - ToDate setter raises redundant/spurious OnPropertyChanged(nameof(FromDateMaxDate)) (#2264)
  • fix(manager): LogTailer.RunFromPosition - per-line byte-offset estimation (incl. unused 'charCount' and per-line GetByteCount) is dead; lastPosition is always overwritten by fs.Position at EOF (#2283)
  • fix(manager): DesignTimeAppConfig.cs - SearchDebounceDelayMs hardcodes literal 300 instead of referencing AppConfig.DefaultSearchDebounceDelayMs (#2325)
  • fix(manager): App.xaml.cs (Manager) - LogLevel uses but IAppConfiguration has no LogLevel member, so docs resolve to nothing (#2333)
  • fix(manager): DesignTimeAppConfig.cs - MaxBulkOperationParallelism declared public while all other IAppConfiguration members use explicit interface implementation (#2335)
  • fix(manager): IServiceCommands.cs / ServiceCommands.cs - ExportServiceToXml/JsonAsync lack CancellationToken (asymmetric with Import and every other async command) (#2336)
  • fix(manager): LogsViewModel.cs - Search() XML doc references nonexistent member (#2337)
  • fix(manager): MainViewModel.cs - SearchServicesAsync finally resets IsBusy/cursor/button even when superseded by a newer search (#2338)
  • fix(manager): IServiceCommands.cs / ServiceCommands.cs - CopyPid returns Task but lacks the Async suffix (inconsistent with every other async member) (#2343)
  • fix(manager): IServiceCommands.cs - CopyPid doc says 'The service to configure' (copy-pasted from ConfigureServiceAsync) (#2344)
  • fix(manager): IServiceConfigurationValidator.cs - Validate returns Task but lacks the Async suffix (#2345)
  • fix(manager): DependenciesView.xaml.cs - class summary copy-pasted from ConsoleView (says 'live-monitoring stdout/stderr') (#2346)
  • fix(manager): MainWindow.xaml.cs - HandleMainTabSelected XML doc omits the consoleVm parameter (#2357)
  • fix(manager): Cannot remove service with status "Not Installed" - Uninstall button greyed out (#2904)
  • fix(manager,core): ServiceMapper.cs / ServiceAccounts.cs - XML doc/comment claims service-account strings come from WMI, but Servy uses no WMI (#2294)
  • fix(cli): BaseCommand.ExecuteWithHandling - sync path missing OperationCanceledException catch present in async sibling (#1473)
  • fix(cli): ImportServiceCommand.ProcessImportInternalAsync - same content is deserialized twice (once for validation, again inside repoImporter) (#1617)
  • fix(cli): Program.cs - 'using System.Diagnostics' under #if !DEBUG is dead; nothing in the file references the namespace in Release (#1649)
  • fix(cli): InstallServiceOptions.cs - XML doc 'Possible values' lists out of sync with [Option] HelpText (missing AutomaticDelayedStart, missing None) (#1656)
  • fix(cli): Servy.CLI/Program.cs - Run() extracts embedded resources on every invocation, including --help and --version (#1760)
  • fix(cli): ImportServiceCommand -> ServiceValidationRules - validator calls NativeMethods.ValidateCredentials (LogonUser) on imported user/password, but the deserializer immediately resets them to LocalSystem; failed imports can lock out the target account for nothing (#1781)
  • fix(cli): ImportServiceCommand.cs - input path lacks the UNC block and junction/symlink resolution that ExportServiceCommand applies; admin imports can silently pull from \attacker\share via a junction (#1783)
  • fix(cli): ImportServiceCommand.cs / ExportServiceCommand.cs - UNC infiltration/exfiltration guard bypassed via NTFS junction; check runs on un-resolved path only (#1786)
  • fix(cli): ImportServiceCommand.cs / ExportServiceCommand.cs - file-level symlink bypasses UNC infiltration/exfiltration guard added in #1786; only directory junctions are resolved (#1790)
  • fix(cli): ImportServiceCommand.cs - repoImporter return value discarded; CLI import reports success even when UpsertAsync persists 0 rows (#1794)
  • fix(cli): ImportServiceCommand.cs - Reparse-point guard only inspects immediate parent directory; intermediate ancestor junctions bypass the check (#1795)
  • fix(cli): Import/ExportServiceCommand - duplicate ConfigFileType parsing logic (TryParseFileType vs inline parse) (#1800)
  • fix(cli): ExportServiceCommand.cs - typeLabel uses culture-dependent ToUpper() instead of ToUpperInvariant() (#1801)
  • fix(cli): InstallServiceOptions.cs - broken XML doc cref 'Constants.DefaultUseLocalTimeForRotation' (no such symbol); actual default lives on AppConfig (#1814)
  • fix(cli): BaseCommand.cs - HandleException builds error messages with hardcoded English ('Failed to {action}', 'Suggestion:'), while other failure paths localize via Strings.resx (#1827)
  • fix(cli): Program.cs - EnsureEventSourceExists runs before argument parsing, forcing admin rights for --help / --version on first run (#1830)
  • fix(cli): ImportServiceCommand.cs - UNC import guard bypassed by mapped network drive (DriveType.Network not checked) (#1831)
  • fix(cli): InstallServiceCommand.cs - duplicate ParseEnumOption shadows ConfigParser.ParseEnum and silently swallows invalid enum values (#1845)
  • fix(cli): ImportServiceCommand.cs - UNC normalization check uses Contains("\UNC"), false-positives on local paths with a folder literally named UNC (#1875)
  • fix(cli): ExportServiceCommand.cs - SaveFile writes content BEFORE the GetFinalPathNameByHandle UNC verification; data leaves the box before the security check fires (#1876)
  • fix(cli): ExportServiceCommand.cs - SaveFile's outer catch re-wraps the deliberate UNC SecurityException, hiding the specific reason (#1887)
  • fix(cli): ImportServiceCommand.cs - reserved-device-name guard only checks the FIRST filename segment; siblings (e.g. service.CON.json) slip through (#1939)
  • fix(cli): ImportServiceCommand.cs - fileInfo.Length size check uses cached state from before the security probes, allowing files that grow past MaxConfigFileSizeBytes to be loaded fully into memory (#1946)
  • fix(cli): ExportServiceCommand.SaveFile - OpenOrCreate without SetLength leaves trailing bytes from previous export when overwriting a longer file (#2019)
  • fix(cli): ImportServiceCommand - Inlines existence and size check instead of using ImportGuard.ValidatePathAndSize (#2022)
  • fix(cli): ExportServiceCommand.SaveFile - reserved-device-name check rejects valid filenames where 'CON'/'NUL'/etc. appears as a non-leading segment (#2038)
  • fix(cli): ExportServiceCommand.SaveFile - generic catch-block File.Delete can erase pre-existing user data on transient I/O failure (#2039)
  • fix(cli): ServiceInstallValidator.MapEnum - Enum.IsDefined rejects valid combined values, silently breaking any future [Flags] enum (same root cause as #2014) (#2040)
  • fix(cli): RestartServiceCommand.cs - GetServiceStartupType drops CancellationToken, inconsistent with StartServiceCommand (#2076)
  • fix(cli): UninstallServiceCommand.cs - repository DeleteAsync drops CancellationToken after SCM uninstall succeeds (#2077)
  • fix(cli): ExportServiceCommand.SaveFile - protected-folder check evaluates input path, not resolved path; symlink can redirect write into a system directory (#2113)
  • fix(cli): Program.cs Main - finally-block disposal cascade is not exception-safe (same pattern as #2109) (#2136)
  • fix(cli): Program.cs Run - embedded ServyServiceCLI.exe copy failure only prints to Console; CLI continues with degraded/missing service binary while Servy.UI throws on the same failure (#2149)
  • fix(cli): ServiceInstallValidator.MapInt - culture-sensitive int.TryParse diverges from ConfigParser.ParseInt (InvariantCulture) (#2156)
  • fix(cli): ExportServiceCommand.SaveFile - SecurityException catch does not delete the stub file created by FileStream.OpenOrCreate, leaving 0-byte artifacts on disk (#2167)
  • fix(cli): ExportServiceCommand.SaveFile - handle-resolution validation block is silently skipped when GetFinalPathNameByHandle returns 0, bypassing UNC and protected-folder re-checks (#2195)
  • fix(cli): ExportServiceCommand.SaveFile - generic catch reports ordinary write failures (disk full, sharing violation, ACL denial) as 'Security Guard Failure: Target file handle validation rejected' (#2237)
  • fix(cli): service-control commands - Start/Stop/Restart/Uninstall share a near-identical Execute skeleton (#2532)
  • fix(psm1): Servy.psm1 - Format-SecureLogMessage hardcodes sensitive field list, decoupled from CLI options (#722)
  • fix(psm1): misleading 'CLI moved or deleted' message also fires when CLI was never present at module load (#1496)
  • fix(psm1): Invoke-ServyCli - 'null check' on $exitCode is dead (Process.ExitCode is non-nullable int) (#1497)
  • fix(psm1): Marshal.PtrToStringAuto used with BSTR; PtrToStringBSTR is the canonical pair to SecureStringToBSTR (#1504)
  • fix(psm1): PreLaunchTimeout PS ValidateRange (0..86400) does not match C# Max (0..1000) (#1516)
  • fix(psm1): Install-ServyService - session-level $env:SERVY_PASSWORD is unconditionally Remove-Item'd in finally, breaking batch use (#1632)
  • fix(psm1): Invoke-ServyCli - magic numbers 50ms (poll interval) and 5000ms (kill timeout) repeated three times inline (#1633)
  • fix(psm1): Set-ServyConfig accepts zero or negative MaxBufferChars/TimeoutSeconds without validation (#1670)
  • fix(psm1): -Deps ValidatePattern rejects valid service names containing dots (#1694)
  • fix(psm1): -User ValidatePattern rejects gMSA accounts and UPN-style usernames (#1707)
  • fix(psm1): Invoke-ServyCli omits StandardOutputEncoding/StandardErrorEncoding, mangles non-ASCII CLI output (#1715)
  • fix(psm1): PreLaunchTimeout ValidateRange caps at 1000s while siblings allow 86400s (#1742)
  • fix(psm1): Format-SecureLogMessage regex breaks on values containing escaped quotes, leaking the tail of sensitive arguments (#1860)
  • fix(psm1): Format-SecureLogMessage uses [regex]::Replace with a script block, but PowerShell 2.0 cannot auto-convert script blocks to MatchEvaluator delegates (#1873)
  • fix(psm1): Assert-Administrator calls $identity.Dispose() but WindowsIdentity does not implement IDisposable until .NET 4.5; throws on PS 2.0 / .NET 3.5 (#1924)
  • fix(psm1): Install-ServyService silently drops password when PtrToStringBSTR yields empty string (#1967)
  • fix(psm1): Invoke-ServyCli timeout calls Process.Kill() which orphans CLI child processes (#1977)
  • fix(psm1): --envVars and --params values are exposed on the CLI process command line, defeating the log-scrubbing in Format-SecureLogMessage (#1986)
  • fix(psm1): Format-SecureLogMessage unquoted-value pattern only masks first whitespace-delimited token, leaving multi-word values exposed (#1992)
  • fix(psm1): Install-ServyService docstring EXAMPLE uses the deprecated -EnableRotation switch that the same module warns about at runtime (#2005)
  • fix(psm1): Install-ServyService - DEVELOPER NOTE describes auto-derived 1:1 param mapping, but code uses an explicit $paramMapping hashtable (#2260)
  • fix(psm1): Stdout/Stderr/PreLaunch* path validators reject bare relative filenames (empty-parent), inconsistent with Export Path validator (#2289)
  • fix(psm1): stale comment: EnvVars 28,000-char cap is justified by 'CLI argument' limit, but EnvVars is now passed via environment variable, not the command line (#2303)
  • fix(psm1): Install-ServyService -Deps ValidatePattern rejects '$', blocking dependencies on SQL Server named-instance services (MSSQL$INSTANCE) (#2312)
  • fix(psm1): Params/PreLaunchParams/PostLaunchParams/PreStopParams/PostStopParams/FailureProgramParams lack the [ValidateLength] cap that EnvVars/PreLaunchEnv have, despite all being injected via environment variables (#2322)
  • fix(test): tests/test.ps1 - Second '--' separator before Exclude filter makes the XAML/g.cs runsettings exclusion never reach vstest (#2023)
  • fix(notifications): ServySecurity.ps1 Protect-SensitiveString - declares ValueFromPipeline=$true but has no process { } block, so pipeline input only processes the last value (#1513)
  • fix(notifications): ServyFailureEmail.ps1 - ConvertTo-HtmlSafe doc claims 'PowerShell 2.0' compatibility, but script #Requires -Version 3.0 (#1503)
  • fix(notifications): Servy-Watermark.psm1 - Update-Watermark failures create a feedback loop in the notification pipeline (no '^Failed to update timestamp file' filter) (#1533)
  • fix(notifications): ServyFailureNotification.ps1 - stale comment references nonexistent EventIds.cs constant 'ScriptDependencyMissing' (#1536)
  • fix(notifications): ServyFailureNotification.ps1 / EventIds.cs - event ID 3104 duplicated between PowerShell ('EVENT_ID_DEPENDENCY_ERROR') and C# ('ScheduledTaskScriptDependencyError') (same family as #1396) (#1618)
  • fix(notifications): Write-ServyLog.ps1 - rotated log files never deleted; unbounded disk growth over time (#1621)
  • fix(notifications): Write-ServyLog.ps1 - same-second rotation overwrites previously rotated log with Rename-Item -Force (#1622)
  • fix(notifications): ServyFailureEmail.ps1 Send-NotificationEmail - ValueFromPipeline=$true without a process { } block (same family as #1513) (#1623)
  • fix(notifications): Servy-Watermark.psm1 Write-FallbackError - Write-Host with -ForegroundColor produces nothing in Task Scheduler context (#1624)
  • fix(notifications): ServyFailureNotification.ps1 - magic numbers for toast tag length (64), expiration (5 min) and inter-toast delay (500ms) (#1625)
  • fix(notifications): ServyFailureEmail.ps1 - Port reader missing whitespace tolerance that UseSsl / TimeoutMs readers have (#1626)
  • fix(notifications): ServyFailureEmail.ps1 - smtpServer/From/To validated with IsNullOrEmpty; whitespace-only values pass the gate (#1627)
  • fix(notifications): Get-ServyLastErrors.ps1 - broken indentation around fallback dependency check (lines 87-101) (#1628)
  • fix(notifications): Write-ServyLog.ps1 - rotation cleanup pattern resolves to '*.log' because $baseName_ is parsed as one variable (#1674)
  • fix(notifications): Servy-Watermark.psm1 - Update-Watermark has a TOCTOU lost-update race between concurrent script instances (#1676)
  • fix(notifications): Write-ServyLog.ps1 - collision-suffix rotation produces files ending in '.N' that the cleanup glob never matches (#1682)
  • fix(notifications): Servy-Watermark.psm1 - Update-Watermark leaks StreamReader/StreamWriter and writes a UTF-8 BOM on every truncate (#1748)
  • fix(notifications): Servy-Watermark.psm1 - ConvertFrom-ServyEventMessage regex fails on multiline event messages; service name shows as 'Unknown Service' whenever an exception is logged (#1774)
  • fix(notifications): ServyFailureEmail.ps1 - Protect-SensitiveString regex timeout crashes the script with the watermark un-advanced, producing an infinite restart loop on the same event (#1780)
  • fix(notifications): ServyFailureNotification.ps1 - Protect-SensitiveString regex timeout in Show-Notification halts the queue and never advances the watermark, producing an infinite retry loop on the poison event (#1811)
  • fix(notifications): Write-ServyLog.ps1 - collision-retry loop's 'while (Test-Path $target -and $attempt -lt 100)' is parsed as a Test-Path command and throws ParameterBindingException (#1899)
  • fix(notifications): ServyFailureNotification.ps1 - Show-Notification returns $true immediately after notifier.Show() (async); watermark advances even when the Failed event later signals delivery failure (#1925)
  • fix(notifications): Servy-Watermark.psm1 - AddTicks(1) + strict > filter can silently drop events whose TimeCreated equals prev+1 tick (#2003)
  • fix(notifications): ServySecurity.ps1 - 'keep in sync' comment incorrectly lists Servy.psm1's Format-SecureLogMessage (#2026)
  • fix(notifications): ServyFailureNotification.ps1 - $ServiceName not masked in toast title, leaks raw event data (#2032)
  • fix(notifications): ServySecurity.ps1 Protect-SensitiveString - space-separator branch only masks first token, leaking multi-word values (same root cause as #1992 but in this PS1 script) (#2056)
  • fix(notifications): Write-ServyLog.ps1 - comment says 'no BOM' but Encoding.UTF8 emits a BOM; first write to a new log file is prefixed with U+FEFF bytes (#2057)
  • fix(notifications): ServyFailureEmail.ps1 - ServicePointManager.SecurityProtocol not pinned, EnableSsl can negotiate down to SSL3/TLS1.0 on older Windows Server hosts (#2078)
  • fix(notifications): Write-ServyLog.ps1 - rotation TOCTOU and unlocked AppendAllText race when multiple processes share a log file (#2088)
  • fix(notifications): ServyFailureEmail.ps1 Send-NotificationEmail - generic catch classifies permanent errors (e.g. CRLF in Subject, MailMessage validation) as TransientFailure, freezing the email queue forever (#2093)
  • fix(notifications): ServyFailureEmail.ps1 - 'To' validation regex rejects multi-recipient configurations even though MailMessage.To.Add supports them (#2107)
  • fix(notifications): ServyFailureNotification.ps1 Show-Notification - 750ms add_Failed wait misses async toast delivery failures (Focus Assist, disabled notifications, OS suppression); watermark advances and error is lost forever (#2108)
  • fix(notifications): Servy-Watermark.psm1 Update-Watermark - duplicate notifications when lock contention exceeds 1s budget (#2194)
  • fix(notifications): ServyFailureEmail.xml - RunOnlyIfNetworkAvailable is false, so the task fires on every Servy error event even when no network is reachable (#2209)
  • fix(notifications): ServyFailureEmail.vbs / ServyFailureNotification.vbs - shell.Run exit code discarded, so Task Scheduler always records 0x0 even when the PowerShell child fails (#2210)
  • fix(notifixations): ServyFailureEmail.ps1 Send-NotificationEmail - '-bor Tls12/Tls13' does not pin TLS; SSL3/TLS 1.0 stay enabled where the runtime default included them (#2225)
  • fix(notifications): setup/taskschd/ServyFailureNotification.xml + ServyFailureEmail.xml - DisallowStartIfOnBatteries=true combined with StartWhenAvailable=false silently drops failure alerts on battery-powered hosts (#2234)
  • fix(notifications): Write-ServyLog.ps1 - SHA256 instance created per call is never disposed (handle/memory churn under frequent logging) (#2252)
  • fix(notifications): Write-ServyLog.ps1 - AbandonedMutexException silently drops the log line and re-abandons the mutex, permanently disabling file logging until process restart (#2253)
  • fix(notifications): ServyFailureEmail.ps1 / ServyFailureNotification.ps1 - dangling, nonsensical comment inside the dependency-missing Write-EventLog block (#2284)
  • fix(notifications): ServyFailureNotification.ps1 - if CreateToastNotifier throws, $notifier stays null and $notifier.Show() NPEs; the 'fall back gracefully to standard delivery' comment is not implemented (#2285)
  • fix(notifications): ServySecurity.ps1 - header says masker parity is with 'Servy.Core' but the inline sync comment correctly points to Servy.Service/Helpers/ServiceHelper.cs (#2290)
  • fix(notifications): ServyFailureEmail.ps1 - multi-recipient validation block (lines 209-224) is dedented to column 0, breaking Send-NotificationEmail indentation (#2291)
  • fix(notifications): ServyFailureNotification.ps1 - no 'permanent failure advances watermark' path (asymmetric with ServyFailureEmail.ps1); a permanently-undeliverable toast stalls the queue forever (#2292)
  • fix(notifications): ServyFailureNotification.ps1 - no-op 'continue' in delivery-status switch with a nonsensical comment; loop break is actually handled by the if below (#2296)
  • fix(notifications): Servy-Watermark.psm1 - Read-Watermark does not Trim() file content before ParseExact, asymmetric with Update-Watermark which does (#2308)
  • fix(notifications): ServyFailureNotification.ps1 - Show-Notification's add_Failed handler calls Write-FallbackError from a WinRT callback thread that has no PowerShell runspace (#2309)
  • fix(notifications): ServyFailureNotification.ps1 - notification-suppressed branch logs "Skipping watermark advance" but returns 'PermanentFailure', which advances the watermark and permanently drops those failures (#2311)
  • fix(notifications): Write-ServyLog.ps1 - Global\ mutex creation fails for non-elevated standard users, silently dropping all fallback file logging (#2314)
  • fix(notifications): Get-ServyLastErrors.ps1 - first-run query is unbounded (no -MaxEvents), loads every historical Servy error just to use the most recent one (#2315)
  • fix(notifications): ServyFailureNotification.ps1 - Notification-suppression probe returns 'TransientFailure' but both comments say permanent / 'save the watermark', causing head-of-line blocking (#2316)
  • fix(notifications): ServyFailureEmail.ps1 - SmtpException classification treats GeneralFailure (-1) as transient, stalling the queue forever on permanent auth/config errors (#2318)
  • fix(notifications): ServyFailureNotification.ps1 - Unreachable catch [RegexMatchTimeoutException] around Protect-SensitiveString (#2320)
  • fix(notifications): Get-ServyLastErrors.ps1 - first-run -MaxEvents 1 defeats the feedback-loop pre-filter, masking a genuine crash (#2321)
  • fix(bump-runtime): per-file try/catch is broken by ErrorActionPreference=Stop; one bad file aborts the whole runtime bump (#1657)
  • fix(bump-runtime): doesn't update global.json SDK version; CI breaks after runtime bump (#1698)
  • fix(bump-runtime): global.json version regex only updates major.minor, leaves stale build number that may not exist as an SDK (#2063)
  • fix(bump-runtime): Get-FileEncoding.ps1 - UTF-32 Big Endian BOM (00 00 FE FF) is not detected; UTF-32 BE files are misclassified as UTF-8 (#2201)
  • fix(bump-version): Update-FileContent - Write-Error + ErrorActionPreference='Stop' makes 'return' dead code; one missing pattern aborts whole bump mid-flight (#1521)
  • fix(bump-version): no per-file isolation; one bad file aborts the entire version bump (related to #1657) (#1658)
  • fix(bump-version): DESCRIPTION lists publish.ps1/publish-sc.ps1/publish-fd.ps1 as updated targets, but the script never touches them (#1756)
  • fix(bump-version): Update-FileContent silently returns and exits 0 when regex pattern stops matching, allowing stale versions to ship (#1789)
  • fix(bump-version): version-tag regex does not support form, silently skipping conditional version metadata (#2086)
  • fix(publish): publish.ps1 / publish-sc.ps1 / publish-fd.ps1 - Tfm + Version defaults duplicated across three scripts (drift risk on bumps) (#1570)
  • fix(publish): tools-config.ps1 - Resolve-Tool reads undefined $envPath; SERVY_TOOL_* env var lookup is dead code (#1620)
  • fix(publish): signpath.ps1 - 'Write-Error … return' pattern at six sites swallows fatal errors when called inside try/catch (#1629)
  • fix(publish): publish-common.ps1 Build-Installer - magic numbers maxRetry=3 and Start-Sleep -Seconds 2 should be parameters/constants (#1630)
  • fix(publish): signpath.ps1 - inline comments treated as part of config value, breaking SIGN flag check (#1688)
  • fix(publish): signpath.ps1 - BuildData.Url constructed with no validation that GitHub env vars are set (#1689)
  • fix(publish): publish-common.ps1 - Build-Installer retry loop catches all exceptions, wasting time on non-transient errors (#1692)
  • fix(publish): publish.ps1 - $LASTEXITCODE check after PowerShell child scripts is unreliable (#1700)
  • fix(publish): publish-sc.ps1 - Step 3 packaging catch block writes an error but never sets a non-zero exit code; failure leaves the workflow green (#1757)
  • fix(publish): publish.ps1 - outer catch swallows failures and never sets a non-zero exit code; CI sees green on red builds (#1768)
  • fix(publish): tools-config.ps1 - Resolve-Tool's Get-Command lookup omits -CommandType Application; an alias/function/script with the tool's name shadows the real executable (#1779)
  • fix(publish): publish-res.ps1 - local Assert-LastExitCode duplicates common-helpers.ps1 but is missing the 'exit $LASTEXITCODE' line (#1791)
  • fix(publish): publish-common.ps1 - New-PortablePackage passes paths via Start-Process -ArgumentList; breaks the 7z command if the package path contains a space (#1926)
  • fix(publish): publish-common.ps1 Build-Installer - AV-lock retry regex never matches; only retries on the rare LASTEXITCODE=-1 (#2033)
  • fix(publish): signpath.ps1 - failed signing leaves orphan $Path.signed on disk; a later run silently moves the stale artifact over the real binary (#2092)
  • fix(publish): publish-res-{debug,release}.ps1 (×8 files) - builds a self-referential path '..<SelfDir>\Resources' instead of 'Resources' (#2186)
  • fix(publish): signpath.ps1 - inline-comment regex strips '\s+#.*' from inside quoted values, dropping the closing quote and corrupting the value (#2199)
  • fix(publish): signpath.ps1 - 'elif' is not a PowerShell keyword; single-quoted and unquoted config values are never parsed (signing silently mis-skipped) (#2313)
  • fix(publish): publish-res.ps1 - two large commented-out artifact-copy blocks left in the script (#2530)
  • fix(publish): setup/publish.ps1 - comment-based help still documents a framework-dependent build and a -IncludeFrameworkDependent switch the script no longer implements (#2563)
  • ci: Multiple workflows (sonar/scoop/choco/changelog/bump-version/dotnet-reflection/build/sbom/wiki/release) - Third-party actions pinned to mutable tags (same family as #607, #1304, #1309, #1342) (#1360)
  • ci(dotnet): .github/actions/setup-dotnet/action.yml - Authenticode SignerCertificate.Subject regex check uses unanchored substring match, allowing any cert whose O= field contains 'Microsoft Corporation' as a substring (#2204)
  • ci(dotnet): setup-dotnet/action.yml - comment claims 'Using -Version instead of -Channel' but the script passes -JsonFile (#2560)
  • ci(build): build.yml - dotnet restore loop ignores $LASTEXITCODE; later --no-restore builds fail with misleading error (#1734)
  • ci(test): test.yml - Third-party actions pinned to mutable tags, supply chain risk (same as #607, different workflow) (#1304)
  • ci(test): test.yml - $args shadowed in test loop; PowerShell automatic variable should not be reassigned (#1699)
  • ci(test): 'dotnet tool install dotnet-reportgenerator-globaltool --version 5.*' uses a floating major version, breaking build reproducibility (#1940)
  • ci(security): security.yml - Third-party actions pinned to mutable tags, supply chain risk in the security-audit workflow itself (same as #607, #1304) (#1309)
  • ci(sonar): sonar.yml SONAR_TOKEN secret interpolated directly into shell command instead of using env var (#1673)
  • ci(publish): publish.yml - Third-party actions pinned to mutable tags, supply chain risk (#607)
  • ci(publish): version extraction reads stale parameter default from publish.ps1, always yields empty string (#1690)
  • ci(publish): publish.yml - section comment says 'Extract Version from setup/publish.ps1' but the step reads setup/build-config.ps1 (#2559)
  • ci(bump-version): minor-version rollover at 9 prevents 10+ minor versions; never publishes 8.10, 9.11, etc. (#1648)
  • ci(bump-version): leftover commented-out tag-resolution block in 'Set version vars' step (#1669)
  • ci(bump-version): unchecked 'git push' calls on main and net48 branches; same failure mode as #1729 and #1869 (#1881)
  • ci(bump-version): ./bump-version.ps1 invoked without Invoke-Git/exit-code check; script failure is silently treated as 'no changes to commit' (#2203)
  • ci(winget): winget.yml - winget-releaser action pinned by SHA with no version comment, unlike every other pinned action in the repo (#2529)
  • ci(choco,scoop,bump-version): choco.yml, scoop.yml & bump-version.yml - identical 'Set version vars' tag-resolution block repeated in three workflows (#2527)
  • ci(choco): chocolateyuninstall.ps1 - Test-Path fails on UninstallString with quoted path or embedded args, silently skips uninstall (#1691)
  • ci(choco): choco.yml - choco pack / choco push native exit codes are not checked; a failed pack still triggers a push that can upload a stale nupkg or report green on a broken package (#1812)
  • ci(choco): chocolateyuninstall.ps1 - $extraArgs is parsed from the registry UninstallString but then silently discarded; any registered uninstall flags are ignored (#2197)
  • ci(choco): choco.yml - retry loop never aborts a paused rebase; one git pull conflict deadlocks all 5 attempts (#2216)
  • ci(choco): choco.yml - 'nothing to commit' check uses '$(git status --porcelain) -eq $null', deviating from the '-ne '''' idiom in scoop.yml/bump-version.yml (#2561)
  • ci(scoop): scoop.yml - Extras PR step uses 'git push --force' instead of '--force-with-lease', risking overwrite of concurrent updates (#1693)
  • ci(scoop): scoop.yml - 'if (git show-ref --verify --quiet ...)' always evaluates to false in PowerShell (#1683)
  • ci(scoop): scoop.yml - Write-ScoopManifest doubles every leading-space block, producing 8-space JSON indentation under pwsh 7 (#1697)
  • ci(scoop): multiple unchecked 'git pull' and 'git push' calls in the manifest-update job; same failure mode as #1729 in a different file (#1869)
  • ci(sonar): 'dotnet build' failure is masked by the trailing 'dotnet-sonarscanner end' step exit code; Sonar workflow reports green on broken builds (#1771)
  • ci(sonar): 'dotnet tool install dotnet-sonarscanner' has no version pin, compromising build reproducibility (#1941)
  • ci(sonar): sonar.yml - SonarCloud scanner cache key omits SONAR_SCANNER_VERSION, so bumping the version constant has no effect after the first cache hit (#2202)
  • ci(security): vulnerable-package detection uses fragile English string match on dotnet list output; non-en locale or SDK rewording silently passes (#1913)
  • ci(sbom): sbom.yml - adds entire $env:USERPROFILE to PATH and skips integrity pinning for dotnet-CycloneDX tool (#1728)
  • ci(sbom): sbom.yml - 'dotnet tool install --add-source' does not restrict the install to the SHA256-verified local nupkg; nuget.org can still satisfy the request (#1912)
  • ci(sbom): sbom.yml - '--ignore-failed-sources' on CycloneDX tool install silently bypasses the SHA-256 pinning when the local source fails (#2217)
  • ci(sbom): sbom.yml - SHA256 pinning only covers the CycloneDX top-level .nupkg; transitive dependencies are fetched unverified from NuGet.org (#2226)
  • ci(release): release.yml - workflow list ['winget','choco','scoop','bump-version','changelog'] hard-coded twice (trigger + script) (#1647)
  • ci(loc): loc.yml - peaceiris/actions-gh-pages@v3 pinned to mutable tag, supply chain risk (same family as #607, #1304, #1309) (#1342)
  • ci(loc): loc.yml - GHA-LoC-Badge action pinned by SHA with no version comment, unlike other pinned actions (#2558)
  • ci(changelog): git pull --rebase failure is not checked; git push runs regardless and can fail or push stale data (#1729)
  • ci(changelog): 'gh api releases' failure (rate limit / 5xx) is unchecked; workflow reports 'No releases found' and exits 0, silently skipping the changelog update (#1868)
  • ci(changelog): changelog.yml - Duplicate dead-code empty-releases check; second if block is unreachable (#2206)
  • chore(deps): update dependencies
  • other fixes: check commits for more details

Full Changelog: v8.4...v8.5

Don't miss a new servy release

NewReleases is sending notifications on new releases.