macOS TCC Accessibility permission granted, yet the Accessibility APIs sporadically (!) return no data

Since the end of 2021, I have been getting reports from my users that my app Timing (https://timingapp.com) no longer records window titles and file paths for the apps they use, despite Accessibility permissions having been granted.

The problem manifests itself such that sometimes (see below on the conditions I was able to identify), the "Timing Tracker" app is shown and appears checked in "Security & Privacy" System Preferences, and calling AXIsProcessTrustedWithOptions() with options @{ (__bridge id) kAXTrustedCheckOptionPrompt : (id) kCFBooleanFalse } returns true. However, any of my actual Accessibility code (e.g. obtaining a process's windows) only returns nil (or empty arrays) when the problem is occurring.

Any pointers as to what could be the reason or what I could investigate would be very appreciated, as I really am at a loss here.

Here are a few additional things to note that may or may not be related to the issue at hand:

  • My use of the Accessibility API usually works fine and has been working fine for quite a while; only recently has it started to sporadically stop working for some users.
  • The app consists of a "main" app, with a helper contained therein that actually performs the Accessibility requests. In the "Security & Privacy" System Preferences, the helper (called "Timing Tracker") is shown and appears checked (i.e. Accessibility permissions seem to be granted).
  • This only seems to affect the Accessibility API; Automation (i.e. Apple Events) continue to function if the user has granted permission for them.
  • It appears that these issues occur more frequently after the app gets updated and the helper restarts itself because it has detected changes to its application bundle, but it appears that's not the only cause for this issue (i.e. it also happens without a recent app update having taken place). The helper uses the following code to relaunch itself:
- (void)relaunchWithDelay:(NSTimeInterval)delay {
	// $N = argv[N]
	// Sleep until our own process has been killed, then sleep for another 15 seconds, then relaunch the app.
	NSString *killArg1AndOpenArg2Script = [NSString stringWithFormat:
										   @"/bin/kill $1; (while /bin/kill -0 $2 >&/dev/null; do /bin/sleep 1; done; /bin/sleep %lf; /usr/bin/open \"$3\") &",
										   delay];

	// NSTask needs its arguments to be strings
	NSString *ourPID = [NSString stringWithFormat:@"%d",
	                                              [NSProcessInfo processInfo].processIdentifier];

	// this will be the path to the .app bundle,
	// not the executable inside it; exactly what `open` wants
	NSString *pathToUs = [NSBundle mainBundle].bundlePath;

	NSArray *shArgs = @[ @"-c", // -c tells sh to execute the next argument, passing it the remaining arguments.
						 killArg1AndOpenArg2Script,
						 @"", // $0 path to script (ignored)
						 ourPID, // $1 in restartScript
						 ourPID, // $2 in restartScript
						 pathToUs ]; // $3 in restartScript

	NSTask *restartTask = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:shArgs];
	[restartTask waitUntilExit]; // wait for killArg1AndOpenArg2Script to finish

	NSLog(@"*** ERROR: %@ should have been terminated, but we are still running", pathToUs);
	assert(false && "We should not be running!");
}

I am unsure whether this invocation somehow relaunches the helper in a state that temporarily strips it of its TCC/Accessibility permissions.

  • According to user reports, this can usually be fixed either relaunching the helper, rebooting the Mac, or (in some cases) unchecking the helper in the "Security & Privacy" System Preferences. (It seems like which of these escalating steps is required for the fix varies from user to user.)
  • I have heard reports of this from both macOS 11 and macOS 12 (not sure whether it also occurs on macOS 10.15).
  • Given reports of some TCC vulnerabilities having been fixed recently, I wonder whether some of the fixes could trigger a denying of Accessibility permission to my app.
  • I still haven't been able to reproduce the issue myself, but have received plenty of credible reports that this is actually happening. As mentioned above, rebooting the Mac usually seems to fix the problem, which makes this particularly hard to investigate and debug.
  • The app itself has been around since at least 2017, yet these problems seem to have only started occurring (or at least became much more frequent) towards the end of 2021. I do not recall any substantial chances to the relevant code paths recently.
  • The app is not sandboxed; it is correctly signed and notarized with Developer ID. Hardened Runtime is enabled; the only Hardened Runtime entitlement requested is "Apple Events".
  • I do have changed the Organization name of my Apple Developer account in 2021, but I don't think that's related, because the designated requirement (csreq) stored in tcc.db for Accessibility with my app is anchor apple generic and identifier "info.eurocomp.TimingHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = NDB5JK3DZG). This seems to be appropriate, and does not include the organization name itself. My Organization's Team ID (NDB5JK3DZG) has not changed.

Replies

First up, check your update process to make sure that you’re not hitting the issue discussed in Updating Mac Software. This is relevant because TCC identies your code via its code signature and if you’re code signature breaks in some weird way then you could easily hit a TCC problem. The fact that the problem goes away when you restart the Mac could just be a coincidence, but it could also be evidence to support this theory.

Beyond that, debugging intermittent problems that only show up in the field is tricky. The best thing to do here is to capture a sysdiagnose log from the user and file a bug about it. Or, if the user isn’t happy to share a sysdiagnose log, have them file a bug with the log.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for the quick reply, Quinn!

For updates, I am using Sparkle, which should perform the updates atomically (see https://github.com/sparkle-project/Sparkle/blob/acc4674c3e769c64434fec958d8ee0a397bfa6c7/Autoupdate/SUPlainInstaller.m#L210). My app is also distributed through Setapp; I am not sure how Setapp updates applications but I would expect them to do it correctly, and the problem occurs there as well.

I will ask the next users who report this issue to provide a sysdiagnose. In the meantime, do any other problems come to mind for you that I could check for? What particularly surprises me is that a) AXIsProcessTrusted still returns true and b) the app still seems to be able to send Apple Events, even though those are protected by TCC as well.

