diff --git a/ui/src/components/account/shared/account-surface-card.tsx b/ui/src/components/account/shared/account-surface-card.tsx index ad61b231..8e680913 100644 --- a/ui/src/components/account/shared/account-surface-card.tsx +++ b/ui/src/components/account/shared/account-surface-card.tsx @@ -172,6 +172,8 @@ export function AccountSurfaceCard({ const { t } = useTranslation(); const identity = getAccountIdentityPresentation(accountId, email, tokenFile); const title = displayEmail || identity.email || accountId; + const sensitiveTitle = (value: string | null | undefined) => + privacyMode ? undefined : (value ?? undefined); const normalizedProvider = provider.toLowerCase(); const effectiveTier = resolveEffectiveTier(tier, quota); const effectiveCodexBadge = @@ -192,6 +194,7 @@ export function AccountSurfaceCard({ @@ -201,9 +204,10 @@ export function AccountSurfaceCard({ {normalizedProvider === 'codex' ? effectiveCodexBadge?.label && ( @@ -212,9 +216,10 @@ export function AccountSurfaceCard({ ) : identity.audienceLabel && ( @@ -223,9 +228,10 @@ export function AccountSurfaceCard({ )} {normalizedProvider !== 'codex' && identity.compactDetailLabel && ( @@ -260,6 +266,7 @@ export function AccountSurfaceCard({ @@ -303,6 +311,7 @@ export function AccountSurfaceCard({ variant="outline" className={cn( 'text-[10px] h-4 px-1.5 border-transparent', + privacyMode && PRIVACY_BLUR_CLASS, getAudienceBadgeClass(identity.audience) )} > @@ -310,7 +319,10 @@ export function AccountSurfaceCard({ )} {!isCompact && normalizedProvider !== 'codex' && identity.detailLabel && ( - + {identity.detailLabel} )} diff --git a/ui/src/components/setup/wizard/steps/account-step.tsx b/ui/src/components/setup/wizard/steps/account-step.tsx index dc9b5930..fc0c194a 100644 --- a/ui/src/components/setup/wizard/steps/account-step.tsx +++ b/ui/src/components/setup/wizard/steps/account-step.tsx @@ -53,6 +53,7 @@ export function AccountStep({ variant="outline" className={cn( 'text-[10px] h-4 px-1.5 border-transparent', + privacyMode && PRIVACY_BLUR_CLASS, codexBadge.audience === 'business' ? 'bg-sky-500/12 text-sky-700 dark:text-sky-300' : codexBadge.audience === 'free' @@ -67,6 +68,7 @@ export function AccountStep({ variant="outline" className={cn( 'text-[10px] h-4 px-1.5 border-transparent', + privacyMode && PRIVACY_BLUR_CLASS, identity.audience === 'business' ? 'bg-sky-500/12 text-sky-700 dark:text-sky-300' : identity.audience === 'free' @@ -78,7 +80,10 @@ export function AccountStep({ ) : null} {!codexBadge?.label && identity.detailLabel && ( - + {identity.detailLabel} )} diff --git a/ui/src/components/setup/wizard/steps/variant-step.tsx b/ui/src/components/setup/wizard/steps/variant-step.tsx index 394212de..396cfe2b 100644 --- a/ui/src/components/setup/wizard/steps/variant-step.tsx +++ b/ui/src/components/setup/wizard/steps/variant-step.tsx @@ -89,6 +89,7 @@ export function VariantStep({ variant="outline" className={cn( 'text-[10px] h-4 px-1.5 border-transparent', + privacyMode && PRIVACY_BLUR_CLASS, selectedCodexBadge.audience === 'business' ? 'bg-sky-500/12 text-sky-700 dark:text-sky-300' : selectedCodexBadge.audience === 'free' @@ -103,6 +104,7 @@ export function VariantStep({ variant="outline" className={cn( 'text-[10px] h-4 px-1.5 border-transparent', + privacyMode && PRIVACY_BLUR_CLASS, selectedAccountIdentity.audience === 'business' ? 'bg-sky-500/12 text-sky-700 dark:text-sky-300' : selectedAccountIdentity.audience === 'free' @@ -114,7 +116,10 @@ export function VariantStep({ ) : null} {!selectedCodexBadge?.label && selectedAccountIdentity?.detailLabel && ( - + {selectedAccountIdentity.detailLabel} )} diff --git a/ui/tests/components/account/account-surface-card.test.tsx b/ui/tests/components/account/account-surface-card.test.tsx new file mode 100644 index 00000000..c65456dc --- /dev/null +++ b/ui/tests/components/account/account-surface-card.test.tsx @@ -0,0 +1,48 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { AccountSurfaceCard } from '@/components/account/shared/account-surface-card'; + +describe('AccountSurfaceCard privacy titles', () => { + it('keeps sensitive account title attributes available outside privacy mode', () => { + const { container } = render( + + ); + + expect(screen.getByText('person@example.com')).toHaveAttribute('title', 'person@example.com'); + expect(container.querySelector('[title="Business"]')).toBeInTheDocument(); + expect(container.querySelector('[title="Workspace abcdef12"]')).toBeInTheDocument(); + }); + + it('removes sensitive account title attributes in privacy mode', () => { + const { container } = render( + <> + + + + ); + + expect(screen.getByText('person@example.com')).not.toHaveAttribute('title'); + expect(screen.getByText('codex@example.com')).not.toHaveAttribute('title'); + expect(container.querySelector('[title="Business"]')).not.toBeInTheDocument(); + expect(container.querySelector('[title="Workspace abcdef12"]')).not.toBeInTheDocument(); + expect(container.querySelector('[title*="Enterprise"]')).not.toBeInTheDocument(); + }); +}); diff --git a/ui/tests/unit/components/setup/account-identity-privacy.test.tsx b/ui/tests/unit/components/setup/account-identity-privacy.test.tsx new file mode 100644 index 00000000..0dc704fa --- /dev/null +++ b/ui/tests/unit/components/setup/account-identity-privacy.test.tsx @@ -0,0 +1,55 @@ +import { render, screen } from '@tests/setup/test-utils'; +import { describe, expect, it, vi } from 'vitest'; +import { AccountStep } from '@/components/setup/wizard/steps/account-step'; +import { VariantStep } from '@/components/setup/wizard/steps/variant-step'; +import type { OAuthAccount } from '@/lib/api-client'; + +const businessAccount: OAuthAccount = { + id: 'victim@example.com#04a0f049-team', + email: 'victim@example.com', + provider: 'gemini', + isDefault: false, + tokenFile: 'gemini-victim@example.com-04a0f049-team.json', + createdAt: '2026-01-01T00:00:00.000Z', +}; + +describe('setup account identity privacy', () => { + it('blurs account metadata badges in the account selection step', () => { + render( + + ); + + expect(screen.getByText('victim@example.com')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Business')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Workspace 04a0f049')).toHaveClass('blur-[4px]'); + }); + + it('blurs account metadata badges in the variant step', () => { + render( + + ); + + expect(screen.getByText('victim@example.com')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Business')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Workspace 04a0f049')).toHaveClass('blur-[4px]'); + }); +}); diff --git a/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx b/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx index 64d88fd0..dce54089 100644 --- a/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx +++ b/ui/tests/unit/ui/components/account/shared/account-surface-card.test.tsx @@ -146,4 +146,42 @@ describe('AccountSurfaceCard', () => { expect(screen.queryByText('Pers')).not.toBeInTheDocument(); expect(screen.queryByTitle('Free')).not.toBeInTheDocument(); }); + + it('blurs detailed account identity metadata when privacy mode is enabled', () => { + render( + + ); + + expect(screen.getByText('victim@example.com')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Business')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Workspace 04a0f049')).toHaveClass('blur-[4px]'); + }); + + it('blurs compact account identity metadata when privacy mode is enabled', () => { + render( + + ); + + expect(screen.getByText('victim@example.com')).toHaveClass('blur-[4px]'); + expect(screen.getByText('Biz')).toHaveClass('blur-[4px]'); + expect(screen.getByText('04a0f049')).toHaveClass('blur-[4px]'); + }); });