Hello everyone,
I'm hoping to get some guidance on a frustrating codesigning issue. I have a macOS application that successfully completes the entire notarization and stapling process, but it is still rejected by Gatekeeper during the final verification step. The rejection only happens when I apply the entitlements that I believe are necessary for my app's functionality.
The application is built with PyInstaller and has the following components:
- A main executable written in Python.
- A bundled Tcl/Tk instance for the GUI.
- Embedded Playwright components, which include the Node.js runtime and a full Chromium browser instance. These are located deep inside the .app bundle.
The Problem
The core of my application relies on Playwright to perform some automated tasks, and its bundled Chromium browser requires specific entitlements to function under the Hardened Runtime. Specifically, it needs com.apple.security.cs.allow-jit and com.apple.security.cs.allow-unsigned-executable-memory.
My signing process is as follows:
- Prepare Entitlements: I use two separate .plist files:
- main_app_entitlements.plist: This is for the main Python executable and only contains com.apple.security.cs.allow-jit.
- jit_helper_entitlements.plist: This is for the node and Chromium Helper executables within the Playwright framework. It contains both com.apple.security.cs.allow-jit and com.apple.security.cs.allow-unsigned-executable-memory.
- Inside-Out Signing: I perform a deep signing process. I find all binaries, dylibs, and frameworks, sort them by path length (deepest first), and sign each one individually with the appropriate entitlements. The main .app bundle is signed last.
- Notarization: I zip the .app bundle and submit it using xcrun notarytool submit --wait. The tool reports a successful notarization every time.
- Stapling: I use xcrun stapler staple on the .app bundle, and it confirms that the ticket was successfully stapled.
The point of failure
The final step is to verify the result with spctl: spctl --assess --type execute --verbose --ignore-cache "MyApp.app" This is where it fails.
The output is: MyApp.app: rejected source=Unnotarized Developer ID This "Unnotarized Developer ID" message is confusing because xcrun notarytool and stapler both report complete success.
The crucial detail
If I run the entire process without any entitlements—just signing with the Hardened Runtime enabled—the final spctl assessment passes. However, the application then crashes at runtime as soon as it tries to use Playwright, which is expected since the browser helpers are missing their required JIT entitlements.
My question
Is there a known issue where using com.apple.security.cs.allow-jit or com.apple.security.cs.allow-unsigned-executable-memory on nested helper executables can invalidate an otherwise successful notarization?
Is my strategy of applying different, granular entitlements to different executables within the same app bundle correct?
Could the issue be related to how or when these entitlements are applied during an "inside-out" signing process? Is there a better way to structure the signing of these complex components?
I'm confident the notarization itself is working, but it seems Gatekeeper's local assessment is stricter and is being tripped up by my entitlement configuration.
Thank you in advance for any help or suggestions you can provide
1. I find it completely illogical that gatekeeper rejects notarised apps.
You’re not the first person to make that observation. If you want to get that feedback to the folks who have the power to enact change, I recommend that you file a bug about it.
Please post your bug number, just for the record.
2. If the errors appear in the system log, why aren't they reported also in the warning message of gatekeeper?
Because Gatekeeper is targeted at users, not developers, and that information would be completely useless to the vast majority of users.
3. syspolicy_check was useless in my case since it didn't provide any extra info that helped me to debug.
Indeed. syspolicy_check
is the right tool for investigating problems like this. We’re using your bug (FB19042844
) to track improving it to debug this case.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"