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 only thing you need to notarize is the top-level installer package. If you are doing something funky like a plug it, then it is a little more complicated. Look for any number of posts by eskimo explaining how to do it.

I've tried notarizing only the outer package but I failed. As I noted in the linked thread, everything started working when I removed all dylib from the plug-in. As official docs were of no help, I've searched web and I've found lots of thread stating issues if dylib files are present but missing any answer. Then I found this thread stating that if they notarized dylib files separatedly, it helped. So I've tried that and really, it made a progress.


It's strange that the assessment fails unless I perform assessment on inside files first. I would expect that manually performing assessments on inside files would not change the outcome of the bundle-wide assessment as it should be either ok or not. The behaviour I experience is quite weird to me as it reports the exactly same binaries both as rejected and as accepted.

In step 3, how did you package up the you’re plug-in?

Share and Enjoy

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

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

1. In production, I tar czf the whole directory which contains the bundle (the parent directory contains additional files like How to PDF etc.).

2. While investigating, I was also trying the exactly same zip file sent for notarization by the xcrun altool --notarize-app command (the zip file was created calling ditto -c -k --keepParent --rsrc)

I think that since notarization requires proper signing, the author of that post assumed there was something else going on. Proper signing is all that is necessary.


That being said, it is difficult to speculate about what is going wrong. The last few questions I've seen about notarization problems all turned out to be caused by doing something really crazy to get those dylibs inside the bundle. I'm not sure what people are doing. 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.


Plugins are more of an outlier since they are based more on what the containing app expects to see. But surely, for every app worth having plug-ins, there have got to be some examples of properly notarized plug-ins. Find them and copy their structure. If you still have problems, then at least you will have something to reference.

I've included the plug-in's file structure at the top of the question above. I've skipped just the files created by codesign and spctl.

OK. One factoid about both of these container formats is that they don’t support stapling. Honestly, I’m not yet sure if that’s relevant.

Earlier you wrote:

I've tried notarizing only the outer package but I failed.

To be clear, notarising the outermost container (which in this case is your plug-in’s bundle) is the correct option here. Notarising subcomponents is a bad idea in general and I’ve seen it trigger weird problems in situations similar to this.

If you do notarise just the outermost container, what does the notarisation log look like? I’m specifically interested in:

  • If there are any warnings in the

    issues
    property
  • The list of all the paths under the

    ticketContents
    property

Also, what does the your plug-in layout look like? If do you do this:

% find MyPlugin.bundle

what does it print? I realise that may be a long list, so feel free to condense stuff that’s all going to be treated the same (for example, if

Contents/Resources/
contains 27 PDFs, you can elide 26 of them).

Finally, one more question on a completely different tack. Does the host app guaranteed to load your plug-in in a separate process? Or does it load it within the host app’s process?

Also, what’s the deployment target for your plug-in?

This matters because, if the host app loads your plug-in into its own process and you support systems prior to 10.14.4, it’s not safe to use Swift in your plug-in. In 10.14.4 we bundled the Swift runtime in the OS, so you don’t need all of those Swift libraries in the first place. On earlier systems the Swift runtime gets loaded from your plug-in, but that’s problematic because any given process can only have one copy of the Swift runtime and you can’t guarantee that when building an in-process plug-in (the app might have its own Swift runtime, or there may be other in-process plug-ins with their own Swift runtime).

Share and Enjoy

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

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

After sending to a notarization service.

% find MyPlugin.bundle
MyPlugin.bundle
MyPlugin.bundle/Contents
MyPlugin.bundle/Contents/_CodeSignature
MyPlugin.bundle/Contents/_CodeSignature/CodeResources
MyPlugin.bundle/Contents/MacOS
MyPlugin.bundle/Contents/MacOS/MyPlugin
MyPlugin.bundle/Contents/Resources
MyPlugin.bundle/Contents/Resources/[pdf, nib, pem files]
MyPlugin.bundle/Contents/Resources/MyPlugin-Prefix.pch
MyPlugin.bundle/Contents/Frameworks
MyPlugin.bundle/Contents/Frameworks/libswiftObjectiveC.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftCore.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftCoreGraphics.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftDispatch.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftCoreFoundation.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftDarwin.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftIOKit.dylib
MyPlugin.bundle/Contents/Frameworks/libswiftFoundation.dylib
MyPlugin.bundle/Contents/Info.plist

