# Может ли автоматизация UI macOS обойти подтверждение агента?

Карточка подтверждения становится средством защиты только тогда, когда действие вызывает человек, а не другой процесс. Это кажется очевидным, пока карточка не оказывается на обычном рабочем столе macOS. Клиенты Accessibility могут изучать элементы управления, инструменты автоматизации способны активировать приложения, а любое окно на переднем плане может перехватить следующий клик.

Не проверяйте это, выясняя, выглядит ли карточка модальной или содержит ли ее кнопка убедительную подпись. Проверьте, может ли процесс с реалистичными локальными разрешениями вызвать защищенное действие без того, чтобы нужный человек сделал осознанный и намеренный выбор. Ценность теста не в эффектном видео с эксплойтом. Нужен воспроизводимый ответ на более узкий вопрос: какие пути ввода пересекают границу подтверждения, при каких разрешениях и какие доказательства подтверждают результат?

Это red team-упражнение для принадлежащего вам программного обеспечения, на Mac и под учетной записью, которыми вы управляете. Запрашиваемое действие должно быть безвредным. Используйте фиктивную HTTP-конечную точку, тестовый SSH-хост, не связанный с рабочей средой, или действие, возвращающее фиксированное тестовое значение. Если в ходе проверки можно случайно потратить деньги, удалить данные или изменить доступ к рабочей системе, тест уже плохо спроектирован, еще до написания скрипта.

## Разрешение Accessibility меняет модель атакующего

Процесс с разрешением Accessibility, это не обычный фоновый процесс. Apple описывает доступ Accessibility как возможность приложений управлять Mac, а macOS требует, чтобы пользователь выдал это разрешение в настройках Privacy & Security. Отдельно Apple описывает разрешение Automation, которое дает возможность получать доступ к другим приложениям и управлять ими. Это разные разрешения, но оба нужно учитывать в модели угроз, если карточка подтверждения находится на рабочем столе.

Команды часто смешивают два разных понятия. Интерфейс подтверждения может быть доступен для специальных возможностей, но действие, пришедшее от клиента Accessibility, не обязано считаться доказательством намерения человека. Accessibility должна предоставлять понятные подписи, роли, состояния и фокус, чтобы пользователи вспомогательных технологий могли работать с приложением. Это требование доступности. Оно не обязывает приложение разрешать чужому процессу вызывать необратимое подтверждение без локального сигнала от пользователя.

Начните тест с точного описания моделируемого атакующего. Не используйте расплывчатое слово «вредоносное ПО»: оно скрывает разрешения, от которых зависит результат. Подходящий базовый вариант, локально запущенный подписанный помощник от имени того же вошедшего пользователя, которому уже выдано разрешение Accessibility. Более сильный вариант также имеет разрешение Automation и может запрашивать активацию приложения. Ни один из вариантов не получает учетные данные администратора, не обходит блокировку экрана и не использует измененную копию приложения с подтверждениями.

Так границы теста остаются честными. Если вывод звучит как «атакующий с полным контролем учетной записи может управлять учетной записью», вы ничего не узнали. Если же вывод такой: «помощник с теми же разрешениями, которые пользователь может выдать оконному менеджеру, тестовому раннеру, инструменту макросов или вспомогательной утилите, может подтвердить использование учетных данных», то вы обнаружили проблему проектирования с понятным путем исправления.

До первого запуска составьте таблицу возможностей:

| Возможность | Состояние теста | Почему это важно |
| --- | --- | --- |
| Разрешение Accessibility | Включено или выключено | Определяет, может ли помощник находить опубликованные элементы интерфейса и вызывать их. |
| Разрешение Automation | Включено или выключено | Определяет, может ли помощник просить macOS управлять другим приложением. |
| Input Monitoring | Включено или выключено | Помогает отделить наблюдение за вводом пользователя от создания действий в интерфейсе. |
| Запись экрана | Включена или выключена | Полезна для сбора доказательств, но снимок экрана не подтверждает право на действие. |
| Та же пользовательская сессия | Обязательно | Позволяет сосредоточиться на реалистичном вмешательстве в рабочий стол. |

