Skip to content

Servy 9.9

Latest

Choose a tag to compare

@aelassas aelassas released this 28 Aug 09:44
· 6 commits to main since this release

This release includes bug fixes, security patches, code quality improvements, and documentation updates. The full changelog is listed below.

Full Changelog

Click to expand release notes!
  • fix(security): explicitly assign admin owner and streamline single-pass ACL hardening in Set-ServyExePermissions.ps1
  • fix(security): add admin privilege check to Set-ServyExePermissions.ps1
  • fix(core): ServiceAccounts.cs - IsGmsa trims and whitespace-checks the account but not the password, so a gMSA with a whitespace-only password is sent through the LSA privilege assignment its own doc forbids (#5702)
  • fix(core): ProcessHelper.cs - FormatRamUsage prints 1024.0 KB/MB/GB just below each unit boundary because the unit is chosen before rounding (#5716)
  • fix(core): SecureXml.cs / JsonSecurity.cs - the eight ignored ServiceDto fields are silently dropped by a JSON import but abort an XML import, and the wiki documents only the JSON behaviour (#5751)
  • fix(core): ServiceAccounts.cs - LocalSystemAliases omits the bare 'Local System' and '.\Local System' forms its two sibling sets gained in #4713/#5021, so the Manager's own display label fails IsBuiltInServiceAccount (#5900)
  • fix(core): EnvironmentVariablesValidator.cs - the key guard covers CR, LF and NUL but not '=', so an escaped = smuggles an equals sign into a variable NAME and slips past the ProtectedVariables check (third case of #3768/#4716) (#5905)
  • fix(core): ServiceAccounts.cs / NativeMethodsHelpers.cs / ServiceManager.cs - three consumers trim the service account name, no producer does, so a padded name passes validation as built-in and reaches CreateService un-trimmed (#5909)
  • fix(core): AffinityHelper.cs - an inverted range and a zero hex mask are both reported as "Core index in range ... is out of bounds", which is false for both (#5911)
  • fix(core): ConfigParser.cs - the Flags parity check in ParseEnum(string) uses long.TryParse, which fails open for ulong-backed values and is culture-sensitive; the third site the #1341 invariant sweep missed (#5913)
  • fix(core): ReservedNames.cs - the list is shared but the normalisation is not: "CON .txt" is blocked by PathSecurityGuard, accepted by IsServiceNameValid and left unescaped by MakeFilenameSafe (residual of #2141) (#5921)
  • fix(core): ResourceHelper.cs - every re-extraction freezes the vault ACL onto the extracted binaries, so a grant or revocation on the vault root never reaches Servy.Service.exe again (#5922)
  • fix(core): EventLogReader.cs - SafeToOffset's DateTimeKind.Utc branch is unreachable from its only caller (EventRecord.TimeCreated comes from DateTime.FromFileTime, always Local), and the MinValue guard precedes the one path that cannot overflow (#5939)
  • fix(core): LogonAsServiceGrant.cs - #5532's 'this is expected, service creation will proceed' catch guards only the grant; the LSA check one line above throws the same exception uncaught, and #3218's exception docs are gone again (#5943)
  • fix(core): RotatingStreamWriter.cs - EnforceMaxRotations resets the failure counter on every successful delete, so the 'disk space growth is no longer bounded' alarm can never fire in the partial-failure case that actually grows the disk (#5944)
  • fix(core): RotatingStreamWriter.cs - the Daily arm's 23-hour DST guard measures from the last rotation rather than from midnight, so local-time daily rotation walks an hour earlier each day instead of aligning to calendar days (#5945)
  • fix(core): Logger.cs - the levelName bounds check in Log() is provably always true and its 'Fallback for safety' arm is unreachable (sibling of #5156) (#5946)
  • fix(core): ProtectedKeyProvider.cs - the #1854 mutex DACL grants WorldSid Synchronize on the lock guarding the key vault, the same SID SecurityHelper.ApplySecurityRules purges from that vault by name (#5953)
  • fix(core): ProtectedKeyProvider.cs - GetOrGenerate's key-generation block sits outside the try that wraps every other failure, so a first-run DPAPI Protect error skips the workaround message the read path gets (#5954)
  • fix(core): SecureData.cs - Decrypt's marked-payload catch logs SecureDataLegacyBlockedException as 'Integrity failure', the classification #5545/#5568 exist to prevent, and duplicates the correct Warn two lines above the throw (#5955)
  • fix(core): SecureData.cs - Encrypt/Decrypt read the key arrays with no lock against ZeroSensitiveData, the TOCTOU the sibling ProtectedKeyProvider takes _cacheLock to prevent; a racing Dispose persists a blob encrypted under a zeroed key (#5956)
  • fix(core): SecurityHelper.cs - the non-admin create fallback returns success with no directory on disk, and its warning promises inherited permissions that cannot exist (#5957)
  • fix(core): ServiceControllerWrapper.cs - unresolvable dependencies sort under the first letter of the localized error sentence, because Msg_DependencyUnavailable is written into DisplayName and DisplayName is the sort key (residual of #1828) (#5966)
  • fix(core): ServiceManager.cs - MapStartupType's protected-service catch expects Win32Exception, but ServiceController.StartType wraps it in InvalidOperationException, so every protected service logs at Error as 'Unexpected' on each service-list refresh (#5970)
  • fix(core): ServiceManager.cs - StartServiceAsync and StopServiceAsync never refresh before their wait loop, so the first check re-reads the status cached by the pre-flight test and every start and stop sleeps a full 500 ms poll interval it does not need (#5979)
  • fix(core): HandleHelper.cs - the InvalidOperationException guard on Process.Start cannot fire with UseShellExecute=false, and the real unstartable-exe failure is an undocumented Win32Exception the test suite already asserts (#6089)
  • fix(core): EventLogReader.cs / EventLogService.cs - the '' fallbacks that #1295 and #2061 added are unreachable; the bracket filter discards every record carrying one (#6128)
  • fix(core): ServiceManager.cs - GetServiceStartupType discards the start type it already resolved when the delayed-auto-start probe throws, while GetAllServices keeps it (#6134)
  • fix(service): Service.cs - a crashing child is the one EvaluateExitOutcome arm that logs no exit code, and its failure counter reports the exit as a failed health check (#5754)
  • fix(service): Service.cs - IsRecoveryEnabled's options != null conjunct is provably always true; OnStart throws on null 39 lines earlier and is its only caller (#5791)
  • fix(service): ServySecurity.ps1 / ServiceHelper.cs - a sensitive keyword preceded by letters with no separator is never masked, so PGPASSWORD= and APITOKEN= leak in full (prefix mirror of #3765) (#5877)
  • fix(service): Service.cs - the RestartProcess recovery action never re-applies Priority or CpuAffinity, so every automatic restart silently drops both settings (#6044)
  • fix(service): Service.cs - the heartbeat fail-flag still reads RecoveryOnCleanExit without the health-monitoring gate #5235 added, so a clean exit-0 stop pings /fail (residual of #5235) (#6045)
  • fix(service): Service.cs - UpdateServiceStatus zeroes dwControlsAccepted for SERVICE_STOPPED but reports the accumulated dwCheckPoint, so a pre-shutdown teardown ends on a stopped-but-progressing status (#6050)
  • fix(service): Service.cs / StreamWriterFactory.cs - a syntactically-bad log path degrades gracefully, but an unreachable one throws out of the factory and aborts OnStart before the child process is launched (residual of #2068) (#6054)
  • fix(service): ProcessLauncher.cs / Service.cs - the !Start() guards cannot fire with UseShellExecute=false, and the real unstartable-exe failure (Win32Exception) is undocumented on ProcessLauncher.Start (residual of #6089) (#6097)
  • fix(ui): AppBootstrapper.cs - the embedded-resource extraction failure dialog is captioned 'Elevation Required', a cause the admin check twelve steps earlier has already ruled out (#6052)
  • fix(ui): AppBootstrapper.cs - the splash floor pads by a fixed 500 ms instead of by the time remaining, so a 50 ms start shows it for 550 ms and a 1001 ms start shows it for less than a 999 ms one (#6053)
  • fix(ui): HelpService.cs - a failed browser launch after a successful update check is reported as 'Failed to check updates', while the sibling documentation path has a purpose-built message (residual of #1585) (#6055)
  • fix(ui): AppBootstrapper.cs - UpdateAvailabilityState re-tests a path its only caller already pinned to non-empty, so the guard is provably always true (class of #1943 / #2007) (#6056)
  • fix(desktop): Strings.resx / MainWindow.xaml (Servy) - the Pre-Launch and Pre-Stop timeouts are the only two second-valued fields whose unit appears nowhere on screen, though their screen-reader text states it (#6061)
  • fix(desktop): ServiceCommands.cs / IServiceCommands.cs (Servy) - the Install path's null-DTO guard returns false without the error report its contract promises, while Export routes the same null into the validator (#6062)
  • fix(desktop): MainViewModel.cs (Servy) - LoadServiceConfigurationAsync's not-found exit is silent, so opening the desktop app for an unknown service shows a defaults form as if it were that service's configuration (#6068)
  • fix(desktop): MainViewModel.cs (Servy) - BindServiceDtoToModel casts all four enum settings from int with no membership check, so an imported out-of-range value renders as a blank ComboBox and still installs (#6069)
  • fix(manager): LogsViewModel.cs - the default log-search window is computed once in the constructor, so a Manager left running past midnight keeps searching a window that ends on the day it started (#6007)
  • fix(manager): DependenciesView.xaml - an unavailable dependency renders as a stopped service, tooltip included; IsUnavailable is the one node flag the template never binds (#6020)
  • fix(manager): MainViewModel.cs (Servy.Manager) - GetServiceUpdateInfo casts the DB StartupType with no membership check, so a not-installed row with an out-of-range value renders as a raw number (residual of #319/#6069) (#6073)
  • fix(manager): ServiceControllerWrapper.cs / DependenciesView.xaml - a shared (diamond) dependency is one node instance at two tree positions, so the TwoWay-bound IsExpanded mirrors between them (residual of #578/#1400) (#6131)
  • fix(cli): ExportServiceCommand.cs - SaveFile probes and creates the directory chain before the UNC guard runs, so an elevated export to a UNC target authenticates to the remote host first (residual of #3162) (#5885)
  • fix(cli): InstallServiceCommand.cs - the #5909 trim landed on the validator's throwaway DTO, not on the InstallServiceOptions that reaches CreateService, so 'servy install --user' still writes a padded account name (#5949)
  • fixcli): IServiceManager.cs / ServiceStatusCommand.cs - GetServiceStatus returns null for access-denied and unexpected failures as well as for 'not installed', and the CLI publishes every null as the machine-readable token NotInstalled (residual of #5153) (#5976)
  • fix(psm1): Servy.psm1 / servy-module-examples.ps1 - the only 2 of 48 PowerShell files indented by 2 spaces, against .editorconfig's indent_size = 4, and Format-SourceHygiene.ps1 does not check that rule (#5494)
  • fix(psm1): Servy.psm1 - 7 of 20 ValidateScript blocks fall back to PowerShell's generic error, including -EnvVars and -PreLaunchEnv whose format is a 92-character escaping regex (#5894)
  • fix(psm1): Servy.psm1 - the parent-directory ValidateScript block is copied verbatim at 5 sites, the same defect #1029 consolidated for the env-var pattern (and the sites #2289 had to patch one by one) (#5895)
  • fix(psm1): servy-module-examples.ps1 - never demonstrates Import-ServyServiceConfig -Install, which its wiki counterpart shows and annotates, and omits 2 of the 11 exported functions the module header calls complete (#5497)
  • fix(psm1): Servy.psm1 - $script:ServyMaxBufferChars is written twice and never read, so Set-ServyConfig -MaxBufferChars is a no-op and the buffers it documents a cap for grow unbounded (#6088)
  • fix(notifications): ServyFailureEmail.ps1 - after #2318 every transport-level SMTP failure is classified PermanentFailure and the alert is discarded; the network catch arm below can never fire for the send (#5878)
  • fix(notifications): ServyFailureEmail.vbs / ServyFailureNotification.vbs - the missing-script error uses WScript.Echo, which is an invisible modal dialog under wscript.exe and blocks the queued task until the 5-minute limit kills it (#5879)
  • fix(setup): servy.iss - AddToPath concatenates onto the raw PATH, so a machine PATH ending in a semicolon gains an empty ';;' element; RemoveFromPath rebuilds through the list (residual of #5467) (#5873)
  • fix: All 22 csproj - AssemblyDescription is not an SDK property, so every assembly ships with a blank Description while its AssemblyTitle neighbour binds (#5988)
  • chore(deps): update dependencies