Skip to content

security(CORE-1122): macOS secure keyboard entry for sensitive inputs#3314

Open
jeanfbrito wants to merge 4 commits into
masterfrom
security/CORE-1122-secure-keyboard
Open

security(CORE-1122): macOS secure keyboard entry for sensitive inputs#3314
jeanfbrito wants to merge 4 commits into
masterfrom
security/CORE-1122-secure-keyboard

Conversation

@jeanfbrito
Copy link
Copy Markdown
Member

@jeanfbrito jeanfbrito commented Apr 29, 2026

What changed

  • Added secure-keyboard-entry/set IPC channel (src/ipc/channels.ts) with payload boolean.
  • New src/ui/main/secureKeyboardEntry.ts: registers an ipcMain.handle for that channel on macOS only, calling app.setSecureKeyboardEntryEnabled(value). No-op on linux/win32.
  • src/ui/main/rootWindow.ts: wires setupSecureKeyboardEntry() into setupRootWindow(); adds a blur listener and a reset at the top of the close handler (both darwin-gated) to force secure entry off when the window loses focus or closes, preventing a stuck-on state.
  • src/ui/components/OutlookCredentialsDialog/index.tsx: the password field's onFocus invokes secure-keyboard-entry/set with true; onBlur invokes it with false.

Why

Q4-2022 security audit (CORE-1122) identified that the app does not protect credential entry with macOS Secure Keyboard Entry. Apple's guidance is to enable it only while a sensitive field is focused and disable it immediately after — leaving it globally enabled interferes with input methods and accessibility tools.

Known limitation

Server-side login (the Rocket.Chat login form) runs inside a webview. Injecting JS into third-party webviews to hook focus/blur is out of scope for this ticket and will be addressed in a follow-up ticket.

Test plan

  • yarn lint — passes
  • npx tsc --noEmit — clean
  • yarn test --testPathPattern="secureKeyboardEntry" — 9/9 tests pass
    • darwin: handler registered, setSecureKeyboardEntryEnabled(true/false) called, cleanup removes handler
    • linux: no handler registered, API not called, cleanup is no-op
    • win32: no handler registered, API not called

Summary by CodeRabbit

  • New Features

    • Password fields on macOS now enable secure keyboard entry when focused and disable it on blur; window focus and app shutdown events correctly re-enable/disable this mode to protect typed credentials.
  • Tests

    • Added platform-specific tests verifying secure keyboard behavior, window-focus handling, IPC registration, and proper cleanup on unmount/shutdown.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d25dfe34-a6df-4c6e-98f8-8bcbcd4ff266

📥 Commits

Reviewing files that changed from the base of the PR and between d6299d1 and 1abb6ea.

📒 Files selected for processing (2)
  • src/ui/components/OutlookCredentialsDialog/index.spec.tsx
  • src/ui/components/OutlookCredentialsDialog/index.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/components/OutlookCredentialsDialog/index.spec.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check (windows-latest)

Walkthrough

Adds macOS-only secure keyboard entry: a new IPC channel and main-process setup to toggle Electron's secure keyboard entry, renderer wiring in the Outlook credentials dialog to invoke it on password focus/blur and window focus, plus platform-specific tests and cleanup integration.

Changes

