macOS Containers and defaults

September 22 2020 by Jeff Johnson

This is a follow-up to the article Enabling the Debug menu in Safari 14 on Big Sur and Catalina by Dan Moren. In the past, you could enable the hidden Debug menu in Safari by using the command defaults write IncludeInternalDebugMenu -bool true in Terminal app. However, Dan discovered that this no longer worked for him, and he was forced to manually edit a plist file in order to enable the Debug menu. So what happened here? This is the question my blog post will answer.

Dan correctly notes that Safari's preferences file has moved from ~/Library/Preferences/ to ~/Library/Containers/, which is inside Safari's "container". What Dan didn't realize is that this has nothing to do with Safari 14. It actually happened quite some time ago, I believe last year with Safari 13. For the first time, Safari 13 was sandboxed. If you do codesign --display --entitlements - /Applications/ in Terminal, you'll see the indicator of a sandboxed app:


Safari 12 and earlier were not sandboxed, and Safari preferences were always stored in the traditional location for preferences files, the ~/Library/Preferences folder. However, a sandboxed app has only limited access to the file system on your Mac. For example, a sandboxed app can't read or write to the ~/Library/Preferences folder. This is why each sandboxed app has its own special container to store the files it needs. In the case of Safari, that's the ~/Library/Containers/ folder. Thus, the Safari preferences file location had to move in Safari 13. The catch is that if you ever ran Safari 12 or earlier on your Mac, the old ~/Library/Preferences/ file was left behind!

Besides sandboxing, there's another factor at work in this scenario: System Integrity Protection, AKA SIP. Certain files and folders on your Mac are protected by the system, and cannot be accessed without special permission, even by non-sandboxed apps. It turns out that Safari's container is one of those protected folders. By default, Finder has special permission to access the whole file system, but Terminal app does not have special permission by default. So if you try to list the contents of Safari's container, you can't.

$ cd Library/Containers/
$ ls
ls: .: Operation not permitted

If you want to give special permission to Terminal, you have to open System Preferences, Security & Privacy pane, Privacy tab, select Full Disk Access in the list, unlock with your administrator password, add Terminal, and relaunch Terminal if it was already running. (The last step is crucial.) Now you have access to Safari's container.

$ cd Library/Containers/
$ ls
Container.plist	Data

How does this affect the defaults command-line tool? If Terminal app has Full Disk Access, then defaults write is smart enough to use the preferences file in Safari's container. But if Terminal does not have Full Disk Access, then defaults falls back to the preferences in the ~/Library/Preferences folder! So if you do defaults write IncludeInternalDebugMenu -bool true without Full Disk Access, it'll write to ~/Library/Preferences/, but that has no effect, because Safari is sandboxed and only reads preferences from its own container. Note that this happens even if there's no old file at ~/Library/Preferences/, because defaults will create a new file when necessary.

$ defaults read
2020-09-22 08:17:23.053 defaults[1782:31894] 
Domain does not exist
$ defaults write IncludeInternalDebugMenu -bool true
$ defaults read
    IncludeInternalDebugMenu = 1;

There are three morals to the story, First, if you want defaults write to work right for Safari, you need to give Full Disk Access to Terminal. Second, if you want to avoid confusion, make sure to delete the old ~/Library/Preferences/ file, because it's no longer used by Safari, but it may be used by defaults, resulting in much hilarity. For example, defaults read IncludeInternalDebugMenu could show the wrong value. If you want to see how this could happen, compare the result of defaults read NewestLaunchedSafariVersion with and without Full Disk Access. They're different!

The third and final moral to the story is that SIP and sandboxing are terrible and ought to be abolished.

Jeff Johnson (My apps, PayPal.Me)