Security Design for Auto-Update - Why HTTPS Alone Is Not Enough

· · Windows Development, Security, Updater, Auto-Update, Signing, MSIX, ClickOnce

Table of Contents

  1. The Conclusion First
  2. Why Auto-Update Is Dangerous Territory
  3. Anti-Patterns
  4. Best Practices
  5. Minimum Safe Configuration
  6. How to Think About It in Windows Projects
  7. Minimum Checklist
  8. Summary
  9. 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.json as 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.

  1. It fetches files from the outside
  2. It trusts those files
  3. 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.json at 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 .pfx uploaded 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=true that 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.

  1. A trusted public key or certificate chain
  2. 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.

  1. Fetch the metadata
  2. Verify the signature, expiry, and version
  3. Download the artifact into a staging area
  4. Verify hash / size / signature
  5. Prepare activation while keeping the old version
  6. Switch over at restart or via a dedicated helper
  7. Health check on first launch
  8. 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

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.

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.

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

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.

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.

Back to the Blog