Commit 55da3531fcd for nodejs

commit 55da3531fcd20287a680c09b5c740b8cb1d0f6d4
Author: Ulises Gascón <ulisesgascongonzalez@gmail.com>
Date:   Sun Nov 15 17:15:31 2026 +0100

    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-<version>.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 <ulisesgascongonzalez@gmail.com>
    PR-URL: https://github.com/nodejs/node/pull/67143
    Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
    Reviewed-By: Stewart X Addison <sxa@redhat.com>

diff --git a/Makefile b/Makefile
index fa6979120dc..cd732faeec2 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 06ba801c9e8..c9b50a2f3b6 100755
--- a/tools/osx-notarize.sh
+++ b/tools/osx-notarize.sh
@@ -1,12 +1,107 @@
 #!/bin/sh

-# Notarize a generated node-<version>.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 <path>
+#
+#   <path> ending in .pkg: the installer is submitted as is, assessed with
+#     spctl and the notarization ticket is stapled to it.
+#   any other <path>: 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-<version>.pkg.

-pkgid="$1"
+# The notarization ticket can take a while to become available from Apple's
+# servers after notarytool reports "Accepted", so retry with backoff.
+retry() {
+  delay=15
+  attempt=1
+  while ! "$@"; do
+    if [ "$attempt" -ge 6 ]; then
+      return 1
+    fi
+    echo "Attempt $attempt failed, retrying in ${delay}s..."
+    sleep "$delay"
+    attempt=$((attempt + 1))
+    delay=$((delay * 2))
+  done
+}
+
+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
+}
+
+# Remove the upload zip on every exit path, including a failed ditto or an
+# interrupted build, the same way the dep_updaters scripts clean their
+# workspace. $zip is only set by notarize_binary.
+cleanup() {
+  EXIT_CODE=$?
+  if [ -n "$zip" ]; then
+    rm -f "$zip"
+  fi
+  exit $EXIT_CODE
+}
+
+notarize_pkg() {
+  submit "$1" || exit 1
+
+  if ! retry xcrun spctl --assess --type install --context context:primary-signature --ignore-cache --verbose=2 "$1"; then
+    echo "error: Signature will not be accepted by Gatekeeper!" 1>&2
+    exit 1
+  fi
+  echo "Verification was successful."
+
+  if ! retry xcrun stapler staple "$1"; then
+    echo "error: Stapling $1 failed." 1>&2
+    exit 1
+  fi
+  echo "Stapler was successful."
+}
+
+notarize_binary() {
+  # 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 and is removed by cleanup.
+  zip="$1.notarize.zip"
+  rm -f "$zip"
+  if ! ditto -c -k --keepParent "$1" "$zip"; then
+    echo "error: Could not create $zip for submission." 1>&2
+    exit 1
+  fi
+
+  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 "$1"; then
+    echo "error: $1 will not be accepted by Gatekeeper!" 1>&2
+    exit 1
+  fi
+  echo "Verification was successful."
+}

-if [ -z "$pkgid" ]; then
-  echo "Usage: $0 <pkgid>"
+target="$1"
+
+if [ -z "$target" ]; then
+  echo "Usage: $0 <path-to-pkg-or-binary>"
   exit 1
 fi

@@ -26,6 +121,16 @@ 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
+
 echo "Notarization process is done with Notarytool."

 if ! command -v xcrun notarytool > /dev/null
@@ -34,45 +139,9 @@ 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() {
-  delay=15
-  attempt=1
-  while ! "$@"; do
-    if [ "$attempt" -ge 6 ]; then
-      return 1
-    fi
-    echo "Attempt $attempt failed, retrying in ${delay}s..."
-    sleep "$delay"
-    attempt=$((attempt + 1))
-    delay=$((delay * 2))
-  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
-else
-  echo "Verification was successful."
-fi
+trap cleanup INT TERM EXIT

-if ! retry xcrun stapler staple "node-$pkgid.pkg"; then
-  echo "error: Stapling node-$pkgid.pkg failed." 1>&2
-  exit 1
-fi
-echo "Stapler was successful."
\ No newline at end of file
+case "$target" in
+  *.pkg) notarize_pkg "$target" ;;
+  *) notarize_binary "$target" ;;
+esac