diff --git a/hermes_cli/main.py b/hermes_cli/main.py index a9ee8865728..fa68091494f 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -5404,6 +5404,34 @@ def _desktop_macos_relaunchable_fixup(desktop_dir: Path) -> None: print(f" (warning: macOS relaunch fixup skipped: {exc})") +def _force_adhoc_macos_signing(env: dict, *, source_mode: bool) -> bool: + """Stop electron-builder grabbing a random keychain identity on self-update. + + The desktop self-updater rebuilds *and re-signs the .app on the end user's + machine* (``hermes desktop --build-only`` → electron-builder ``--dir``). + With ``CSC_IDENTITY_AUTO_DISCOVERY`` on (its default), electron-builder + signs the ``type=distribution``, hardened-runtime bundle with whatever it + finds in that user's keychain — typically a personal "Apple Development" + cert. That stalls/fails the sign step (no Developer ID + no provisioning + profile) or clobbers your real notarized signature with an unusable one, so + every post-update launch trips Gatekeeper. + + Force ad-hoc signing for the local packaged rebuild instead: deterministic, + and exactly what ``_desktop_macos_relaunchable_fixup`` already finishes off. + No-op for source runs, off-macOS, when a real identity is configured + (``CSC_LINK`` / ``APPLE_SIGNING_IDENTITY``), or when the caller already + pinned the flag. Mutates ``env``; returns True when it set the flag. + """ + if sys.platform != "darwin" or source_mode: + return False + if env.get("CSC_LINK") or env.get("APPLE_SIGNING_IDENTITY"): + return False + if "CSC_IDENTITY_AUTO_DISCOVERY" in env: + return False + env["CSC_IDENTITY_AUTO_DISCOVERY"] = "false" + return True + + def _desktop_linux_sandbox_fixup(packaged_executable: Path) -> bool: """Configure Electron's Linux SUID sandbox helper when required.""" if sys.platform != "linux": @@ -5535,6 +5563,9 @@ def cmd_gui(args: argparse.Namespace): build_label = "source build" if source_mode else "packaged app" print(f"→ Building desktop {build_label}...") build_script = "build" if source_mode else "pack" + if _force_adhoc_macos_signing(env, source_mode=source_mode): + print(" → No Developer ID configured; ad-hoc signing this local rebuild " + "(CSC_IDENTITY_AUTO_DISCOVERY=false)") if not source_mode: # A running desktop instance launched from release/win-unpacked # holds Hermes.exe locked on Windows, so the pack can't replace diff --git a/tests/hermes_cli/test_gui_command.py b/tests/hermes_cli/test_gui_command.py index 4995a7897ac..c488944855f 100644 --- a/tests/hermes_cli/test_gui_command.py +++ b/tests/hermes_cli/test_gui_command.py @@ -877,3 +877,40 @@ def test_stop_desktop_build_lock_no_release_dir(tmp_path, monkeypatch): with patch("psutil.process_iter") as it: assert cli_main._stop_desktop_processes_locking_build(desktop_dir) == [] it.assert_not_called() + + +def test_force_adhoc_signing_disables_discovery_on_local_packaged_rebuild(monkeypatch): + monkeypatch.setattr(cli_main.sys, "platform", "darwin") + env = {} + assert cli_main._force_adhoc_macos_signing(env, source_mode=False) is True + assert env["CSC_IDENTITY_AUTO_DISCOVERY"] == "false" + + +@pytest.mark.parametrize("platform", ["linux", "win32"]) +def test_force_adhoc_signing_noop_off_macos(monkeypatch, platform): + monkeypatch.setattr(cli_main.sys, "platform", platform) + env = {} + assert cli_main._force_adhoc_macos_signing(env, source_mode=False) is False + assert "CSC_IDENTITY_AUTO_DISCOVERY" not in env + + +def test_force_adhoc_signing_noop_for_source_mode(monkeypatch): + monkeypatch.setattr(cli_main.sys, "platform", "darwin") + env = {} + assert cli_main._force_adhoc_macos_signing(env, source_mode=True) is False + assert "CSC_IDENTITY_AUTO_DISCOVERY" not in env + + +@pytest.mark.parametrize("key", ["CSC_LINK", "APPLE_SIGNING_IDENTITY"]) +def test_force_adhoc_signing_preserves_real_identity(monkeypatch, key): + monkeypatch.setattr(cli_main.sys, "platform", "darwin") + env = {key: "secret"} + assert cli_main._force_adhoc_macos_signing(env, source_mode=False) is False + assert "CSC_IDENTITY_AUTO_DISCOVERY" not in env + + +def test_force_adhoc_signing_respects_explicit_caller_flag(monkeypatch): + monkeypatch.setattr(cli_main.sys, "platform", "darwin") + env = {"CSC_IDENTITY_AUTO_DISCOVERY": "true"} + assert cli_main._force_adhoc_macos_signing(env, source_mode=False) is False + assert env["CSC_IDENTITY_AUTO_DISCOVERY"] == "true"