From 627f2d0aa9db3098130aecf7504959147f0bf602 Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:19:00 +0700 Subject: [PATCH 1/2] Port upstream 0.60.5: redact all Codex System Account labels under Hide Personal Info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream 0.60.5 (#3702, 141ecf642): the System Account submenu redacts every account, not only the ambient one — managed accounts keep their promotion targets, checked state and visibility, but their labels become the generic 'Account N' while Hide Personal Info is on. The local redaction filter (privacy_safe_display_name, b4a55b98) gated on CodexAccountSource::Ambient, so managed accounts kept email/nickname labels in the tray System Account submenu. The filter now applies to every account source; ordinals stay per-account so entries remain distinguishable. The privacy-off path is unchanged. The test module's CodexAccountSource import moves into the test module so the production surface no longer needs the variant. Test: privacy_safe_display_name_hides_every_account_under_hide_personal_info pins ambient + managed redaction (Account 1 / Account 2, no email, no nickname), per-account ordinals, and unchanged privacy-off labels. --- rust/src/codex_accounts/labels.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/rust/src/codex_accounts/labels.rs b/rust/src/codex_accounts/labels.rs index 7de945638c2..a7e1713fee9 100644 --- a/rust/src/codex_accounts/labels.rs +++ b/rust/src/codex_accounts/labels.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use uuid::Uuid; -use super::models::{CodexAccount, CodexAccountSource}; +use super::models::CodexAccount; impl CodexAccount { /// The user-facing account label. @@ -18,13 +18,17 @@ impl CodexAccount { } /// The label used by a user-facing account surface. + /// + /// Upstream 0.60.5 (#3702, `141ecf642`): Hide Personal Info redacts every + /// account in the System Account submenu, not only ambient ones — managed + /// accounts get the same generic label while the setting is on. pub fn privacy_safe_display_name( &self, hide_personal_info: bool, ordinal: usize, generic_label: &str, ) -> String { - if hide_personal_info && self.source == CodexAccountSource::Ambient { + if hide_personal_info { let label = generic_label.trim(); let label = if label.is_empty() { "Account" } else { label }; return format!("{label} {ordinal}"); @@ -134,6 +138,8 @@ pub fn ordinals_by_id(accounts: &[CodexAccount]) -> HashMap { #[cfg(test)] mod tests { use super::*; + + use crate::codex_accounts::models::CodexAccountSource; use crate::codex_accounts::models::utc_now; use std::path::PathBuf; @@ -211,22 +217,31 @@ mod tests { } #[test] - fn privacy_safe_display_name_hides_system_identity_but_preserves_managed_labels() { + fn privacy_safe_display_name_hides_every_account_under_hide_personal_info() { let mut system = display_account("11111111-1111-1111-1111-111111111111", "system-workspace"); system.source = CodexAccountSource::Ambient; system.nickname = Some("Private System Name".to_string()); let mut managed = display_account("22222222-2222-2222-2222-222222222222", "managed-workspace"); + managed.source = CodexAccountSource::ManagedByApp; managed.nickname = Some("Work".to_string()); + // Upstream 0.60.5 #3702 (141ecf642): every account is redacted, not only + // ambient ones — managed accounts get the generic label too. let hidden_system = system.privacy_safe_display_name(true, 1, "Account"); let hidden_managed = managed.privacy_safe_display_name(true, 2, "Account"); assert_eq!(hidden_system, "Account 1"); + assert_eq!(hidden_managed, "Account 2"); assert!(!hidden_system.contains("@")); assert!(!hidden_system.contains("Private System Name")); - assert_eq!(hidden_managed, "user@example.com — Work"); + assert!(!hidden_managed.contains("Work")); + assert!(!hidden_managed.contains("@")); + + // Ordinals stay account-specific so entries remain distinguishable. + assert_ne!(hidden_system, hidden_managed); + // With privacy off every account keeps its raw label. assert_eq!( system.privacy_safe_display_name(false, 1, "Account"), "user@example.com — Private System Name" From fd0cd451492af1013e9fefb06ed1943d59a8cbdd Mon Sep 17 00:00:00 2001 From: RCD <90105158+Finesssee@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:52:41 +0700 Subject: [PATCH 2/2] Port 0.60.5 GAP-03: redact all Codex accounts in the frontend surfaces too The frontend mirror (codexAccountDisplay.ts buildCodexAccountSurfaceLabels) still redacted only source === "ambient"; Settings/UsageSection, the metric picker rows (CodexAccountsSection) and the tray-panel CodexAccountsMenu kept managed email/nickname labels while Hide Personal Info was on. The mirror now redacts every account (mirroring the Rust fix); the three affected suites pin ambient + managed "Account N" labels with no email or nickname leakage, unchanged privacy-off labels, and per-account ordinals. vitest: 393 passed, 0 failed (65 files). --- .../src/components/CodexAccountsMenu.test.tsx | 20 +++++++++++++------ .../components/codexAccountDisplay.test.ts | 12 ++++++++--- .../src/components/codexAccountDisplay.ts | 10 +++++----- .../credentials/CodexAccountsSection.test.tsx | 10 +++++++--- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx b/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx index 2cd1c79c0a9..a6e9edeb035 100644 --- a/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx +++ b/apps/desktop-tauri/src/components/CodexAccountsMenu.test.tsx @@ -202,7 +202,7 @@ describe("CodexAccountsMenu", () => { expect(tauriMocks.codexAccountSwitch).toHaveBeenCalledWith("2"); expect(tauriMocks.refreshProviders).toHaveBeenCalledTimes(1); }); - it("uses an opaque ordinal only for the ambient account while hideEmail is on", async () => { + it("uses an opaque ordinal for every account while hideEmail is on", async () => { const { container: hidden } = renderMenu(true, { accounts: [account("1", { source: "ambient" }), account("2")], accountOrdinals: { "1": 1, "2": 2 }, @@ -213,6 +213,8 @@ describe("CodexAccountsMenu", () => { hidden.querySelectorAll(".codex-menu-accounts__email").length, ).toBe(2); }); + // Upstream 0.60.5 #3702 (141ecf642): every account is redacted, not only + // ambient ones — managed accounts get the generic label too. const ambientLabel = hidden.querySelectorAll( ".codex-menu-accounts__email", )[0] as HTMLElement; @@ -221,10 +223,12 @@ describe("CodexAccountsMenu", () => { expect(ambientLabel.firstChild?.textContent).not.toContain("@"); expect(ambientLabel.firstChild?.textContent).not.toContain("example.com"); - const managedEmail = hidden.querySelectorAll( + const managedLabel = hidden.querySelectorAll( ".codex-menu-accounts__email", )[1] as HTMLElement; - expect(managedEmail.textContent).toBe("user-2@example.com"); + expect(managedLabel.getAttribute("title")).toBe("Account 2"); + expect(managedLabel.textContent).toBe("Account 2"); + expect(managedLabel.textContent).not.toContain("user-2@example.com"); const switches = hidden.querySelectorAll( ".codex-menu-accounts__switch", ) as NodeListOf; @@ -247,7 +251,7 @@ describe("CodexAccountsMenu", () => { expect(rawEmail.getAttribute("title")).toBe("user-2@example.com"); }); - it("uses canonical opaque ordinals supplied by the account bridge", async () => { + it("uses canonical opaque ordinals for every account supplied by the bridge", async () => { const first = account("uuid-b", { emailHint: "alice@example.com", nickname: "team@example.com", @@ -263,12 +267,16 @@ describe("CodexAccountsMenu", () => { accountOrdinals: { "uuid-b": 2, "uuid-a": 1 }, snapshots: {}, }); + // Upstream 0.60.5 #3702 (141ecf642): every account is redacted, not only + // ambient ones — managed accounts get the generic label too. await screen.findByText("Account 2"); + await screen.findByText("Account 1"); const labels = container.querySelectorAll(".codex-menu-accounts__email"); expect(labels[0].firstChild?.textContent).toBe("Account 2"); - expect(labels[1].textContent).toBe("bob@example.com — Private workspace"); + expect(labels[1].textContent).toBe("Account 1"); expect(labels[0].firstChild?.textContent).not.toContain("@"); - expect(labels[1].textContent).toContain("Private workspace"); + expect(labels[1].textContent).not.toContain("bob@example.com"); + expect(labels[1].textContent).not.toContain("Private workspace"); }); }); diff --git a/apps/desktop-tauri/src/components/codexAccountDisplay.test.ts b/apps/desktop-tauri/src/components/codexAccountDisplay.test.ts index 8422bc1afef..325421a3e0f 100644 --- a/apps/desktop-tauri/src/components/codexAccountDisplay.test.ts +++ b/apps/desktop-tauri/src/components/codexAccountDisplay.test.ts @@ -86,12 +86,12 @@ describe("Codex account display labels", () => { expect(label).not.toContain("C:/private"); }); - it("redacts only the ambient account while preserving managed labels", () => { + it("redacts every account under hide personal info (ambient + managed)", () => { const system = account("11111111-1111-1111-1111-111111111111", "system"); const managed = account("22222222-2222-2222-2222-222222222222", "managed"); const accounts = [ { ...system, source: "ambient" as const, nickname: "Private System Name" }, - { ...managed, nickname: "Work" }, + { ...managed, source: "managedByApp" as const, nickname: "Work" }, ]; const hidden = buildCodexAccountSurfaceLabels( accounts, @@ -100,10 +100,15 @@ describe("Codex account display labels", () => { true, "Account", ); + // Upstream 0.60.5 #3702 (141ecf642): every account is redacted, not only + // ambient ones — managed accounts get the generic label too. expect(hidden[system.id]).toBe("Account 1"); expect(hidden[system.id]).not.toContain("@"); expect(hidden[system.id]).not.toContain("Private System Name"); - expect(hidden[managed.id]).toBe("same@example.com — Work"); + expect(hidden[managed.id]).toBe("Account 2"); + expect(hidden[managed.id]).not.toContain("@"); + expect(hidden[managed.id]).not.toContain("Work"); + expect(hidden[system.id]).not.toBe(hidden[managed.id]); const reordered = buildCodexAccountSurfaceLabels( [accounts[1], accounts[0]], @@ -113,6 +118,7 @@ describe("Codex account display labels", () => { "Account", ); expect(reordered[system.id]).toBe("Account 1"); + expect(reordered[managed.id]).toBe("Account 2"); const visible = buildCodexAccountSurfaceLabels( accounts, diff --git a/apps/desktop-tauri/src/components/codexAccountDisplay.ts b/apps/desktop-tauri/src/components/codexAccountDisplay.ts index 3c72eaf036c..67276caf33b 100644 --- a/apps/desktop-tauri/src/components/codexAccountDisplay.ts +++ b/apps/desktop-tauri/src/components/codexAccountDisplay.ts @@ -39,10 +39,10 @@ export function buildCodexAccountDisplayNames( /** * Project the labels rendered by account rows in the tray and Settings. * The redaction rule mirrors `CodexAccount::privacy_safe_display_name` in the - * Rust model: privacy mode redacts only the ambient/System account; managed - * account labels keep the canonical display-name projection. Client-side - * projection keeps the relabel reactive when the setting toggles without a - * refetch. + * Rust model (upstream 0.60.5 #3702, 141ecf642): privacy mode redacts EVERY + * account, not only the ambient/System one — managed accounts get the same + * generic label too. Client-side projection keeps the relabel reactive when + * the setting toggles without a refetch. */ export function buildCodexAccountSurfaceLabels( accounts: readonly CodexAccount[], @@ -55,7 +55,7 @@ export function buildCodexAccountSurfaceLabels( return Object.fromEntries( accounts.map((account) => { - if (hidePersonalInfo && account.source === "ambient") { + if (hidePersonalInfo) { return [account.id, `${accountWord.trim()} ${accountOrdinals[account.id]}`]; } return [account.id, displayNames[account.id]]; diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/CodexAccountsSection.test.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/CodexAccountsSection.test.tsx index 50f52d15f98..002254f091b 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/CodexAccountsSection.test.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/credentials/CodexAccountsSection.test.tsx @@ -84,7 +84,7 @@ describe("CodexAccountsSection", () => { expect(screen.getAllByText("CodexAccountsReauthenticateButton")).toHaveLength(1); }); - it("redacts the ambient row while preserving the managed row when privacy is on", async () => { + it("redacts every row (ambient + managed) when privacy is on", async () => { const ambient = account("ambient", { source: "ambient", nickname: "Private System Name", @@ -106,8 +106,12 @@ describe("CodexAccountsSection", () => { render(); await screen.findByText("Account 1"); - expect(screen.queryByText(/system@example\.com|Private System Name/)).toBeNull(); - expect(screen.getByText("work@example.com — Work")).toBeDefined(); + // Upstream 0.60.5 #3702 (141ecf642): every account is redacted, not only + // ambient ones — managed accounts get the generic label too. + expect(screen.getByText("Account 2")).toBeDefined(); + expect( + screen.queryByText(/system@example\.com|work@example\.com|Private System Name|Work/), + ).toBeNull(); expect(screen.getByText("CodexAccountsSourceAmbient")).toBeDefined(); expect(screen.getByText("CodexAccountsSourceManaged")).toBeDefined(); });