Security Design for Auto-Update - Why HTTPS Alone Is Not Enough
· Go Komura · Windows Development, Security, Updater, Auto-Update, Signing, MSIX, ClickOnce
Table of Contents
- The Conclusion First
- Why Auto-Update Is Dangerous Territory
- Anti-Patterns
- Best Practices
- Minimum Safe Configuration
- How to Think About It in Windows Projects
- Minimum Checklist
- Summary
- References
1. The Conclusion First
Let me start with where things tend to land in practice.
- If the requirements fit, prefer an existing update infrastructure such as MSIX App Installer or ClickOnce first
- If you need a custom updater, the first thing to build is not the UI but signature verification and failure recovery
- Treat update information like
latest.jsonas signed metadata, not as an unsigned configuration file - TLS is necessary but not sufficient
- Base the update decision not on “because the server says so” but on “because the client verified it and judged it to be correct”
- Separate development and production signing keys, and protect them with an HSM or a signing service
- On update failure, fail closed, not open
- An updater without rollback protection should be assumed to be vulnerable to being reverted to a vulnerable version
- If you cannot yet add signature verification, distributing manually signed installers is safer than auto-update
In short, the core of auto-update is not “how to download” but “what to trust, where to verify it, and how to recover when things break.”
2. Why Auto-Update Is Dangerous Territory
Ordinary features stay contained inside the app. An updater, on the other hand, holds all three of the following at once.
- It fetches files from the outside
- It trusts those files
- It replaces the existing executables
In other words, a path to arbitrary code execution is built into the product from the start.
A common misconception here is “it’s HTTPS, so it’s safe.” Of course TLS is necessary. But what it protects is mainly the communication channel and the legitimacy of the endpoint. It does not, by itself, cover cases where the update server itself was compromised, the wrong artifact was placed on the legitimate CDN, or an unsigned manifest was swapped out.
In fact, looking just at the threats TUF catalogs, update systems face all of these.
- Getting arbitrary malicious software installed
- Rollback attacks that revert to a vulnerable old version
- Freeze attacks that hide new versions
- Mix-and-match attacks that combine mutually inconsistent metadata and artifacts
That is, auto-update is not “file transfer” but “distribution of trust.” Only once you design for this does auto-update start running safely.
3. Anti-Patterns
First, here is a summary of dangerous shapes we often see in practice.
| Anti-pattern | Why it is dangerous | Minimum fix |
|---|---|---|
Fetch version.json over HTTPS and run the zip / exe at the URL as is |
Weak against origin compromise, config tampering, misdelivery | Switch to client-side verification of signed metadata and artifacts |
| Only the binary is signed; the manifest is unsigned | URL, version, channel, and mandatory-update flag can be tampered with | Use a signed manifest including version / hash / size / channel / expiry |
| Signing key lives in a file on a dev PC or in CI | If compromised, legitimately signed malware can be distributed | HSM / signing service + approval flow + audit log |
| On update failure, “ignore the verification error and continue” | The weakest path opens up exactly when an incident occurs | Fail closed |
| Overwrite update that does not keep the old version | Power loss, disk shortage, or mid-update failure leaves the app unable to start | Staging + atomic activate + rollback |
| Allow old versions based on version comparison alone | Rollback to a vulnerable version goes through | A monotonically increasing release version and storage of the highest known version |
| Run the entire updater with administrator privileges | Large blast radius when compromised | Download/verify at low privilege, separate only the replacement into a minimal helper |
| Start with delta updates | Implementation is complex and verification gaps multiply | Start with full-package updates first |
Let’s look at each in a bit more detail.
3.1 Stopping at “it’s HTTPS, so it’s fine”
This is the most common one.
- Read
latest.jsonat startup - Extract
downloadUrl - Download the zip / exe
- Extract and replace
- Done
It looks plausible, but the root of trust leans too heavily on the server’s response. If the update server or the delivery configuration is compromised, a malicious update can be delivered over perfectly valid HTTPS.
TLS is necessary. But TLS alone does not complete the design of an updater.
3.2 Files are signed, but the client does not verify them
Even if files are signed at release time, it means nothing if the client never looks at the signature.
A common shape is:
- CI does sign the files
- But the updater only checks a hash
- And that hash itself comes from an unsigned manifest
In that case, the moment the manifest is swapped out, the hash gets swapped along with it. “We check the hash, so it’s safe” only holds if the provenance of the hash is protected too.
3.3 The manifest is unsigned
What an update system really needs to protect is not just the executable itself. At minimum, the following information is dangerous if tampered with.
- version / release id
- URL or file name of the download target
- hash / size
- channel (stable / beta, etc.)
- whether the update is mandatory
- applicable OS / architecture
- metadata expiry
- minimum required updater version
In other words, a good rule of thumb is: put everything used in the update decision into signed metadata.
3.4 Sloppy handling of signing keys
The security of an update feature is, to a large degree, the security of its key management.
If the production signing key is kept in any of the following ways, it is quite dangerous.
- Left sitting in the certificate store of a dev PC
- A
.pfxuploaded as a CI secret - The same private key distributed locally to multiple people
- Development signing and production signing share the same trust chain
In that case, even if the updater itself is correct, you cannot stop a “legitimately signed malicious update.”
3.5 Overwrite updates that do not keep the old version
For updates, the design for the failure case matters more than the success case.
- The download was cut off midway
- Extraction failed
- Power was lost mid-replacement
- The new version launched but crashed during its first migration
If the old version is gone at that point, recovery becomes heavy. In practice, “the app no longer starts on site” is a bigger problem than the fact that “the update failed.”
3.6 Not thinking about rollback
Even a signed, legitimate version can be convenient for an attacker if it is an old, vulnerable one.
For example:
- Version 1.8 has a known vulnerability
- The field has moved up to 2.3
- An attacker re-delivers 1.8
If this goes through, it is dangerous even though the signature itself is valid.
It is not enough to check “is it signed”; you also have to check “is it acceptable to install this version now.”
3.7 Fail-open
This is the one thing you must never do in production.
- On signature verification failure, just show a warning and continue
- A hidden flag that lets you ignore certificate expiry errors
- A debug-only
skipVerify=truethat remains in production
During outages and attacks, these escape hatches become the primary path.
4. Best Practices
4.1 Ride on an existing update infrastructure first
It is safest to start by questioning whether you really need a custom updater.
On Windows, as long as the requirements fit, these are the easiest candidates to consider first.
- MSIX + App Installer
- ClickOnce
- Store / MDM / internal distribution infrastructure
- MSI + the company’s distribution management
The reason is simple: you can push a fair amount of the responsibility for updates onto the platform. You do lose some flexibility, but the update UI, distribution manifest, package signing, and operational alignment become easier to keep consistent.
A custom updater becomes necessary in cases like these.
- You want strict control over multiple channels: stable / beta / preview
- You want staged delivery and rollout percentages
- You need fine-grained control of update timing for business-specific reasons
- You have configurations that do not fit MSIX / ClickOnce
Even then, you will stay grounded if you understand it not as “we want flexibility” but as “we are taking on the responsibility for updates ourselves.”
4.2 Put the root of trust on the client side
A safe updater does not take the server’s response at face value. The client side needs at least these two things.
- A trusted public key or certificate chain
- A mechanism to verify metadata signed with that key
Put differently, you need to create a state where the client can confirm not “the server says this is the latest version” but “this metadata is the latest version issued by a signer we trust.”
4.3 Design around signed metadata
At minimum, include the following in the update metadata and make it part of what gets signed.
| Item | Why include it |
|---|---|
| release version / release id | Rollback prevention, auditing |
| artifact name, URL, package type | Pin down which file gets fetched |
| hash, size | Tamper detection, detection of corrupted delivery |
| channel | Do not mix beta into stable |
| target OS / architecture | Prevent misdelivery |
| minimum updater version | Stop old updaters when the protocol changes |
| expires_at | Freeze protection |
| published_at | Auditing, triage |
| mandatory / optional | Make even the update UX branching tamper-proof |
The key point here is to consolidate all the inputs to the update decision into signed metadata. Accidents decrease when you converge on a shape where the logic lives on the client and the authenticity of the information is protected by signatures.
4.4 Verify the artifact itself too
After verifying the metadata, also check the following on the downloaded artifact.
- size
- hash
- Package signature / code signature
- Publisher or expected identifier
If you handle Windows PE / MSI / MSIX, it is safer to assume Authenticode or package signature verification happens on the client side. On macOS, it is most consistent to assume Developer ID and notarization on the update path as well.
4.5 Protect keys through operations, not features
Key management shows its differences in operations more than in implementation.
At minimum, separate the following.
- Development signing key
- Staging signing key
- Production signing key
And for production, you want to design all the way through:
- HSM
- Cloud signing service
- Signing system with an approval flow
- Audit logs
- Key rotation procedure
- Timestamped signatures
“CI auto-signs whenever a production build passes” is convenient, but the blast radius on compromise grows accordingly. At the very least, you should be able to trace who signed what and when.
Once operations mature, it is even safer to separate the rarely-changed root trust from the keys used for frequently re-signed update metadata. Keeping the root mostly offline and using a separate key for update metadata makes it easier to reduce the blast radius of a key compromise.
4.6 Fail-closed and staged updates
The update flow basically goes in this order.
- Fetch the metadata
- Verify the signature, expiry, and version
- Download the artifact into a staging area
- Verify hash / size / signature
- Prepare activation while keeping the old version
- Switch over at restart or via a dedicated helper
- Health check on first launch
- Roll back if there is a problem
What matters here are these two things: Do not replace anything before verification completes. Do not proceed on failure.
4.7 Restrict the updater’s privileges
Avoid running the entire updater with administrator privileges.
The ideal separation is:
- Download and verification: low privilege
- Only the actual file replacement: a helper with minimal privileges
- The helper does nothing beyond “place a verified package into the designated location”
The more a design requires privilege elevation, the more dangerous it becomes unless you clearly separate what has been verified before elevation.
4.8 Kill rollback / freeze / mix-and-match from the start
These are painful to retrofit, so it is better to build them in from the beginning.
-
Rollback protection The client keeps “the highest metadata version / release version seen so far” and rejects anything older
-
Freeze protection Give the metadata an expiry and reject metadata that is too old
-
Mix-and-match protection Keep the pieces of metadata consistent with each other. At minimum, pin the hash / size / version of the target artifacts inside the manifest itself
In addition, if you can distribute a blocklist of specific builds or a minimum allowed version through signed metadata, containment during incidents gets much faster.
Even if you do not adopt TUF wholesale, these three properties are very important.
4.9 Start with full updates
Delta updates save bandwidth, but they are complex as a first implementation.
- Which old version to which new version does the delta apply
- The precondition hash before applying the delta
- The final hash after applying the delta
- Recovery from mid-application failure
- Cleanup of partial applications and stale deltas
All of this piles up at once. For the initial version, safely swapping in a signed full package is plenty.
5. Minimum Safe Configuration
Even if you do not go as far as full TUF, the minimum safe configuration for a custom updater looks roughly like this.
5.1 What the client holds
- A trusted root public key, or a pinned certificate chain
- The currently running version
- The highest metadata version / release version seen in the past
- The allowed channel
- The previous version, kept for rollback
5.2 What the server returns
- Signed update metadata
- Artifacts that are signed, or platform-signed
- If needed, blocklist / minimum allowed version information
5.3 Typical flow
Fetch metadata
↓
Verify signature, expiry, version, channel
↓
Download artifact into staging
↓
Verify size / hash / package signature
↓
Activate while keeping the old version
↓
Roll back if first launch fails
The important point here is that nothing is established by the update server’s response alone. What establishes it is the trust anchor the client holds and the verification logic.
6. How to Think About It in Windows Projects
For Windows apps, it is easiest to organize things by working backward from the distribution method.
- If the requirements fit, MSIX App Installer
- For internal .NET apps where per-user works, ClickOnce
- If you need services, drivers, shell extensions, or custom channel control, MSI + a custom updater is also worth comparing
However, even if you choose a custom updater, the work does not shrink. If anything, it grows.
- Authenticode / package signature verification
- Signed manifest
- Rollback protection
- Privilege separation for the update helper
- An update strategy for the updater itself
The classic dangerous shape on Windows is the straight line of DownloadFile -> unzip -> kill process -> overwrite -> restart.
It can work, but it is weak on both security and recoverability.
Operating by having users click through SmartScreen or UAC warnings via “More info → Run anyway” is not update design; it is warning habituation. If you are building a proper update path, you should converge on a distribution and verification setup that rarely triggers warnings, rather than training users to ignore them.
We also organized the comparison of distribution methods themselves in this article: How to Choose a Distribution Method for Windows Apps - A Decision Table for MSI / MSIX / ClickOnce / xcopy / Custom Updater
7. Minimum Checklist
Before shipping a custom updater, you want to confirm at least this much.
- The update metadata is signed
- The metadata contains version / hash / size / channel / expiry
- The client verifies the signature and version
- The artifact’s hash and platform signature are verified
- The production signing key is separated from development environments
- Key usage logs and approval records are kept
- Timestamped signatures are used
- Staged updates switch over while keeping the old version
- There are conditions and a procedure for rollback
- On verification failure, the updater fails closed and stops
- There is an update policy for the updater itself
- A blocklist / minimum allowed version can be distributed
- There is a kill switch to halt staged delivery
- Failure rate, rollback rate, and signature verification failures are observable
If many of these boxes are still empty, refining the distribution trust model will pay off more than building the updater’s UI first.
8. Summary
In the end, the security of an auto-update feature comes down to this.
Design not the convenience of updates, but whom you trust and how the client verifies that trust.
On top of that, the practical judgment roughly goes like this.
- If an existing infrastructure suffices, ride on it first
- If you build a custom updater, put in signed metadata and key management before worrying about HTTPS
- An updater without designed failure recovery and rollback will hurt in production
- The updater is not a distribution feature; it is the product’s security boundary itself
If your current setup is close to latest.json + zip swap, the first thing to fix is not the download logic but where you place your trust.
Fixing just that changes the risk profile considerably.
9. References
- CISA Secure by Design Pledge
- NIST: Security Considerations for Code Signing
- NIST Secure Software Development Framework (SSDF)
- The Update Framework Specification
- TUF: Roles and metadata
- TUF: Security
- Microsoft Learn: Authenticode Digital Signatures
- Microsoft Learn: Auto-update and repair apps - MSIX
- Microsoft Learn: ClickOnce Deployment and Security
- Apple Developer: Developer ID
- CA/Browser Forum: Baseline Requirements for the Issuance and Management of Publicly-Trusted Code Signing Certificates
Related Topics
These topic pages are close to this theme. Starting from this article, you can move on to related services and other articles.
Windows Technical Topics
An entry point that gathers technical topics on Windows development, bug investigation, and reusing existing assets.
Services Related to This Theme
Windows App Development
Auto-update is not just a UI matter; it is a design that spans distribution method, privileges, recovery, and operations. For new Windows app development or a review of existing software, we can start from organizing the update approach.
Technical Consulting & Design Review
You can consult with us from the early organizing stage: “Do we really need a custom updater?”, “Is MSIX / ClickOnce enough?”, “Where is our current update design risky?”
Author Profile
Go Komura
Representative, KomuraSoft LLC
Specializing in Windows software development, technical consulting, and bug investigation, with particular strength in projects involving existing assets and in investigating failures whose causes are hard to see.
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Why Windows Shows "Windows protected your PC"
A practical look at why SmartScreen warnings appear when distributing Windows apps, covering code signing, EV/OV certificates, Azure Arti...
Choosing a Windows App Distribution Method - MSI/MSIX/ClickOnce/xcopy/Custom Updater
Choosing a Windows app distribution method is not about installer-format preference - it is a choice about OS coupling and update respons...
Why Windows Became What It Is Today: The Evolution of Windows Through a Developer's Eyes
A look at the changes from Windows 95 to Windows 11 — not as a visual timeline, but from a Windows application developer's perspective: c...
What Is ClickOnce? - How It Works, How It Updates, and Where It Fits (and Doesn't) in Practice
An overview of ClickOnce, the deployment technology used for .NET Windows desktop apps - manifests, updates, the cache, signing, and whic...
How Windows DLL Name Resolution Works - Search Order and SxS
A practical guide to Windows DLL name resolution, covering the DLL search order, Known DLLs, the loaded-module list, API sets, SxS manife...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
Distribution, updates, signing, rollback, and choosing between MSIX and ClickOnce for Windows apps require thinking through not just the implementation but the distribution method and operational design as well.
Technical Consulting & Design Review
Designing the trust boundary of auto-update, signed metadata, key operations, and fail-closed behavior is more about organizing the overall architecture than about any individual implementation.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links