nexe: macOS codesign fails

This is a

  • Bug Report
  • Feature Request
  • Other

Codesigning the binary produced by nexe on macOS fails. I’m not sure if it’s a bug or just that it is not handled by nexe, but I wish to have your point of view about this.

Steps to reproduce:

  1. Create a dummy NodeJS script:
// ~/nexetest/index.js
console.log('hello world')
  1. Compile it using nexe:
nexe index.js
  1. Try to codesign it:
codesign --verbose -s - nexetest 
nexetest: main executable failed strict validation

I also tried to codesign the raw build in ~/.nexe directory. It works but, after building the code, the signature verification fails (obviously). If we try to force a resign, we get the same error as above.

  • Platform(OS/Version): macOS 10.13.2
  • Host Node Version: 8.9.4
  • Target Node Version: 8.9.4
  • Nexe version: 2.0.0-rc.22
  • Python Version: 2.7

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Comments: 22 (5 by maintainers)

Most upvoted comments

Hi @calebboyd,

Thank you for your feedback. ~I tried to apply the patch but unfortunately it’s not working (still main executable failed strict validation error when signing)~. Here is my build configuration ~am I doing something wrong ?~

{ 
  build: true,
  make: [ '-j4' ],
  input: './dist/entrypoint.js',
  targets: [ 'macos' ],
  output: 'daemon',
  patches: [
    (x, next) => {
      x.code = () => [x.shims.join(''), x.input].join(';')
      return next()
    },
   async (compiler, next) => {
      await compiler.setFileContentsAsync(
        'lib/_third_party_main.js',
        compiler.code()
      )
      compiler.options.empty = true // <-- ADDED THIS (hack)
      return next()
    }
  ]
}

I would be glad to help on this feature but I fear my knowledge on this subject is very limited.

Okay, I think I might know what the problem is. The executable is still mangled with metadata, setting empty after input is picked up but before the output begins should fix it… Pretty weird hack… but should work (edited your example)

For people coming to this issue now, some of the Nexe API has changed but the core issue has not been documented so well. I thought I would add our config here that got both signing and notarization working on v4.0.0-beta.6 with Node 12.4.0. The configure option was just our build boxes not being up to date, but shouldn’t matter.

await compile({
  build: true,
  mangle: false,
  configure: ['--openssl-no-asm'],
  input: this.input,
  output: this.output,
  loglevel: 'verbose',
  targets: [ target ],
  patches: [
    (compiler, next) => {
      compiler.code = () => [ compiler.shims.join(''), compiler.startup ].join(';')
      return next();
    },
    (compiler, next) => {
      return compiler.setFileContentsAsync(
        'lib/_third_party_main.js',
        compiler.code()
      ).then(next);
    }
  ]
});

The { mangle: false, build: true, patches: [] } were the options that set us free, as well as not relying on resources and using Parcel as our bundling tool since it handled a complex Lerna monorepo and all its dependencies. Also note that in recent versions compiler.input from the previous comments became compiler.startup.

@calebboyd Using version 3.x I got such edits in Node’s source code (left is downloaded by hand, right is downloaded by nexe and it seems to be edited in some way by nexe): image Of course it can’t work so compiler broke. Trying to comment it properly by hand didn’t work because nexe did those edits again when fired. Is it some kind of bug?

@durran So I upgraded to v4.0.0-beta6 and I used following config:

compile({
  build: true,
  make: [ '-j10' ],
  mangle: false,
  configure: ['--openssl-no-asm'],
  input: '../path/to/main/entry.js',
  loglevel: 'verbose',
  targets: [ 'macos-v12.16.3' ],
  patches: [
    (compiler, next) => {
      compiler.code = () => [ compiler.shims.join(''), compiler.startup ].join(';')
      return next();
    },
    (compiler, next) => {
      return compiler.setFileContentsAsync(
        'lib/_third_party_main.js',
        compiler.code()
      ).then(next);
    }
  ]
});

It seems that compilation errors were fixed in some version, so Node compiled and executable with source code is packed successfully. But for above config I get: internal/bootstrap/pre_execution.js:83 throw ‘Invalid Nexe binary’; ^ Invalid Nexe binary (Use node --trace-uncaught ... to show where the exception was thrown)

