Skip to content

[Feature] Add in-app auto-update for GitHub Release builds#520

Open
NAME0x0 wants to merge 10 commits intounchihugo:masterfrom
NAME0x0:master
Open

[Feature] Add in-app auto-update for GitHub Release builds#520
NAME0x0 wants to merge 10 commits intounchihugo:masterfrom
NAME0x0:master

Conversation

@NAME0x0
Copy link
Copy Markdown

@NAME0x0 NAME0x0 commented Mar 19, 2026

Summary

GitHub Release users can download and install MSIX updates directly from the app without leaving it. Downloads the release ZIP from GitHub, extracts the .msixbundle and .cer, verifies the package through a 6-layer trust chain, installs the certificate (with UAC prompt), and runs Add-AppxPackage. All auto-update code is gated behind #if GITHUB_RELEASE
so Microsoft Store builds are completely unaffected.

Motivation

Sideloaded GitHub Release users currently have to manually check for updates, download the ZIP, and run the installer. This adds in-app auto-update so they can update without leaving the app while preserving their settings.

Type of Change

  • Feature
  • Bug fix
  • Refactor (no functional changes)
  • Style (formatting, naming)
  • Other

What Changed

  • Added AutoUpdater.cs — core download, extract, 6-layer verification, and install logic
  • Added GetGitHubReleaseAssetAsync() to UpdateChecker.cs with URL origin pinning
  • Update notification opens settings window (not browser) on GitHub Release builds
  • Background auto-download on startup; cleanup on exit in MainWindow.xaml.cs
  • Download progress bar, install button, installing spinner, and error bar in HomePage.xaml
  • "Automatically download updates" toggle in SystemPage.xaml
  • 6 new localization strings in Dictionary-en-US.xaml
  • UpdateState.cs — runtime update state properties (IsDownloading, DownloadProgress, IsInstalling, UpdateError, DownloadedBundlePath)
  • AutoUpdateEnabled setting in UserSettings.cs (default: true)

Additional Information

How it works

  1. On startup (or manual check), the app queries the GitHub Releases API for the latest version
  2. If an update is available and AutoUpdateEnabled is on, the ZIP is downloaded in the background with progress reporting
  3. The user clicks Install Update → the 6-layer trust chain runs → certificate is installed (UAC prompt) → Add-AppxPackage -ForceApplicationShutdown handles the rest
  4. Settings at %APPDATA%\FluentFlyout\settings.xml are saved before installation and survive the MSIX update automatically

Trust model (defense in depth)

Each layer is independent — compromising one does not bypass the others:

Layer Check What it prevents
1 Source pinning — download URL must start with https://github.com/unchihugo/FluentFlyout/releases/download/ Redirect attacks, domain spoofing
2 Authenticode verification — rejects HashMismatch and NotSigned Tampered or unsigned packages
3 Publisher identity — signer certificate CN must equal CN=49793F74-1457-4B66-A672-4ED3A640FC76 Packages signed by wrong publisher
4 Certificate cross-verification.cer thumbprint must match the .msixbundle's embedded signer thumbprint Cert injection/swapping in the ZIP (breaks circular trust)
5 UAC elevation — certificate install requires administrator approval Silent privilege escalation
6 OS enforcement — Windows Add-AppxPackage rejects publisher mismatches for updates Publisher impersonation at OS level

Why CN-based (not thumbprint/public key pinning)?
The signing certificate is fully regenerated each release — different key pair, different thumbprint, same CN. Thumbprint and public key pinning are impossible under this scheme. The cert cross-verification (layer 4) compensates: an attacker who creates a cert with the same CN cannot get it installed because its thumbprint won't match the bundle's embedded Authenticode signer.

Failure handling

Every failure path sets an actionable user-facing error message and leaves the app fully functional. The user can always retry by clicking the update button again.

Failure User sees Recovery
Network error "Download failed due to a network error..." Retry
Corrupt ZIP / size mismatch "Downloaded file is corrupted" Retry
Signature tampered "Package signature verification failed..." Retry
Cert cross-verification fails "Certificate does not match the package signer..." Retry
UAC denied "Certificate installation was denied... accept the prompt" Retry + accept UAC
Add-AppxPackage fails "Installation failed. You can retry, or download manually..." Retry or manual install
GitHub API failure "Could not find update package on GitHub" Try later

E2E verification

Tested against real release artifacts:

  • v2.8.0 → v2.9.1: all trust layers passed programmatically
  • v2.9.1 → v2.9.2: full end-to-end update completed (certificate installed via UAC, Add-AppxPackage succeeded, app updated)

