Безопасны ли запасные сценарии Touch ID для подтверждений агента?
Запасные сценарии Touch ID для подтверждений агента: режим «раскладушка», внешние клавиатуры, неудачные сканирования, блокировка датчика, ожидание и жесткая остановка.

Процессы подтверждения через Touch ID ломаются по обычным причинам: крышка ноутбука закрыта, внешняя клавиатура не поддерживает нужный режим, датчик не распознал палец или macOS заблокировала биометрию после нескольких неудачных попыток. Если агент может превратить такие условия в неоднозначное состояние подтверждения, система уже совершила опасную ошибку. Запасной сценарий должен четко сообщать, остается ли действие ожидающим, точно отклонено или ждет отдельного события аутентификации.
На первый взгляд это работа над интерфейсом, пока агент не держит SSH-полномочие или не готовит аутентифицированный API-запрос. Тогда каждое расплывчатое состояние превращается в операционное поведение. Агент не поймет «попробуйте позже», если шлюз не вернет точный результат. Люди тоже принимают плохие решения, когда единственный сигнал, который они видят, это упрямый биометрический запрос и непонятно, остается ли запрос активным.
Правило, которым я пользуюсь, простое: сбой биометрии может задержать запрос, но никогда не расширяет полномочия. Недоступный датчик может перевести процесс к решению человека, но агент не должен из-за этого выбирать более слабый путь. Заблокированное хранилище учетных данных останавливает работу с секретами, пока его заявленный шлюз снова не откроется.
Доступность Touch ID не равна подтверждению
Touch ID отвечает на вопрос, может ли macOS прямо сейчас проверить человека через биометрический датчик. Подтверждение отвечает на другой вопрос: разрешает ли проверенный человек этот процесс агента или предложенное действие. Если считать их одним событием, возникают плохие запасные сценарии, потому что причины сбоя относятся к разным уровням.
Подтверждение на сессию может быть простым решением человека: он видит идентификатор запрашивающего процесса и принимает или отклоняет запуск. Подтверждение на вызов запрашивается снова, потому что владелец учетных данных отметил конкретный ключ как достаточно чувствительный. Ни одно из этих решений не означает, что зашифрованное хранилище учетных данных доступно. Хранилище может оставаться закрытым, Mac может находиться на экране входа, а физический датчик может быть недоступен.
Sallyport сохраняет это разделение рабочим, а не декоративным. Шлюз хранилища абсолютен: пока хранилище закрыто, любое действие отклоняется. Авторизация на сессию и ключи с подтверждением на вызов определяют полномочия агента только после того, как этот шлюз разрешит действие. Поэтому удобная карточка подтверждения не может стать черным ходом вокруг заблокированного хранилища.
Это различие также предотвращает распространенный, но небрежный совет: «Если Touch ID не сработал, просто покажите обычную кнопку подтверждения». Такой подход может быть правильным для запроса авторизации человека, где нажатие кнопки разрешено. Но он неверен, если неудачный запрос Touch ID защищает доступ к хранилищу учетных данных. На одном экране могут находиться оба понятия, но изменения состояния у них должны быть разными.
Используйте отдельные состояния и в протоколе агента:
awaiting_human_approvalозначает, что действие не запускалось и человек может одобрить или отклонить указанный запрос.awaiting_vault_unlockозначает, что действие не может продолжиться, потому что граница секретов закрыта.deniedозначает, что шлюз не будет сохранять полномочия для этого запроса.expiredозначает, что запрос слишком долго ждал и его нужно предложить заново.
Не называйте все четыре состояния «требуется подтверждение». Такая формулировка скрывает главный факт, который нужен агенту: ждать ему, остановиться или подготовить новый запрос.
Режим «раскладушка» убирает встроенный датчик
Закрытая крышка MacBook делает встроенный датчик Touch ID физически недоступным. Apple называет режим «раскладушка» актуальным примером недоступности встроенного биометрического датчика в macOS: закрытый MacBook, подключенный к внешнему монитору и клавиатуре, не может использовать внутренний датчик Touch ID, если на внешней клавиатуре нет Touch ID.
Для людей, которые запускают агентов программирования, это не редкий крайний случай. MacBook стоит под столом или рядом с монитором именно потому, что компьютер долго остается включенным. Если дизайн подтверждения предполагает, что пользователь сможет дотянуться до кнопки питания, на демонстрации все сработает, а в обычной работе нет.
Сначала определите конфигурацию рабочего места, а уже потом решайте, каким будет запасной сценарий:
| Физическая конфигурация | Путь Touch ID | Правильное поведение шлюза |
|---|---|---|
| Ноутбук открыт | Встроенный датчик может быть доступен | Предложите Touch ID, если это разрешено политикой управления. |
| Ноутбук закрыт, на внешней клавиатуре нет Touch ID | Встроенный датчик недоступен | Не предлагайте сканирование. Покажите разрешенный этим элементом управления способ подтверждения или запретите работу с секретами, пока хранилище закрыто. |
| Ноутбук закрыт, на внешней клавиатуре есть Touch ID | Внешний датчик может предоставить Touch ID | Предлагайте сканирование только после того, как macOS сообщит, что биометрия доступна. |
| Ноутбук закрыт, клавиатура Touch ID отключена или разряжена | Рабочего биометрического пути нет | Пометьте биометрическое подтверждение как недоступное и примените то же правило, что к любому недоступному датчику. |
Важное слово в этой таблице, «может». Наличие оборудования не доказывает, что оно доступно сейчас. Беспроводная клавиатура может быть выключена, отключена, сопряжена с другим компьютером или просто недоступна текущему сеансу пользователя. Приложение должно спросить операционную систему, может ли выполняться нужная политика, прежде чем показывать текст с предложением коснуться датчика.
Не делайте режим «раскладушка» автоматической причиной заменить подтверждение на вызов подтверждением на сессию. Такое изменение переживет физическую проблему и даст более широкие полномочия, чем видел пользователь. Если запрос требует решения на каждый вызов, оставьте его таким. Покажите подтверждение нажатием, если этот способ разрешен, либо переведите действие в ожидание или завершите его с ошибкой согласно классу действия.
Есть практическая разница между «человек не может отсканировать палец» и «человек не может подтвердить действие». Пользователь с обычной внешней клавиатурой все еще может прочитать карточку и нажать кнопку подтверждения. Этого достаточно для ключа с подтверждением на вызов, рассчитанного на нажатие кнопки или Touch ID. Но это не разблокирует хранилище, защищенное Touch ID. Текст на экране тоже должен быть прямым: «Подтверждение доступно, хранилище закрыто» намного лучше общей красной ошибки.
Неудачное сканирование должно удерживать один запрос, а не создавать новые полномочия
Одно неудачное сканирование отпечатка нормально. Сухая кожа, неудачный угол пальца, загрязнение датчика и поспешное прикосновение случаются. Правильная реакция, это ограниченное состояние повторной попытки, а не мгновенный отказ и не бесконечно активный запрос.
В документации Apple по LocalAuthentication простая ошибка аутентификации отделена от блокировки биометрии. Неудачная проверка учетных данных возвращает authenticationFailed, а блокировка возникает после слишком большого числа неудачных попыток и является отдельным состоянием. Шлюз должен сохранять это различие, потому что пути восстановления у них разные.
При обычном неудачном сканировании оставьте исходное действие неизменным и ожидающим в течение короткого времени. Пользователь должен видеть, что он подтверждает, какой процесс агента отправил запрос, какой хост или API является целью, какую метку имеет учетная запись и в чем состоит операция. Агент должен получить машиночитаемый результат ожидания, а не тайм-аут, замаскированный под ошибку.
Полезная форма ответа выглядит так:
{
"status": "awaiting_human_approval",
"request_id": "apr_7f3c",
"reason": "biometric_retry",
"expires_at": "2026-07-22T18:42:00Z",
"retry_after_ms": 1500,
"action_started": false
}
request_id должен связывать весь предложенный запрос, а не только учетные данные. Если агент просит выполнить git push, а затем меняет удаленный репозиторий, ветку или команду, пока пользователь повторяет попытку Touch ID, это уже другой запрос. Отклоните его или потребуйте новую карточку подтверждения. Повторное использование подтверждения только потому, что прежний процесс все еще существует, превращает безобидные повторы в уязвимость confused deputy.
Установите два ограничения. Во-первых, ограничьте частоту попыток в интерфейсе, чтобы неисправный агент не мог постоянно открывать привлекающие внимание запросы. Во-вторых, завершайте ожидающий запрос через короткий, видимый промежуток времени. Пользователь, вернувшийся через десять минут, должен принять решение по заново отрисованному запросу, потому что состояние репозитория агента, тело API-запроса или удаленная система могли измениться.
Состояние повторной попытки не должно сохранять материалы учетных данных в процессе агента. Агент может держать предполагаемую команду или тело запроса, но шлюз не должен передавать ему токен «пока ждет» аутентификацию. Внедрение секрета происходит только при выполнении одобренной операции через канал шлюза.
Именно здесь команды часто помещают неправильный цикл повторов не на тот уровень. Они заставляют агента каждые несколько секунд повторять весь вызов инструмента. Это создает дублирующиеся карточки, повышает вероятность повторных API-операций и учит модель считать настойчивость способом обойти колебания пользователя. Ожидающим запросом управляет шлюз. Агент опрашивает его или ждет по этому идентификатору. Он не создает новые запросы, пока первый не истечет или не будет отклонен.
Блокировка датчика означает, что биометрический путь завершен
Блокировка датчика не означает, что нужен еще один отпечаток. Это сообщение macOS о том, что биометрия отключена, пока пользователь не выполнит требуемое операционной системой восстановление. Apple описывает biometryLockout как состояние после слишком большого числа неудачных попыток и говорит, что для разблокировки биометрии нужен пароль.
Для действий агента следствие прямое: прекратите предлагать Touch ID. Диалог, который продолжает просить отпечаток после блокировки датчика операционной системой, вводит в заблуждение и может заставить пользователя снова и снова прикладывать палец. Сообщите, что macOS требует аутентификацию учетной записи для восстановления Touch ID, а затем завершите или приостановите запрос шлюза согласно классу действия.
Не считайте пароль учетной записи заменой Touch ID без явного решения. Политика Apple deviceOwnerAuthentication может использовать Touch ID, сопряженные Apple Watch или пароль macOS. Политика Apple только для биометрии завершается ошибкой, если биометрия недоступна, не настроена или заблокирована. Обе политики допустимы, но обещания безопасности у них разные.
Если шлюз вашего хранилища определяет Touch ID как средство доступа, запасной пароль меняет этот шлюз. Вы можете решить, что пароль macOS подходит как альтернативный аутентификатор в другой модели продукта, но это решение нужно явно заявить и реализовать на границе хранилища. Не перенимайте его случайно только потому, что фреймворк предлагает удобное значение по умолчанию.
Для хранилища, связанного с Touch ID, блокировка должна давать конечный операционный результат каждому ожидающему вызову с секретом:
status: denied
reason: vault_authentication_unavailable
recovery: authenticate with macOS to restore Touch ID, then submit a new request
action_started: false
Это намеренно строже, чем временный сбой сканирования. Пользователь должен выполнить восстановление за пределами процесса подтверждения шлюза. Если сохранить старый запрос во время восстановления, появится неприятная неоднозначность: ввел ли пользователь пароль учетной записи, чтобы разрешить исходную SSH-команду, или только восстановить датчик? Ответ должен быть однозначным: датчик восстановлен. Агент должен снова запросить операцию.
Для подтверждений, не зависящих от разблокировки хранилища, результат может быть другим. Карточка авторизации на сессию может остаться доступной как решение одним нажатием, если это допускает ее дизайн и самому действию не нужен закрытый секрет. Интерфейс должен точно сообщать, что именно не сработало. «Touch ID заблокирован, подтверждение нажатием все еще доступно» является понятным сообщением. «Ошибка аутентификации» таким не является.
Решение ждать или остановиться должно зависеть от действия
Не решайте, ждать ли, только по коду ошибки. Учитывайте последствия действия, требования к свежести и состояние границы учетных данных. Один и тот же недоступный датчик может означать ожидание для безобидного запроса статуса, остановку для необратимой команды и отказ для запроса с секретом, пока хранилище не откроется.
Я использую три класса действий.
Класс первый: короткое ожидание ответа человека
Оставляйте действие в ожидании только если верны все условия:
- Шлюз еще не начал операцию и не внедрил учетные данные.
- У запроса есть стабильный идентификатор и видимый срок действия.
- Повторное выполнение после подтверждения не удивит пользователя, потому что цель и полезная нагрузка остаются неизменными.
- Действие обратимо, доступно только для чтения или достаточно идемпотентно, чтобы короткая задержка не изменила его смысл.
Сюда относятся, например, чтение версии закрытого пакета, получение настроек защищенной ветки репозитория или отправка четко обозначенного пробного API-запроса. Даже в таких случаях ожиданием должен управлять шлюз, а не цикл повторов агента.
Класс второй: остановка и новый запрос
Останавливайте действие, если задержка меняет практический смысл команды. Развертывание, миграция производственной базы данных, принудительная отправка, ротация учетных данных, проведение платежа или SSH-команда, удаляющая данные, не должны лежать в ожидании с незаметным подтверждением. Пользователь, увидевший такую карточку позже, заслуживает новую карточку с актуальным контекстом.
Останавливайте действие и тогда, когда запрос содержит меняющиеся значения. Подписанный API-запрос, одноразовый артефакт развертывания, короткоживущий URL или команда, локальное рабочее пространство которой изменилось, не должны возобновляться из устаревшего намерения. Шлюз не может знать, что агент все еще имеет в виду то же самое, лишь потому, что процесс не завершился.
Класс третий: немедленный отказ при закрытом хранилище
Закрытое хранилище важнее удобства. Если предложенный HTTP-вызов или SSH-команда требуют сохраненного секрета, а шлюз хранилища закрыт, отклоняйте действие, а не ставьте его в очередь для автоматического запуска после разблокировки. Пользователь может открыть хранилище, после чего агент отправит новый запрос. Так сохраняется чистая причинная последовательность: сначала разблокировать, затем предложить, затем выполнить.
Здесь полезна фиксированная лестница решений Sallyport. Шлюз хранилища отклоняет любое действие, пока хранилище закрыто. Затем к запросу применяются авторизация на сессию и подтверждение на вызов. Нет политики, которая пытается решить, достаточно ли безобиден отложенный curl, чтобы возродить его после события биометрии.
Соблазнительная альтернатива, очередь выполнения, которая просыпается, когда пользователь касается датчика. На демонстрации это выглядит плавно. В реальной работе аутентификация превращается в триггер для действия, которое пользователь уже мог не планировать. Подтверждение должно выпускать запрос, который человек все еще видит, а не опустошать очередь, собранную, пока он отсутствовал.
Для внешних клавиатур Touch ID нужна проверка доступности
Внешняя клавиатура Touch ID решает одну проблему режима «раскладушка», но добавляет другую зависимость. Клавиатура должна быть подключена и доступна в активном сеансе Mac в момент подтверждения. Считайте это текущим условием, а не фактом первоначальной настройки.
Ошибки LocalAuthentication от Apple включают biometryDisconnected и biometryNotPaired для съемных биометрических аксессуаров. Эти коды важны, потому что отличают отсутствующее оборудование от неудачной аутентификации пользователя. Отключенная клавиатура не должна расходовать попытку повтора или учитываться в статистике пользовательских ошибок.
Интерфейс и протокол должны по-разному реагировать на четыре состояния:
| Состояние | Что видит человек | Что получает агент |
|---|---|---|
| Датчик готов | Понятный запрос и элемент управления Touch ID | awaiting_human_approval |
| Датчик недоступен в режиме «раскладушка» | Объяснение, что до встроенного датчика нельзя добраться | approval_path_unavailable или разрешенный путь через нажатие |
| Внешний датчик отключен | Предложение подключить или зарядить клавиатуру либо использовать разрешенный альтернативный способ подтверждения | approval_path_unavailable |
| Сканирование отклонено | Тот же неизменный запрос и подсказка для повтора | awaiting_human_approval с biometric_retry |
Не показывайте человеку необработанные имена ошибок фреймворка, но сохраняйте их в локальной записи активности. «Внешняя клавиатура Touch ID недоступна» помогает. LAError.biometryDisconnected относится к диагностике и тестам.
Карточка подтверждения также должна оставаться доступной без датчика. Это вопрос и безопасности, и базового удобства. Мышь, трекпад, фокус клавиатуры и средства доступности должны позволять человеку отклонить запрос или выбрать разрешенное подтверждение нажатием. Недоступный биометрический датчик не должен запирать пользователя в запросе, на который невозможно ответить.
Экран подтверждения должен называть заблокированную границу
Большая часть путаницы возникает из-за одного общего модального окна, которое пытается представить все формы аутентификации. Разделяйте сообщения по той границе, которая сейчас ждет.
Для запроса на сессию сначала показывайте полномочия подписи кода запрашивающего процесса, а затем давайте человеку ясный выбор: одобрить или отклонить. В этот момент человек решает, может ли этот запуск агента работать. Если Touch ID доступен, он может подтвердить выбор. Если дизайн разрешает нажатие, режим «раскладушка» не должен превращать карточку в тупик.
Для ключа с подтверждением на каждый вызов показывайте точное действие и метку учетных данных. Подтверждение одним нажатием все еще является решением для одного вызова, только если оно относится к одному неизменному запросу и быстро истекает. Не позволяйте агенту объединять пять вызовов за одной кнопкой только потому, что Touch ID неудобно использовать за этим столом.
Для закрытого хранилища прямо скажите, что хранилище закрыто и действие не начиналось. Не описывайте это как отклоненный запрос агента, иначе человек может решить, что нужно нажать еще раз. Восстановление должно происходить через заявленный механизм аутентификации хранилища. После открытия для любого действия, которому нужен секрет, требуйте новый запрос.
Хорошая запись аудита разделяет эти переходы. Например:
2026-07-22T18:40:12Z request.created id=apr_7f3c channel=ssh action="git push origin main"
2026-07-22T18:40:13Z approval.pending id=apr_7f3c method=touch_id
2026-07-22T18:40:16Z biometric.failed id=apr_7f3c source=built_in_sensor
2026-07-22T18:41:02Z request.expired id=apr_7f3c action_started=false
Журнал действий не должен выдавать неудачное сканирование за отказ в авторизации. Это была неудачная попытка аутентификации. Запрос истек, не выполнившись. Эти слова важны при разборе инцидентов, особенно когда агент заявляет, что «не смог выполнить развертывание», а оператору нужно понять, заблокировала ли его система, отклонил ли пользователь запрос или никто не завершил подтверждение.
В защищенных от подделки системах аудита записывайте переход состояния до того, как вернете его агенту. Sallyport строит журналы Sessions и Activity из одного зашифрованного журнала с цепочкой хешей, а команда sp audit verify проверяет эту цепочку офлайн поверх шифротекста. Поздний проверяющий получает доказательство того, что запрос истек или был отклонен, и ему не приходится полагаться на собственную расшифровку агента.
Тестируйте рабочее место, а не только успешный API-сценарий
Модульный тест LocalAuthentication, который возвращает коды успеха и ошибки, необходим, но почти ничего не говорит о процессе подтверждения. Сбои, раздражающие пользователей, возникают на пересечении конфигурации оборудования, состояния рабочего стола и времени работы агента.
Перед тем как объявить процесс готовым, выполните эту последовательность на реальном Mac:
- Запустите сессию агента с открытым ноутбуком, отправьте один запрос с подтверждением на вызов, отклоните его, затем отправьте новый запрос и одобрите его. Убедитесь, что отклоненный запрос никогда не выполняется.
- Закройте крышку и подключите внешний монитор и клавиатуру без Touch ID. Убедитесь, что интерфейс не просит прикоснуться к недоступному датчику. Проверьте и подтверждение нажатием, и запрос с хранилищем учетных данных.
- Повторите тест с исправной внешней клавиатурой Touch ID. Отключите ее или выключите питание, пока подтверждение ожидает. Убедитесь, что запрос сообщает о недоступном оборудовании, а не о неудачной биометрической попытке.
- Выполните несколько неудачных сканирований, пока macOS не заблокирует биометрию. Убедитесь, что запросы Touch ID прекращаются, запросы с секретами не ставятся в очередь для последующего выполнения, а восстановление требует заново отправить действие.
- Оставьте ожидающий запрос до истечения срока. До отправки нового запроса измените ветку репозитория, аргументы команды или тело API-запроса. Убедитесь, что новое предложение получает другой идентификатор и новое решение человека.
После каждого запуска проверяйте журналы. Вы должны видеть одно событие создания запроса, последовательность изменений состояния и либо одно событие выполнения, либо ни одного. Несколько выполнений после одного сигнала подтверждения означают дефект повтора или повторного воспроизведения. Отсутствующее конечное событие заставит службу поддержки гадать, продолжает ли агент ждать.
Проверяйте и отмену. Пользователь должен иметь возможность отклонить запрос при недоступном Touch ID, агент должен иметь возможность отказаться от ожидающего запроса, а завершение приложения должно аннулировать активные подтверждения. Apple различает отмену пользователем, приложением и системой в LocalAuthentication. Ваша модель аудита должна сохранять это различие, даже если интерфейс объединяет их под простым сообщением «отменено».
Надежный запасной сценарий почти незаметен, когда он работает. Экран честно сообщает о состоянии датчика, агент получает состояние, которому может следовать, секреты остаются в хранилище, а ни одно действие не выходит за пределы политики из-за закрытой крышки ноутбука. Именно это и должно быть стандартом: каждый физический сбой приводит к конкретному результату, который не расширяет полномочия.
Вопросы и ответы
Работает ли Touch ID, когда MacBook закрыт в режиме «раскладушка»?
В режиме «раскладушка» доступ к встроенному датчику Touch ID ноутбука пропадает, потому что крышка закрыта. Apple описывает это как проблему доступности встроенного датчика. Touch ID все еще может работать, если на внешней клавиатуре есть собственный датчик Touch ID.
Что должен делать агент после неудачного сканирования Touch ID?
Неудачное сканирование означает, что биометрическая проверка не подтвердила личность. Оставьте запрос ожидающим в течение короткого, явно обозначенного периода для повторной попытки, но не запускайте действие и не превращайте ошибку в скрытое одобрение.
Можно ли обойти блокировку Touch ID с помощью пароля?
Нет. При блокировке датчика macOS требует, чтобы пользователь ввел пароль учетной записи, прежде чем биометрия снова заработает. Считайте это изменением состояния аутентификации устройства, а не обычной неудачной попыткой сканирования.
Равно ли нажатие кнопки подтверждения разблокировке хранилища учетных данных?
Это разные элементы управления. Подтверждение решает, может ли конкретный агент или вызов продолжиться. Шлюз хранилища решает, может ли вообще выполняться действие с секретом. Нажатие кнопки не может заменить недоступную разблокировку хранилища без ослабления этой границы.
Должен ли агент ждать, пока Touch ID снова станет доступен?
Только если действие еще не запущено, у него есть понятный срок действия и его безопасно возобновить с теми же данными запроса. Поставленный в очередь запрос никогда не должен давать агенту право подменить его более поздним запросом.
Какие действия агента нужно остановить, а не оставлять в ожидании подтверждения?
Останавливайте разрушительные и срочные запросы, запросы, которые позже трудно точно идентифицировать, а также запросы, зависящие от заблокированного хранилища. Ждать можно только по ограниченным, еще не выполняющимся запросам с понятным для человека способом продолжения.
Как интерфейсу подтверждения обрабатывать отключенную клавиатуру Touch ID?
Интерфейс должен ясно показать состояние, дать возможность подключить или зарядить клавиатуру и отменить ожидающий запрос. Не следует считать клавиатуру доступной только потому, что Bluetooth-устройство было замечено раньше.
Означает ли запасной пароль macOS, что любое подтверждение Touch ID должно принимать пароль?
Нет. macOS может предложить пароль учетной записи в рамках общей политики аутентификации владельца устройства. Но продукт, который обещает хранилище с защитой Touch ID, должен явно решить, сохраняет ли такой запасной вариант его обещание безопасности. Это разные варианты дизайна.
Что делать после слишком большого числа неудачных попыток Touch ID?
Введите пароль учетной записи macOS, когда операционная система попросит это сделать для восстановления биометрии. Для самого действия агента соблюдайте заявленные правила шлюза и подтверждения. Не считайте запрос пароля автоматическим разрешением.
Что командам нужно тестировать в запасных сценариях подтверждения Touch ID?
Проверьте реальные физические конфигурации: открытая крышка, закрытая крышка с клавиатурой без Touch ID, закрытая крышка с клавиатурой Touch ID, отключенная клавиатура, повторные неудачные сканирования и блокировка биометрии. В каждом случае зафиксируйте, ждет ли запрос, истекает ли его срок или он отклоняется.