
Ваше Mac-приложение получает доступ к микрофону — и забывает его при каждом запуске
GeekBye запросил доступ к микрофону, вы его выдали, и всё заработало. Следующий запуск: доступа нет. А в Системных настройках → Микрофон приложение вообще не появилось. Виновником оказалась функция безопасности macOS, тихо запускающая приложение из пути, который исчезает — вот диагноз и исправление в одно нажатие.
Вот баг, из-за которого начинаешь сомневаться в собственных глазах. Вы устанавливаете GeekBye, он просит доступ к микрофону, вы нажимаете Разрешить, и транскрипция работает. Отлично. Вы закрываете, открываете на следующее утро — а он просит доступ к микрофону снова. Вы идёте в Системные настройки → Конфиденциальность и безопасность → Микрофон, чтобы исправить это вручную, а GeekBye вообще нет в списке. Не запрещён. Не разрешён. Просто отсутствует, как будто он никогда и не спрашивал.
Каждый отдельный кусочек выглядел правильным. Запрос был настоящим. Разрешение работало в тот момент. Приложение было корректно подписано и нотаризовано с правильными строками использования. И всё же выданное разрешение испарялось при каждом запуске. GeekBye v2.0.6 это исправил — а первопричина в одной из самых коварных вещей, которые macOS делает, чтобы вас защитить.
Функция, которая прятала приложение от него самого
Виновник — macOS App Translocation, функция безопасности Gatekeeper. Когда вы скачиваете приложение и запускаете его прямо из DMG или из папки ~/Downloads — везде, где оно всё ещё «в карантине» — macOS на самом деле запускает его не оттуда, где вы его видите. Он прозрачно копирует его в случайный путь только для чтения глубоко в /private/var/folders/.../AppTranslocation/… и запускает эту копию. Это хорошая защита: она мешает вредоносной загрузке подменять файлы рядом с собой.
Но вот столкновение. Система разрешений macOS (TCC — то, что отслеживает, кто может использовать ваш микрофон, камеру, экран) идентифицирует приложение по его пути и идентичности кода. Когда приложение транслоцировано, этот путь случаен и временен. Так что когда вы выдаёте доступ к микрофону, macOS исправно записывает выдачу — к пути, которого не будет при следующем запуске. Открываете приложение снова, macOS транслоцирует его в другой случайный путь, видит приложение, о котором у него нет записи, и спрашивает опять. А поскольку этот фантомный путь никогда не бывает стабильным местом, приложение так и не получает постоянную строку в Системных настройках → Микрофон.
Приложение выдавало разрешение призраку.
Именно поэтому с этим сталкивались лишь некоторые люди. Если ваша копия GeekBye уже жила в /Applications — потому что вы перетащили её туда или потому что она попала туда через автообновление — нет карантина, нет транслокации, стабильный путь и всё сохраняется идеально. Баг был невидим для нас и для всех, кто прошёл первую установку, а это ровно тот тип бага, который выживает дольше всего.
Исправление: дайте приложению настоящий дом
Раз вся проблема — в нестабильном пути, исправление в том, чтобы переместить приложение на стабильный. v2.0.6 обнаруживает, когда GeekBye работает транслоцированным (или просто работает вне /Applications), и предлагает в одно нажатие «Move to Applications» — используя вызов перемещения macOS, который копирует бандл в /Applications и перезапускает его оттуда. С этого момента у приложения фиксированная идентичность: выдача микрофона держится, запись экрана держится, и GeekBye наконец появляется в Системных настройках там, где вы этого ожидаете.
Запрос вежлив на этот счёт. Он предлагает Move to Applications, Not Now и Don't Ask Again — и запоминает последний выбор, так что приложение никогда не докучает тому, у кого есть осознанная причина запускать его из другого места. Решение о том, показывать ли запрос вообще, изолировано в маленькие чистые функции (транслоцирована ли эта сборка? находится ли она вне /Applications? заглушил ли её пользователь?), так что логика покрыта юнит-тестами без запуска настоящей нотаризованной сборки на настоящем карантинном томе.
Тот же релиз упростил и сам опыт работы с разрешениями. GeekBye раньше выбрасывал кастомное, самописное встроенное окно разрешений — пару сотен строк интерфейса, пытающихся воспроизвести то, что ОС и так делает хорошо. v2.0.6 удалил его и опёрся на нативный запрос разрешений macOS, подкреплённый тихим неблокирующим баннером, который появляется только тогда, когда требуемого разрешения действительно не хватает. Меньше кода, и поведение, которое пользователи уже узнают, потому что любое другое Mac-приложение работает так же.
Часть, которой я горжусь больше всего: мы доказали это, прежде чем поверили
Было бы легко угадать транслокацию и выпустить исправление. Вместо этого релиз сначала выпустил стартовую диагностику: при запуске GeekBye теперь сообщает собственный путь исполняемого файла и то, транслоцирован ли он, находится ли внутри /Applications, а также текущий статус разрешений на микрофон и экран. Эта телеметрия превратила правдоподобную теорию в подтверждённую — производственные данные показали, что затронутые сессии действительно работали из транслоцированных путей вне /Applications, ровно как и предсказывалось.
Этот порядок важен. У «запрос появляется, но разрешение не держится» есть несколько возможных объяснений — проблема с подписью, отсутствующая строка использования, вопрос с entitlement, причуда базы TCC. Мы исключили причины на уровне API (путь запроса был очевидно корректным), а затем позволили реальным данным указать на слой идентичности/пути, вместо того чтобы выпускать исправление на авось и надеяться, что обращения в поддержку прекратятся.
Три вещи, которым учит этот баг
- Разрешение, которое запрашивается, но не сохраняется — это проблема идентичности, а не API. Если код запроса корректен, а выдача всё равно исчезает, перестаньте переписывать запрос. Спросите, к какому пути и идентичности кода ОС привязывает выдачу — и стабильна ли эта идентичность между запусками.
- Выпускайте диагностику вместе с (или до) исправлением. Несколько полей — откуда я работаю, каков мой статус разрешений — превратили обоснованную догадку в проверенную первопричину и сказали нам точно, какие пользователи были затронуты. Оснастите инструментами границу, которую подозреваете, прежде чем её латать.
- Баги, которые прячутся от разработчиков — это те, что работают в «неправильной» среде. Наш жил в
/Applicationsна каждой машине разработчика, так что был структурно невидим для нас, попадая при этом в новых пользователей. Когда обращение не воспроизводится, первый вопрос — что отличается в месте, где оно работает, а не не ошибается ли пользователь.
GeekBye v2.0.6 выпустил запрос перемещения и диагностику вместе. О более широкой дуге надёжности, в которую это вписывается, см. сколько на самом деле стоит версия 2 (v2.0.0) и почему запись экрана захватывает не тот монитор (v2.0.10) — ещё один баг, всплывший только в конкретной среде. О релизе с мелкими деталями по соседству — спокойное ПО: исправление мерцания и чип режима ответа (v2.0.3 + v2.0.5).