code signature in [app] not valid for use in process using library validation: library load disallowed by system policy.

I have an Electron app that uses a SQLite database, and has as a dependency the Sqlite3 npm package. I am building it as a sandboxed app to be distributed in the Mac App Store.


When I build the app and try to open it I immediately get an error that says appName cannot be opened because developer cannot be identified. Then a more detailed error appears that includes:

code signature in [app] not valid for use in process using library validation: library load disallowed by system policy.


If I build it for distribution outside the app store (with hardened runtime and notarization) it works as long as I include the entitlement:

com.apple.security.cs.disable-library-validation


But it appears there is no similar entitlement for sandbox apps and that sandbox apps will just ignore this entitlement.


The app itself is signed with a valid certificate. And it opens fine if I don't require the sqlite3 library/package. What steps do I need to take to fix this?

Replies

App Store apps must be self contained. That is, all the code in the app must be included in the package you submit to the store.

Given that, there’s never any need to disable library validation. Rather, you should sign your dependencies as part of the process of signing your app.

Share and Enjoy

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

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

Thanks for the confirmation. After quite some digging I found out how to sign the dependency.

(For anyone else encountering this issue I added "asarUnpack": ["**/*.node"] to the build > mac key in the package.json file which takes all .node binary files out of the asar package, otherwise even if it is signed Apple can't read the signature and will throw an error).

So the signing part is taken care of, and the code signature error is gone but now something is happening that I don't know how to interpret.


Quick background:

A) Packaging Electron apps: Electron does not package apps for distribution directly. Instead they recommend a third party packaging utility called electron-builder. You add configuration to a package.json file to build for a target platform, in this case the Mac App Store, then run the build utility. It will return (1) an executable app for that configuration, and (2) the same app inside a .pkg file ready for submission to the Mac App Store for review. From my understanding the two apps are identical, but one happens to be wrapped in a pkg installation file.


B) Incorporating Node packages (i.e., libraries) in an app: Electon is build on top of Node.js and Chromium, both of which are included in the app. If your app needs third-party Node.js packages (referred to as native dependencies) such as Sqlite3 to use sqlite database then they will be installed as binary executable files having a .node extension. Getting these signed was my original problem.



To test my app I created a second "volume" on my Mac running on Catalina OS with no developer certificates so simulating some random user. Until now I have been getting the same results whether I execute the generated app or install it from the .pkg file. But in this case, if I launch the generated app it opens with no errors. But if I use the .pkg version to install it first then launch I now get this error:

A JavaScript error occurred in the main process
Uncaught Exception:
Error: dlopen(/path/to/node_sqlite3.node, 1): no suitable image found.
Did find: /path/to/node_sqlite3.node: stat() failed with errno=13


So I am still having some issue with the third party node package/library. And it's not specific to Sqlite, I installed a different node package and got this same error only for that package.


I don't know what errno 13 means. From search engines I found this unofficial explanation: "when stat() fails with errno=13, usually it's a permissions problem."


So if that's true what would be causing it? I confirmed the binary is signed in both the generated app and the one installed from the .pkg file. In either case, if I run:

codesign --display --verbose ./path/to/node_sqlite3.node

I get:

Executable=/path/to/node_sqlite3.node
Identifier=node_sqlite3
Format=Mach-O thin (x86_64)
CodeDirectory v=20200 size=15436 flags=0x0(none) hashes=475+5 location=embedded
Signature size=4816
Signed Time=Jan 29, 2020 at 2:06:51 PM
Info.plist=not bound
TeamIdentifier=myID number
Sealed Resources=none
Internal requirements count=1 size=192


Is it possible I am getting this error because I didn't install it from the Mac App Store and if I submitted the pkg file as is to Apple for review they would not get this error?


Anyway, not sure what the problem is or how to proceed. Any help will be greaaatly appreciated. Thanks.

To test my app I created a second "volume" on my Mac running on Catalina OS with no developer certificates so simulating some random user.

