Skip to content

feat: fill password fields from the Keychain or with approved agent text, and refuse typing or pasting into them otherwise - #124

Merged
iamnbutler merged 8 commits into
mainfrom
feat/password-fill
Oct 17, 2026
Merged

iamnbutler merged 8 commits into
mainfrom
feat/password-fill

Conversation

@iamnbutler

Copy link
Copy Markdown
Contributor

Implements #117. Stacked on #120 (caller approval): it uses #120's prompt and caller identity, and lands after it.

desktop_fill_password fills the password field that has keyboard focus in a snapshot.

  • Keychain by reference. service names a generic password and server an internet password, with an optional account. The runtime reads the item with SecItemCopyMatching, so macOS asks the user for each item, and the agent never sees it.
    • It reads attributes first, so no match or several matches are refused without a prompt.
    • It waits up to 60 s, and denial or cancel is a refusal.
    • An internet item fills only into a web area whose URL host is its server.
  • Agent text. A per-fill Desktop Tools prompt, through feat: ask once per agent before it controls the Mac, and keep agent input off Desktop Tools' own UI (#24) #120's Confirmations, names the caller (Callers.shared), the app and the site. Always Allow Agent-Supplied Passwords in the menu skips it; it is off by default. After 60 s the prompt is withdrawn with cancel(key:) and the fill is refused.
  • Delivery. Clear, then Unicode key events at the observed focus. The host rechecks that the field is still focused after the prompt or Keychain read.
  • Result. Completed only when the field's value is all mask characters, as many as the secret has; otherwise unknown. No unit counts, and error messages that could count characters are replaced.
  • Refused:
    • SecurityAgent, LocalAuthentication, loginwindow and coreautha targets;
    • a field that isn't secure or isn't the snapshot's focus;
    • control characters.
  • No leaks. The secret is never in results, logs or activity, which records only "Fill password" with the caller and target. The TS layer never echoes the text, and a test checks it.

Behavior change. While the live focused element is secure:

  • desktop_key refuses keys that enter characters and Command-V with any modifiers.
  • desktop_insert refuses every route: type, paste, and attachments.
  • Both refuse before input, with SECURE_FIELD_REQUIRES_FILL and a hint naming the fill tool.
  • desktop_type, set_value and select on a secure element now use the same code.
  • Return, Tab, Escape, arrows, Delete and other shortcuts still pass.
  • desktop_menu refuses a Paste command the same way. The host matches the resolved leaf by AXMenuItemCmdChar V with Command, or an AXIdentifier starting with paste, so localized titles are covered. desktop_menubar_click only opens a status item and never chooses a menu item, so it needs no check.
  • Documented gaps: a context-menu Paste click through desktop_click, and drag-and-drop.

Native.

  • peekaboo-secure-fill.patch comes last, after caller-admission. It adds the secureTextFill op, its hook, the guard in handleAuthorized, and HotkeyService.entersText.
  • The runtime adds password.swift with desktop_tools_set_password_autofill, desktop_tools_set_confirmation_handlers(ask, cancel) and desktop_tools_answer_confirmation.

Tests.

  • npm test: 77 pass, including a new password-fill test. npm run check is clean.
  • Peekaboo: HotkeyEntersTextTests, PeekabooBridgeRequestPlanTests, PeekabooBridgeOperationSemanticPlanTests and PeekabooBridgeOperationResultSemanticsTests pass (74 tests). The wire count is 134.
  • Live checks are pending a dev app at the shared fixed path: Keychain fill, approved text fill in Chrome, the key/insert/paste refusals, SecurityAgent refused, and activity with no secret.

@iamnbutler
iamnbutler merged commit 10247ad into main Oct 17, 2026
2 checks passed
@iamnbutler
iamnbutler deleted the feat/password-fill branch October 17, 2026 19:28
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