What particularly surprises me is that a) AXIsProcessTrusted still returns true and b) the app still seems to be able to send Apple Events, even though those are protected by TCC as well.

You’re assuming that this problem is TCC related, but that’s not guaranteed. It could be that something else in the Accessibility subsystem has fallen over and that’s why you’re seeing these failures.

Unfortunately I don’t know enough about these components to offer any alternative theories that you could reasonably test.


Oh, I have one other trick up my sleeve here (-:

It’s possible that a bunch of your users are seeing this problem and not reporting it to you. Speaking personally, if I see a weird problem like this and it’s cleared by a restart, I wouldn’t bother the app developer. However, there may be something you could do to improve that.

AFAICT you could reasonably write code to detect this problem. That is, if AXIsProcessTrusted returns true and you’re not seeing window titles for, say, the Finder, you know you’re in this bogus state. If so, you could do this detection and specifically prompt the user to trigger a sysdiagnose log and get in touch.

Depending on the size and expertise of your user base you may not want to add this to your release version; in that case, you might consider adding it to the version you ship to your beta testers.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

You’re assuming that this problem is TCC related, but that’s not guaranteed. It could be that something else in the Accessibility subsystem has fallen over and that’s why you’re seeing these failures. Unfortunately I don’t know enough about these components to offer any alternative theories that you could reasonably test.

Good point, I did not think of that! Unfortunately, I am also unsure about how to best test that theory. Given that I'm checking and rechecking the app in system preferences sometimes seems to help, though, I find it likely that this somehow has to do with TCC.

Oh, I have one other trick up my sleeve here (-:

It’s possible that a bunch of your users are seeing this problem and not reporting it to you. Speaking personally, if I see a weird problem like this and it’s cleared by a restart, I wouldn’t bother the app developer. However, there may be something you could do to improve that. AFAICT you could reasonably write code to detect this problem. That is, if AXIsProcessTrusted returns true and you’re not seeing window titles for, say, the Finder, you know you’re in this bogus state. If so, you could do this detection and specifically prompt the user to trigger a sysdiagnose log and get in touch. Depending on the size and expertise of your user base you may not want to add this to your release version; in that case, you might consider adding it to the version you ship to your beta testers.

Thank you for the suggestion! I have indeed been collecting analytics and just the way you described for a couple of weeks, and it seems like a substantial portion of my user base is affected; large enough that I wouldn't want to notify them all (although random sampling might help with that). My group of beta testers on the other hand might be too small to solicit feedback, I fear. I'll need to think more about how I handle that.

Another question related to this: Do you know of a good way to determine whether a given app mattress a particular designated requirement? I know that I can extract the designated requirement using codesign -d -r-, but am unsure of how to test a given requirement. I'm thinking of spctl, but am unsure of the appropriate syntax.

Do you know of a good way to determine whether a given app mattress a particular designated requirement?

From the command line, use codesign -v -R. See the codesign man page for the details.

At the API level, call SecCodeCheckValidityWithErrors passing the requirement to the (optional) requirement parameter.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Quick heads-up: I have now filed FB10014020 on this issue.

Any updates on this? We are encountering similar issues, which are also resolved via rebooting.