Skip to content

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

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

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

Conversation

@jeanfbrito

@jeanfbrito jeanfbrito commented Apr 29, 2026

Copy link
Copy Markdown
Member

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

    • Added macOS secure keyboard entry support for password fields in the credentials dialog and app window lifecycle.
    • Secure keyboard entry now turns on when entering a password field and turns off when focus is lost, the dialog closes, or the app window shuts down.
  • Tests

    • Added coverage for platform-specific behavior, including macOS, Linux, and Windows.
    • Expanded UI and main-process tests to verify focus, blur, cleanup, and window events.

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 12f53896-c350-4099-853c-735a74880c09

📥 Commits

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

📒 Files selected for processing (7)
  • src/ipc/channels.ts
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/components/OutlookCredentialsDialog/secureKeyboardEntry.spec.tsx
  • src/ui/main/rootWindow.spec.ts
  • src/ui/main/rootWindow.ts
  • src/ui/main/secureKeyboardEntry.main.spec.ts
  • src/ui/main/secureKeyboardEntry.ts
💤 Files with no reviewable changes (6)
  • src/ui/main/secureKeyboardEntry.main.spec.ts
  • src/ui/main/rootWindow.spec.ts
  • src/ui/main/secureKeyboardEntry.ts
  • src/ui/components/OutlookCredentialsDialog/secureKeyboardEntry.spec.tsx
  • src/ui/components/OutlookCredentialsDialog/index.tsx
  • src/ui/main/rootWindow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ipc/channels.ts

Walkthrough

Adds a new secure-keyboard-entry/set IPC channel and a macOS-specific setupSecureKeyboardEntry handler that toggles app.setSecureKeyboardEntryEnabled. Wires this into root window lifecycle events (blur, close, before-quit) and the Outlook credentials dialog's password field focus/blur, with accompanying tests.

Changes

Secure Keyboard Entry Feature

Layer / File(s) Summary
IPC channel contract
src/ipc/channels.ts
Adds secure-keyboard-entry/set to ChannelToArgsMap with an enabled: boolean parameter and void return.
Main-process setup function
src/ui/main/secureKeyboardEntry.ts, src/ui/main/secureKeyboardEntry.main.spec.ts
Implements setupSecureKeyboardEntry, registering a macOS-only IPC handler forwarding to app.setSecureKeyboardEntryEnabled and returning a cleanup function; tests cover darwin, linux, and win32 behavior.
Root window lifecycle wiring
src/ui/main/rootWindow.ts, src/ui/main/rootWindow.spec.ts
Calls setupSecureKeyboardEntry() during setupRootWindow, disables secure keyboard on macOS window blur/close, and removes the handler on before-quit; test mocks updated accordingly.
Password field UI wiring
src/ui/components/OutlookCredentialsDialog/index.tsx
Tracks password field focus via passwordFieldActiveRef, invokes the IPC channel on focus/blur, disables on dialog hide/unmount, and re-enables on window focus if the password field is still active.
Dialog secure keyboard tests
src/ui/components/OutlookCredentialsDialog/secureKeyboardEntry.spec.tsx
Adds render helpers and darwin/linux/win32 test suites verifying secure keyboard IPC calls across focus, blur, window focus, unmount, and dialog close scenarios.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PasswordInput
  participant OutlookCredentialsDialog
  participant MainProcess
  participant App

  User->>PasswordInput: focus
  PasswordInput->>OutlookCredentialsDialog: onFocus
  OutlookCredentialsDialog->>MainProcess: invoke('secure-keyboard-entry/set', true)
  MainProcess->>App: setSecureKeyboardEntryEnabled(true)
  User->>PasswordInput: blur
  PasswordInput->>OutlookCredentialsDialog: onBlur
  OutlookCredentialsDialog->>MainProcess: invoke('secure-keyboard-entry/set', false)
  MainProcess->>App: setSecureKeyboardEntryEnabled(false)
  Note over OutlookCredentialsDialog,MainProcess: window focus re-enables if password field still active
Loading

Suggested labels: type: feature

🚥 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 title clearly and concisely summarizes the main change: adding macOS secure keyboard entry for sensitive inputs.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (2)
  • CORE-1122: Request failed with status code 401
  • Q4-2022: 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

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

@coderabbitai coderabbitai Bot left a comment

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.

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.

@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.

@coderabbitai coderabbitai Bot left a comment

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.

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.

@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

@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
…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.
…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.
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.
- Preserve react-hook-form onBlur handler on password field while
  still disabling secure keyboard entry (pass onBlur via register
  options instead of overriding the returned handler)
- Return a jest.fn() cleanup spy from the secureKeyboardEntry mock
  so before-quit cleanup can be asserted in tests
@jeanfbrito jeanfbrito force-pushed the security/CORE-1122-secure-keyboard branch from 1abb6ea to 1e817df Compare July 8, 2026 08:44
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

macOS installer download

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