Skip to content

Touch ID: явно відкривати вікно перед підписом - #68

Merged
0design merged 2 commits into
mainfrom
claude/touchid-explicit-prompt-20261028
Oct 28, 2026
Merged

0design merged 2 commits into
mainfrom
claude/touchid-explicit-prompt-20261028

Conversation

@0design

@0design 0design commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Причина

Підтвердження кейса через MCP зависало: вікна Touch ID не було, helper через 120 с убивав тайм-аут Node, у помилці лишалося Command failed: …/touchid-approve approve без stderr.

approve() (і setup()) підписували ключем Secure Enclave з неоціненим LAContext: context(reason:) лише перевіряв canEvaluatePolicy і ніколи не викликав evaluatePolicy. Вікно мав показати сам Security framework під час підпису. Реєстрація з терміналу так спрацювала, а з ланцюжка MCP-підтвердження вікно не з'явилось, і виклик підпису блокувався без меж. Окрема проба з явним evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) показала вікно й повернула OK і в терміналі, і через node execFile з тим самим оточенням.

Точну причину, чому неявне вікно не з'являється саме в ланцюжку MCP, не встановлено. Виправлення не залежить від неї: вікно відкривається явно, а будь-яка відмова має межу в часі.

Що змінено в tools/touchid-approve/main.swift

  • Новий authenticate(_:reason:): evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason:) з тим самим текстом вікна (Decision / SHA-256 / Run / Gate; для setup — «Set up the Touch ID approval key»). Чекає на семафорі не довше 90 с. Тайм-аут → invalidate() закриває вікно → вихід 1 зі stderr.
  • Після успіху контекст стає interactionNotAllowed = true, і підпис іде вже оціненим контекстом. Якщо оцінка чомусь не покриває ACL ключа, підпис одразу падає й не чекає на невидиме неявне вікно.
  • approve(): спершу перевіряє, що ключ зареєстровано, потім показує вікно, потім завантажує ключ і підписує. setup(): вікно перед створенням або завантаженням ключа й підписом.
  • Загальна межа операції 110 с: якщо нативний виклик не повернувся, helper сам пише в stderr і виходить з кодом 1. Це менше за 120 с APPROVAL_TTL_MS і за тайм-аут Node.
  • Скасування в системному вікні → «Touch ID was cancelled. Nothing was signed.»
  • Без змін: біометрія без пароля й без програмного запасного варіанта, localizedFallbackTitle = "", touchIDAuthenticationAllowableReuseDuration = 0, ACL [.biometryCurrentSet, .privateKeyUsage], текст у вікні, формат ключа й файлів.

Чому evaluatePolicy, а не evaluateAccessControl. Саме evaluatePolicy з біометричною політикою показало вікно в цьому ланцюжку. evaluateAccessControl тут ще не перевіряли. До того ж у approve довелося б заново будувати SecAccessControl, бо його не зберігають разом із ключем, і будь-яке розходження з ACL ключа повернуло б підпис до неявного вікна. Біометрична політика не допускає пароля й задовольняє вимогу .biometryCurrentSet. Зміну набору відбитків і далі відхиляє Secure Enclave.

Тести (без вікна Touch ID і без справжнього ключа)

  • test/touchid-explicit-prompt.test.mjs:
    • статична перевірка: в approve() і setup() явна оцінка стоїть перед завантаженням, створенням ключа й підписом; політика лише біометрична; є очікування з тайм-аутом, invalidate, неінтерактивний контекст після оцінки; виконується співвідношення 90 < 110 < TTL; не змінились fallback, reuse, ACL і текст вікна;
    • негативні приклади: 10 мутацій джерела (прибрана оцінка, оцінка після використання ключа, політика з паролем, очікування без меж, межа понад TTL, fallback, reuse, інший текст) перевірка відхиляє. На main.swift з main перевірка падає;
    • Node-сторона: справжній execFile зі скриптом, що завершується з кодом 1, повертає stderr helper у помилці TouchIdAnchor.confirm.
  • Компіляція helper (touchid-compile) проходить. Повний набір тестів падає в тих самих 48 тестах «CLAUDECODE is set», що й на main; набір назв збігається.

Закріплення каталогу

Артефакт Core змінився (main.swift і README helper входять у пакет). Закріплення оновлене тим самим шляхом, що й у попередніх злиттях: npm pack --ignore-scripts → registry/catalog.source.json → scripts/build-registry.mjs (export + --check).

Межі

  • Справжнє вікно Touch ID з цим helper ще не перевірено. Для цього підготовлено пробу з одноразовим ключем у пам'яті; зареєстрований ключ вона не чіпає.
  • Межа D-0392 не змінюється: код із правами користувача може змінити клон, helper чи ~/.q-core.
  • Не зливати без окремого «так».

🤖 Generated with Claude Code

0design and others added 2 commits October 28, 2026 20:06
The approval helper relied on an implicit prompt from the Security
framework when it signed with an unevaluated LAContext. From the MCP
approval chain that prompt never appeared, so the helper blocked until
Node killed it after 120 seconds with an empty stderr.

setup and approve now call evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)
with the unchanged prompt text, wait at most 90 seconds, then sign with the
evaluated context made non-interactive. A whole-operation deadline of
110 seconds keeps every failure below the 120-second approval TTL, with
exit status 1 and a message on stderr. No password or software fallback,
reuse stays 0, and the key ACL is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@0design
0design marked this pull request as ready for review October 28, 2026 18:31
@0design
0design merged commit 4781317 into main Oct 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant