@@ -22,6 +22,13 @@ export interface SessionDetail {
2222 isRemote ?: boolean ;
2323}
2424
25+ const UUID_RE = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
26+
27+ /** Validate that a session ID is a well-formed UUID (prevents path traversal) */
28+ export function isValidSessionId ( id : string ) : boolean {
29+ return UUID_RE . test ( id ) ;
30+ }
31+
2532/** Resolve the session-state root directory */
2633export function getSessionStateDir ( ) : string {
2734 const base = config . copilotConfigDir || join ( homedir ( ) , '.copilot' ) ;
@@ -113,6 +120,7 @@ export async function enrichSessionMetadata(
113120
114121/** Get full session detail for the preview panel */
115122export async function getSessionDetail ( sessionId : string ) : Promise < SessionDetail | null > {
123+ if ( ! isValidSessionId ( sessionId ) ) return null ;
116124 const sessionDir = join ( getSessionStateDir ( ) , sessionId ) ;
117125 if ( ! await pathExists ( sessionDir ) ) return null ;
118126
@@ -216,11 +224,9 @@ export async function listSessionsFromFilesystem(): Promise<FilesystemSession[]>
216224 }
217225
218226 // UUID pattern — only pick directories that look like session IDs
219- const uuidRe = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
220-
221227 const results = await Promise . all (
222228 entries
223- . filter ( ( name ) => uuidRe . test ( name ) )
229+ . filter ( ( name ) => isValidSessionId ( name ) )
224230 . map ( async ( sessionId ) : Promise < FilesystemSession | null > => {
225231 const sessionDir = join ( stateDir , sessionId ) ;
226232
@@ -267,6 +273,7 @@ export async function listSessionsFromFilesystem(): Promise<FilesystemSession[]>
267273 * Used as a fallback when resumeSession() fails for bundled/filesystem-only sessions.
268274 */
269275export async function buildSessionContext ( sessionId : string ) : Promise < string | null > {
276+ if ( ! isValidSessionId ( sessionId ) ) return null ;
270277 const sessionDir = join ( getSessionStateDir ( ) , sessionId ) ;
271278 if ( ! await pathExists ( sessionDir ) ) return null ;
272279
@@ -339,8 +346,7 @@ export async function buildSessionContext(sessionId: string): Promise<string | n
339346 * (e.g. bundled or filesystem-only sessions).
340347 */
341348export async function deleteSessionFromFilesystem ( sessionId : string ) : Promise < boolean > {
342- const uuidRe = / ^ [ 0 - 9 a - f ] { 8 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 4 } - [ 0 - 9 a - f ] { 12 } $ / i;
343- if ( ! uuidRe . test ( sessionId ) ) return false ;
349+ if ( ! isValidSessionId ( sessionId ) ) return false ;
344350
345351 const sessionDir = join ( getSessionStateDir ( ) , sessionId ) ;
346352 if ( ! await pathExists ( sessionDir ) ) return false ;
0 commit comments