Skip to content

fix: auto-update now performs in-place self-replace with SHA256 verification#274

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/auto-update
May 12, 2026
Merged

fix: auto-update now performs in-place self-replace with SHA256 verification#274
laurentiu021 merged 1 commit into
mainfrom
fix/auto-update

Conversation

@laurentiu021

@laurentiu021 laurentiu021 commented May 12, 2026

Copy link
Copy Markdown
Owner

Summary

Auto-update previously only launched the new exe from a temp folder without replacing the running executable. Users ended up with the old version on next restart.

Changes

  • AboutViewModel.InstallUpdateAsync — now performs a true in-place update:
    1. Verifies SHA256 hash of the downloaded build against the release .sha256 asset
    2. Writes an updater batch script that waits for the current process to exit
    3. Copies the new exe over the original executable path
    4. Restarts SysManager from the original location
    5. Updater script self-deletes after completion
  • Handles access-denied gracefully (suggests running as administrator)
  • Test updated to async + 2 new tests for edge cases (fake path, no release info)
  • README updated to reflect SHA256 verification and in-place replacement
  • CHANGELOG updated

Files changed

  • \SysManager/ViewModels/AboutViewModel.cs\ — InstallUpdate → InstallUpdateAsync with full self-replace logic
  • \SysManager.Tests/AboutViewModelTests.cs\ — async test + 2 new edge-case tests
  • \README.md\ — Updates section clarified
  • \CHANGELOG.md\ — Fixed entry added

Testing

  • Build: 0 errors (main + tests)
  • Unit tests cover: null path, non-existent path, no release info scenarios
  • Full update flow requires manual testing (process replacement)

Closes #240

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced auto-update mechanism with SHA256 hash verification of downloaded builds before installation, in-place replacement of the current executable, and automatic app restart. This replaces the previous temporary folder approach.
  • Tests

    • Added comprehensive test coverage for update installation validation, error handling, and edge cases.

Review Change Stack

…ication

Previously, Install only launched the new exe from a temp folder without
replacing the running executable. Now it:
1. Verifies the SHA256 hash of the downloaded build against the release asset
2. Writes an updater script that waits for the current process to exit
3. Copies the new exe over the original
4. Restarts SysManager from the original path

Also handles access-denied gracefully (suggests running as admin).

Closes #240
@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR replaces the app's self-update mechanism from launching a downloaded executable from a temporary location to performing SHA256 hash verification, writing an updater script that waits for process exit, overwriting the current executable in-place, and restarting. Test coverage is expanded with three async test cases, and user-facing documentation is updated to describe the new behavior.

Changes

In-Place Update with SHA256 Verification

Layer / File(s) Summary
Update installation with SHA256 verification
SysManager/SysManager/ViewModels/AboutViewModel.cs
InstallUpdateAsync replaces the synchronous InstallUpdate, adding preflight checks for the downloaded file and release metadata, SHA256 hash verification via UpdateService.VerifyHashAsync, retrieval of the current executable path, generation of an update.cmd script that waits for process exit and overwrites the executable, execution of that script, and granular error handling for hash mismatches, I/O failures, access-denied, invalid operation, and Win32 exceptions.
Test coverage for in-place update flow
SysManager/SysManager.Tests/AboutViewModelTests.cs
Added System.IO import. Converted InstallUpdateCommand_WithoutDownload_SetsErrorStatus to async and added two additional async test cases: one for a nonexistent downloaded path setting "no file" status, and one for a temp file without release info setting "no release info" status, with proper temp file cleanup.
Documentation of update mechanism
CHANGELOG.md, README.md
CHANGELOG documents the new SHA256-verified in-place update flow with process-aware script execution. README user documentation replaces the prior "hands off cleanly" description with explicit SHA-256 hash verification and in-place executable replacement followed by automatic restart.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • laurentiu021/SystemManager#270: Modifies the same AboutViewModel.cs class (this PR replaces the update installation flow; that PR adds an OpenChangelog command).

Poem