I generally test this sort of stuff in a VM. This has two key benefits:

  • Ease of use — You don’t have to restart between tests.

  • Test isolation — You can set up a snapshot, run your test, and then restore from that snapshot, which guarantees that test N doesn’t interfere with test N+1.

I don't know what errno 13 means.

In this context error 13 is

EACCES
Permission denied. It is interesting that you get that error. There are actually two standard permission errors,
EACCES
and
EPERM
(code 1, Operation not permitted). In most cases the former is used for actual file system errors whereas the latter in used for system policy errors. For example, in a sandboxed app you’d typically get
EPERM
when you try to read outside of your sandbox.

In your specific case, I’d only expect

dlopen
to fail with
EACCES
when it can’t access the time in the file system and with
EPERM
if, say, something were blocking the code from loading. However, it’s hard to say for sure without more details.
Did find: /path/to/node_sqlite3.node: stat() failed with errno=13

What the actual path?

Share and Enjoy

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

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

I think the way Electon apps work is by expanded themselves into a temporary directory and then executing from there. It sounds like your binaries are not in the location they are expected to be, or are not in a location that your sandboxed app has access to.


Normally, apps live wherever and load dynamic libraries from previously definied locations for that purpose, such as /Library/Frameworks. Traditional 3rd party apps may install new frameworks in /Library/Frameworks. Lower-level Unix tools may use more traditional Unix locations like /usr/local/lib.


A sandboxed apps doesn't have any access to those locations. It must include any supporting frameworks or dynamic libraries inside its app bundle. This is also useful for non-sandboxed apps so that they don't need superuser permissions to install and can be more easily updated and uninstalled. There are some special flags that can be used in bundled frameworks to allow them to be loaded from their location inside the app bundle. Lower-level Unix dynamic libraries can be loaded at runtime with dlopen() and the runtime path to the library. Becaues app bundled can be moved around, they can't use hard-coded paths to these supporting frameworks or libraries. It all has to happen dynamically, at runtime.


Electron tries to do all of the above at once. Electon apps are just an installer. The app installs the node executable, supporting packages, and client (your) code into temporary, or app-specific, locations and then tries to run it from there. And all of this works radically differently in development than it does when deployed on an end-user's machine. It sounds like the app is being defined to look for development paths instead of the runtime paths. Why that is would be a question for someone who knows something about Electron.


I suggest you contact the vendor for support. It seems that these cross-platform app builder systems cause major problems with every new version of macOS. Their developers have access to macOS betas just like everyone else. I don't know why they don't ensure that their systems work properly on their advertised platforms.

Thanks for the input. Here's the entire error message:


A JavaScript error occurred in the main process
Uncaught Exception:
Error: dlopen(/Applications/MyApp.app/Contents/Resources/app.asar.unpacked/node_modules/sqlite3/lib/binding/
    electron-v6.1-darwin-x64/node_sqlite3.node, 1): no suitable image found.
Did find: /Applications/MyApp.app/Contents/Resources/app.asar.unpacked/node_modules/sqlite3/lib/binding/
    electron-v6.1-darwin-x64/node_sqlite3.node: stat() failed with errno=13
  at process.func (electron/js2c/asar.js:138:31)
  at process.func [as dlopen] (electron/js2c/asar.js:138:31)
  at Object.Module_extensions..node (interval/modules/cjs/loader.js:828:18)
  at Object.func (electron/js2c/asar.js:138:31)
  at process.func [as .node] (electron/js2c/asar.js:148:18)
  at Module.load (internal/modules/cjs/loader.js:645:32)
  at Function.Module_load (internal/modules/cjs/loader.js:560:12)
  at Module.require (internal/modules/cjs/loader.js:685:19)
  at require (internal/modules/cjs/helpers.js:16:16)
  at Object. (/Applications/MyApp.app/Contents/Resources/app.asar/node_modules/sqlite3/lib/sqlite3.js:4:15)


That last line references a function in the sqlite3 node module that calls the node_sqlite3.node binary file.


