Problem
Every launch contacts GitHub, and the user cannot turn it off. AboutViewModel's public constructor hardcodes the check on (: this(updates, reportService, autoCheck: true), AboutViewModel.cs:62-63 -> if (autoCheck) InitializeAsync(InitAsync) at :75-76), and the About VM is deliberately resolved EAGERLY at startup precisely so this fires - MainWindowViewModel.cs:80 (About = Eager<AboutViewModel>();) with the reason stated at MainWindowViewModel.cs:30-32. The request goes to api.github.com/repos/.../releases/latest (UpdateService.cs:81), and CheckAtStartupAsync also chains a second call for the last 10 releases (AboutViewModel.cs:96 -> GetRecentAsync(10) at :159 -> UpdateService.cs:113-118). I grepped for a toggle: grep -rn "AutoCheck|UpdateCheckEnabled|CheckForUpdatesOnStartup|updateCheck" across the app returns only the internal test-only constructor parameter and the unrelated UpdateCheckFailed display flag - no setting, no UI, no persistence. There is also no throttle: nothing records when the last check ran, so N launches in an hour make 2N requests against a limit the code itself documents as 60/hour/IP (UpdateService.cs:19-20). Only the autoCheck: false path exists, and it is internal for tests (AboutViewModel.cs:71).
Proposed solution
Persist one boolean and gate the call on it, reusing the pattern that already exists rather than inventing one. ThemeService writes its settings to %APPDATA%\SysManager\theme.json via JsonSerializer + File.WriteAllText (ThemeService.cs:19-21, Save at :354-367, Load at :369-376) - follow it exactly. Then: (1) gate AboutViewModel.cs:75 on the persisted value instead of the hardcoded true at line 63; (2) add a plain-language checkbox in the About version card next to the existing "Check for updates" button (AboutView.xaml:44-47) - wording for the persona, e.g. "Check GitHub for a new version when SysManager starts", with AutomationProperties.Name per Gate-UX; (3) record a last-checked timestamp alongside it and skip the startup check if it ran within the last 24h - the manual "Check for updates" button (AboutView.xaml:45) and "Retry" (:149) always bypass the throttle, so the user is never blocked; (4) register the new file in ProfileService's Catalog (ProfileService.cs:48-51, which today lists exactly two sections - theme.json and speedtest-history.json) so the preference survives profile export/import.
Rationale
This is the one place the product's strongest marketing claim is contestable. The README's competitor table leads with "No telemetry / no account" and "Fully local (no cloud)" (README.md:74-76), and the network disclosure at README.md:48-52 lists the update check among things that happen only "for features you explicitly use" - but the startup check is not a feature the user chose to use; it fires before any interaction, on every launch, with no switch. A skeptical reviewer will find that gap, and so will anyone on a metered connection, behind a corporate proxy, or on an air-gapped machine. Adding the toggle costs little and makes the privacy claim airtight instead of arguable. The 24h throttle additionally removes a real failure mode: a user who restarts the app repeatedly can exhaust the documented anonymous limit and then sees the error state (AboutViewModel.cs:114-118) for no reason.
Evidence
AboutViewModel.cs:62-63 (public ctor forces autoCheck: true), :71 (internal ctor is the only opt-out, for tests), :75-76 (if (autoCheck) InitializeAsync(InitAsync)), :90-100 (CheckAtStartupAsync -> CheckForUpdates + LoadHistory), :159 (GetRecentAsync(10)). Eager resolution and its stated rationale: MainWindowViewModel.cs:26-32 and :78-80. Endpoints: UpdateService.cs:81, :117. Rate limit documented in the service's own summary: UpdateService.cs:19-20 ("no auth needed as long as we stay under the anonymous rate limit (60 req/hour/IP)"). Absence of any setting: grep for AutoCheck|UpdateCheckEnabled|CheckForUpdatesOnStartup|updateCheck -> only AboutViewModel.cs:36 (_updateCheckFailed), :63, :66, :71, :75. Settings-persistence pattern to copy: ThemeService.cs:19-21, :354-367, :369-376. Profile catalog with its current two entries: ProfileService.cs:48-51.
Risk / trade-off
If a user switches the check off, they stop learning about fixes - including security fixes - so the default must stay ON and the copy must not read as a scare-off. Turning it off must also blank the shell update banner cleanly (MainWindow.xaml:385 binds About.UpdateAvailable) rather than leaving a stale value. The About VM is eager specifically to feed that banner (MainWindowViewModel.cs:30-32); gating the network call must not accidentally make the VM lazy, or the version label at MainWindow.xaml:332 goes blank. Adding a settings file also expands ProfileService's surface - cover it with a round-trip test.
Affected area
About
Effort: M | priority: value 4/5, fit 5/5
Identified during the trust, distribution and reach audit audit.
Problem
Every launch contacts GitHub, and the user cannot turn it off.
AboutViewModel's public constructor hardcodes the check on (: this(updates, reportService, autoCheck: true), AboutViewModel.cs:62-63 ->if (autoCheck) InitializeAsync(InitAsync)at :75-76), and the About VM is deliberately resolved EAGERLY at startup precisely so this fires - MainWindowViewModel.cs:80 (About = Eager<AboutViewModel>();) with the reason stated at MainWindowViewModel.cs:30-32. The request goes toapi.github.com/repos/.../releases/latest(UpdateService.cs:81), andCheckAtStartupAsyncalso chains a second call for the last 10 releases (AboutViewModel.cs:96 ->GetRecentAsync(10)at :159 -> UpdateService.cs:113-118). I grepped for a toggle:grep -rn "AutoCheck|UpdateCheckEnabled|CheckForUpdatesOnStartup|updateCheck"across the app returns only the internal test-only constructor parameter and the unrelatedUpdateCheckFaileddisplay flag - no setting, no UI, no persistence. There is also no throttle: nothing records when the last check ran, so N launches in an hour make 2N requests against a limit the code itself documents as 60/hour/IP (UpdateService.cs:19-20). Only theautoCheck: falsepath exists, and it isinternalfor tests (AboutViewModel.cs:71).Proposed solution
Persist one boolean and gate the call on it, reusing the pattern that already exists rather than inventing one.
ThemeServicewrites its settings to%APPDATA%\SysManager\theme.jsonviaJsonSerializer+File.WriteAllText(ThemeService.cs:19-21, Save at :354-367, Load at :369-376) - follow it exactly. Then: (1) gate AboutViewModel.cs:75 on the persisted value instead of the hardcodedtrueat line 63; (2) add a plain-language checkbox in the About version card next to the existing "Check for updates" button (AboutView.xaml:44-47) - wording for the persona, e.g. "Check GitHub for a new version when SysManager starts", withAutomationProperties.Nameper Gate-UX; (3) record a last-checked timestamp alongside it and skip the startup check if it ran within the last 24h - the manual "Check for updates" button (AboutView.xaml:45) and "Retry" (:149) always bypass the throttle, so the user is never blocked; (4) register the new file inProfileService'sCatalog(ProfileService.cs:48-51, which today lists exactly two sections -theme.jsonandspeedtest-history.json) so the preference survives profile export/import.Rationale
This is the one place the product's strongest marketing claim is contestable. The README's competitor table leads with "No telemetry / no account" and "Fully local (no cloud)" (README.md:74-76), and the network disclosure at README.md:48-52 lists the update check among things that happen only "for features you explicitly use" - but the startup check is not a feature the user chose to use; it fires before any interaction, on every launch, with no switch. A skeptical reviewer will find that gap, and so will anyone on a metered connection, behind a corporate proxy, or on an air-gapped machine. Adding the toggle costs little and makes the privacy claim airtight instead of arguable. The 24h throttle additionally removes a real failure mode: a user who restarts the app repeatedly can exhaust the documented anonymous limit and then sees the error state (AboutViewModel.cs:114-118) for no reason.
Evidence
AboutViewModel.cs:62-63 (public ctor forces
autoCheck: true), :71 (internal ctor is the only opt-out, for tests), :75-76 (if (autoCheck) InitializeAsync(InitAsync)), :90-100 (CheckAtStartupAsync-> CheckForUpdates + LoadHistory), :159 (GetRecentAsync(10)). Eager resolution and its stated rationale: MainWindowViewModel.cs:26-32 and :78-80. Endpoints: UpdateService.cs:81, :117. Rate limit documented in the service's own summary: UpdateService.cs:19-20 ("no auth needed as long as we stay under the anonymous rate limit (60 req/hour/IP)"). Absence of any setting: grep forAutoCheck|UpdateCheckEnabled|CheckForUpdatesOnStartup|updateCheck-> only AboutViewModel.cs:36 (_updateCheckFailed), :63, :66, :71, :75. Settings-persistence pattern to copy: ThemeService.cs:19-21, :354-367, :369-376. Profile catalog with its current two entries: ProfileService.cs:48-51.Risk / trade-off
If a user switches the check off, they stop learning about fixes - including security fixes - so the default must stay ON and the copy must not read as a scare-off. Turning it off must also blank the shell update banner cleanly (MainWindow.xaml:385 binds
About.UpdateAvailable) rather than leaving a stale value. The About VM is eager specifically to feed that banner (MainWindowViewModel.cs:30-32); gating the network call must not accidentally make the VM lazy, or the version label at MainWindow.xaml:332 goes blank. Adding a settings file also expands ProfileService's surface - cover it with a round-trip test.Affected area
About
Effort: M | priority: value 4/5, fit 5/5
Identified during the trust, distribution and reach audit audit.