Repository navigation
Conversation
…433) focusContext() only ever checked role == AXTextField/AXComboBox for isOneLineField - nothing anywhere checked for AXSecureTextField, macOS's own role for a password field. Adds isSecure to the tuple and the context reply, following the same caution isOneLineField already uses: an element that never resolves can't be confirmed secure either way, so it defaults to false rather than guessing true. Compile-verified (swift build --package-path native/accessibility-helper -c release). Never executed - this sandbox can't meaningfully drive a real AX role read against a live password field. See docs/testing/secure-field-433.md. Claude-Session: https://claude.ai/code/session_0144sxo5CJiyk99vaR7sgjuj
Only true when the helper positively said so - a helper built before this field existed (reply.isSecure undefined) parses to false, the same unchanged behaviour a stale binary already gets for isOneLineField and axReady. Claude-Session: https://claude.ai/code/session_0144sxo5CJiyk99vaR7sgjuj
#433) Every return site that carries text - the main delivered/held path, both app-switch and context-read-failure salvage paths, and the paste command's delivered/held paths - now carries isSecure alongside it, so main.js can gate what gets persisted without re-deriving anything. Extended the fix to the paste command too, not just ordinary dictation: pasting into a password field is the same leak (clipboard content landing in plaintext history) even though the words weren't spoken. Not literally what the issue asked for, but the same one-line check applied to the same underlying focusContext, and the same protective intent. A context read that fails outright can't confirm secure either way - defaults to false, same call the Swift side makes for an unresolved element. Claude-Session: https://claude.ai/code/session_0144sxo5CJiyk99vaR7sgjuj
Delivery itself is unchanged - this only touches what gets persisted after. The Held-result overlay still shows held text either way (the user's own recovery path if delivery didn't land), since that's ephemeral and never written anywhere; only the clipboard copy and the recording-history write are skipped. Claude-Session: https://claude.ai/code/session_0144sxo5CJiyk99vaR7sgjuj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #433. Dictating (or pasting) into a password field no longer gets logged, copied, or recorded to history in plaintext.
The gap
focusContext()in the accessibility helper only ever checkedrole == "AXTextField" || role == "AXComboBox"forisOneLineField- nothing anywhere checked forAXSecureTextField, macOS's own role for a password field. Every delivered dictation's transcript was unconditionally logged to the console and written torecording-history.json(persisted for up tohistoryRetentionDays, default 30 days), regardless of what kind of field it landed in. Push-to-talk is a global hotkey - it fires the same way everywhere, so habit or muscle memory triggering it while a password field happens to be focused was a real, if narrow, path to a spoken secret ending up as a plaintext log entry on disk.What's built
accessibility-helper:focusContext()now also reportsisSecure, true only for a role-confirmedAXSecureTextField- never guessed true when the element doesn't resolve, same cautionisOneLineField's own default already uses.dictationCoordinator.js:isSecurethreaded onto every result that carries text - delivered, held (all the salvage paths too), and the paste command's delivered/held paths.main.js: whenisSecureis true, the dictation still delivers exactly as normal - this is purely about what gets persisted afterward. Not logged, not copied (even with "Copy transcript to clipboard" on), not written to history. The Held-result overlay is deliberately untouched - it's ephemeral and never written anywhere, and suppressing it would mean silently discarding a password the user could otherwise recover and paste manually.Judgement call to flag
Extended to the paste command too, not just ordinary dictation - pasting into a password field is the same leak (clipboard content landing in plaintext history) even though the words weren't spoken. Not literally what the issue asked for, but the same one-line check on the same
focusContext, same protective intent. Easy to revert if you'd rather keep this strictly to dictation.What I could not verify
Compile-verified only.
swift build --package-path native/accessibility-helper -c releasesucceeds. This sandbox can't meaningfully drive a real AX role read against a live password field - unliketranscription-helper(which gets killed outright touching CoreML/ANE),accessibility-helpercan technically run here, but reading a realAXSecureTextFieldrole needs a live GUI session with the Accessibility grant and an actual focused password field, neither of which exist in this environment.docs/testing/secure-field-433.mdhas the manual checklist - please run it before trusting this, especially the regression check (an ordinary text field should be completely unaffected).Tests
accessibilityHelper.test.js(+2: a role-confirmed secure field parsesisSecure: true; a reply that omits the field entirely defaults tofalse),dictationCoordinator.test.js(+5, plus the pre-existing full-result and full-diagnostics-array assertions updated for the new field - same class of update as #401/#259's). Full suite (424) + typecheck + build green.🤖 Generated with Claude Code