Taking the Shine Off

I have used Sparkle, the open source project for automating in-app updates to Mac apps, for years. It’s been an invaluable gift to myself and hundreds if not thousands of other developers.

It’s precisely because of this popularity that I want to share a convoluted scenario in which catastrophic data loss may occur.

Sparkle’s basic operation consists of checking for an updated version of the host app, downloading it, replacing the host app, and relaunching the freshly-installed version. To achieve this, it makes use of a secondary helper app called finish_installation.app. The app is built and bundled into the Sparkle.framework which a host app links to and bundles in the Frameworks folder of its own app bundle.

If, for any reason, finish_installation.app cannot be located at update time, the host app’s entire application support folder is wiped out.

The very good news is it’s extremely unlikely your app would find itself unable to locate the finish_installation.app in Sparkle’s bundle. Three obvious scenarios jump to mind for how this could happen in practice:

  1. The helper app could be removed from the Sparkle bundle after your app is downloaded and installed on a user’s Mac. This could involve a user fishing around inside your app and deciding that the helper app is not needed, or perhaps a misguided utility app deciding that the helper should be deleted.
  2. The NSBundle-based code for dynamically locating the Sparkle bundle and its contents could fail at runtime. For this to happen I think that there would need to be some bug in Apple’s bundle registration, or change in the expected behavior of either -[NSBundle bundleWithIdentifier:] or -[NSBundle pathForResource:ofType:]. This scenario seems extremely unlikely but nonetheless worth guarding against.
  3. The helper app could be missing from the Sparkle bundle because it was omitted at build time. If in the course of your own mucking about with Sparkle’s Xcode project, you make some change that causes the helper to be removed from the Copy Files phase that normally adds it to Sparkle’s bundle, you would end up with a copy of Sparkle that exhibits the bug.

Why do I know about this bug? Because I fell for scenario #3 above. While merging changes from another version of Sparkle with my own repository, something happened to cause the file to come off the Copy Files list. This sounds unlikely, but anybody who has used Xcode extensively knows that sometimes little changes, a drag here or there, can cause unexpected side effects to membership in targets or copy phase lists.

How does the bug manifest, exactly? During the previously described process of updating an app, Sparkle gets to the point where it wants to run the helper. Before running, it copies it into the host app’s Application Support folder. At least, that’s what it intends to do. It determines the destination path based on the name of the helper app found in the Sparkle bundle. But when that name is nil, the constructed path consists only the host’s Application Support folder:

“Library/Application Support/YourApp” + (nil) = Library/Application Support/YourApp

It then proceeds to try copying from “nil” to the target folder, and … you can probably guess how well that turns out.

The simple fix that prevents catastrophic damage in all cases is to simply skip that copying process whenever the source path comes up nil. For any reason whatsoever. This will cause Sparkle to fall into another failure mode which displays a somewhat cryptic error message, but which at least doesn’t wipe out all of your user’s data for your app.

You can find my patch for addressing the bug on GitHub.