Some further background: As part of the build process, the entire app is put into a binary file called app.asar so that users can't just read or mess with the source code. That includes any native dependencies/libraries such as sqlite3. Apple requires these libraries to be signed but Apple cannot verify the signing certificate if it is in the asar binary file and will throw the "Developer cannot be verified" error. I confirmed this by unpacking the binary asar file and verifying with codesign that the signing certificate is there. Per a comment in one of electron-builder's github issues pages this behaviour started with Catalina and the solution is to unpack all .node binary files outside the asar.app bundle (in a folder called app.asar.unpacked). Then Apple can confirm the signature. But there was no mention of this errno=13 problem.


Does that shed more light on it? Looking at John's message I believe everything is self contained in the app so within the sandbox. I was thinking maybe moving the binaries to the separate folder is causing the issue, but it looks like it is going to the right path where the binary file is if I read it correctly.


---------------------------------------------------------------------------------------------------------------------------------

FEB 11, 2020 EDIT

In case anyone encounters the same issue you can skip the discourse below. Here is the issue encountered and the fix.


For some reason the unpacked executable files have their permissions/ownership changed when they are flattened into the .pkg file which is causing the error. I don't know why this happens but there is a fix:

• Run the mas build

electron-builder --mac

• Keep the MyApp.app file that gets generated but delete the MyApp-1.0.0.pkg file.

• Change the permissions to allow read and execute access to all directories and files in the app.asar.unpacked folder. From MyApp/dist/mas run:

sudo chmod -R 755 MyApp.app/Contents/Resources/app.asar.unpacked

• Then create a signed pkg file:

npx electron-osx-flat "MyApp.app" --verbose

You can't unpack inside your app bundle. That will always invalidate the signature of the app itself. A sandboxed app shouldn't even have write access to itself. What you will have to do is extract these files to another location and load them, at runtime, from there. In the sandbox, that location will have to be somewhere inside the app's sandbox container. However, I'm not sure that will work in the sandbox. At this point, you are deep within the implementation details of Electron and Gatekeeper.


One way to simplify this would be to ensure that your app bundle has all of the files it needs, already unpacked. Don't worry about your DRM scheme for now. This is not really the way Electron is supposed to work, but I can't help with that. My own apps are a melange of both native code and HTML, and a few more esoteric types. If you tell all the parts where the other parts are supposed to live, then you can make it work. It may not be drop-in portable to Android anymore, which pretty much invalidates the point of using Electron in the first place. But such is life.

Thanks for the input. When you say "One way to simplify this would be to ensure that your app bundle has all of the files it needs, already unpacked." I probably should have shown the file structure because if I understand your statement correctly, the 3rd party binary files are already unpacked when the app bundle is built. Here's the structure below. The app.asar binary folder is where my app's code is and the app.asar.unpacked folder is where the signed 3rd-party binary files are.


