XPC service for Lion Sandboxing #165

wants to merge 1 commit into


None yet

This fixes #163. It requires that you copy the XPC service into your main application bundle in Contents/XPCServices and runs the remove from quarantine step of copying the finish_installation app as well as launching finish_installation app outside of the sandbox. Sparkle will function exactly as it does now for applications that do not copy the XPC service into their application bundle.

@wbyoung wbyoung Added an XPC service to deal with sandboxing.
This fixes #163. It requires that you copy the XPC service into your main application bundle in
Contents/XPCServices and runs the remove from quarantine step of copying the finish_installation
app as well as launching finish_installation app outside of the sandbox. Sparkle will function
exactly as it does now for applications that do not copy the XPC service into their application

Thanks very much for the patch. I have made only a first pass at review, as I am seriously writing this from a beach on Maui.

I don't think that the launch_task command of the service can be permitted to be so general. An exploit in the host app could use this to execute an arbitrary task outside the sandbox—no good! I think this could be remedied by expressing the path to finish_installation in relative terms from the service. We'd also need to ensure something like the path being installed and the path being updated are signed with the same code signing identity as the parent app.

I'm also concerned about the usage of synchronous XPC messages. We can't block the main thread of the client app in that way.

While I don't presently have time to handle these issues myself, I would be happy to assist you in doing so if needed. Thanks again for taking the time to do this.


I'll try to work on some of these pieces when I get time. I've been pretty busy recently, though. Any help would be appreciated!

Some thoughts:

How should the launch_task command be specified relatively? Should there be just a single argument passed to the service that is the name of the finish_installation app (since it could be renamed) and allow the launch_task to figure out the path to the application support folder? I think this should mostly work, but are there cases where you can customize things that might change which bundle is used for calculating the application support folder name (or anything like that)?

The code signing part makes sense as well, but is there a good way to extract information about who signed a binary? All I know is codesign, but that won't work. Is there an API for this?

In testing the synchronous XPC messages weren't really a problem. I guess there's a possibility of a hang, but there's also file IO going on in the same method which could hang as well. I can't test right now, but is this method running on the main thread anyway?


Seems good. I'll take a look when I get a chance.

It seems the better way to go would be to verify that the finish_installation binary is code-signed with the same key as the client application. This will provide better security than using a checksum (which would still allow someone to pad an executable to match the hash). At that point, the path tot the executable doesn't really matter, would you agree? I'll take a look at the API and try to get that together.

I didn't know that the _sync variants were deprecated. Thanks for letting me know. I'll refactor and get that fixed for sure.


I've been looking over this code with the hope that it could help bail me out of a difficult decision (whether or not to go App Store-only) and I have a couple of comments:

  • I think Andy is right regarding the synchronous XPC calls. The documentation is very clear.
  • I'm not sure I agree about needing to authenticate the caller of the XPC service. While it's not the best, the rest of app is running in a sandbox and that's a significant security improvement over the non-sandbox alternative.
  • In general, Whitney's solution is a valiant effort to solve a difficult problem, with surprisingly little code. The problem occurs when trying to graft an asynchronous API (XPC) into places where Sparkle uses synchronous APIs.
  • There are no xpc_release() calls and therefore, memory leaks are occurring.
  • I'm thinking the solution probably involves another branch of Sparkle, where in the parts that need to be reworked for asynchronous calls are substantially different as opposed to more conditionals all over the place. And yes, that would have to be Lion only.


Good points. Since the only XPC calls are in a function returning void, there wouldn't be that much more work to make it async. I don't think another branch would be necessary.


Did this work get any further? Happy to try to help, but it would be handy to know that I'm not duplicating effort.


Looking at the code, it seems like installWithToolAndRelaunch should be easy enough to split into two, with the completion of the copying triggering the second part.

However, there are two issues.

First, the existing file copying code is effectively synchronous, so I'm not convinced that there's an absolute requirement to make the xpc one async. I guess that this is a good thing...

Second, the last thing that the routine does is to terminate the app. If this happens before the launch task has completed, there might be an issue with the xpc service - are we sure that it will survive the app that launched it going away?

Perhaps more to the point, if we make either of the xpc calls asynchronous, that means that the original installWithToolAndRelaunch needs to return without having called NSApp terminate. This presumably has the potential to change the behaviour of whatever called it, which might lead to all sorts of mess.

Anyone got any comments? Are my concerns relevant - bearing in mind that I don't know this code base at all!



I think you'd want to terminate after the XPC service completed. I believe that'd be possible, but this was the first time that I worked with XPC, so I don't know for sure.

Other than that I think Andy would know better because he'd be more familiar with the code base.

I probably won't be finishing the work on this now, though. We just recently decided that the App we were using this for with sandboxing will be only sold through the Mac App Store now.


Thanks Whitney. I think you're right, the main code should wait until the XPC service completes.

This means that installWithToolAndRelaunch would return before the launching had completed. Hopefully that won't cause problems, but maybe Andy can say.


w.r.t. the security-in-XPC-service issue, I expect that the new bits in https://github.com/andymatuschak/Sparkle/tree/CodeSigning should help a great deal!


It's true that making the XPC calls asynchronous will change the code flow somewhat, but I don't think that will be particularly troublesome to any of the callers.


Can I help out on getting this working and secure? I've got some time available in the next few weeks.


@lwdupont go for it - I was planning on having a look at it next week, but I won't have time before then, and something else will probably come up to distract me!


@samdeane Ok, thanks. I'll probably give it a start tomorrow morning.


I changed wbyoung's patch to make the XPC calls asynchronous (commit here). A few issues remain:

  • The host app still needs entitlements for full network access. If I want to use the same entitlements file for the App Store and non-App Store versions of my app (and I do), this seems overly excessive. This means I need to put all the URL downloading code in an XPC service as well.
  • The launch_task command is still quite general. I don't know if this is a big problem or not.

Ok, so now I put the networking code in an XPC as well. This means that Sparkle will not require any extra entitlements; the Sparkle Test App runs without any entitlements at all. You can find the branch here. I've also made a few config changes to get Sparkle to build in Xcode 4.4 on 10.7; my git-fu is too weak to split this up after the fact, sorry.

So, how does it work?

  • I added a new service com.andymatuschak.Sparkle.download-service. This builds on the SandboxedFetch sample from Apple.
  • I added a class SUXPCURLDownload, that emulates NSURLDownload and calls the same delegate methods.
  • The SUXPCURLDownload class talks to a new XPC that internally uses libcurl.


  • Remote release note URLs are not supported.
  • https:// is not supported because libcurl doesn't do this out-of-the-box. Can probably be added if required. Now we all sign apps with Developer-ID anyway, right?
  • Not tested in any case other than the default Sparkle Test App case.

Hi Erik, I'm going to give this a go for a new beta build of Ambientweet. Anything I should watch out for?


Just tested this in the wild.

Running a debug build under Xcode worked fine - it successfully downloaded and installed.

However, running a release build outside of Xcode resulted in this:

Process: Ambientweet FastSpring [57842]
Path: /Users/USER/*/Ambientweet.app/Contents/MacOS/Ambientweet FastSpring
Identifier: Ambientweet FastSpring
Version: 1.1.2b1 (107)
Code Type: X86-64 (Native)
Parent Process: Ambientweet FastSpring [57828]
User ID: 502

Date/Time: 2012-08-22 18:19:30.615 +0100
OS Version: Mac OS X 10.8 (12A269)
Report Version: 10

Crashed Thread: 0 Dispatch queue: com.apple.main-thread

Exception Codes: 0x0000000000000002, 0x0000000000000000

Application Specific Information:
*** NSTask: Task create for path '/Volumes/titan/Users/sam/Library/Containers/com.elegantchaos.ambientweet/Data/Library/Application Support/Ambientweet/finish_installation.app/Contents/MacOS/finish_installation' failed: 22, "Invalid argument". Terminating temporary process.
Performing @selector(installAndRestart:) from sender NSButton 0x1002d4d90
*** multi-threaded process forked ***

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 com.apple.Foundation 0x00007fff8cd3e488 NEW_PROCESS_COULD_NOT_BE_EXECD + 5
1 com.apple.Foundation 0x00007fff8cc17b41 -[NSConcreteTask launchWithDictionary:] + 3544
2 com.apple.Foundation 0x00007fff8cc1661d +[NSTask launchedTaskWithLaunchPath:arguments:] + 205
3 org.andymatuschak.Sparkle 0x0000000100097345 0x100089000 + 58181
4 org.andymatuschak.Sparkle 0x0000000100096d1e 0x100089000 + 56606
5 org.andymatuschak.Sparkle 0x000000010009886b 0x100089000 + 63595
6 com.apple.AppKit 0x00007fff88831219 -[NSApplication sendAction:to:from:] + 342
7 com.apple.AppKit 0x00007fff88831077 -[NSControl sendAction:to:] + 85
8 com.apple.AppKit 0x00007fff88830fab -[NSCell _sendActionFrom:] + 138
9 com.apple.AppKit 0x00007fff8882f493 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 1855
10 com.apple.AppKit 0x00007fff8882ece1 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 504
11 com.apple.AppKit 0x00007fff8882e45c -[NSControl mouseDown:] + 820
12 com.apple.AppKit 0x00007fff88825dce -[NSWindow sendEvent:] + 6853
13 com.apple.AppKit 0x00007fff88821f04 -[NSApplication sendEvent:] + 5761
14 com.apple.AppKit 0x00007fff88737c7a -[NSApplication run] + 636
15 com.apple.AppKit 0x00007fff886dc656 NSApplicationMain + 869
16 com.elegantchaos.ambientweet 0x0000000100001d04 start + 52


Do you see any sandbox violations in Console?


Ah, I'm an idiot :)

I didn't add the XPC services to my application...


It appears to be working now.

If you download http://downloads.elegantchaos.com/ambientweet/ambientweet-v1.1.2b2.zip, it should try to update to 1.1.2b3. Seems to work for me, but it would be good to hear if it's broken for others :)


My version of @erikaderstedt's branch is samdeane/Sparkle branch:xpc-service (here)

It's pretty much identical at this point.


prodding andy to evaluate the submitted patch/pull request to see if it meets his approval for inclusion in master


Erik, thanks so much for your work here! I've reviewed your branch, and aside from cosmetic issues, I have a few questions and a few concerns:

  • install_service is still insecure to the extent that it defeats the purpose of the parent app being in the sandbox in the first place: it's a privileged service which will copy an arbitrary file to an arbitrary place. That's not okay!
    • Since this is the privileged process, it must be the entity which verifies the integrity of the update, not the host (as is done now).
    • This service should ensure that the update satisfies the host's designated requirement. Check out the CodeSigning branch's SUCodeSigningVerifier.
    • Since the parent app has read/write access to the update path, the install service must defend against symlink attacks which would allow an exploited parent app to swap a malicious update in immediately after the service verifies the code signature.
    • This can be accomplished by chdir()ing to the update path before performing the check, then using only relative paths to access anything in the update's path henceforth.
  • Why does the downloading service use libcurl, not NSURLDownload?
  • I like very much that you tried to make it so that the parent application doesn't have to have a network access entitlement, but I'm concerned that in general, the download service won't be sufficient for release notes: many of them have images, JavaScript, etc.
    • I guess we could document that if you're going to use "rich" release notes, your app must support full network access? Seems kind of lame, but maybe it's better than nothing.
  • Why do you pass the file descriptor of the downloaded file from the downloading service back to the host? It doesn't seem to do anything with it.
  • Why make another XPC connection to send progress reports on from the downloading service, rather than simply sending messages over the connection already established from the host?
  • I think we should prohibit the installation service from accessing the network via sandbox_init and probably prohibit the downloading service from accessing anything but /tmp via same.

I may have time this or next weekend to try to help finish this off, if that'd be useful. Thanks again for your contributions here!


Thanks for the answers, Erik. I understand now why you weren't too concerned about the security ramifications—that makes sense. I'm thinking about it more for apps which never ship on the App Store, but which are still sandboxed; they should still be secure! I'll try to make time to keep iterating on this soon.


Hi there,

Thanks for all this very exciting and interesting discussion.
Is someone can provide a simple process o follow to be able to use sparkle in a sandboxed environment? For exemple, I have absolutly no idea how I can create or retrieve XPC-service and define a class susch as SUXPCURLDownload, that emulates NSURLDownload.

Thank you!


ok, after looking for by myself, solved, everything work fine for me too.
just forgot to clone the good repo... thanks to samdeane for this very helpful work.


would love to take the credit, but it was @erikaderstedt who did the work!


and @wbyoung before that!


These changes have worked quite well, in our testing. Thank you for taking point on this @erikaderstedt and @wbyoung!

I have two diffs that I strongly encourage you to take: tumult@82eaa63 and tumult@512f9d7

They should really be one diff, but I inadvertently pushed the wrong change initially.

By default, all XPC services create their own independent security session. However, with NSDocument based applications using Sparkle, this triggers what appears to be a bug in 10.8.

The newly updated app is launched and inherits the XPC service's security session. On launch, com.apple.security.pboxd is invoked (I presume to restore state if the app has documents saved outside of its sandbox that aren't tracked by security-scoped bookmarks?). Normally, if the app is in the user's security session, this is an invisible, unremarkable event. However, in the special case of the app being launched by Sparkle's XPC service living in its own security session, this pboxd invocation actually appears as a running process in the Dock that looks identical to the newly launched app.

After following a ton of dead ends, it turns out fixing this issue is quite simple: Sparkle's XPC service needs to declare it should exist in the invoking app's security session. This is done easily, by setting JoinExistingSession to YES in the XPCService dictionary of the service's plist.

I created a test project that can be used to verify this change: https://github.com/ryannielsen/NSDocumentSandboxSparkleTest

That project will pull from our Sparkle fork https://github.com/tumult/Sparkle when you do a submodule init/update. The JoinExistingSessions key will be set to YES, so you'll need to flip that to NO if you wish to reproduce the bug.

After that one minor change, things seem to be working perfectly!


(Since it feels the behavior I've seen is a bug – I'd either expect launching the app in its own security session to be invalid and thus it shouldn't appear as a running app in the user's login session; or that is completely valid and thus com.apple.security.pboxd should not appear as a second, apparently broken instance of the launched app – I filed a bug with Apple. radar://12648274, for those who care and can view.)


Hm, but that means that the XPC service can't have a disjoint set of permissions from its host…


Hello! Thank you for providing a solution for this issue with the Sandbox, but I still can't manage to make my app update with the Sandbox.
I got the test project from https://github.com/ryannielsen/NSDocumentSandboxSparkleTest , but on update it says that "An error occurred in retrieving update information. Please try again later.", have anyone tried this project and works for him? (In this project the Sparkle is missing and I copied from here the Sparkle project, is possible that I don't have the right Sparkle?)
There some new changes from Apple and this solution doesn't work anymore?

What are the steps that should I follow exactly...?

Thank you.


@andymatuschak I want to say (though I may be mis-remembering) that we tested Sparkle updating with our app being sandboxed and the Sparkle XPCServices being signed but not sandboxed and we were able to successfully upgrade, so that would imply that disjoint permissions are allowed even though the security sessions are different.

@ClaudiuD Did you have a server running which was hosting the appcast? The solution I described earlier is definitely still working. We're using it in our app right now.


Thank you for the response. No, what files should I add to my server?
On my real app, I have the dmg and the xml file on the server, for making the update.

I am happy that your solution works, because I really need to implement it on my app.


The README.md file in https://github.com/ryannielsen/NSDocumentSandboxSparkleTest describes how to create the test… read the last two paragraphs and the step-by-step instructions at the bottom. In a nutshell, you need

1) A server that's hosting a valid appcast with a "new" version of the test app
2) The test app needs to know the hostname and url for the app cast
3) You need to run an "older" version of the test app that will cause Sparkle to trigger an update

Without those three things, the test will fail.


I implemented this solution to my project too and I respected all the steps, but still that error.
I am using Mac OS 10.7 ... works only for Mac OS 10.8?


So start again.... I take the Sparkle project from this location https://github.com/tumult/Sparkle (is this the right version?) and I copy in the folder Sparkle from your test app. I change the server base url path constantes and current version of project to 2 and build, but not Appcast folder on the desktop.... I am doing something wrong?


Can you please tell me the steps I need to follow for integrating this Sparkle on my project.
Because I did all of this and I see the service on the build for it still doesn't work and I really need to make this update.

I don't know what could be wrong...


On console I get, for my project, not the test project:
1/9/13 5:50:25.630 PM sandboxd: ([26997]) hdiutil(26997) deny sysctl-write
1/9/13 5:50:25.945 PM sandboxd: ([26997]) hdiutil(26997) deny sysctl-write
1/9/13 5:50:26.334 PM sandboxd: ([26999]) hdiutil(26999) deny sysctl-write
1/9/13 5:50:26.354 PM sandboxd: ([26999]) hdiutil(26999) deny sysctl-write
1/9/13 5:50:26.441 PM sandboxd: ([27001]) diskimages-helpe(27001) deny mach-lookup BE03ABED-723B-4772-B435-9B734C05E3A4
1/9/13 5:50:26.442 PM diskimages-helper: ERROR: couldn't connect to framework.
1/9/13 5:50:26.442 PM diskimages-helper: DIHelper: setupConnectionToFrameworkWithUUID: failed

Its clearly that I don't have permissions to write, because of the Sandbox, but how this service starts on non-sandbox....
I respected the steps, but I am not sure if the service starts...


Hi @ClaudiuD this isn't the proper venue for diagnosing what's going wrong with my example project. I'm also afraid I cannot offer support for the project. I created it to ensure the changes in this pull request would meet our needs for our project, nothing more and nothing less.

A quick google on "diskimages sandbox" returns http://stackoverflow.com/questions/9119191/sandbox-and-nstask, which then led me to search devforums.apple.com. At the bottom of this thread, you'll see what's causing problems: https://devforums.apple.com/message/745710

It seems that sandboxed apps won't be able to use dmgs as their archive and distribution mechanism. The Sparkle changes it tests work for us on our app which is sandboxed on 10.7 and 10.8, but we distribute zip files.


Thank you for you answer. You save me a lot of time and nerves :) . Best.


This Sparkle works with zip and in my project too. Great job.
Thank you.


Fantastic work! Gonna give a shot.


@andymatuschak: would it be worth making a branch for this stuff on your repo, if you don't have the time to actually review the changes and merge them into your master branch?

That way people have an obvious place to go to get the latest sandbox-friendly version. At the moment it's a bit confusing - you have to read all the way through this thread to work out which commit to take from where.

I've got a "sandboxing" branch on my fork which I'm going to attempt to use for the latest version of Neu, but I'm a not 100% certain at the moment that I've picked up everything I should :)


Ok, that makes sense, and I wasn't meaning to imply that you were slacking ;)

It looks like some additional work has been done since you posted those comments. If we could collectively figure out a list of jobs still to be done to get it to an acceptable state for merging, then that would increase the likelihood of one of us picking up the baton and doing those jobs.

Personally, I'm in a similar position to Erik. I want to sandbox the non-App store version of my app, just for the sake of parity with the App store version, and so that I don't have to maintain two wildly different code-paths.

Of course I'd like it to be secure ultimately, but getting a new version of my app out there is higher priority in the short term. Of course, as long as Sparkle is working, I can update the app as Sparkle's sandboxing support gets improved.


It seems to me that one of the main vulnerabilities could be eliminated by simply not passing the path of finish_installation to the XPC service. That stops it from being an arbitrary copying tool. Can't the service figure that out for itself (it should be a fixed relative path from the xpc service's own bundle location, as Andy mentioned in an earlier post).

For the other vulnerability, is it enough to have finish_installation verify that the package that it's going to install, the package it's going to replace, and itself were all signed by the same entity? This seems like it ought to be a way to generalise the code so that no information needs to be passed in other than the location of the package to be installed. An exploited app can do whatever it wants with the package or the path to it, but it won't be able to make these signatures match.

What am I missing?


It seems to me that an even simpler approach would be to launch the finish_installation tool in the downloaded bundle directly. I don't think it would have a problem with copying the application into place (including itself) even though it's running. The only problem it would have would be in deleting the downloaded bundle afterwards, since it would be inside it. However, this step could just as easily be performed by the updated app, the next time it launched.

I'm probably missing something with this idea, but it would eliminate the need for an xpc helper at all.


FWIW, I also wonder if this might be the point to perform some more radical pruning of the code.

I added an issue: #248


Is there a fork of Sparkle that supports Sandboxing with CodeSigning right now? I could really use this in my project. I'm distributing a private beta of an app to beta testers and this would be really helpful.

@mikeabdullah mikeabdullah pushed a commit to karelia/Sparkle that referenced this pull request Nov 27, 2013
@ryannielsen ryannielsen Merge in changes for sandboxing and XPC
Pull request here: sparkle-project#165
Changes here: wbyoung@dbd2b9b
@mikeabdullah mikeabdullah pushed a commit to karelia/Sparkle that referenced this pull request Nov 27, 2013
@pburleson pburleson Merge commit 'ea50a33b46ceeef19b92534a29cc8d3463f67b23' into sandbox-…

Merging in Sam Dean's sandboxing branch which contains the work to get Sparkle to work with sandboxing done by several folks in this pull request thread: sparkle-project#165

Resolved with changes from upstream
Sparkle Project member

Because this branch is very old and has security issues (#165 (comment)) — I'm closing this pull request.

I realize that sandboxing support is very important and it's a high priority issue, so I'm starting a discussion in #363 to work out how can we restart this effort with a more secure approach.

@pornel pornel closed this Jul 2, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment