Repository navigation
feat(privacy): mask account emails across all display surfaces - #164
Conversation
Extend the existing maskEmail config so it applies consistently to every human-facing account display, not just the TUI quota status. Previously maskEmail only reached the quota path while the account menu, command output, runtime messages, and standalone login menu still rendered full emails, exposing private addresses in screenshots and screen shares. - Add lib/account-display.ts with maskEmailForDisplay (domain-preserving, e.g. us***@example.com) and resolveDisplayEmail helpers. - Thread maskEmail through formatAccountLabel (lib/accounts.ts), formatCommandAccountLabel and its tool call sites, the interactive auth menu (accountTitle), and the standalone CLI login menu. - Prefer account labels and mask the email fallback in the check-all and verify-flagged command output. - Keep raw emails in opt-in --includeSensitive JSON output unchanged. - Document the broadened behavior in configuration.md and privacy.md. Closes #163
|
Caution Review failedPull request was closed or merged during review Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdd shared email-masking helpers, expose resolveMaskEmail on ToolContext, wire maskEmail through index/CLI/UI and all codex-* tools, update tests, and broaden docs to describe masking across account-display surfaces. ChangesEmail masking across account displays
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
lib/tools/codex-remove.ts (1)
213-217:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPost-remove hint still exposes raw email
The follow-up hint uses
account.emaildirectly, so masking can be bypassed in normal output even though the main label is masked.Suggested fix
- const postRemoveHint = - matchingEmailRemaining > 0 && account.email - ? formatUiItem( - ui, - `Other entries for ${account.email} remain: ${matchingEmailRemaining}`, - "muted", - ) + const postRemoveHint = + matchingEmailRemaining > 0 + ? formatUiItem( + ui, + `Other entries for this account identity remain: ${matchingEmailRemaining}`, + "muted", + ) : formatUiItem( ui, "Only the selected entry was removed.", "muted", ); ... - const postRemoveHint = - matchingEmailRemaining > 0 && account.email - ? `Other entries for ${account.email} remain: ${matchingEmailRemaining}` + const postRemoveHint = + matchingEmailRemaining > 0 + ? `Other entries for this account identity remain: ${matchingEmailRemaining}` : "Only the selected entry was removed.";As per coding guidelines,
lib/**/*.ts: “Do not expose account emails, access tokens, refresh tokens, or raw prompt/response bodies in normal diagnostics”.Also applies to: 243-245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/tools/codex-remove.ts` around lines 213 - 217, The follow-up hint is exposing raw emails by using account.email directly in formatUiItem; update the calls (the one around matchingEmailRemaining and the similar occurrences at lines ~243-245) to pass a masked email instead (e.g., maskEmail(account.email) or an existing mask utility) before calling formatUiItem so diagnostics never include raw account.email; modify where formatUiItem is invoked and ensure the masking function handles null/undefined safely.index.ts (1)
949-950:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInteractive picker/setup labels still bypass
maskEmail
promptAccountIndexSelectionandtoBeginnerAccountSnapshotscallformatCommandAccountLabelwithout{ maskEmail }, so these human-facing paths can still show raw emails when masking is enabled.Suggested fix
const promptAccountIndexSelection = async ( ui: UiRuntimeOptions, storage: AccountStorageV3, title: string, ): Promise<number | null> => { + const maskEmail = resolveMaskEmail(); if (!supportsInteractiveMenus()) return null; try { const { select } = await import("./lib/ui/select.js"); const selected = await select<number>( storage.accounts.map((account, index) => ({ - label: formatCommandAccountLabel(account, index), + label: formatCommandAccountLabel(account, index, { maskEmail }), value: index, })),const toBeginnerAccountSnapshots = ( storage: AccountStorageV3, activeIndex: number, now: number, ): BeginnerAccountSnapshot[] => { + const maskEmail = resolveMaskEmail(); return storage.accounts.map((account, index) => ({ index, - label: formatCommandAccountLabel(account, index), + label: formatCommandAccountLabel(account, index, { maskEmail }),Also applies to: 974-974
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@index.ts` around lines 949 - 950, Calls to formatCommandAccountLabel in promptAccountIndexSelection and toBeginnerAccountSnapshots are missing the maskEmail option so masked-email mode can be bypassed; update both call sites to pass the maskEmail flag (e.g., formatCommandAccountLabel(account, index, { maskEmail })) sourcing maskEmail from the surrounding options/args/context (or add it to the function parameters if not available) so the label creation honors masking consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/configuration.md`:
- Around line 162-163: Update the docs so the env-var documentation for
CODEX_TUI_MASK_EMAIL (and the related maskEmailInQuotaDetails setting) matches
the expanded behavior described under maskEmail: change the line that currently
limits CODEX_TUI_MASK_EMAIL to "prompt quota status" to state it controls
masking across all account-display surfaces (TUI prompt quota status, command
outputs like codex-list/codex-status/.../codex-remove, interactive account menu,
and standalone login menu), and ensure maskEmailInQuotaDetails is documented as
additionally toggling active account masking inside the quota details dialog;
reference the config keys maskEmail and maskEmailInQuotaDetails and the env-var
CODEX_TUI_MASK_EMAIL so the two sections are consistent.
In `@test/account-display.test.ts`:
- Line 1: Remove the explicit import line "import { describe, expect, it } from
'vitest';" from test/account-display.test.ts and rely on the Vitest globals
describe, it, and expect defined by the test runner; update the file to call
describe/it/expect directly (no import) and ensure the test uses the existing
global symbols throughout (look for usages of describe, it, expect in this
file).
---
Outside diff comments:
In `@index.ts`:
- Around line 949-950: Calls to formatCommandAccountLabel in
promptAccountIndexSelection and toBeginnerAccountSnapshots are missing the
maskEmail option so masked-email mode can be bypassed; update both call sites to
pass the maskEmail flag (e.g., formatCommandAccountLabel(account, index, {
maskEmail })) sourcing maskEmail from the surrounding options/args/context (or
add it to the function parameters if not available) so the label creation honors
masking consistently.
In `@lib/tools/codex-remove.ts`:
- Around line 213-217: The follow-up hint is exposing raw emails by using
account.email directly in formatUiItem; update the calls (the one around
matchingEmailRemaining and the similar occurrences at lines ~243-245) to pass a
masked email instead (e.g., maskEmail(account.email) or an existing mask
utility) before calling formatUiItem so diagnostics never include raw
account.email; modify where formatUiItem is invoked and ensure the masking
function handles null/undefined safely.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f355dfde-b952-47c2-8d74-4aaf6b5457c5
📒 Files selected for processing (24)
docs/configuration.mddocs/privacy.mdindex.tslib/account-display.tslib/accounts.tslib/cli.tslib/tools/codex-dashboard.tslib/tools/codex-health.tslib/tools/codex-label.tslib/tools/codex-limits.tslib/tools/codex-list.tslib/tools/codex-note.tslib/tools/codex-refresh.tslib/tools/codex-remove.tslib/tools/codex-status.tslib/tools/codex-switch.tslib/tools/codex-tag.tslib/tools/index.tslib/ui/auth-menu.tstest/account-display.test.tstest/accounts.test.tstest/auth-menu.test.tstest/cli.test.tstest/index.test.ts
Address review feedback on PR #164: - List codex-refresh among the maskEmail-affected command surfaces. - Update the CODEX_TUI_MASK_EMAIL env-var description so it no longer contradicts the broadened maskEmail behavior documented above it. - Add test/tools-codex-refresh.test.ts covering masked and raw output so a dropped maskEmail argument at the refresh call site is caught.
Address remaining review feedback on PR #164: - Make the accounts.js formatAccountLabel mock a spyable vi.fn and add a test asserting the auth-failure removal path builds the account label with { maskEmail: true }. Verified it fails if the option is dropped from the call site, so the three runtime sites are now guarded. - Make getCodexTuiMaskEmail mockable per test (vi.fn) so the masking path can be toggled without affecting other tests. - Drop the explicit vitest import from account-display.test.ts now that the file relies on the configured globals.
PR #163/#164 threaded maskEmail through most label formatters but four human-facing surfaces still printed raw emails when maskEmail is enabled, defeating the masking on exactly the screenshot/screen-share threat model the feature targets: - index.ts promptAccountIndexSelection: the interactive account picker used by codex-switch/remove/label/tag/note rendered raw emails (no { maskEmail }). - lib/tools/codex-remove.ts: the post-remove "Other entries for X remain" hint (both v2 and plain branches) interpolated the raw account.email even though the main label above it was masked. - index.ts interactive manage menu: the delete confirmation and the enable/disable toggle confirmation console.log'd the raw target.email. All four now route through resolveDisplayEmail with the resolved maskEmail flag. Default (maskEmail=false) behavior is unchanged. Regression tests: - test/tools-codex-remove.test.ts (new): asserts the duplicate-entries hint masks/unmasks correctly. - test/index.test.ts: drive the interactive delete and toggle confirmation paths (previously zero coverage), asserting masked output and no raw email. Verified: build, typecheck, lint clean; 2419 tests pass (+4), 1 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an issue-#163 regression suite that drives the REAL plugin tools (and thus the real `formatCommandAccountLabel` closure), so a dropped `{ maskEmail }` at any call site fails here rather than slipping through. Invariant under test: when maskEmail is enabled, no human-facing surface renders a raw email; when disabled, the raw email still renders (backward compatible). Coverage: - Parametrized masked-on / raw-off across codex-list, codex-status, codex-health, codex-switch, codex-label, codex-tag, codex-note, codex-remove. - Multi-account listing masks every email. - codex-remove duplicate-entries hint masks/unmasks (guards the leak fixed in 24ce61d). - Interactive account picker (promptAccountIndexSelection) masks menu labels (guards the originally-flagged leak) — drives the real closure via a captured select() mock with a stubbed TTY. - --includeSensitive JSON still emits the raw email (privacy boundary intact). Each new assertion was mutation-verified: reverting the corresponding fix makes the test fail. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minor release on top of 6.2.0: email masking across all display surfaces (#164), 16 deep-audit bug fixes (#165), and the deep stress suite (#166). Bumps package.json, .release-please-manifest.json, and .codex-plugin/plugin.json to 6.3.0. 2487 tests pass; build/lint/typecheck clean; publish dry-run verified.
Summary
Closes #163.
maskEmailpreviously only reached the TUI prompt quota status. Every other account-display surface (account menu, command output, runtime messages, standalone login menu) still rendered full emails, so private addresses could leak into screenshots, screen sharing, and terminal recordings.This change extends the existing
maskEmailflag so it applies consistently to all human-facing account displays. No new config keys are introduced. WhenmaskEmailis enabled, emails are reduced to a domain-preserving masked form (for exampleus***@example.com); account labels set viacodex-labelare always preferred over emails.What changed
lib/account-display.tswithmaskEmailForDisplay(domain-preserving) andresolveDisplayEmailhelpers as a single source of truth.maskEmailthrough the account-display formatters:formatAccountLabel(lib/accounts.ts) used by runtime and rotation messages.formatCommandAccountLabel(index.ts) and its tool call sites:codex-list,codex-status,codex-limits,codex-health,codex-dashboard,codex-switch,codex-label,codex-tag,codex-note,codex-remove.accountTitlein the interactive auth menu (lib/ui/auth-menu.ts) plus its detail and delete/refresh confirmation prompts.formatAccountLabelin the standalone CLI login menu (lib/cli.ts).index.ts, now label-first with a masked email fallback.resolveMaskEmailtoToolContextso each tool resolves the flag once per invocation rather than per account.docs/configuration.mdanddocs/privacy.md.Privacy boundary
--includeSensitiveJSON output. Incodex-healthandcodex-limitsthe sensitive JSONlabelfield keeps the raw value while the rendered text uses the masked label, so--includeSensitivebehavior is unchanged.maskEmaildefaults tofalse, so emails render exactly as before unless a user opts in.Verification
npm run typecheck: clean.npm run lint: clean.npm test: 86 files, 2412 passed, 1 skipped.npm run build: succeeds.test/account-display.test.ts, plus masking cases intest/accounts.test.ts,test/auth-menu.test.ts, andtest/cli.test.ts.Notes
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
this pr extends the existing
maskEmailflag from the tui quota status to every human-facing account-display surface — command output, interactive menus, standalone login menu, and runtime rotation/failure messages — by centralizing masking logic in a newlib/account-display.tsmodule and threadingresolveMaskEmail()through theToolContext.lib/account-display.ts:maskEmailForDisplay(domain-preserving, handles single-char local parts and invalid emails) andresolveDisplayEmailas the single source of truth; all call sites now delegate here instead of rolling their own email trimming.test/tools-codex-refresh.test.tsandtest/tools-codex-remove.test.tsadded;test/index.test.tsgains a fulltextSurfacesregression matrix plus interactive-picker and runtime-path guard tests;codex-limitsandcodex-dashboardmasking paths are not yet in the matrix.--includeSensitivejson output via a separate unmaskedlabelvariable incodex-healthandcodex-limits; default behavior unchanged (maskEmailremainsfalse).Confidence Score: 5/5
safe to merge — masking is display-only and defaults to false, so existing behavior is unchanged unless the user opts in.
the change is well-scoped: a single new helper module, a consistent option threaded through every call site, and the privacy boundary preserved by keeping a separate unmasked label variable in the tools that need it. the regression suite is comprehensive for all but two tool surfaces.
test/index.test.ts —
codex-limitsandcodex-dashboardare not in thetextSurfacesmasking matrix.Important Files Changed
maskEmailForDisplayandresolveDisplayEmail— clean, well-tested, correct edge-case handling for single-char locals, missing @, and empty input.resolveMaskEmailclosure added toToolContext;promptAccountIndexSelectionnow masks the interactive picker labels; runtime paths (auth-failure, rate-limit toast, workspace-deactivated) wired throughmaskEmailEnabled.labelkept for--includeSensitiveJSON,displayLabelused for all rendered text output.codex-health— raw label for JSON,displayLabelfor text; masking test coverage not yet added to the surface matrix intest/index.test.ts.resolveMaskEmaildestructured and{ maskEmail }passed to bothformatCommandAccountLabelcalls; no dedicated masking test yet.test/tools-codex-refresh.test.tsadded with masked and raw cases using a faithfulformatCommandAccountLabelstand-in.displayEmailused for the duplicate-entries hint guard and message; semantics preserved — falsy only when originalaccount.emailwas empty/undefined.textSurfacesmatrix missingcodex-limitsandcodex-dashboard; runtime auth-failure path now has a spy-based guard test.maskEmailForDisplayandresolveDisplayEmail, covering single-char local, multi-part domain, non-email fallback, empty/whitespace, and whitespace trimming.Prompt To Fix All With AI
Reviews (5): Last reviewed commit: "test(privacy): broaden #163 coverage acr..." | Re-trigger Greptile