From a67d9bcbfdba8575c3ddf691a26290fe7a18c9e5 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Fri, 30 Oct 2026 15:00:39 +0100 Subject: [PATCH] tools: notarize bin/node in the darwin tarballs The binary tarball target only codesigns bin/node. Notarization runs only for the pkg, and tools/osx-notarize.sh only knows how to submit a pkg, so every darwin tarball binary ships signed but without a ticket. Gatekeeper then applies a full trust evaluation to each process spawned through it, which slows down every CLI shim on macOS. Make tools/osx-notarize.sh take a path instead of a version id. A .pkg keeps the existing flow. Any other file is treated as a Mach-O binary: it is zipped with ditto, submitted with notarytool, and the ticket is verified with `codesign --verify -R=notarized` using the existing retry helper. spctl cannot be used for that check, it rejects command line tools as "not an app". The binary itself is not modified, so published checksums do not change. A bare version id is still accepted and resolved to node-.pkg for the release branches. Call the script from the binary target after codesign. It is a no-op unless the NOTARIZATION_* variables are exported, so the tarball jobs keep behaving as today until the release job injects them. Refs: https://github.com/nodejs/node/issues/67140 Refs: https://github.com/nodejs/build/issues/3538 Refs: https://github.com/nodejs/build/issues/4545 Refs: https://github.com/nodejs/node/pull/66855 Assisted-by: Claude Assisted-by: Codex Signed-off-by: Ulises Gascon --- Makefile | 3 +- tools/osx-notarize.sh | 117 ++++++++++++++++++++++++++++++++---------- 2 files changed, 92 insertions(+), 28 deletions(-) diff --git a/Makefile b/Makefile index 0af75b42c293..ed96a2438e28 100644 --- a/Makefile +++ b/Makefile @@ -1247,7 +1247,7 @@ $(PKG): release-only --resources $(MACOSOUTDIR)/installer/productbuild/Resources \ --package-path $(MACOSOUTDIR)/pkgs ./$(PKG) SIGN="$(PRODUCTSIGN_CERT)" PKG="$(PKG)" sh tools/osx-productsign.sh - sh tools/osx-notarize.sh $(FULLVERSION) + sh tools/osx-notarize.sh $(PKG) .PHONY: pkg pkg: $(PKG) ## Build the macOS installer for releases. @@ -1433,6 +1433,7 @@ else endif ifeq ($(OSTYPE),darwin) SIGN="$(CODESIGN_CERT)" PKGDIR="$(BINARYNAME)" sh tools/osx-codesign.sh + sh tools/osx-notarize.sh $(BINARYNAME)/bin/node endif tar -cf $(BINARYNAME).tar $(BINARYNAME) $(RM) -r $(BINARYNAME) diff --git a/tools/osx-notarize.sh b/tools/osx-notarize.sh index 06ba801c9e81..ea9401b48166 100755 --- a/tools/osx-notarize.sh +++ b/tools/osx-notarize.sh @@ -1,12 +1,27 @@ #!/bin/sh -# Notarize a generated node-.pkg file as an Apple requirement for installation on macOS Catalina and later, as validated by Gatekeeper. +# Notarize a macOS release artifact as an Apple requirement for running or +# installing on macOS Catalina and later, as validated by Gatekeeper. # Uses notarytool and requires Xcode >= 13.0. +# +# Usage: osx-notarize.sh +# +# ending in .pkg: the installer is submitted as is, assessed with +# spctl and the notarization ticket is stapled to it. +# any other : a Mach-O binary, such as bin/node from the release +# tarball. Apple only accepts zip, dmg or pkg submissions, so the binary +# is zipped for the upload and the zip is discarded afterwards. A bare +# binary cannot carry a stapled ticket; Gatekeeper fetches it online by +# the code directory hash, so the binary itself is left untouched and the +# published checksums do not change. +# +# For backwards compatibility a bare version id (for example v26.13.0) is +# accepted and resolved to node-.pkg. -pkgid="$1" +target="$1" -if [ -z "$pkgid" ]; then - echo "Usage: $0 " +if [ -z "$target" ]; then + echo "Usage: $0 " exit 1 fi @@ -26,6 +41,21 @@ if [ -z "$NOTARIZATION_TEAM_ID" ]; then exit 0 fi +# Legacy call form: a version id instead of a path. +if [ ! -e "$target" ] && [ -e "node-$target.pkg" ]; then + target="node-$target.pkg" +fi + +if [ ! -e "$target" ]; then + echo "error: $target does not exist." 1>&2 + exit 1 +fi + +case "$target" in + *.pkg) kind=pkg ;; + *) kind=binary ;; +esac + echo "Notarization process is done with Notarytool." if ! command -v xcrun notarytool > /dev/null @@ -34,20 +64,6 @@ then exit 1 fi -echo "Submitting node-$pkgid.pkg for notarization..." - -xcrun notarytool submit \ - --keychain-profile "NODE_RELEASE_PROFILE" \ - --wait \ - "node-$pkgid.pkg" - -if [ $? -eq 0 ]; then - echo "Notarization node-$pkgid.pkg submitted successfully." -else - echo "Notarization node-$pkgid.pkg failed." - exit 1 -fi - # The notarization ticket can take a while to become available from Apple's # servers after notarytool reports "Accepted", so retry with backoff. retry() { @@ -64,15 +80,62 @@ retry() { done } -if ! retry xcrun spctl --assess --type install --context context:primary-signature --ignore-cache --verbose=2 "node-$pkgid.pkg"; then - echo "error: Signature will not be accepted by Gatekeeper!" 1>&2 - exit 1 +submit() { + echo "Submitting $1 for notarization..." + if xcrun notarytool submit \ + --keychain-profile "NODE_RELEASE_PROFILE" \ + --wait \ + "$1"; then + echo "Notarization $1 submitted successfully." + else + echo "Notarization $1 failed." + return 1 + fi +} + +if [ "$kind" = "pkg" ]; then + submit "$target" || exit 1 + + if ! retry xcrun spctl --assess --type install --context context:primary-signature --ignore-cache --verbose=2 "$target"; then + echo "error: Signature will not be accepted by Gatekeeper!" 1>&2 + exit 1 + else + echo "Verification was successful." + fi + + if ! retry xcrun stapler staple "$target"; then + echo "error: Stapling $target failed." 1>&2 + exit 1 + fi + echo "Stapler was successful." else - echo "Verification was successful." -fi + # ditto preserves the code signature and resource forks, which plain zip + # does not, so it is the archiver Apple documents for notarization uploads. + # The zip only exists for the upload. Remove it on every exit path, + # including a failed ditto or an interrupted build, the same way the + # dep_updaters scripts clean their workspace. + zip="$target.notarize.zip" + cleanup () { + EXIT_CODE=$? + rm -f "$zip" + exit $EXIT_CODE + } + trap cleanup INT TERM EXIT + rm -f "$zip" + if ! ditto -c -k --keepParent "$target" "$zip"; then + echo "error: Could not create $zip for submission." 1>&2 + exit 1 + fi -if ! retry xcrun stapler staple "node-$pkgid.pkg"; then - echo "error: Stapling node-$pkgid.pkg failed." 1>&2 - exit 1 + submit "$zip" || exit 1 + rm -f "$zip" + + # There is nothing to staple to a bare binary, so the only way to confirm + # the ticket is live is to have codesign look it up. spctl cannot be used + # here: it rejects command line tools with "does not seem to be an app". + if ! retry codesign --verify --verbose=2 -R=notarized "$target"; then + echo "error: $target will not be accepted by Gatekeeper!" 1>&2 + exit 1 + fi + echo "Verification was successful." fi -echo "Stapler was successful." \ No newline at end of file