@@ -89,6 +89,20 @@ async function listWorkspaceDirectory(
8989 relativePath ?: string
9090) : Promise < Result < FileTreeNode [ ] > > {
9191 try {
92+ // Validate relativePath doesn't escape workspace
93+ if ( relativePath ) {
94+ // Reject absolute paths
95+ if ( path . isAbsolute ( relativePath ) ) {
96+ return Err ( "Absolute paths are not allowed" ) ;
97+ }
98+ // Normalize and verify it stays within workspace
99+ const resolved = path . resolve ( workspacePath , relativePath ) ;
100+ const normalizedWorkspace = path . resolve ( workspacePath ) ;
101+ if ( ! resolved . startsWith ( normalizedWorkspace + path . sep ) && resolved !== normalizedWorkspace ) {
102+ return Err ( "Path traversal not allowed" ) ;
103+ }
104+ }
105+
92106 const targetPath = relativePath ? path . join ( workspacePath , relativePath ) : workspacePath ;
93107 const normalizedPath = path . resolve ( targetPath ) ;
94108
@@ -102,7 +116,9 @@ async function listWorkspaceDirectory(
102116 . map ( ( entry ) => {
103117 const entryPath = relativePath ? path . join ( relativePath , entry . name ) : entry . name ;
104118 // For directories, append / to match gitignore directory patterns
105- const pathToCheck = entry . isDirectory ( ) ? `${ entryPath } /` : entryPath ;
119+ // Use POSIX separators for gitignore matching (Windows uses backslashes)
120+ const posixPath = entryPath . split ( path . sep ) . join ( "/" ) ;
121+ const pathToCheck = entry . isDirectory ( ) ? `${ posixPath } /` : posixPath ;
106122 const ignored = ig ? ig . ignores ( pathToCheck ) : false ;
107123
108124 return {
0 commit comments