Не выдавайте разрешение только потому, что его запрашивает тестовый инструмент. Каждое дополнительное разрешение меняет вывод, который можно сделать по результатам. Записывайте точную комбинацию в названии результата, например «помощник только с Accessibility не может подтвердить отдельный вызов», а не «подтверждение безопасно». Второе утверждение не выдержит проверки на другой конфигурации рабочего стола.

Также зафиксируйте bundle identifier помощника, полномочия подписи кода, идентификатор процесса, родительский процесс и путь запуска. Хорошая система подтверждений должна показывать человеку достаточно сведений об идентичности процесса, чтобы оценить запрос. Но в записи теста нужно больше деталей, чем разумно помещать на карточке. Оператор должен понять, унаследовал ли дочерний процесс внешне доверенного родителя, запустила ли помощник оболочка терминала и создала ли повторная загрузка новую идентичность процесса.

## Кликабельная кнопка еще не доказывает слабость подтверждения

Кнопка, опубликованная в иерархии специальных возможностей macOS, не становится уязвимостью автоматически. AppKit ожидает, что элементы Accessibility будут участвовать в этой иерархии, чтобы вспомогательные клиенты могли находить функциональные элементы управления. Apple указывает, что такие элементы предоставляют свойства, включая подписи, заголовки, рамки и сведения о доступности для нажатия.

Слабая архитектура появляется тогда, когда вызова опубликованной кнопки достаточно для чувствительного действия. В таком случае синтетическое действие Accessibility неотличимо от осознанного клика. Проблема часто остается незаметной: команда проверяет приложение мышью, видит ожидаемый callback и не спрашивает, что еще может вызвать тот же callback.

Свяжите решение о подтверждении с локальным событием авторизации, а не только с обработчиком действия кнопки. Элемент интерфейса может начать запрос, но защищенное действие должно ждать результата авторизации, в котором записано, как именно было получено подтверждение. Для обычного подтверждения сессии это может быть намеренный клик после проверки, что собственная поверхность подтверждения приложения активна и актуальна. Для действия с высоким риском потребуйте системное локальное событие аутентификации, например Touch ID, если он доступен.

Не совершайте распространенную ошибку, скрывая или переименовывая кнопку Approve в дереве Accessibility. Это наказывает пользователей вспомогательных технологий и почти не мешает опытному локальному атакующему, который все еще может управлять фокусом или использовать альтернативные пути. Вместо этого проверьте, отклоняет ли конвейер подтверждения синтетический путь, устаревшую карточку, неактивную карточку и запрос, отображаемые детали которого больше не соответствуют действию, которое собираются выполнить.

Полезная внутренняя запись решения может выглядеть так:

```json
{
  "request_id": "test-4d8f",
  "decision": "approved",
  "decision_method": "touch_id",
  "card_generation": 7,
  "request_generation": 7,
  "app_active": true,
  "card_frontmost": true,
  "requester_pid": 48102,
  "requester_signing_authority": "Example Development Team",
  "action_started_after_decision": true
}
```

Названия полей менее важны, чем их разделение. `decision` показывает, что произошло. `decision_method` объясняет, как это произошло. Значения generation не дают старой карточке подтвердить новый запрос после перерисовки, повтора или подмены действия. Поля активации фиксируют, смотрел ли пользователь на вашу карточку в момент решения. Последнее поле позволяет проверить порядок операций.

Такое разделение также выявляет тонкую ошибку реализации: приложение принимает подтверждение заранее, а потом показывает карточку для вида. При этой ошибке все визуальные тесты проходят. Защищенное действие уже началось, поэтому карточка создает видимость контроля, а не сам контроль. Исполнитель действия должен требовать токен решения, который подсистема подтверждений создает только после завершения соответствующих проверок.