MyApp.app
  |- Contents
    |- _CodeSignature
      |- CodeResources (xml file with keys pointing to code signatures including sqlite3)
    |- Frameworks
      |- Electron Framework.framework
      |- MyApp Helper.app
      |- MyApp Helper (GPU).app
      |- MyApp Helper (Plugin).app
      |- MyApp Helper (Renderer).app
    |- Library
      |- LoginItems
        |- MyApp Login Helper.app
    |- MacOS
      |- MyApp (binary file, not sure what it does)
    |- embedded.provisionprofile
    |- Info.plist
    |- Resources
      |- app.asar (binary file containing app's code including node_modules)
      |- electron.asar (binary file containing Electron's code)
      |- MyApp.icns (App Icon file)
      |- app.asar.unpacked (unpacked node_module path to 3rd party library binary file)
        |- node_modules
          |- sqlite3
            |- lib
              |-binding
                |- electron-v6.1-darwin-x64
                  |- node_sqlite3.node


I don't know, I may be just not understanding something since this is my first Electron app but everything seems like it should be working.

That would probably explain your problems. You can't have signed, executable code in the Resources folder. See the "Nested code" section of this Apple Tech note. You need to move those binaries into the Frameworks directory. This document just says "Frameworks, dylibs". It doesn't explicitly say what to do with ***** dylibs like this. I guess you can try moving the entire "app.asar.unpacked" directory over to frameworks and see if that fixes it.


I'm not sure if the ".node" extension is going to work. You might need to keep use the original dylib extension and create a symbolic link with a different name if node needs that. I'm also not sure if the electron code will accept it in a differenet directory.


What are those "Multiple MyApp Helper.app bundles"? Those should probably go into a "Helpers" directory as per the above document.

Thanks for the input. I edited my last message to fully break out the Frameworks directory, and I added the Libraries directory which only has a login helper which I assume is for apps that require login (mine doesn't). Well, this is shedding more light on the situation, including a possible catch-22. In your link to the Apple docs after listing the folders where code is expected it says:


"putting code into other places will cause it to be sealed as data (resource) files. This causes this code to be sealed twice; once in the nested code, and once in the outer signature. This wastes both signing and verification time and storage space. Also, this can break the outer signature of apps that use their own update mechanisms to replace nested code. If this nested code is being treated as resources, the outer signature doesn't know that this nested content is actually code."


But then it says:


"Note that a location where code is expected to reside cannot generally contain directories full of nested code, because those directories tend to be interpreted as bundles. So this occasional practice is not recommended and not officially supported. If you do do this, do not use periods in the directory names. The code signing machinery interprets directories with periods in their names as code bundles and will reject them if they don't conform to the expected code bundle layout."


So maybe it's a damned if you do damned if you don't situation. And that may explain why this issue doesn't seem to have been resolved by Electron-builder. It's open source, so run by voluteers doing it on their free time, and maybe they tried tacking this issue and gave up. Only guessing. That would be bad news for me cuz if they can't figure it out, I don't know how I would.

I added the Libraries directory which only has a login helper which I assume is for apps that require login


That directory is for Service Management LoginItems, as documented here. Note that these are different from what you might see in System Preferences > Users & Groups > (your account) > Login Items. They allow your app to run a helper much like a LaunchAgent. The difference is that a login item is acceptable in the Mac App Store and end users have no control over them unless the developer allows it. If your helper app isn't a true login item, it shouldn't go there.


I wouldn't worry too much about the nitty-gritty details of sealed resources and directories that may, or may not contain periods. If you are looking seriously at that level of detail, you are already going way down the wrong path. It is a basic question of technological economy. If you construct your app like 90% of all other apps in the world, you should be fine. If something breaks, that's Apple's problem to fix. But if you construct your app unlike 90% of all other apps in the world, you've got a problem. If something breaks, Apple doesn't care. It's your problem to fix. You are deep into that 10% of outlier apps.


I don't know anything about Electron, so I can't comment on that. But, in general, if you are really dependent on some 3rd party tool for your app, then you have to acknowledge that. It effectively isn't your app anymore. Other people control it. Your app lives and dies by how well their code runs in the next version of macOS. If something breaks, Apple isn't going to help. Electron also might not help if you are using a beta or doing anything out of the ordinary, which you are. I'm sure they have their own community discussion forums. You are more likely to find people who know something about Electron in those forums.


My suggestion it to just build native apps instead. Then, you only have one master, Apple, instead of two, Apple & Electron. And it seems to me that developers have more freedom with Apple than they would with Electron. Apple has the more powerful marketing departement. They are able to convince developers to do what Apple wants instead of what is best for the developer. But if you insist, you can do it your own way. Whereas in the open source community, it is all about freedom from Big Bad Apple. But you don't dare go your own way.

Thanks for all your info. Enough to go back with specifics to the Electron-builder folks. I mean maybe most people aren't using native node modules like sqlite3, but that surprises me. Maybe they are just all distributing direct. Anyway, if I find a solution I'll add it to the thread in case other's have the same issue. Going native, I've thought of it. Maybe.


Cheers,

Steve