Problem with event tap permission in Sequoia

I have a Mac app with a background-only helper app that needs to have Accessibility permission in order to use an event tap that can modify events. This has worked OK through Sonoma, but in the Sequoia beta it is failing to create the tap. C code to test the ability to create the event tap:

static CGEventRef  _Nullable DummyTap(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *userInfo)
{
	return NULL;
}

static bool	CanFilterEvents( void )
{
	CFMachPortRef thePort = CGEventTapCreate(
		kCGSessionEventTap,
		kCGTailAppendEventTap,
		kCGEventTapOptionDefault,	// active filter, not passive listener
		CGEventMaskBit(kCGEventKeyDown),
		DummyTap,
		NULL );
	bool madeTap = (thePort != NULL);
	if (madeTap)
	{
		CFMachPortInvalidate( thePort );
		CFRelease( thePort );
	}
	
	return madeTap;
}

So, on Sequoia, CanFilterEvents returns false in spite of Accessibility permission being granted in System Settings. CGPreflightPostEventAccess also returns false, but AXIsProcessTrusted returns true.

I tried making a non-background-only test app, and when that has Accessibility permission, CanFilterEvents, CGPreflightPostEventAccess, and AXIsProcessTrusted all return true. Suggestions on what to try next?

Answered by DTS Engineer in 793915022

I tried making a non-background-only test app, and when that has Accessibility permission, CanFilterEvents, CGPreflightPostEventAccess, and AXIsProcessTrusted all return true. Suggestions on what to try next?

First off, please file a bug report on this and post the feedback number here. If something that used to work stops working then a bug is always warranted.

As for what you do "now", there are few things:

  1. How exactly is your "background-only helper app" setup? Is it an app bundle that marked as "faceless" with LSUIElement set? Or is it a standalone executable? My general recommendation is that the "default" should be to bundle ALL executables, even if you won't actually be calling/using that executable as an "app". For example, I even think it's a good idea to bundle "command line" executables that simply perform specific tasks and then exit. As far as the system is concerned, any executable will work fine inside an app bundle and, in my experience, the bundle structure is far less likely to have issues.

  2. How are you launching your helper app? In particular, "direct execution" (Process, NSTask, fork/exec, posix_spawn...) is NOT equivalent to having "the system" launch the app (NSWorkspace). There's a long history of "nonsensical" API failures like this occurring because an internal framework change ended up breaking a complicated chain of "details" that were allowing the direct execution case to work.

  3. What have you actually granted permission to? If you haven't authorized your main app, that would be worth trying as well.

  4. What happens if you transition your test app into "accessory"? Or transition your real app into "regular", then back to "accessory"? Our API history obscures this, but apps can freely transition in/out of app types using setActivationPolicy(_:)->.accessory/.regular. What LSUIElement basically does is tell LaunchServices not to create a dock icon during early launch the "set" your app into NSApplication.ActivationPolicy.accessory once it's actual up and running.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Accepted Answer

I tried making a non-background-only test app, and when that has Accessibility permission, CanFilterEvents, CGPreflightPostEventAccess, and AXIsProcessTrusted all return true. Suggestions on what to try next?

First off, please file a bug report on this and post the feedback number here. If something that used to work stops working then a bug is always warranted.

As for what you do "now", there are few things:

  1. How exactly is your "background-only helper app" setup? Is it an app bundle that marked as "faceless" with LSUIElement set? Or is it a standalone executable? My general recommendation is that the "default" should be to bundle ALL executables, even if you won't actually be calling/using that executable as an "app". For example, I even think it's a good idea to bundle "command line" executables that simply perform specific tasks and then exit. As far as the system is concerned, any executable will work fine inside an app bundle and, in my experience, the bundle structure is far less likely to have issues.

  2. How are you launching your helper app? In particular, "direct execution" (Process, NSTask, fork/exec, posix_spawn...) is NOT equivalent to having "the system" launch the app (NSWorkspace). There's a long history of "nonsensical" API failures like this occurring because an internal framework change ended up breaking a complicated chain of "details" that were allowing the direct execution case to work.

  3. What have you actually granted permission to? If you haven't authorized your main app, that would be worth trying as well.

  4. What happens if you transition your test app into "accessory"? Or transition your real app into "regular", then back to "accessory"? Our API history obscures this, but apps can freely transition in/out of app types using setActivationPolicy(_:)->.accessory/.regular. What LSUIElement basically does is tell LaunchServices not to create a dock icon during early launch the "set" your app into NSApplication.ActivationPolicy.accessory once it's actual up and running.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

After more fooling around with deleting builds, deleting the copy from the App Store, and so forth, the problem no longer reproduces. So, that's good, I guess, but I'm not in the position of being able to file a useful bug report.

But to answer your questions:

  1. The helper is an app bundle with LSBackgroundOnly set true in its Info.plist.
  2. I launch it using NSWorkspace.
  3. I granted permission to the helper, not the main app.
  4. Since the helper is background only, not a LSUIElement, I'm not sure that what you're saying there is relevant.
  1. Since the helper is background only, not a LSUIElement, I'm not sure that what you're saying there is relevant.

So, a bit of background/context here. In theory, the system defines three activation types controlled "setActivationPolicy", then provides plist keys which define what mode your app launches into:

  1. NSApplicationActivationPolicyRegular ->no plist key or LSUIElement=1. This is the standard app case.

  2. NSApplicationActivationPolicyAccessory ->LSUIElement=0. "The application doesn’t appear in the Dock and doesn’t have a menu bar, but it may be activated programmatically or by clicking on one of its windows."

  3. NSApplicationActivationPolicyProhibited-> LSBackgroundOnly. "The application doesn’t appear in the Dock and may not create windows or be activated."

What's tricky here is how theses policies fit into the lower level CG API set, as well was as more complicated edge cases. These policies were designed to help apps "get the behavior they wanted", NOT as formal "security" restrictions intended to restrict what was possible. An app can change activation policy at any time and the plist keys simply define how the app should be launched, not how the app should behave after it's already running.

The problem here is that EXACTLY what "LSBackgroundOnly" should prevent isn't very well defined. It says "create windows or be activated", but that's a pretty vague description that doesn't really map to a specific API "block". That "vagueness" is what then opens the door to failures like the one that started this all off:

So, on Sequoia, CanFilterEvents returns false in spite of Accessibility permission being granted in System Settings. CGPreflightPostEventAccess also returns false, but AXIsProcessTrusted returns true.

Using "NSApplicationActivationPolicyProhibited" means you've told the system that your process is not allowed to make certain kinds of API request, creating a new failure path that would otherwise not exist.

My recommendation would be to default to "NSApplicationActivationPolicyAccessory" unless you're specifically avoiding ANY interaction with the window system and AppKit (or other "interface" APIs). There isn't any user visible difference between "NSApplicationActivationPolicyAccessory" and "NSApplicationActivationPolicyProhibited", but using "NSApplicationActivationPolicyProhibited" introduces API failures/differences that won't occur with NSApplicationActivationPolicyAccessory

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

@DTS Engineer I am a little confused by your mentions of LSUIElement=0 and LSUIElement=1 in your numbered list. Do you have them backward maybe? As I read the docs, the plist key LSUIElement has a boolean value, and the true value means that the app does not appear in the Dock, as in your case 2. So case 2 should say LSUIElement=1, right?

Yep, I flipped the values. I was bouncing between things switched them while I was editing.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Problem with event tap permission in Sequoia
 
 
Q