From 39933f758b5b0ee50cdf345eaaf99cc485c9e3ee Mon Sep 17 00:00:00 2001 From: xxxigm Date: Wed, 3 Jun 2026 07:32:00 +0700 Subject: [PATCH] test(desktop): assert macOS device entitlements are inherited Pin #37718: the inherit plist must grant audio-input, every device.* entitlement on the main app must also be inherited by the Helper/Setup processes, and both entitlement files must stay valid plists. --- tests/test_desktop_mac_entitlements.py | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_desktop_mac_entitlements.py diff --git a/tests/test_desktop_mac_entitlements.py b/tests/test_desktop_mac_entitlements.py new file mode 100644 index 000000000..5877d5b25 --- /dev/null +++ b/tests/test_desktop_mac_entitlements.py @@ -0,0 +1,76 @@ +"""Regression for #37718: macOS microphone entitlement must be inherited. + +Hermes Desktop signs with ``hardenedRuntime: true`` and points electron-builder +at two entitlement files (see ``apps/desktop/package.json``): + +* ``entitlements`` → ``electron/entitlements.mac.plist`` (the main app), and +* ``entitlementsInherit`` → ``electron/entitlements.mac.inherit.plist`` (the + Electron Helper / Setup processes). + +Under the hardened runtime, the process that actually opens the microphone is a +Helper, which inherits the *inherit* plist. ``com.apple.security.device.audio-input`` +lived only in the main plist, so macOS' TCC layer refused the microphone with:: + + Prompting policy for hardened runtime; service: kTCCServiceMicrophone + requires entitlement com.apple.security.device.audio-input but it is missing + +and never showed the permission prompt. These tests pin that every device +entitlement granted to the main app is also granted to the inherited helpers. +""" + +from __future__ import annotations + +import plistlib +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parent.parent +ELECTRON_DIR = REPO_ROOT / "apps" / "desktop" / "electron" +MAIN_PLIST = ELECTRON_DIR / "entitlements.mac.plist" +INHERIT_PLIST = ELECTRON_DIR / "entitlements.mac.inherit.plist" + +DEVICE_PREFIX = "com.apple.security.device." + + +def _load(plist: Path) -> dict: + assert plist.is_file(), f"missing entitlements file: {plist}" + with plist.open("rb") as fh: + return plistlib.load(fh) + + +def test_inherit_plist_grants_microphone() -> None: + """The helper-inherited plist must grant audio-input (regression #37718).""" + inherit = _load(INHERIT_PLIST) + assert inherit.get("com.apple.security.device.audio-input") is True, ( + "entitlements.mac.inherit.plist must grant " + "`com.apple.security.device.audio-input`; without it the hardened-runtime " + "Helper process is denied the microphone and no TCC prompt appears (#37718)." + ) + + +def test_device_entitlements_are_inherited() -> None: + """Every device.* entitlement on the main app must also be inherited.""" + main = _load(MAIN_PLIST) + inherit = _load(INHERIT_PLIST) + + main_device = { + key: val + for key, val in main.items() + if key.startswith(DEVICE_PREFIX) and val is True + } + missing = [key for key in main_device if inherit.get(key) is not True] + assert not missing, ( + "Device entitlements present in entitlements.mac.plist but missing from " + f"entitlements.mac.inherit.plist: {missing}. Helper/Setup processes inherit " + "the latter under hardenedRuntime, so any device access the app needs must " + "be listed in both (#37718)." + ) + + +@pytest.mark.parametrize("plist", [MAIN_PLIST, INHERIT_PLIST]) +def test_entitlement_files_are_valid_plists(plist: Path) -> None: + """Both entitlement files must remain well-formed plist dictionaries.""" + data = _load(plist) + assert isinstance(data, dict) and data, f"{plist.name} should be a non-empty dict"