macOS App Sandbox
Originally, I set out to avoid the appearance of a system prompt of our app wanting to access data from other apps when it actually just tried to create a local socket file in its own group container for interprocess communication.
⚠️ Key Changes
- Our app and its contained extensions now use an app group which complies with the conventional naming scheme. Instead of reusing the main bundle identifier as an app group identifier, the used app group identifier now has a prefix based on the development team. This is the option to have "unprovisioned" group containers at the cost of restricting compatibility to macOS and not being abled to use entitlements like keychain sharing. It simplifies the identity management and signing a lot in which we are also stuck with legacy hurdles impossible to overcome smoothly. See "Accessing app group containers in your existing macOS app" by Apple for further information.
- The build settings of the NextcloudIntegration.xcodeproj were updated to use a new common base identifier based on Nextcloud instead of ownCloud. Also, redundant build settings around signing were removed.
- Due to the use of the provisioned app group which requires a provisioning profile, automatically managed development signing had to be enabled for every target in the Xcode project to maintain the possibility to build.
- Added app sandbox entitlement to the main app. The other relevant targets already had this as it is imposed on extensions as a requirement by the platform. This is an important step for the long term goal of enabling Mac App Store distribution. ✨ See "App Sandbox" by Apple for further information.
- Added network client entitlement to the main app for obvious reasons.
- Added entitlement for read-write access to files selected by users. This is required for features like the debug archive export which enables saving files outside the sandbox.
- The socket files created for interprocess communication now are located within the sandboxed containers for the app. The previous location in the root of the container were still breaking the sandbox.
- A container migration manifest was added which lets the macOS container service automatically migrate application data from legacy locations into the sandbox container specifically set up for it. See "Migrating your app’s files to its App Sandbox container" by Apple for further information.
- Added Objective-C++ bridging code for security scoped URL handling because Qt does not take care of it. This is required to access files outside of the sandbox, in example when writing a ZIP archive during the debug archive export.
- The file provider extension must place its logs and databases inside the new group container (
group.prefix) so the main app can access it for creation of a debug archive. This renders the shared legacy account database migration code obsolete because it could not be accessed anyway. - The synchronization folder setup was changed to force the user to select the local target through a system dialog. This is necessary for the system to grant access to the location outside the sandbox.
☑️ To Do
A migration wizard might be necessary which requires the user to select every synchronization root folder once which set up in previous releases through an "open directory" panel by the system because that is how granting access permission to user-selected file system items work and required to persist access to those out of the sandbox.
😵 Caveats
Breaking File Provider Change
This is a breaking change for our file provider extension. Currently, I do not see a way to enable the app sandbox without setting up file provider domains for accounts from scratch. The compliance with the app sandbox requires to use a proper group container identifier and app sandbox migration manifests only support containers (not group containers), to my knowledge.
Should a 4.1 build look for the file provider extension data, it will look in the new group container initially and not find anything. This is equal to a complete reset.
UNIX Sockets
Long identifiers may break the UNIX socket-based IPC. The app sandbox makes long path prefixes inevitable. In example:
/Users/<redacted>/Library/Group Containers/NKUJUXUJ3B.com.nextcloud.desktopclient/Library/Application Support/.fileprovidersocket
It is about 122 characters long and there is a problem with that:
The issue is that the socket path is too long for QLocalServer on macOS. Unix domain sockets have a maximum path length of typically 104 characters, and your path exceeds this limit.
Apple XNU kernel source code for reference.
The solution for now is to keep it as short as possible but this may break with branded identifiers which are significantly longer than this reference case.
/Users/<redacted>/Library/Group Containers/NKUJUXUJ3B.com.nextcloud.desktopclient/fps
⚡️ Impact
This is not a simple bug fix but foundational changes to how security is implemented by our client on macOS. It requires extensive testing and cannot be delivered or ported back as a patch release. This is not just flipping a switch in some settings. The debug archive creation feature is an example how the technical debt of a missing app sandbox requires code refactoring.
🔗 Dependencies
This pull request requires these changes to NextcloudFileProviderKit.
🔬 Testing
When upgrading from a previous release:
- The accounts having their file provider domain enabled should still have it enabled
- The file provider domain should be set up from scratch for those accounts, though
- The configuration file no longer contains mappings between accounts and file provider domain identifiers but has individual properties on the namespaced account configurations
- The
UserDefaultsare no longer used by the file provider extension - The configuration file contains a
fileProviderDomainsAppSandboxMigrationCompleted=true - Classic synchronization still works with the preexisting synchronization folders
- Classic synchronization works with newly set up synchronization folders
I need to fix how the build settings are passed through now before it is ready for review.
Artifact containing the AppImage: nextcloud-appimage-pr-9023.zip
Digest: sha256:77bffcb86fb2f4d0d66c09c726b345ea58fba13a8a2006e6b5ebcd618c446e26
To test this change/fix you can download the above artifact file, unzip it, and run it.
Please make sure to quit your existing Nextcloud app and backup your data.
Quality Gate failed
Failed conditions
1 Security Hotspot
0.0% Coverage on New Code (required ≥ 80%)
240 New Code Smells (required ≤ 0)
D Maintainability Rating on New Code (required ≥ A)
See analysis details on SonarQube Cloud
Catch issues before they fail your Quality Gate with our IDE extension
SonarQube for IDE