Checklist

  • Code changes are manually tested and working.
  • Formatting and naming are consistent with the project.
  • Self-review of changes is done.
  • AI tools were used (if yes, I reviewed and fully understand the changes myself).

NAME0x0 added 5 commits March 19, 2026 21:51
…ut leaving the app. Gated behind #if GITHUB_RELEASE so Microsoft Store builds are unaffected.

	- Add AutoUpdater class for downloading, verifying, and installing .msixbundle updates with HTTPS-only, file size validation, path traversal prevention, and Authenticode signature verification against CN=unchihugo
	- Add GetGitHubReleaseAssetAsync to UpdateChecker for fetching the latest release asset from the GitHub API
	- Add download progress UI and install button to HomePage
	- Add AutoUpdateEnabled toggle to SystemPage (default: on)
	- Background-download update on startup when available and enabled
	- Save settings before ForceApplicationShutdown installation
	- Clean up temp files on app exit
	- Add localization keys for auto-update UI strings
The release assets are ZIP archives containing the .msixbundle inside a SystemFiles/ subdirectory, not bare .msixbundle files.
Also fixes signature verification to use certificate thumbprint instead of subject CN, which is stronger for self-signed certs.

	- Download .zip asset and extract .msixbundle via ZipArchive
	- Verify certificate thumbprint (SHA-1 hash) instead of subject CN, since anyone can create a self-signed cert with the same CN
	- Fix indentation and normalize line endings in UpdateChecker.cs
…icate before update

The GitHub Release assets are ZIPs (not bare .msixbundle files), so downloads are now extracted via ZipArchive to get the .msixbundle and .cer. Signature verification uses the publisher CN instead of thumbprint since the signing certificate is regenerated each release with a new thumbprint but the same CN. The .cer is installed to TrustedPeople via elevated certutil before Add-AppxPackage, with a UAC prompt for user consent.
…and actionable errors

Addresses PR review blocking issues:

Trust model (6 layers, each independent):
	1. Source pinning — download URL must match the pinned GitHub repo prefix
	2. Authenticode — rejects HashMismatch/NotSigned statuses
	3. Publisher identity — signer CN must match expected value
	4. Cert cross-verification — .cer thumbprint must match bundle signer thumbprint before installation (breaks circular trust from ZIP)
	5. UAC prompt — user must approve certificate installation
	6. OS enforcement — Windows rejects publisher mismatches for MSIX updates
@github-actions github-actions bot added MainWindow / Media Flyout Changes to MainWindow including the Media Flyout SettingsWindow Changes to SettingsWindow or settings pages not related to flyouts/widgets labels Mar 19, 2026
@NAME0x0 NAME0x0 changed the title Add in-app auto-update for GitHub Release builds (Feature) Add in-app auto-update for GitHub Release builds Mar 20, 2026
@NAME0x0 NAME0x0 changed the title (Feature) Add in-app auto-update for GitHub Release builds [Feature] Add in-app auto-update for GitHub Release builds Mar 20, 2026
@unchihugo
Copy link
Copy Markdown
Owner

Hi @NAME0x0, before I review the code, have you used AI to assist you with coding?

@NAME0x0
Copy link
Copy Markdown
Author

NAME0x0 commented Mar 20, 2026

The final pass and review was done by AI to help find gaps in the implementation @unchihugo

@NAME0x0
Copy link
Copy Markdown
Author

NAME0x0 commented Mar 22, 2026

Could you please let me know if you find any issue or anything I overlooked @unchihugo . I will get back to fixing it ASAP.

NAME0x0 added 2 commits March 22, 2026 22:04
certutil -addstore failed with E_INVALIDARG when the .cer path contained
spaces (e.g. "GitHub Release.cer") due to PowerShell Start-Process
splitting comma-separated -ArgumentList values without quoting. Replaced
the PowerShell wrapper with direct certutil invocation using
UseShellExecute + Verb=runas for UAC. Also catches Win32Exception 1223
(ERROR_CANCELLED) when the user declines the UAC prompt.

Found during E2E testing of v2.9.1 -> v2.9.2 update.
@NAME0x0
Copy link
Copy Markdown
Author

NAME0x0 commented Mar 22, 2026

Completed E2E testing with the v2.9.2 release — updated from v2.9.1 successfully. All trust layers passed, certificate installed via UAC, and Add-AppxPackage completed the update. Also caught and fixed a bug during testing where certutil failed on file paths with spaces.

All test plan items are now checked off. Happy to address any feedback from your review @unchihugo .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MainWindow / Media Flyout Changes to MainWindow including the Media Flyout SettingsWindow Changes to SettingsWindow or settings pages not related to flyouts/widgets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants