Merge pull request #37745 from xxxigm/fix/macos-mic-entitlement-inherit

fix(desktop): inherit microphone entitlement for macOS helpers (#37718)
This commit is contained in:
brooklyn!
2026-06-02 20:43:05 -05:00
committed by GitHub
2 changed files with 78 additions and 0 deletions

View File

@ -8,5 +8,7 @@
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>

View File

@ -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"