🐰 A script that waits, a hash to check,
In place it swaps, no download wreck,
SHA-256 seals the trusted way,
Process exits, new build at play! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: converting auto-update from launching a new exe to performing an in-place executable replacement with SHA256 verification.
Linked Issues check ✅ Passed The PR directly addresses issue #240 by implementing a complete auto-update solution with SHA256 verification, in-place executable replacement, and proper error handling as described in the PR objectives.
Out of Scope Changes check ✅ Passed All changes are scoped to the auto-update feature: AboutViewModel implementation, unit tests, documentation updates, and changelog entries—nothing extraneous is included.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auto-update

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SysManager/SysManager/ViewModels/AboutViewModel.cs`:
- Around line 379-388: The current VerifyHashAsync call (via
_updates.VerifyHashAsync with _latest and DownloadedPath) is vulnerable to a
TOCTOU race because the downloaded file can be swapped before the later
replace/copy step; fix by adding a re-check and atomic replace: have the updater
component that performs the actual file replace/copy re-run the SHA256
verification against the same expected hash (pass expected from VerifyHashAsync
or compute expected from metadata) immediately before performing the
copy/replace, and perform the replacement atomically (e.g., replace temp file to
final location or use OS atomic rename) and/or hold an exclusive handle while
copying to prevent a swap. Ensure the verifier and the replacer share the same
expected hash value (from _updates/_latest) and that the final copy occurs only
if the re-verification succeeds.
- Around line 419-423: The updater batch's error path is hidden because the
process is started with CreateNoWindow = true and the app exits before the user
can see failures; update the code that generates/runs the batch (look for the
batch text containing the echo "Update failed — could not copy file." and the
launcher that sets CreateNoWindow) so that on copy failure the script writes the
error to a visible log file or to stderr (e.g., append error details and
%ERRORLEVEL% to a well-known log path) and/or launches the batch without
CreateNoWindow or with a visible MsgBox/pause that will actually be shown;
ensure the error branch includes the actual error info and that the process
launching the batch (the code that sets CreateNoWindow = true) either waits or
allows the window to be visible so failures aren’t silent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 704ea789-62bb-4b01-95b2-4a63e30ffc36

📥 Commits

Reviewing files that changed from the base of the PR and between 22e3842 and 15e68b8.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • README.md
  • SysManager/SysManager.Tests/AboutViewModelTests.cs
  • SysManager/SysManager/ViewModels/AboutViewModel.cs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & unit tests
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (3)
README.md (1)

270-272: Update documentation now accurately describes the in-place installer flow.

Nice clarification here; this aligns the user-facing docs with the new verify-and-replace behavior.

CHANGELOG.md (1)

47-51: Changelog entry is clear and implementation-consistent.

The fix note captures both the old behavior and the new in-place update path well.

SysManager/SysManager.Tests/AboutViewModelTests.cs (1)

144-174: Good edge-case coverage for the new async install guard clauses.

These tests validate the new early-return states (no file / no release info) and match the updated command behavior.

Comment on lines +379 to +388
// Step 1: Verify SHA256 hash before installing.
DownloadStatus = "Verifying file integrity...";
var (verified, expected, actual) = await _updates.VerifyHashAsync(_latest, DownloadedPath);
if (!verified)
{
DownloadStatus = expected != null && actual != null
? $"SHA256 mismatch — file may be corrupted. Expected: {expected[..12]}… Got: {actual[..12]}…"
: "Hash verification failed — file may be corrupted. Try downloading again.";
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Hash verification is vulnerable to TOCTOU between validation and replace.

Line 381 verifies the file, but Line 418 copies it later from disk via another process. A local file swap in-between bypasses the integrity check.

🔐 Suggested hardening (re-verify in updater script right before copy)
-        var (verified, expected, actual) = await _updates.VerifyHashAsync(_latest, DownloadedPath);
+        var (verified, expected, actual) = await _updates.VerifyHashAsync(_latest, DownloadedPath);
         if (!verified)
         {
             DownloadStatus = expected != null && actual != null
                 ? $"SHA256 mismatch — file may be corrupted. Expected: {expected[..12]}… Got: {actual[..12]}…"
                 : "Hash verification failed — file may be corrupted. Try downloading again.";
             return;
         }
+        var expectedHash = (expected ?? actual ?? string.Empty).ToUpperInvariant();
...
-                echo Applying update...
+                echo Verifying hash again...
+                for /f "delims=" %%H in ('powershell -NoProfile -Command "(Get-FileHash -Path ''{DownloadedPath}'' -Algorithm SHA256).Hash"') do set "SRC_HASH=%%H"
+                if /I not "%SRC_HASH%"=="{expectedHash}" (
+                    echo Update failed — hash changed after verification.
+                    pause >NUL
+                    exit /b 1
+                )
+                echo Applying update...
                 copy /Y "{DownloadedPath}" "{currentExe}" >NUL

Also applies to: 418-419

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/ViewModels/AboutViewModel.cs` around lines 379 - 388,
The current VerifyHashAsync call (via _updates.VerifyHashAsync with _latest and
DownloadedPath) is vulnerable to a TOCTOU race because the downloaded file can
be swapped before the later replace/copy step; fix by adding a re-check and
atomic replace: have the updater component that performs the actual file
replace/copy re-run the SHA256 verification against the same expected hash (pass
expected from VerifyHashAsync or compute expected from metadata) immediately
before performing the copy/replace, and perform the replacement atomically
(e.g., replace temp file to final location or use OS atomic rename) and/or hold
an exclusive handle while copying to prevent a swap. Ensure the verifier and the
replacer share the same expected hash value (from _updates/_latest) and that the
final copy occurs only if the re-verification succeeds.