Notarization log (I've just stripped jobId, uploadDate, sha256 and cdhash, renamed actual zip and bundle names to a dummy names):

{
  "logFormatVersion": 1,
  "status": "Accepted",
  "statusSummary": "Ready for distribution",
  "statusCode": 0,
  "archiveFilename": "MyPlugin.zip",
  "ticketContents": [
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/MacOS/MyPlugin",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftObjectiveC.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftCore.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftCoreGraphics.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftDispatch.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftCoreFoundation.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftDarwin.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftIOKit.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    },
    {
      "path": "MyPlugin.zip/MyPlugin.bundle/Contents/Frameworks/libswiftFoundation.dylib",
      "digestAlgorithm": "SHA-256",
      "arch": "x86_64"
    }
  ],
  "issues": null
}

I am not sure but I think the plug-in is loaded within the host app's process.

Deployment target is 10.14.

Swift language version is Swift 5.

I'm aware of the possible compatibility issues if different Swift runtimes are present in a process. The host app is pure Objective-C and our plug-in is the only plug-in using Swift. We re in a process of moving the Swift part to Objective-C. The replacement is not ready yet so we need to get the Swift version working in Catalina for a while.

I've then copied the zip file sent to the notarization service to the 10.15 virtual machine, unzipped and run spctl --assess:


% spctl --assess --type install -vvvv MyPlugin.bundle
MyPlugin.bundle: rejected
source=Unnotarized Developer ID
origin=Developer ID Application: OurTeamName (OutTeamId)

% spctl --assess --type exec -vvvv MyPlugin.bundle
MyPlugin.bundle: rejected (the code is valid but does not seem to be an app)
origin=Developer ID Application: OurTeamName (OutTeamId)

% spctl --assess -vvvv MyPlugin.bundle
MyPlugin.bundle: rejected (the code is valid but does not seem to be an app)
origin=Developer ID Application: OurTeamName (OutTeamId)

Thanks for all the info. To start, a simple FYI: You should not be shipping the following to users:

MyPlugin.bundle/Contents/Resources/MyPlugin-Prefix.pch

It’s essentially a source file. This isn’t a problem for notarisation, it’s just a bit weird.

As to what’s going wrong with your plug-in, I have a theory. I created and notarised a test bundle here in my office. Consider its notarisation log:

{
  …
  "ticketContents": [
    {
      "path": "Test129024.dmg",
      "digestAlgorithm": "SHA-256",
      "cdhash": "359cbf51ee16eddc4e61430b2b8757abae42747b"
    },
    {
      "path": "Test129024.dmg/Test129024.bundle",
      "digestAlgorithm": "SHA-256",
      "cdhash": "7a5ee947ed19761162d342c4a511126cc55c8459",
      "arch": "x86_64"
    },
    {
      "path": "Test129024.dmg/Test129024.bundle/Contents/MacOS/Test129024",
      "digestAlgorithm": "SHA-256",
      "cdhash": "7a5ee947ed19761162d342c4a511126cc55c8459",
      "arch": "x86_64"
    }
  ],
  "issues": null
}

Note how there’s an entry for the bundle and an entry for the executable within the bundle. In your case you only have an entry for the executable, you don’t have an entry for the bundle as a whole. Something about your submission is causing the notary service to not recognise your bundle as a bundle.

I’ve seen problems like this before where folks accidentally built their zip archive without the parent directory of their app. However, that doesn’t look like the case here.

To test this theory, I have a new diagnostic test for you: Try submitting a disk image. To get you started, here’s the commands I used to create a disk image from my bundle:

% mkdir Test129024
% mv Test129024.bundle Test129024
% hdiutil create -srcFolder Test129024 Test129024.dmg
% codesign -s "Developer ID App" --timestamp Test129024.dmg

One of two things will happen here:

  • The disk image’s ticket will have an entry for the overall bundle. In that case we know there’s something wonky about your zip archive packaging.

  • Or it won’t, in which case we need to look at how your bundle is structured.

Share and Enjoy

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

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

Thanks for the pch recommendation. I'll remove it from the bundle after we fix the notarization.


Neither the disk image's ticket has an entry for the overall bundle - it contains every entry present in the zip's ticket (the executable file and the dylib files) plus an entry for the dmg file, but not for the overall bundle. It also has issues: null.


The bundle file structure is in an above reply. If I understand Bundle Structures correctly, our bundle is a Loadable Bundle and all the paths are standard and all the required files of Loadable Bundle are there.


Here's the dmg content:

% find /Volumes/1
/Volumes/1
/Volumes/1/MyPlugin.bundle
/Volumes/1/MyPlugin.bundle/Contents
/Volumes/1/MyPlugin.bundle/Contents/_CodeSignature
/Volumes/1/MyPlugin.bundle/Contents/_CodeSignature/CodeResources
/Volumes/1/MyPlugin.bundle/Contents/MacOS
/Volumes/1/MyPlugin.bundle/Contents/MacOS/MyPlugin
/Volumes/1/MyPlugin.bundle/Contents/Resources
/Volumes/1/MyPlugin.bundle/Contents/Resources/[pdf, nib, pem files]
/Volumes/1/MyPlugin.bundle/Contents/Resources/MyPlugin-Prefix.pch
/Volumes/1/MyPlugin.bundle/Contents/Frameworks
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftObjectiveC.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftCore.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftCoreGraphics.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftDispatch.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftCoreFoundation.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftDarwin.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftIOKit.dylib
/Volumes/1/MyPlugin.bundle/Contents/Frameworks/libswiftFoundation.dylib
/Volumes/1/MyPlugin.bundle/Contents/Info.plist


Here's the Info.plist.
/ It looks exactly the same as the Info.plist file in a bundle in which I have removed all Swift code (so that bundle no longer contains the Frameworks directory) and in which the notarization works as expected - it gets notarized and makes Gatekeepr happy. /

% cat Info.plist
<?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>BuildMachineOSBuild</key>
  <string>18G3020</string>
  <key>CFBundleDevelopmentRegion</key>
  <string>en</string>
  <key>CFBundleExecutable</key>
  <string>MyPlugin</string>
  <key>CFBundleIdentifier</key>
  <string>com.MyPlugin</string>
  <key>CFBundleInfoDictionaryVersion</key>
  <string>6.0</string>
  <key>CFBundleName</key>
  <string>MyPlugin</string>
  <key>CFBundleShortVersionString</key>
  <string>1.0</string>
  <key>CFBundleSignature</key>
  <string>????</string>
  <key>CFBundleSupportedPlatforms</key>
  <array>
  <string>MacOSX</string>
  </array>
  <key>CFBundleVersion</key>
  <string>1</string>
  <key>DTCompiler</key>
  <string>com.apple.compilers.llvm.clang.1_0</string>
  <key>DTPlatformBuild</key>
  <string>11C504</string>
  <key>DTPlatformVersion</key>
  <string>GM</string>
  <key>DTSDKBuild</key>
  <string>19B90</string>
  <key>DTSDKName</key>
  <string>macosx10.15</string>
  <key>DTXcode</key>
  <string>1130</string>
  <key>DTXcodeBuild</key>
  <string>11C504</string>
  <key>LSMinimumSystemVersion</key>
  <string>10.14</string>
  <key>NSPrincipalClass</key>
  <string>MyPlugin</string>
</dict>
</plist>

Hmmm, it’s seems that something in your bundle is making the notary service grumpy. I looked through the info you posted and I can’t see anything obvious. My advice is that you start iteratively bringing your working and non-working cases closer together until you isolate the problem.

If you need help with that, you should open a DTS tech support incident, which will give me a chance to look at your issue in depth.

Also, I noticed that

LSMinimumSystemVersion
is only 10.14. If you raise it to 10.14.4, you can get rid of the bundled Swift runtime completely. This simplifies your code signing and notarisation, and also avoids any possibility of a Swift runtime conflict within your host app.

Share and Enjoy

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

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

Maybe go back to the issue of notarizing each dylib individually. Perhaps you are doing something to corrupt them or the executable. The past couple of notarization problems that I've looked at have been caused by people doing things that are just really off-the-wall. In one instance, someone was using the command-line zip tool to unzip their own submission via altool, thereby corrupting it. In another case, someone was essentially creating their own framework format. In addition, all of their dylibs had invalid rpath paths embedded in them. I was able to reorganize the project into a proper app and get it notarized, but it failed to launch with what looked like a gatekeeper failure. But it was more of an initial loadability check that was failing.


Are you doing something to those dylibs? Does your bundle "executable" have valid rpaths? You can check this with "otool -l".


It looks like there is a big discrepancy between what kinds of executables a Mac will run when notarization is out of the picture, and what is necessary to pass both notarization and initial gatekeeper checks. People have become very invested in, and attached to, some crazy build practices and now notarization is breaking it. But they never should have been doing that stuff to begin with. I can't be any more specific about what "that stuff" might be. In ever single case that I've seen, it's always "You're doing what? Why?"


In your case, your plugin bundle is complicating things. Have you tried running Console.app just before trying to load your plug-in? In the case above, I was able to get meaningful information about of Console. It was XProtectService that reported the problem with the rpath.