## Тестовый стенд должен создавать безвредные и сопоставимые запуски

Соберите одну тестовую заготовку, которая создает предсказуемый запрос подтверждения, и одного наблюдателя, который собирает доказательства. Не начинайте с универсального фреймворка автоматизации рабочего стола, направленного на реальные учетные данные. Тестовый стенд должен уменьшать неоднозначность, а не добавлять ее.

Заготовке нужен запрос со стабильным идентификатором, видимыми деталями, меняющимися от запуска к запуску, коротким сроком действия и безвредным результатом. Например, защищенное действие может обращаться к локальной конечной точке, которая возвращает `approved:test-4d8f` только после разрешения системой подтверждений. Если действие выполняется без действительной записи о подтверждении, это однозначный провал теста. Если оно завершается по тайм-ауту или фиксирует отказ, это однозначный результат без подтверждения.

Используйте короткоживущий nonce и в карточке, и в защищенном запросе. Карточка с одной надписью «Разрешить доступ агенту?» не поможет обнаружить ошибку устаревшего окна. Карточка «Разрешить тестовому запросу 4d8f вызвать staging echo service?» поможет. Меняйте nonce при каждом запуске и проверяйте, что результат действия содержит тот же nonce.

В доказательствах храните три независимых времени:

1. Время создания запроса.
2. Время решения.
3. Время запуска защищенного действия.

По возможности используйте внутри приложения монотонные часы. Системное время полезно для человеческой временной шкалы, но оно может измениться из-за синхронизации по сети, выхода из сна или ручной корректировки. Правило порядка простое: действие должно начаться после действительного решения для той же генерации запроса. Снимок экрана не доказывает этот порядок.

Наблюдатель должен фиксировать активное приложение, сфокусированное окно, видимый ID запроса на карточке и итоговый результат действия. Запись экрана помогает разобраться в неожиданном поведении, но не заменяет структурированные события. На записи теряются идентификаторы процессов, быстрые переходы сливаются, а важное событие часто скрывается за движением указателя.

Для каждой попытки используйте такую запись:

```text
case=AX-invoke-approve
request=test-4d8f
helper=TestHelper pid=48102
permissions=accessibility
approval_surface=active
attempt=accessibility_action
decision=denied
protected_action=not_started
artifact=activity-event-0192
```

Значение `attempt` должно описывать механизм, а не желаемый результат. Пишите `activate-then-click`, `stale-window`, `keyboard-focus-shift` или `voiceover-navigation`, а не `attack-1`. Через шесть недель это простое описание поможет не запускать неправильный сценарий повторно.

Сначала запустите ту же заготовку без разрешений помощника. Это контрольный запуск. Затем включайте по одной возможности. Результат red team-теста без контрольного запуска трудно интерпретировать. Возможно, карточка вообще не была доступна с клавиатуры. Возможно, не сработала конечная точка. Возможно, у помощника не было нужного разрешения. Контрольный запуск превращает такие предположения в видимые различия.

## Проверяйте синтетическое подтверждение, не превращая тест в набор эксплойтов

Цель этого сценария, выяснить, может ли клиент Accessibility с разрешением вызвать элемент подтверждения и запустить защищенное действие. Для ответа не нужны публичный кликер или набор хрупких скриптов с координатами.

Используйте собственного тестового помощника с узкой задачей: найти окно подтверждения тестового приложения по его идентичности и nonce тестового запроса, запросить то же семантическое действие, которое запросил бы клиент Accessibility, а затем сообщить, завершилось ли защищенное действие. Не ищите все окна на рабочем столе. Не нацеливайтесь на диалоги сторонних приложений. Не храните учетные данные и не взаимодействуйте с реальными служебными аккаунтами.

Проверяйте эти пути отдельно, поскольку каждый доказывает разное:

