Accessing preferences in another app's sandbox - operation denied and NSAppDataUsageDescription never shows

macOS 27.0 beta 4

I have an installer app which needs to set a key/value inside a plist file during installation. This is for a screensaver that runs under the legacyScreensaver system, so the plist lives at:

~/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver.x86-64/Data/Library/Preferences/com.foobar.plist

Although I can see the plist file in the Finder, my installer app can't read or write it, and the NSAppDataUsageDescription string is not shown, nor does the OS ask the user for permission.

Also, trying to do this via the Terminal app is also blocked (even using 'sudo'). I understand this is part of the new Golden Gate security system.

In Golden Gate, is there a legitimate way to accomplish this so it works like it did in macOS 26 and earlier?

I'd like my installer to request access, the NSAppDataUsageDescription string is shown, and the user can grant or deny permission.

Answered by DTS Engineer in 899095022
I agree that using Appex format would be ideal. I submitted FB24013514

Thank you.

As for workarounds using legacyScreenSaver

Yeah, that’s basically the direction I was gonna send you. It works because the process that loads and runs your screen saver isn’t tightly sandboxed [1].

The only question is whether it’s better to continue using the preferences subsystem or whether you can store this state in a file that’s entirely under your control (so a property list in ~/Library/Application Support/YOUR_PRODUCT_NAME). Honestly, I’d lean towards the latter. It has various benefits:

  • It’s in the user’s home directory, so each user gets their own preferences.
  • And you don’t need privilege escalation to write it.
  • And while I can’t predict the future with 100% fidelity, my experience is that completely custom stuff is less likely to bump into these sorts of restrictions.

Oh, and if you want your screen saver to react on the fly to changes in this file, your app can post a distributed notification to tell the screen saver that it’s changed.

Share and Enjoy

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

[1] It’s still sandboxed mind you, just not so tightly that it can’t access files on disk.

I don’t think there’s any reasonable way to achieve this specific goal )-: Recent versions of macOS have become increasingly strict about app and group container protection, as explained in the WWDC talks linked to by Trusted Execution Resources. This is just a further refinement of that overall goal.

IMO the correct long-term fix for this is for Apple to support app extensions based screen savers. If that happened, this problem would go away because you could store these preferences in an app group container that’s available both the screen saver appex and its container app. However, there’s no sign of that happening )-:

If you’d like to see that happen in the future, I encourage you to file an enhancement request describing this problem and any other problems you see caused by the legacy screen saver architecture. And please post your bug number, just for the record.


As to what you can do about this today, lemme start with question: How was your installer app previously writing this property list?

Share and Enjoy

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

Thanks, @DTS Engineer for your (as always) thoughtful comments.

I agree that using Appex format would be ideal. I submitted FB24013514 (under macOS / ExtensionKit / Suggestion) which I hope was the right category choice.

As for workarounds using legacyScreenSaver, I believe I have found one.

While it is true that a .saver running in the legacyScreenSaver sandbox can not read or write data from ~/Library/Preferences/com.foobar.plist

...it appears that it can read data from /Library/Preferences/com.foobar.plist (the shared Preferences location) with this code:

let val = CFPreferencesCopyValue(key as CFString, self.bundleID as CFString, kCFPreferencesAnyUser, kCFPreferencesAnyHost)

So a possible workaround is for the installer to write data to this location, which the screen saver can then read.

I'm not sure if this is a clever and legitimate workaround or a bad idea.

It seems unlikely to me that Apple would decide to restrict read-only access to /Library/Preferences in the future, but I don't have a great track record making these sorts of predictions.

I agree that using Appex format would be ideal. I submitted FB24013514

Thank you.

As for workarounds using legacyScreenSaver

Yeah, that’s basically the direction I was gonna send you. It works because the process that loads and runs your screen saver isn’t tightly sandboxed [1].

The only question is whether it’s better to continue using the preferences subsystem or whether you can store this state in a file that’s entirely under your control (so a property list in ~/Library/Application Support/YOUR_PRODUCT_NAME). Honestly, I’d lean towards the latter. It has various benefits:

  • It’s in the user’s home directory, so each user gets their own preferences.
  • And you don’t need privilege escalation to write it.
  • And while I can’t predict the future with 100% fidelity, my experience is that completely custom stuff is less likely to bump into these sorts of restrictions.

Oh, and if you want your screen saver to react on the fly to changes in this file, your app can post a distributed notification to tell the screen saver that it’s changed.

Share and Enjoy

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

[1] It’s still sandboxed mind you, just not so tightly that it can’t access files on disk.

I like your suggestion, but I just tried it and it seems that ~/Library/Application Support/ is re-routed inside the container, e.g.

let rawPath = "~/Application Support/com.example/\(self.bundleID).plist"
let path = (rawPath as NSString).expandingTildeInPath
let url = URL(fileURLWithPath: path)
guard let dict = NSDictionary(contentsOf: url) as? [String: Any] else {
  print("Error: Failed to read or parse plist at: \(path)")

When run in context of a .saver running inside LegacyScreenSaver, this happens:

Failed to read or parse plist at: /Users/username/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver.x86-64/Data/Application Support/com.example/com.example.foobar.plist

I did another test where, instead of using ~/Library/Application Support/ I used /Users/Shared/com.example and that one seemed to work.

Using /Users/Shared/ is not very elegant, but seems to be a location of last resort that some other apps use in some cases.

Am I missing something? Is there a way from inside a sandbox, to get to the real ~/Library/Application Support/ folder, not the containerized one?

it seems that ~/Library/Application Support/ is re-routed inside the container

Ah, interesting. As I mentioned above, the process that loads your screen saver is sandboxed, and that’s what’s causing this redirect.

Is there a way from inside a sandbox, to get to the real ~/Library/Application Support/ folder …?

Yes. Well, there’s a way to get the real home directory and then navigate from there. See the code in this post.

Of course it’s still possible that the screen saver sandbox might block that access. But, yeah, give it a whirl and let me know what you get.

Share and Enjoy

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

Accessing preferences in another app's sandbox - operation denied and NSAppDataUsageDescription never shows
 
 
Q