kovra-native-macos 0.8.0

macOS LocalAuthentication (Touch ID) Confirmer for kovra (free core, [host]-validated).
Documentation

kovra-native-macos

The macOS Touch ID Confirmer for kovra (spec §8, §14.1; layer L8, [host]).

This crate is the native half of the confirmation broker. It renders the core-authored ConfirmRequest in a macOS LocalAuthentication (LAContext) dialog and returns Approved | Denied | TimedOut. It is a third Confirmer implementation beside CliApproveConfirmer and FileConfirmer.

  • Free core, not enterprise: core never depends on this crate; injection points the other way (this crate depends on kovra-core).
  • No invariant is touched. The dialog only shows what the core put in the request (I16); approval happens at the sensor, outside the model's process (§8.2, no self-approve); timeout fails safe to denial (§8); no secret value is ever rendered or logged (I7/I12).
  • Cross-platform: the LAContext binding is gated under cfg(target_os = "macos"). Off-macOS the crate compiles to a no-op stub (biometrics_available()false, prompt()Denied), so Linux CI builds the whole workspace and the CLI auto-falls-back to the file broker.

Confirmer selection (KOVRA_CONFIRMER)

Selection lives in the CLI's Ctx::confirmer() factory:

KOVRA_CONFIRMER Behavior
unset (default) biometric on macOS with automatic fallback to file when biometrics is unavailable; always file on non-macOS
biometric native Touch ID prompt if the host can prompt; otherwise fall back to file
file always the cross-process kovra approve <id> file broker

"Biometrics unavailable" = not macOS, or no biometric hardware, or the user is not enrolled (LAContext canEvaluatePolicy: fails). Any unrecognized value uses the default.

[host] hardware-validation checklist (M4) — blocking sign-off for Done

The real LAContext path is not exercised by automated tests (no hardware in CI). A human must validate the following on an M4 with Touch ID enrolled. This is the blocking gate for marking KOV-15 Done.

Setup: a throwaway vault in passphrase mode, a high/prod secret, and KOVRA_CONFIRMER unset (default biometric) unless noted.

  1. Approve (happy path). kovra show secret:prod/db/password → Touch ID dialog appears → touch the sensor → the value is revealed. Audit log records an approval (no value).
  2. Deny. Trigger the prompt again → tap Cancel / use the wrong finger → the operation is denied, no value revealed, audit records a denial.
  3. Timeout ⇒ deny. Trigger the prompt and do not respond until the confirm_timeout (CLI default 120s) elapses → the operation fails closed (timed out, treated as denial), no value revealed.
  4. I16 dialog content. For an injection (kovra run … -- /usr/bin/deploy … against a prod/high ref), confirm the dialog shows:
    • the exact resolved command/argv as the prominent first line (not paraphrased),
    • the coordinate address, sensitivity, environment (with prod highlighted), and origin,
    • any requester description rendered only under the "provided by requester (untrusted)" fence — never as the headline.
  5. No self-approve (§8.2). Confirm there is no way for the invoking process (the agent / kovra run) to satisfy the prompt programmatically — only a human touch at the sensor approves.
  6. Biometric-disabled fallback. Disable/disenroll Touch ID (or run on a Mac without it) → with KOVRA_CONFIRMER unset or =biometric, the CLI must fall back to the file broker (kovra approve --list shows the pending request; approving in another terminal releases the blocked show/run).
  7. No leak (I7/I12). Across all of the above, confirm the secret value never appears in: the dialog text, the audit log, stdout/stderr of the prompt, or any file written by the confirm path. Only addresses/commands appear.

Threading & shim notes

  • The native prompt is invoked from the CLI main thread (the CLI is synchronous). evaluatePolicy:reply: is non-blocking and fires its reply on a private framework queue; we block the main thread on an mpsc channel the reply signals — no dedicated runloop pump is built.
  • No extern "C" shim was needed: objc2-local-authentication 0.3.2 exposes the full surface used (LAContext::new, canEvaluatePolicy:, evaluatePolicy:localizedReason:reply:).