- Семантический вызов элемента Approve через Accessibility.
- Навигация с клавиатуры до Approve с последующей синтетической активацией.
- Действие указателя после того, как помощник вывел приложение с подтверждением на передний план.
- Действие указателя, когда помощник не выводил его на передний план.
- Отложенное действие после истечения срока карточки или изменения запроса.

Клики по координатам стоит оставлять только как регрессионную проверку визуального перекрытия. Они показывают, может ли клик в определенной точке экрана после изменения макета попасть не на тот элемент. Но они не показывают, может ли чужой процесс найти и активировать нужный элемент. Семантическая автоматизация интерфейса лучше проверяет логику авторизации, а координаты, ошибки макета и фокуса.

Ожидайте различий в результатах для разных режимов подтверждения. Подтверждение на уровне сессии обычно разрешает известному процессу агента действовать в течение всего времени его работы. Если подтверждение сессии принимает обычный клик, помощник с Accessibility может имитировать его, если приложение не различает путь ввода или не связывает подтверждение с более сильным локальным событием. Для действий с низким риском это может быть приемлемым компромиссом, но его нужно описать прямо.

От подтверждения каждого вызова следует требовать большего. Если действие использует учетные данные, позволяющие изменять данные, публиковать релиз или обращаться к рабочему хосту, одного нажатия кнопки недостаточно. Запросите Touch ID или другой локальный сигнал аутентификации, управляемый системой. Тогда тест проверяет следующее: может ли помощник вызвать запрос, активировать доступные элементы вокруг него и все равно не завершить защищенное действие без аутентификации человека? Это гораздо более четкое свойство безопасности.

Не сообщайте просто «заблокировано», если кнопка не сработала. Записывайте всю цепочку: нашел ли помощник карточку, вызвал ли элемент, зафиксировало ли приложение решение, выдала ли подсистема авторизации токен и начал ли исполнитель действие. Сегодня помощник может не суметь нажать кнопку, а завтра сработает альтернативный путь клавиатуры. Важная граница, это токен решения.

## Кража фокуса может превратить честный клик в неправильное решение

Прямые скриптовые клики привлекают внимание, потому что выглядят вредоносно. Кража фокуса часто опаснее и реалистичнее: она может превратить настоящий жест пользователя в подтверждение другого запроса.

Документация Apple по AppKit ясно показывает, что активация приложения, это запрос, а не гарантия, и что состояние активации может измениться, пока macOS управляет рабочим столом. Apple также предоставляет API для запуска и открытия приложений с разными вариантами активации. Поэтому код подтверждения должен воспринимать состояние окна на переднем плане как событие, которое нужно наблюдать и проверять, а не как постоянный факт, предполагаемый в момент отображения карточки.

Намеренно проверьте такую гонку. Покажите безвредную карточку подтверждения с уникальным nonce. Переместите указатель на элемент подтверждения, но не нажимайте его. Затем попросите контролируемого помощника активировать второе тестовое приложение или показать безобидное тестовое окно. После этого выполните задуманный клик в нужный момент и повторите сценарий много раз. Записывайте, какое приложение было активным в момент нажатия и отпускания кнопки мыши, какому окну принадлежал сфокусированный элемент и началось ли защищенное действие.

Не считайте успешным результатом то, что «карточка осталась видимой». Видимая карточка может быть неактивной, находиться за другим окном, а ее элементы могут оставаться нарисованными, пока клавиатурный фокус уже перешел в другое место. Перед использованием подтверждения приложение должно повторно проверить, что текущий запрос все еще владеет отображенной карточкой и что приложение активно в этот момент.

Надежный набор тестов на кражу фокуса включает следующие случаи:

| Случай | Вмешательство | Ожидаемый результат |
| --- | --- | --- |
| Активация до клика | Помощник активирует другое приложение до нажатия кнопки мыши | Подтверждения нет; карточка требует нового намеренного действия. |
| Активация между нажатием и отпусканием | Фокус меняется во время жеста клика | Подтверждения нет; переход фокуса записывается. |
| Окно поверх карточки | Контролируемое окно закрывает часть карточки | Действие не выполняется, пока пользователь снова не взаимодействует с поверхностью подтверждения. |
| Замена запроса | Пока первая карточка видима, приходит новый запрос | Старая карточка не может подтвердить новый запрос. |
| Деактивация приложения | Пользователь переключается на другое приложение до подтверждения | Карточка истекает, отзывается или требует нового подтверждения. |

Фраза «требует нового взаимодействия» важна. Автоматически возвращать фокус и принимать исходный клик после краткого прерывания опасно. Пользователь может считать, что клик закрыл помеху, тогда как приложение воспримет его как подтверждение. Отзовите ожидающее решение или явно сбросьте его. После того как приложение снова станет активным, пользователь должен начать новое подтверждение.

Не решайте проблему агрессивным принудительным выводом приложения на передний план. Так возникает другая проблема: неожиданное поведение учит людей машинально нажимать на появившиеся окна. Карточка должна заметно появляться при поступлении запроса, но не должна вступать в борьбу за фокус с рабочим столом. Если в критический момент она теряет активацию, безопаснее отказать и попросить подтвердить запрос снова.

## Устаревшие карточки и подмена запросов требуют отдельных сценариев

Карточка может выдержать прямой синтетический клик и все равно разрешить неправильное действие. Это происходит, когда интерфейс представляет один запрос, а серверная часть уже перешла к другому.

Типичный пример, ошибка при повторной попытке. Агент запрашивает HTTP-вызов. Появляется карточка. Агент отключается, подключается снова и отправляет второй запрос с немного другими заголовками. Интерфейс повторно использует существующую карточку, потому что запросы выглядят похожими. Клик, который пользователь считает подтверждением запроса A, освобождает запрос B. Текст идентичности может быть безупречным, но подтверждение все равно окажется неправильным.

Связывайте подтверждение с неизменяемым дайджестом запроса. Включайте тип операции, назначение, идентичность или псевдоним учетных данных, важные сведения о цели, идентичность процесса агента и nonce. При изменении любого защищенного поля текущая карточка должна стать недействительной. Не обновляйте подпись на месте, сохраняя готовую к нажатию кнопку Approve. Создайте новую генерацию запроса и сделайте предыдущий экземпляр интерфейса неспособным что-либо завершить.

В тестовой заготовке отправьте два запроса почти одновременно:

```text
A: POST staging.example.invalid/echo
   header X-Test-Nonce: alpha-4d8f

B: POST staging.example.invalid/echo
   header X-Test-Nonce: bravo-7a21
```

Оставьте карточку запроса A видимой достаточно долго, чтобы оператор мог ее распознать. Затем отправьте B через тот же процесс агента. Попробуйте подтвердить запрос мышью, клавиатурой, действием Accessibility и после изменения фокуса. Допустимы только такие результаты: A завершается с `alpha-4d8f`, B завершается после собственного нового подтверждения с `bravo-7a21`, либо не завершается ни один запрос. Если видимая карточка A освобождает B, это дефект авторизации, блокирующий выпуск.

Повторите проверку для изменения цели, которое может показаться спешащему пользователю несущественным. В SSH-действии команда может остаться прежней, а хост измениться. В HTTP-действии хост может сохраниться, а путь или передаваемые учетные данные измениться. Карточка не обязана печатать каждый байт запроса, но должна показывать детали, от которых зависит решение безопасности. Серверная часть должна связывать подтверждение с полным каноническим запросом, а не только с отображаемыми деталями.

Проверки срока действия тоже важны. Таймер не должен быть только визуальным эффектом. Когда срок запроса истекает, отзовите серверный путь авторизации для этой генерации запроса. Затем проверьте действие Accessibility точно на границе истечения и отпускание кнопки мыши сразу после истечения. Ожидаемый результат, отказ с причиной, из которой понятно, что остановило действие: истечение срока, потеря фокуса, несовпадение генерации или ошибка авторизации.

