fix(security): encode session navigation parameters (#11232)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const navigateMock = vi.hoisted(() => vi.fn());
|
||||
const routerState = vi.hoisted(() => ({ searchParams: new URLSearchParams() }));
|
||||
|
||||
vi.mock('react-router', () => ({
|
||||
useNavigate: () => navigateMock,
|
||||
useLocation: () => ({ pathname: '/pair', search: routerState.searchParams.toString() }),
|
||||
useSearchParams: () => [routerState.searchParams, vi.fn()],
|
||||
}));
|
||||
|
||||
vi.mock('../acp/sessions', () => ({
|
||||
acpGetSessionListItem: vi.fn(() => new Promise(() => {})),
|
||||
acpListRecentSessions: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
vi.mock('../contexts/ChatContext', () => ({
|
||||
useChatContext: () => ({ chat: { sessionId: undefined } }),
|
||||
}));
|
||||
|
||||
import { useNavigationSessions } from './useNavigationSessions';
|
||||
|
||||
const injectedSessionId = 'session-1&shouldStartAgent=true';
|
||||
|
||||
function expectSingleSessionParameter(target: string) {
|
||||
const url = new URL(target, 'http://localhost');
|
||||
expect(url.searchParams.get('resumeSessionId')).toBe(injectedSessionId);
|
||||
expect(url.searchParams.get('shouldStartAgent')).toBeNull();
|
||||
}
|
||||
|
||||
describe('useNavigationSessions', () => {
|
||||
beforeEach(() => {
|
||||
navigateMock.mockReset();
|
||||
routerState.searchParams = new URLSearchParams();
|
||||
});
|
||||
|
||||
it('keeps a selected session ID in one query parameter', () => {
|
||||
const { result } = renderHook(() => useNavigationSessions());
|
||||
|
||||
act(() => result.current.handleSessionClick(injectedSessionId));
|
||||
|
||||
expectSingleSessionParameter(navigateMock.mock.calls[0][0]);
|
||||
});
|
||||
|
||||
it('keeps a retained session ID in one query parameter', () => {
|
||||
routerState.searchParams = new URLSearchParams({ resumeSessionId: injectedSessionId });
|
||||
const { result } = renderHook(() => useNavigationSessions());
|
||||
|
||||
act(() => result.current.handleNavClick('/pair'));
|
||||
|
||||
expectSingleSessionParameter(navigateMock.mock.calls[0][0]);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,11 @@ import { groupSessionsByProject } from '../utils/projectSessions';
|
||||
|
||||
const MAX_RECENT_SESSIONS = 25;
|
||||
|
||||
function pairSessionPath(sessionId: string): string {
|
||||
const searchParams = new URLSearchParams({ resumeSessionId: sessionId });
|
||||
return `/pair?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
export function prependUnique(
|
||||
prev: SessionListItem[],
|
||||
session: SessionListItem
|
||||
@@ -188,7 +193,7 @@ export function useNavigationSessions() {
|
||||
const sessionId =
|
||||
currentSessionId || lastSessionIdRef.current || chatContext?.chat?.sessionId;
|
||||
if (sessionId && sessionId.length > 0) {
|
||||
navigate(`/pair?resumeSessionId=${sessionId}`);
|
||||
navigate(pairSessionPath(sessionId));
|
||||
} else {
|
||||
navigate('/');
|
||||
}
|
||||
@@ -201,7 +206,7 @@ export function useNavigationSessions() {
|
||||
|
||||
const handleSessionClick = useCallback(
|
||||
(sessionId: string) => {
|
||||
navigate(`/pair?resumeSessionId=${sessionId}`);
|
||||
navigate(pairSessionPath(sessionId));
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user