From 1b28395abf5f8d8a3160990b2b8eb0a96d988d94 Mon Sep 17 00:00:00 2001 From: Devin Gould Date: Thu, 1 Oct 2026 10:56:15 -0400 Subject: [PATCH] fix(ui): Don't label agent sessions as impersonation in Active devices Co-Authored-By: Claude Opus 5.5 --- .changeset/active-devices-agent-sessions.md | 5 +++ .../UserProfile/ActiveDevicesSection.tsx | 4 +- .../__tests__/SecurityPage.test.tsx | 44 ++++++++++++++++++- 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 .changeset/active-devices-agent-sessions.md diff --git a/.changeset/active-devices-agent-sessions.md b/.changeset/active-devices-agent-sessions.md new file mode 100644 index 00000000000..8efd5e0774e --- /dev/null +++ b/.changeset/active-devices-agent-sessions.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix the Active devices list in `` treating Agent Task sessions as impersonation. Agent sessions no longer show the "Other impersonator device" badge, and an agent viewing the list no longer sees a red "This device" badge or "User device" badges on the user's other devices. diff --git a/packages/ui/src/components/UserProfile/ActiveDevicesSection.tsx b/packages/ui/src/components/UserProfile/ActiveDevicesSection.tsx index a8215cd8c94..9c5a767d9c7 100644 --- a/packages/ui/src/components/UserProfile/ActiveDevicesSection.tsx +++ b/packages/ui/src/components/UserProfile/ActiveDevicesSection.tsx @@ -93,8 +93,8 @@ const DeviceItem = ({ session }: { session: SessionWithActivitiesResource }) => const DeviceInfo = (props: { session: SessionWithActivitiesResource }) => { const { session } = useSession(); const isCurrent = session?.id === props.session.id; - const isCurrentlyImpersonating = !!session?.actor; - const isImpersonationSession = !!props.session.actor; + const isCurrentlyImpersonating = !!session?.actor && !session.agent; + const isImpersonationSession = !!props.session.actor && props.session.actor.type !== 'agent'; const { city, country, browserName, browserVersion, deviceType, ipAddress, isMobile } = props.session.latestActivity; const title = deviceType ? deviceType : isMobile ? 'Mobile device' : 'Desktop device'; const browser = `${browserName || ''} ${browserVersion || ''}`.trim() || 'Web browser'; diff --git a/packages/ui/src/components/UserProfile/__tests__/SecurityPage.test.tsx b/packages/ui/src/components/UserProfile/__tests__/SecurityPage.test.tsx index 01884a4a37a..58132eb700c 100644 --- a/packages/ui/src/components/UserProfile/__tests__/SecurityPage.test.tsx +++ b/packages/ui/src/components/UserProfile/__tests__/SecurityPage.test.tsx @@ -1,4 +1,4 @@ -import type { SessionWithActivitiesResource } from '@clerk/shared/types'; +import type { ActClaim, SessionWithActivitiesResource } from '@clerk/shared/types'; import { within } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -367,6 +367,48 @@ describe('SecurityPage', () => { }); }); + it('does not label agent sessions as impersonation', async () => { + const agentActor: ActClaim = { sub: 'agent_123', type: 'agent', task_id: 'agt_task_123' }; + const makeSession = (id: string, deviceType: string, actor: ActClaim | null) => + ({ + pathRoot: '/me/sessions', + id, + status: 'active', + expireAt: '2022-12-01T01:55:44.636Z', + abandonAt: '2022-12-24T01:55:44.636Z', + lastActiveAt: '2022-11-24T12:11:49.328Z', + latestActivity: { + id: `sess_activity_${id}`, + deviceType, + browserName: 'Chrome', + browserVersion: '107.0.0.0', + country: 'Greece', + city: 'Athens', + isMobile: false, + }, + actor, + revoke: vi.fn().mockResolvedValue({}), + }) as any as SessionWithActivitiesResource; + + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({ email_addresses: ['test@clerk.com'], actor: agentActor }); + }); + fixtures.clerk.user!.getSessions.mockReturnValue( + Promise.resolve([ + makeSession(fixtures.clerk.session!.id, 'Macintosh', agentActor), + makeSession('sess_agent', 'Macintosh', { ...agentActor, task_id: 'agt_task_456' }), + makeSession('sess_impersonator', 'Windows', { sub: 'user_impersonator' }), + makeSession('sess_user', 'Macintosh', null), + ]), + ); + + render(, { wrapper }); + + expect(await screen.findByText('This device')).toHaveAttribute('data-color', 'primary'); + expect(screen.getByText('Other impersonator device').parentElement).toHaveTextContent('Windows'); + expect(screen.queryByText('User device')).not.toBeInTheDocument(); + }); + it('does not leak the previous user device activity across a user switch', async () => { const makeSession = (sessionId: string, city: string) => ({