## Журналы должны разрешать спор после окончания записи экрана

Тесты подтверждения превращаются в спор, когда единственное доказательство звучит как «я нажал, и это сработало». Нужны записи, по которым другой инженер сможет восстановить причинную цепочку, не полагаясь на интерпретацию тестировщика.

Записывайте два связанных потока: запуск агента и отдельный защищенный вызов. Запись запуска отвечает на вопросы о том, кто начал работу и был ли запуск впоследствии отозван. Запись вызова показывает, какое именно действие запрашивалось, как разрешилось подтверждение и началось ли выполнение. События подтверждения должны входить в ту же упорядоченную историю аудита, что создание запроса, переходы фокуса, результаты авторизации и выполнение.

Sallyport хранит журнал Sessions для запусков агентов и журнал Activity для отдельных вызовов. Оба журнала строятся из зашифрованного аудита с цепочкой хешей. Команда `sp audit verify` проверяет эту цепочку автономно по шифротексту, без ключа хранилища. Это дает команде тестирования проверку целостности доказательств вместо изменяемого экспорта, который можно незаметно отредактировать после неожиданного результата.

Для каждого red team-запуска сохраняйте вместе следующие факты:

- Название тестового сценария и матрицу разрешений.
- Идентичность процесса, отправившего запрос, и его полномочия подписи кода.
- Дайджест запроса, отображаемый nonce и генерацию запроса.
- Метод и результат решения, а при отказе, причину отказа.
- Первое событие выполнения или явное доказательство того, что события выполнения нет.

Цепочки хешей не доказывают правильность политики подтверждения. Они доказывают более узкое, но полезное свойство: после записи журнала расследователь может проверить, не были ли записи изменены или переставлены. Вторую часть дает проверка политики, показывающая, какие события приложение позволяет довести до выполнения.

Проверку журналов нужно включить в критерии успешного прохождения теста. Запуск, который правильно блокирует действие, но записывает только «отменено», еще не завершен. Нужно отличать синтетический вызов Accessibility от отмены пользователем, устаревшую генерацию от истекшего запроса, отказ из-за потери фокуса от сбоя приложения. Эти различия превращают один отчет об ошибке в набор регрессионных тестов.

## Успешный тест означает, что действие осталось за границей решения человека

Не объявляйте поверхность подтверждения безопасной только потому, что первый скрипт автоматизации не сработал. Хороший результат red team-теста имеет небольшой и точный охват: на контролируемом Mac, при оговоренных разрешениях, каждая проверенная синтетическая активация и каждый сценарий вмешательства в фокус не смогли создать действительный токен авторизации или запустить защищенное действие.

Самые сильные тесты оставляют после себя неудобный, но полезный список исключений. Возможно, подтверждение сессии разрешает обычный клик, потому что риск низкий, а пользователь уже подтвердил процесс агента. Возможно, для выбранных ключей подтверждение каждого вызова требует Touch ID. Возможно, карточка отменяется каждый раз, когда приложение теряет активацию, что раздражает некоторых пользователей, но предотвращает гонку фокуса. Эти решения нужно заложить в поведение продукта и проверять тестами. Безопасность, существующая только на совещании по проектированию, не выдержит рабочего стола, заполненного помощниками.

Запускайте этот набор при каждом изменении отображения подтверждения, добавлении нового канала действий, изменении обработки идентичности процесса или переработке кода, который переводит решение интерфейса в выполнение. Важная регрессия не объявит себя как «обход Accessibility». Она появится после безобидной правки, которая переместит callback, повторно использует окно или сочтет старый запрос эквивалентным новому.

Финальное утверждение должно быть предельно простым: защищенное действие начинается только после того, как текущий запрос получил необходимую локальную авторизацию. Если какой-либо путь автоматизации интерфейса способен сделать это утверждение ложным, карточка подтверждения остается декорацией, пока вы не исправите проблему.