I debugged that ‘mangle: false’ option causes it. After commenting it works just fine. But I can’t sign code then (I suppose mangling is the most important factor here).

Note that with mangle: false option I can sign the code (but as I said binary does not work)

Glad it works! Here is the summary:

Normally the binary looks like this:

| Custom (Nexe) Node Binary | Metadata + Your Code | (mangled)

The Patch does this

| Custom (Nexe + Your Code) Node Binary | Metadata | (mangled)

Setting empty: true at that point in time does this

| Custom (Nexe + Your Code) Node Binary | (not mangled!)

For people coming to this issue now, some of the Nexe API has changed but the core issue has not been documented so well. I thought I would add our config here that got both signing and notarization working on v4.0.0-beta.6 with Node 12.4.0. The configure option was just our build boxes not being up to date, but shouldn’t matter.

await compile({
  build: true,
  mangle: false,
  configure: ['--openssl-no-asm'],
  input: this.input,
  output: this.output,
  loglevel: 'verbose',
  targets: [ target ],
  patches: [
    (compiler, next) => {
      compiler.code = () => [ compiler.shims.join(''), compiler.startup ].join(';')
      return next();
    },
    (compiler, next) => {
      return compiler.setFileContentsAsync(
        'lib/_third_party_main.js',
        compiler.code()
      ).then(next);
    }
  ]
});

The { mangle: false, build: true, patches: [] } were the options that set us free, as well as not relying on resources and using Parcel as our bundling tool since it handled a complex Lerna monorepo and all its dependencies. Also note that in recent versions compiler.input from the previous comments became compiler.startup.

@durran I am afraid that this is the final mention of a successfully signed Nexe-build binary. May I ask you to attempt this again, or maybe go into some more details with what and how you managed to both build, run and sign?

آقا دمتون گرم کار کرد

Unfortunately node dropped support for _third_party_main.js in Node 12. So unless the version is less than 12 patching _third_party_main as described by the mangle docs, won’t work.

https://github.com/nexe/nexe/blob/0a2e8dba008c7629b156be15cda4f96d8c455cfb/README.md#L113-L116

I’ll reopen this issue to track this case

For people coming to this issue now, some of the Nexe API has changed but the core issue has not been documented so well. I thought I would add our config here that got both signing and notarization working on v4.0.0-beta.6 with Node 12.4.0. The configure option was just our build boxes not being up to date, but shouldn’t matter.

await compile({
  build: true,
  mangle: false,
  configure: ['--openssl-no-asm'],
  input: this.input,
  output: this.output,
  loglevel: 'verbose',
  targets: [ target ],
  patches: [
    (compiler, next) => {
      compiler.code = () => [ compiler.shims.join(''), compiler.startup ].join(';')
      return next();
    },
    (compiler, next) => {
      return compiler.setFileContentsAsync(
        'lib/_third_party_main.js',
        compiler.code()
      ).then(next);
    }
  ]
});

The { mangle: false, build: true, patches: [] } were the options that set us free, as well as not relying on resources and using Parcel as our bundling tool since it handled a complex Lerna monorepo and all its dependencies. Also note that in recent versions compiler.input from the previous comments became compiler.startup.

I’m in a similar situation as @brikendr I’m quite new to js and ts world and now have working sets for Windows and linux but the Mac notarization really does not like the executable coming out of nexe (nor any other similar tools). For me nexe calls have to be command-line so I’m trying to decipher how the --patches script.js would need to be formatted and what other arguments need to be given to be able to test this solution.

Are there any .js patch examples anywhere on this? How should I list multiple patches etc. Simply put this noob has too many variables at the moment 😉

@brikendr I made this a build file and the build works. codesign --verbose -s - still fails with main executable failed strict validation even though I added the line compiler.options.empty = true to the script. I am using nexe 4.0.0-beta.4 on MacOS 10.15.3.

Does anyone have more information or example on how this can work? @calebboyd you also mentioned that this won’t work with resource - is this because files would be included at runtime and invalidate the signature?

I am trying other options in the meantime. Node compilation takes an hour for me, so this is a slow process 😃