Cohort / File(s) Summary
IPC Type Definition
src/ipc/channels.ts
Added 'secure-keyboard-entry/set' to ChannelToArgsMap — accepts a single boolean, returns void.
Secure Keyboard Entry Implementation
src/ui/main/secureKeyboardEntry.ts, src/ui/main/rootWindow.ts
New exported setupSecureKeyboardEntry() registers a macOS-only ipcMain handler that calls app.setSecureKeyboardEntryEnabled(enabled); integrated into root window lifecycle with cleanup on blur, close, and before-quit.
Renderer Integration
src/ui/components/OutlookCredentialsDialog/index.tsx
Password input tracks focus state and invokes `invoke('secure-keyboard-entry/set', true
Tests
src/ui/main/secureKeyboardEntry.main.spec.ts, src/ui/main/rootWindow.spec.ts, src/ui/components/OutlookCredentialsDialog/index.spec.tsx
Added tests verifying macOS registers the IPC handler and toggles app.setSecureKeyboardEntryEnabled; non-macOS is a no-op. Renderer tests assert IPC invocations on focus/blur/window focus and listener cleanup.

Sequence Diagram

sequenceDiagram
    participant User
    participant Dialog as OutlookCredentialsDialog
    participant RendererIPC as Renderer IPC (invoke)
    participant Main as Main Process (ipcMain handler)
    participant ElectronApp as Electron.app

    User->>Dialog: focus password field
    Dialog->>RendererIPC: invoke('secure-keyboard-entry/set', true)
    RendererIPC->>Main: deliver 'secure-keyboard-entry/set' true
    Main->>ElectronApp: app.setSecureKeyboardEntryEnabled(true)

    User->>Dialog: blur password field
    Dialog->>RendererIPC: invoke('secure-keyboard-entry/set', false)
    RendererIPC->>Main: deliver 'secure-keyboard-entry/set' false
    Main->>ElectronApp: app.setSecureKeyboardEntryEnabled(false)

    Note over Dialog,Main: On window focus, Dialog may re-invoke when password still active
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

area: authentication

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically summarizes the main change: adding macOS secure keyboard entry support for sensitive inputs, which is the primary objective across all file changes in the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CORE-1122: Request failed with status code 401

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 5/8 reviews remaining, refill in 19 minutes and 57 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/components/OutlookCredentialsDialog/index.tsx`:
- Around line 157-161: The PasswordInput onFocus/onBlur currently calls
invoke('secure-keyboard-entry/set', ...) unconditionally and overrides
react-hook-form's blur handler; wrap the IPC calls in a platform guard (only
call invoke when process.platform === 'darwin' or when
setupSecureKeyboardEntry() is active) and preserve react-hook-form handlers by
calling the form's onBlur returned from register inside your onBlur wrapper
(e.g., call the registered onBlur first/last) and similarly preserve any
registered onFocus if present; update the PasswordInput usage that spreads
{...register('password', { required: true })} to capture the returned
onBlur/onFocus and call those along with the guarded
invoke('secure-keyboard-entry/set', ...) so non-macOS platforms do not trigger a
rejected IPC and react-hook-form touched state remains correct.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a43a301b-5d3d-4a78-8126-2195d2cb7d01

📥 Commits

Reviewing files that changed from the base of the PR and between e1fe77e and b561dd9.

📒 Files selected for processing (5)
  • src/ipc/channels.ts
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/main/rootWindow.ts
  • src/ui/main/secureKeyboardEntry.main.spec.ts
  • src/ui/main/secureKeyboardEntry.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript for all new code in this codebase unless explicitly told otherwise
Use Fuselage components from @rocket.chat/fuselage for all UI work — only create custom components when Fuselage doesn't provide the needed functionality
Check Theme.d.ts for valid color tokens when working with Fuselage components
Use optional chaining with fallbacks for platform-specific APIs instead of mocks (e.g., process.getuid?.() ?? 1000) to ensure code works across all platforms without requiring mocks
TypeScript code must use strict mode
Use React functional components with hooks instead of class components
Redux actions must follow the FSA (Flux Standard Action) pattern
Use camelCase for file naming
Use PascalCase for component file names (React components)
Write self-documenting code through clear naming — avoid unnecessary comments

Files:

  • src/ipc/channels.ts
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/main/secureKeyboardEntry.ts
  • src/ui/main/secureKeyboardEntry.main.spec.ts
  • src/ui/main/rootWindow.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.spec.ts file naming convention for Renderer process tests

Files:

  • src/ui/main/secureKeyboardEntry.main.spec.ts