Comment on lines +419 to +423
if errorlevel 1 (
echo Update failed — could not copy file. Press any key to exit.
pause >NUL
exit /b 1
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Updater copy failures become invisible because the batch runs hidden before shutdown.

The error path at Line 420-Line 422 is unreachable to users in practice: the script runs with CreateNoWindow = true/hidden and the app exits at Line 444. A failed copy can look like “app closed and never restarted.”

🛠️ Minimal fix to avoid silent failures
             Process.Start(new ProcessStartInfo
             {
                 FileName = "cmd.exe",
                 Arguments = $"/C \"{scriptPath}\"",
-                UseShellExecute = false,
-                CreateNoWindow = true,
-                WindowStyle = ProcessWindowStyle.Hidden
+                UseShellExecute = true,
+                CreateNoWindow = false,
+                WindowStyle = ProcessWindowStyle.Normal
             });

Also applies to: 433-444

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SysManager/SysManager/ViewModels/AboutViewModel.cs` around lines 419 - 423,
The updater batch's error path is hidden because the process is started with
CreateNoWindow = true and the app exits before the user can see failures; update
the code that generates/runs the batch (look for the batch text containing the
echo "Update failed — could not copy file." and the launcher that sets
CreateNoWindow) so that on copy failure the script writes the error to a visible
log file or to stderr (e.g., append error details and %ERRORLEVEL% to a
well-known log path) and/or launches the batch without CreateNoWindow or with a
visible MsgBox/pause that will actually be shown; ensure the error branch
includes the actual error info and that the process launching the batch (the
code that sets CreateNoWindow = true) either waits or allows the window to be
visible so failures aren’t silent.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 3.63636% with 53 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
SysManager/SysManager/ViewModels/AboutViewModel.cs 3.63% 52 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@laurentiu021
laurentiu021 merged commit cb18a1b into main May 12, 2026
5 checks passed
Comment on lines +403 to +405
var scriptPath = Path.Combine(
Path.GetDirectoryName(DownloadedPath)!,
"update.cmd");
@laurentiu021
laurentiu021 deleted the fix/auto-update branch May 12, 2026 13:26
laurentiu021 added a commit that referenced this pull request May 22, 2026
Test PR to verify branch protection rules work correctly.

Changes: minor punctuation fix (period to exclamation mark).

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
laurentiu021 added a commit that referenced this pull request May 22, 2026
…ication (#274)

Previously, Install only launched the new exe from a temp folder without
replacing the running executable. Now it:
1. Verifies the SHA256 hash of the downloaded build against the release asset
2. Writes an updater script that waits for the current process to exit
3. Copies the new exe over the original
4. Restarts SysManager from the original path

Also handles access-denied gracefully (suggests running as admin).

Closes #240

Co-authored-by: laurentiu021 <laurentiu021@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

auto update does not work

3 participants