Gatekeeper does not allow a bundle to run before manually running spctl --assess of contained dylib files

Hi,


I am creating a plug-in written in a combination of Objective-C and Swift. As such, the bundle contains the following structure:

/Contents/Frameworks/libswiftCore.dylib (and few other Swift's dylib files)

/Contents/Info.plist

/Contents/MacOS/MainProductBinary

/Contents/Resources/ - contains pdfs, nibs etc.


1. I've codesigned and notarized each *.dylib file individually

2. I've codesigned and notarized the bundle as a whole

3. I've packed the bundle and transfered via web (so it becomes quarantined) to a fresh Catalina virtual machime

4. I've installed the third-party host application for which the our product is a plug-in onto the Catalina virtual machine

5. I've placed the plug-in into a folder where the host application expects it


When I start the host application, Gatekeeper complaines that it cannot check the bundle for malvare.


I manually assess the *.dylib files using a Terminal using the commands bellow:

for filename in MyPlugin.bundle/Contents/Frameworks/*.dylib; do
  spctl --assess --verbose=4 --type install "$filename"
done

After running these commands, Gatekeeper becomes satisfied and allows the plug-in to run when I start the host application.


However, I'm not a supporter of an idea to force users to run these commands before installing the plug-in.


Additional notes:

- Running spctl --assess --type install on the whole bundle before manually assessing the *.dylib files reports "rejected". However, running the same command after manually assessing the *.dylib files reports "accepted".


What should I do to make Gatekeeper satisfied without the need to run these commands?

Accepted Reply

Thanks for your collaboration. After changing the deployment target to 10.14.4 so Swift libraries not being included in the bundle, the Gatekeeper seems to let the plug-in run and the stapler checking tool reports Accepted. Even that the ticketContents still does not contain the overall bundle entry.


I made no other change to the code nor to the commands to build and notarize the bundle.


However, I finally found how to force the notarization service to recognize the overall bundle. The notarization service started to list the overall bundle as well as the Gatekeeper becomes satisfied (even after lowering back to 10.14 so Swift libraries becomes present again) after I added CFBundlePackageType: BNDL into Info.plist. Strange that the notarization service depends on it as if I understand docs correctly, macOS should default to BNDL if the key is not found or not recognized.


Thanks for your collaboration in triggering this issue.

Replies

The plug-in is built solely by Xcode itself. There is no script outside of Xcode nor there are "complications" intentionally added into the built process. I just call xcode archive. If theres anything non-standard or not-recommended, please, let me know what, ideally with tips how to correct it. Thanks.


There are 5 build phases in Xcode:

Dependencies (0 items)
Compile sources (x items - m and swift files)
Link Binary With Libraries (2 items - Swift and the host app SDK)
Copy Bundle Resources (x items - pdf, xib, pem, pch)
Copy Files (0 items)


These are Release's Build Settings (I've copied everything from Build Settings - Customized - Combined):

COMBINE_HIDPI_IMAGES = YES
DSTROOT = (-PathWhereHostAppExpectsPlugins-)
INSTALL_PATH = /
MACOSX_DEPLOYMENT_TARGET = 10.14
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks
DEFINES_MODULE = YES
INFOPLIST_FILE = $(SRCROOT)/MyPlugin/Info.plist
PRODUCT_BUNDLE_IDENTIFIER = com.MyPlugin
PRODUCT_NAME = $(TARGET_NAME)
WRAPPER_EXTENSION = bundle
FRAMEWORK_SEARCH_PATHS = $(SYSTEM_APPS_DIR)/(-HostApp-).app/Contents/Frameworks
CODE_SIGN_IDENTITY = Developer ID Application
CODE_SIGN_STYLE = Manual
DEVELOPMENT_TEAM = (-TeamID-)
ENABLE_HARDENED_RUNTIME = YES
PROVISIONING_PROFILE_SPECIFIER =
GCC_PREFIX_HEADER = MyPlugin/MyPlugin-Prefix.pch
CLANG_ENABLE_MODULES = YES
SWIFT_OBJC_BRIDGING_HEADER = MyPlugin/MyPlugin-Bridging-Header.h
SWIFT_VERSION = 5.0
SWIFT_INCLUDE_PATHS =


Here's what otool prints out (LC_LOAD_DYLIB and LC_RPATH only):

Load command 9
          cmd LC_LOAD_DYLIB
      cmdsize 80
         name @rpath/(-HostApp-).framework/Versions/A/(-HostApp-)Core (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 0.0.0
compatibility version 1.0.0
Load command 10
          cmd LC_LOAD_DYLIB
      cmdsize 88
         name /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 23.0.0
compatibility version 1.0.0
Load command 11
          cmd LC_LOAD_DYLIB
      cmdsize 96
         name /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1673.126.0
compatibility version 300.0.0
Load command 12
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libobjc.A.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 228.0.0
compatibility version 1.0.0
Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libSystem.B.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1281.0.0
compatibility version 1.0.0
Load command 14
          cmd LC_LOAD_DYLIB
      cmdsize 88
         name /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1894.10.126
compatibility version 45.0.0
Load command 15
          cmd LC_LOAD_DYLIB
      cmdsize 104
         name /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1673.126.0
compatibility version 150.0.0
Load command 16
          cmd LC_LOAD_DYLIB
      cmdsize 104
         name /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1348.12.4
compatibility version 64.0.0
Load command 17
          cmd LC_LOAD_DYLIB
      cmdsize 96
         name /System/Library/Frameworks/Security.framework/Versions/A/Security (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 59306.41.2
compatibility version 1.0.0
Load command 18
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name @rpath/libswiftCore.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 1100.8.255
compatibility version 1.0.0
Load command 19
          cmd LC_LOAD_DYLIB
      cmdsize 64
         name @rpath/libswiftCoreGraphics.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 0.0.0
compatibility version 1.0.0
Load command 20
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name @rpath/libswiftFoundation.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 0.0.0
compatibility version 1.0.0
Load command 21
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name @rpath/libswiftObjectiveC.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 0.0.0
compatibility version 1.0.0
Load command 22
          cmd LC_RPATH
      cmdsize 32
         path /usr/lib/swift (offset 12)
Load command 23
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)
Load command 24
          cmd LC_RPATH
      cmdsize 40
         path @loader_path/../Frameworks (offset 12)


Is everything as expected? What other places to look else for potential issues?

I've tried to set the deployment target to 14.4.4. Yes, the Swift's dylibs disappeard from the bundle but the notarization log still misses include the overall bundle in the list.

OK. This is different. I read your original post again. Can you clarify a couple of things?


3. I've packed the bundle and transfered via web (so it becomes quarantined) to a fresh Catalina virtual machime


How did you pack it? Are you distributing it as a pkg file?


4. I've installed the third-party host application for which the our product is a plug-in onto the Catalina virtual machine


Did you run the host application first?


5. I've placed the plug-in into a folder where the host application expects it


Where is that?


Here are some possible complications...

4. You are not running the host app first. Therefore, the host hasn't passed Gatekeeper yet. There could be some extra checks as part of the initial Gatekeeper check that are failing due to your differently-signed plug-in that is being loaded.

5. Maybe the host application is doing it wrong. Maybe it is asking you to install the plugin inside its app bundle, thus invalidating its signature. And/or some of 4) above is still complicating things.

3. Are you distributing this as a pkg file? If so, you could just run your fix as part of a post-install script. If not, you would write a custom installer (maybe even in AppleScript) that performs those tasks, in addition to installing the plugin.


After a bit more research, I went ahead and tracked down the host. You didn't redact it well enough. Points 4) and 3) above are still valid. I noticed the following additional issues:

1) The host app has its own plugin manager. When it downloads plugins, their signatures may be invalid or might not even exist. This may cause problems for said host app in the future. Apple has said that unsigned code will not run by default in a future version of macOS. But that's not your problem.

2) Again, revisit how those Apple dylibs are getting into your bundle. Are you still codesigning and notarizing them? What if you don't do that?

3) The host app provides some templates and instructions. Are you using those? Specifically, the instructions tell you to use the "--deep" flag on the codesign. That flag is frowned upon, but you might need it in this case, assuming 2) above doesn't fix it.


I think eskimo's suggestion to set your deployment to 10.14.4 and omit those dylibs is probably the easiest solution. That will make your plug in available. In this market, there are likely some people running old versions so this might actually be a problem for you.


If all else fails, you can use your original hack inside a pkg installer for now.


Edited: The host app seems to be doing everything properly. I used:

codesign -d --entitlements :- /path/to/app


and the correct entitlements are there:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>com.apple.security.automation.apple-events</key>

<true/>

<key>com.apple.security.cs.allow-unsigned-executable-memory</key>

<true/>

<key>com.apple.security.cs.disable-library-validation</key>

<true/>

<key>com.apple.security.personal-information.addressbook</key>

<true/>

</dict>

</plist>

First of all, I am going to summarize recent findings:


  • The notarization service does not include the overall bundle in the ticketContents list so it does not recognize the overall bundle as a candidate for notarization. It lists just the dylib files and the main executable. As said by eskimo, the overall bundle should be listed as well.
  • The dylib files themself do not seem to be the issue - I've changed deployment target to 14.4.4 so the swift's dylib files were no longer put into the bundle. The notarization service still did not included the overall bundle in the ticketContents list.
  • Sending dmg instead of zip to the notarization service result in no change (except of including the dmg file in the ticketContents list). The overall bundle ticket is still missing.

As long as the eskimo is right that the overall bundle should be included in the ticketContents list, it seems that something is causing the notarization service to skip the overall bundle.


3. I've packed the bundle and transfered via web (so it becomes quarantined) to a fresh Catalina virtual machime


How did you pack it? Are you distributing it as a pkg file?


For the sake of these tests, I were transfering the same zip file as was sent to the notarization service.

When distributing to real users, the bundle is copied in a directory next to several other files, than tar-ed and gzip-ed. The server replaces a content of one "other" file found in the archive before serving it to the user while keeping other file entries intact in the stream. Little complicated but there is no known issue with this approach yet.


4. I've installed the third-party host application for which the our product is a plug-in onto the Catalina virtual machine


Did you run the host application first?


It does not matter. I've tried both variants.


5. I've placed the plug-in into a folder where the host application expects it


Where is that?


~/Library/Application Support/TheApp/Plugins


4. You are not running the host app first. Therefore, the host hasn't passed Gatekeeper yet. There could be some extra checks as part of the initial Gatekeeper check that are failing due to your differently-signed plug-in that is being loaded.


The failure appears on subsequent app starts as well.


3. Are you distributing this as a pkg file? If so, you could just run your fix as part of a post-install script. If not, you would write a custom installer (maybe even in AppleScript) that performs those tasks, in addition to installing the plugin.

1) The host app has its own plugin manager. When it downloads plugins, their signatures may be invalid or might not even exist. This may cause problems for said host app in the future. Apple has said that unsigned code will not run by default in a future version of macOS. But that's not your problem.


We are not distributing the plugin via the plugin manager (yet).

The user downloads the plugin from our website, uncompresses and double clicks on the bundle. This starts the host app (as the host app registers itself as a handler to that file extension) and the host app copies the bundle to its plugins folder located inside its Application Support subdirectory. On start-up, the host app tries to load all the bundles presents in that directory.


2) Again, revisit how those Apple dylibs are getting into your bundle. Are you still codesigning and notarizing them? What if you don't do that?


I were not codesigning and notarizing the dylib files in subsequent tests. I built the app to a new separate xcarchive for subsequent tests to have a clear starting point.


3) The host app provides some templates and instructions. Are you using those? Specifically, the instructions tell you to use the "--deep" flag on the codesign. That flag is frowned upon, but you might need it in this case, assuming 2) above doesn't fix it.


I've tried running codesign command initially as well. Now I let the Xcode to codesign (it did some codesigning as part of xcode archive).


I think eskimo's suggestion to set your deployment to 10.14.4 and omit those dylibs is probably the easiest solution. That will make your plug in available. In this market, there are likely some people running old versions so this might actually be a problem for you.


It did not help. I have not tried to run it yet but I followed eskimo's answer that the overall bundle should be listed in the notarization log. So I'm checking the log whether the overall bundle entry appeared.

For the sake of these tests, I were transfering the same zip file as was sent to the notarization service.


Did you staple the bundle? Normally, after sending that zip file to the notarization service, your next step should be to delete that zip. You don't want it. Its only purpose is to upload the bundle. Once notarization succeeds, you staple the ticket to your bundle, as described in the documentation: Customizing the Notarization Workflow. Then you create a new zip file and distribute that.


I'm not sure what eskimo was saying above about those container formats not supporting stapling. I assume he was referring to tarballs and zipfiles. I'm not familiar with plugins, but as far as I know, they can, and should, be stapled.


I don't know about the overall container. The only time I ever check notarization logs is when looking at other people's notarization problems here in the forums. You didn't say how you originally built this project. Did you use the host app's plug-in template? Did you use a plug-in template? I've never tried a plugin and there are many potential differences between your project and the one that eskimo tried.


Lacking any additional information, it looks like you just weren't stapling the bundle. Try that. If it doesnt' work, review the docs again, and review the host app's docs and templates again.

OK, so that confirms that the Swift libraries aren’t the problem. The next step is to determine whether it’s the

Info.plist
or the contents of the bundle. If you replace your product bundle’s
Info.plist
with the one from your working test bundle, does that notarise? [You’ll have to adjust the
CFBundleExecutable
value, obviously.]

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

The Info.plist files are identical.


Both working and non-working versions are from the same code base. I've just deleted all Swift files, the Swift-ObjectiveC bridging header file and the part of Objective-C code calling the Swift code. I did no other change - neither in the project metadata, all potential metadata/setting changes were made automatically by Xcode because there was no longer any Swift code in the project so the Swift-related options disappeared. However, the Info.plist file is identical.

OK then, I’ve got nothing. At this point I’m going to fall back to what I wrote yesterday: You should open a DTS tech support incident so that can look at your setup in detail.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'm not stappling during these tests. I understand from the docs that stappling is meant mainly for offline purposes, otherwise, online checks are equal to stappling.


Edit: I found the reason. Check my last reply to Eskimo. Thanks for your collaboration in triggering this issue.

Thanks for your collaboration. After changing the deployment target to 10.14.4 so Swift libraries not being included in the bundle, the Gatekeeper seems to let the plug-in run and the stapler checking tool reports Accepted. Even that the ticketContents still does not contain the overall bundle entry.


I made no other change to the code nor to the commands to build and notarize the bundle.


However, I finally found how to force the notarization service to recognize the overall bundle. The notarization service started to list the overall bundle as well as the Gatekeeper becomes satisfied (even after lowering back to 10.14 so Swift libraries becomes present again) after I added CFBundlePackageType: BNDL into Info.plist. Strange that the notarization service depends on it as if I understand docs correctly, macOS should default to BNDL if the key is not found or not recognized.


Thanks for your collaboration in triggering this issue.

I have to remind you. In my second reply in this thread, I said:

The last few questions I've seen about notarization problems all turned out to be caused by doing something really crazy


And I suggested:

Look at existing plug-in bundles and notice a structural pattern that all well-written plug-ins share. Copy that structure. I've been using OS X since before day one. All of the reports of notarization problems that I've seen (yes, all of them) involve bundle structures unlike anything that I've seen in 20 years.


Raw bundles and plugins are edge cases. The default Xcode Bundle project includes a proper CFBundlePackageType. I would be extremely surprised to see any functional bundle on a Mac that lacks this key. Maybe, if it is missing, the OS will assume BNDL when it loads. But that doesn't imply that all Apple services will behave that way. This particular issue has actually been known for years, since Apple started using online validation for things like the Mac App Store. The official fix is to set a CFBundlePackageType.


Humans write docs. Humans write web services. Humans write dynamic library loading code. But it is very unlikely that same human did all of that. Many people were involved over decades, with different evaluations of what structures were valid enough to be accepted.


Until this thread, I actually didn't know about that edge case of Notarization without a stapled ticket. I suggest that you do not rely on that.

Hi, thanks for your reply. Just to make few things clear and some follow-up notes.


- Stappling the bundle was of no help. The Gatekeeper still refused to allow the bundle to run.


- It's the first and the only thing I have ever written for MacOS, so I have no previous experience to mine from. As such, I rely mostly on docs, sample codes and forums.


- The linked official fix has a topic/message which is quite far from what I was experiencing. I had Info.plist included and I get no error message, so there was no chance I would ever consider looking into it.


- If the docs are not consistent, non-existant or they use too vague language, there is a high change I would follow the version which is not correct.

- If the implementations of the same requirement are not consistent across different parts of the same product and its related services, its hard to find the cause. Xcode was happy, macOS loader was happy, Gatekeeper was sometimes happy, notarization were false-happy.

- If the docs says the field is optional, has a default value and macOS behaves like that, I expect that all the macOS-related parts and services (I consider the notarization service and the Gatekeeper as a native part of macOS) either follow the same logic or issue clear message if they are more strict. If not, I consider that as a bug, either in docs, in design or in a product.


Either way, thanks for your collaboration. It helped me to focus on the right metric (having the overall bundle included in the logs) and to isolate the cause of the issue.