**/*.main.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.main.spec.ts file naming convention for Main process tests

Files:

  • src/ui/main/secureKeyboardEntry.main.spec.ts
**/*.{spec,main.spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{spec,main.spec}.ts: Tests must run and pass on Windows, macOS, and Linux CI environments — always verify cross-platform compatibility
Only mock Linux-only APIs (process.getuid(), process.getgid(), process.geteuid(), process.getegid()) when defensive coding with optional chaining isn't possible

Files:

  • src/ui/main/secureKeyboardEntry.main.spec.ts
🧠 Learnings (2)
📚 Learning: 2026-04-06T18:34:44.375Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T18:34:44.375Z
Learning: Applies to **/*.{spec,main.spec}.ts : Only mock Linux-only APIs (process.getuid(), process.getgid(), process.geteuid(), process.getegid()) when defensive coding with optional chaining isn't possible

Applied to files:

  • src/ui/main/secureKeyboardEntry.main.spec.ts
📚 Learning: 2026-04-06T18:34:44.375Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T18:34:44.375Z
Learning: Applies to **/*.{spec,main.spec}.ts : Tests must run and pass on Windows, macOS, and Linux CI environments — always verify cross-platform compatibility

Applied to files:

  • src/ui/main/secureKeyboardEntry.main.spec.ts
🔇 Additional comments (6)
src/ui/components/OutlookCredentialsDialog/index.tsx (1)

24-24: Import wiring looks good.

invoke is correctly pulled from the typed renderer IPC layer for this UI flow.

src/ui/main/rootWindow.ts (2)

252-253: Lifecycle setup/cleanup is correctly wired.

Registering once in setupRootWindow() and removing on before-quit is a clean ownership model for this IPC handler.

Also applies to: 531-531


348-352: Good macOS reset safeguards on focus loss and close.

Disabling secure keyboard entry on both blur and close reduces risk of it staying enabled longer than intended.

Also applies to: 355-357

src/ipc/channels.ts (1)

139-139: Channel typing is clean and consistent.

The boolean argument signature matches both renderer invocation and main-process handler expectations.

src/ui/main/secureKeyboardEntry.ts (1)

5-18: Implementation is compact and maintainable.

The darwin gate plus returned cleanup function keeps the main-process integration straightforward and easy to reason about.

src/ui/main/secureKeyboardEntry.main.spec.ts (1)

15-96: Solid platform-branch coverage.

The spec validates darwin behavior and non-darwin no-op behavior clearly, including cleanup on macOS.

Comment on lines +157 to +161
<PasswordInput
{...register('password', { required: true })}
onFocus={() => invoke('secure-keyboard-entry/set', true)}
onBlur={() => invoke('secure-keyboard-entry/set', false)}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify darwin-only handler registration vs unguarded renderer invocation,
# and verify explicit onBlur override after register spread.
rg -n "secure-keyboard-entry/set|process.platform !== 'darwin'|register\('password'|onBlur=" \
  src/ui/components/OutlookCredentialsDialog/index.tsx \
  src/ui/main/secureKeyboardEntry.ts

Repository: RocketChat/Rocket.Chat.Electron

Length of output: 603


🏁 Script executed:

cat -n src/ui/main/secureKeyboardEntry.ts

Repository: RocketChat/Rocket.Chat.Electron

Length of output: 592


🏁 Script executed:

head -200 src/ui/components/OutlookCredentialsDialog/index.tsx | tail -100

Repository: RocketChat/Rocket.Chat.Electron

Length of output: 3135


🏁 Script executed:

# Check for any other guards or error handling around invoke calls
rg -B5 -A5 "invoke\('secure-keyboard-entry" src/ui/components/OutlookCredentialsDialog/index.tsx

Repository: RocketChat/Rocket.Chat.Electron

Length of output: 561


Guard IPC invocation by platform and preserve react-hook-form's blur handler

Lines 159–160 invoke the IPC channel unconditionally, but the handler is only registered on macOS (see setupSecureKeyboardEntry() in src/ui/main/secureKeyboardEntry.ts). On Linux and Windows, this will reject. Additionally, the onBlur override replaces react-hook-form's blur handler entirely, breaking the form's touched state tracking.

🔧 Proposed fix
+  const passwordField = register('password', { required: true });
+
+  const setSecureKeyboardEntry = (enabled: boolean): void => {
+    if (process.platform !== 'darwin') {
+      return;
+    }
+    void invoke('secure-keyboard-entry/set', enabled).catch(() => undefined);
+  };
+
              <PasswordInput
-               {...register('password', { required: true })}
+               {...passwordField}
-               onFocus={() => invoke('secure-keyboard-entry/set', true)}
-               onBlur={() => invoke('secure-keyboard-entry/set', false)}
+               onFocus={() => setSecureKeyboardEntry(true)}
+               onBlur={(event) => {
+                 passwordField.onBlur(event);
+                 setSecureKeyboardEntry(false);
+               }}
              />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/components/OutlookCredentialsDialog/index.tsx` around lines 157 - 161,
The PasswordInput onFocus/onBlur currently calls
invoke('secure-keyboard-entry/set', ...) unconditionally and overrides
react-hook-form's blur handler; wrap the IPC calls in a platform guard (only
call invoke when process.platform === 'darwin' or when
setupSecureKeyboardEntry() is active) and preserve react-hook-form handlers by
calling the form's onBlur returned from register inside your onBlur wrapper
(e.g., call the registered onBlur first/last) and similarly preserve any
registered onFocus if present; update the PasswordInput usage that spreads
{...register('password', { required: true })} to capture the returned
onBlur/onFocus and call those along with the guarded
invoke('secure-keyboard-entry/set', ...) so non-macOS platforms do not trigger a
rejected IPC and react-hook-form touched state remains correct.

…spec

On non-darwin platforms the secure-keyboard-entry IPC handler is never
registered; guard the onFocus/onBlur invoke calls in OutlookCredentialsDialog
with a process.platform === 'darwin' check to prevent unhandled rejections.

Add jest.mock for secureKeyboardEntry in rootWindow.spec.ts so the darwin
close-handler path does not call ipcMain.handle on an undefined mock, and
stub app.setSecureKeyboardEntryEnabled so the macOS close test passes.
@jeanfbrito
Copy link
Copy Markdown
Member Author

Codex P2 findings addressed (commit c80bd6a)

Finding 1 — renderer call site unguarded on non-darwin
Fixed by gating the invoke('secure-keyboard-entry/set', ...) calls in OutlookCredentialsDialog with process.platform === 'darwin'. On Windows/Linux the calls are skipped entirely; no fake main-process handler needed.

Finding 2 — rootWindow.spec.ts breaks on darwin path
Fixed by:

  1. Adding jest.mock('./secureKeyboardEntry', ...) to isolate the new module from the spec.
  2. Adding setSecureKeyboardEntryEnabled: jest.fn() to the electron app mock — rootWindow.ts calls this directly on close when platform is darwin, and the existing mock lacked the stub.

Verification: yarn lint clean, npx tsc --noEmit clean, yarn test — 222/222 passed across 18 suites.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/main/rootWindow.spec.ts`:
- Around line 42-44: The mock for setupSecureKeyboardEntry should return a
callable cleanup spy instead of a plain no-op so the test can assert cleanup was
invoked; update the jest.mock implementation (mock of setupSecureKeyboardEntry)
to return jest.fn() as the cleanup function and expose that spy to the spec,
then in the test call setupRootWindow() and trigger the shutdown path (e.g.,
invoke the before-quit handler) and assert the returned cleanup spy was called,
ensuring the IPC handler/unregister logic in setupRootWindow is exercised and
observed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 646a3a5c-8593-4f95-b72c-08ab5a163965

📥 Commits

Reviewing files that changed from the base of the PR and between b561dd9 and c80bd6a.

📒 Files selected for processing (2)
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/main/rootWindow.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ui/components/OutlookCredentialsDialog/index.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check (windows-latest)
  • GitHub Check: check (ubuntu-latest)
  • GitHub Check: Analyze (javascript)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript for all new code in this codebase unless explicitly told otherwise
Use Fuselage components from @rocket.chat/fuselage for all UI work — only create custom components when Fuselage doesn't provide the needed functionality
Check Theme.d.ts for valid color tokens when working with Fuselage components
Use optional chaining with fallbacks for platform-specific APIs instead of mocks (e.g., process.getuid?.() ?? 1000) to ensure code works across all platforms without requiring mocks
TypeScript code must use strict mode
Use React functional components with hooks instead of class components
Redux actions must follow the FSA (Flux Standard Action) pattern
Use camelCase for file naming
Use PascalCase for component file names (React components)
Write self-documenting code through clear naming — avoid unnecessary comments

Files:

  • src/ui/main/rootWindow.spec.ts
**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use *.spec.ts file naming convention for Renderer process tests

Files:

  • src/ui/main/rootWindow.spec.ts
**/*.{spec,main.spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{spec,main.spec}.ts: Tests must run and pass on Windows, macOS, and Linux CI environments — always verify cross-platform compatibility
Only mock Linux-only APIs (process.getuid(), process.getgid(), process.geteuid(), process.getegid()) when defensive coding with optional chaining isn't possible

Files:

  • src/ui/main/rootWindow.spec.ts
🧠 Learnings (2)
📚 Learning: 2026-04-06T18:34:44.375Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T18:34:44.375Z
Learning: Applies to **/*.{spec,main.spec}.ts : Only mock Linux-only APIs (process.getuid(), process.getgid(), process.geteuid(), process.getegid()) when defensive coding with optional chaining isn't possible

Applied to files:

  • src/ui/main/rootWindow.spec.ts
📚 Learning: 2026-04-06T18:34:44.375Z
Learnt from: CR
Repo: RocketChat/Rocket.Chat.Electron PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T18:34:44.375Z
Learning: Applies to **/*.{ts,tsx} : Use optional chaining with fallbacks for platform-specific APIs instead of mocks (e.g., process.getuid?.() ?? 1000) to ensure code works across all platforms without requiring mocks

Applied to files:

  • src/ui/main/rootWindow.spec.ts
🔇 Additional comments (1)
src/ui/main/rootWindow.spec.ts (1)

9-9: Good addition: the Electron mock now covers the new macOS API.

This keeps the rootWindow close-path test from failing on the app.setSecureKeyboardEntryEnabled(...) call.

Comment on lines +42 to +44
jest.mock('./secureKeyboardEntry', () => ({
setupSecureKeyboardEntry: jest.fn(() => () => undefined),
}));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Expose the cleanup function in the secure-keyboard mock.

Returning a plain no-op hides regressions in the before-quit cleanup path. The spec should be able to observe that setupRootWindow() unregisters the IPC handler on shutdown.

Suggested test mock
 jest.mock('./secureKeyboardEntry', () => ({
-  setupSecureKeyboardEntry: jest.fn(() => () => undefined),
+  setupSecureKeyboardEntry: jest.fn(() => {
+    const removeSecureKeyboardHandler = jest.fn();
+    return removeSecureKeyboardHandler;
+  }),
 }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
jest.mock('./secureKeyboardEntry', () => ({
setupSecureKeyboardEntry: jest.fn(() => () => undefined),
}));
jest.mock('./secureKeyboardEntry', () => ({
setupSecureKeyboardEntry: jest.fn(() => {
const removeSecureKeyboardHandler = jest.fn();
return removeSecureKeyboardHandler;
}),
}));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/main/rootWindow.spec.ts` around lines 42 - 44, The mock for
setupSecureKeyboardEntry should return a callable cleanup spy instead of a plain
no-op so the test can assert cleanup was invoked; update the jest.mock
implementation (mock of setupSecureKeyboardEntry) to return jest.fn() as the
cleanup function and expose that spy to the spec, then in the test call
setupRootWindow() and trigger the shutdown path (e.g., invoke the before-quit
handler) and assert the returned cleanup spy was called, ensuring the IPC
handler/unregister logic in setupRootWindow is exercised and observed.

…le password field active

When the BrowserWindow loses and regains OS focus while the password input is
the active DOM element, the input's onFocus does not re-fire (DOM focus never
left it). Track password-field active state in a ref and listen to the window
'focus' event on darwin to re-invoke secure-keyboard-entry/set(true) when
needed. Adds renderer spec covering focus, blur, window-refocus, and
cross-platform (linux/win32) guard paths.
@jeanfbrito
Copy link
Copy Markdown
Member Author

Fix applied: re-enable secure keyboard entry on window refocus (Fix 1)

Scenario addressed: user focuses the password field (secure entry ON), switches to another app (window blur fires → secure entry forced OFF), switches back. The password input is still the active DOM element but its onFocus does not re-fire — OS focus returned to the BrowserWindow, not to the input. Subsequent typing was unprotected.

Chosen fix: Fix 1 — track DOM-side state and re-enable on window focus.

What changed

src/ui/components/OutlookCredentialsDialog/index.tsx

  • Added passwordFieldActiveRef (a useRef<boolean>) to track whether the password input is the active element.
  • onFocus sets passwordFieldActiveRef.current = true before invoking secure-keyboard-entry/set(true).
  • onBlur sets passwordFieldActiveRef.current = false before invoking secure-keyboard-entry/set(false).
  • New useEffect (darwin-gated) adds a window 'focus' listener: if passwordFieldActiveRef.current is true when the BrowserWindow regains OS focus, it re-invokes secure-keyboard-entry/set(true). Listener is cleaned up on unmount.

src/ui/components/OutlookCredentialsDialog/index.spec.tsx (new file)

  • 12 renderer tests covering: focus enables, blur disables, window-refocus re-enables when active, window-refocus no-ops when never focused, window-refocus no-ops after blur, unmount removes listener, linux no-ops, win32 no-ops.

Verification

  • npx tsc --noEmit — clean
  • yarn lint — clean
  • yarn test --testPathPattern="OutlookCredentialsDialog|secureKeyboardEntry" — 18/18 passing

Commit: d6299d14d

Adds cleanup for the case where OutlookCredentialsDialog is dismissed via
Esc, overlay click, or state-driven hide without password onBlur firing.
Two new useEffect hooks: one watching isVisible (covers state-driven close),
one returning a cleanup on unmount. Both check passwordFieldActiveRef and
call secure-keyboard-entry/set false only when needed, darwin-only.
Extends index.spec.tsx with 7 new test cases covering unmount and
state-change close paths on darwin, linux, and win32.
@jeanfbrito
Copy link
Copy Markdown
Member Author

Fix: secure keyboard entry cleanup on dialog close paths

Commit: fix(CORE-1122): disable secure keyboard entry on dialog close paths

Problem

When OutlookCredentialsDialog was dismissed via Esc, overlay click, or state-driven hide (without the password field firing onBlur), the secure-keyboard-entry/set false call never happened. Global secure entry stayed ON, breaking input methods and accessibility outside the dialog.

Fix (src/ui/components/OutlookCredentialsDialog/index.tsx)

Two new useEffect hooks added:

  1. State-change guard — watches isVisible; when it flips to false, if passwordFieldActiveRef.current is true (and platform is darwin), calls invoke('secure-keyboard-entry/set', false) and resets the ref.

  2. Unmount cleanup — returns a cleanup function from a mount-only effect; same logic fires when the component unmounts entirely.

Both are darwin-only, consistent with the existing onFocus/onBlur logic.

Tests (src/ui/components/OutlookCredentialsDialog/index.spec.tsx)

7 new test cases added (17 total, all passing):

darwin:

  • calls secure-keyboard-entry/set false on unmount when password was focused
  • does NOT call secure-keyboard-entry/set false on unmount when password was never focused
  • calls secure-keyboard-entry/set false when dialog closes via state change (isVisible flips false)
  • does NOT call secure-keyboard-entry/set false on dialog close when password was never focused

linux:

  • does not call secure-keyboard-entry/set on unmount
  • does not call secure-keyboard-entry/set on dialog close via state change

win32:

  • does not call secure-keyboard-entry/set on unmount
  • does not call secure-keyboard-entry/set on dialog close via state change

Lint and typecheck both pass clean.

@jeanfbrito jeanfbrito changed the base branch from master to dev April 29, 2026 19:53
Base automatically changed from dev to master May 4, 2026 22:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant