@@ -1317,41 +1317,65 @@ export namespace ESLint {
13171317 hasPackageJson : false
13181318 } ;
13191319
1320+ let files : string [ ] ;
1321+ try {
1322+ files = fs . readdirSync ( directory ) ;
1323+ } catch {
1324+ // Directory doesn't exist or can't be read
1325+ return indicators ;
1326+ }
1327+
1328+ const fileSet = new Set ( files ) ;
1329+
13201330 for ( const fileName of flatConfigFiles ) {
1321- if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1331+ if ( fileSet . has ( fileName ) ) {
13221332 indicators . flatConfigs . push ( fileName ) ;
13231333 }
13241334 }
13251335
13261336 for ( const fileName of legacyConfigFiles ) {
1327- if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1337+ if ( fileSet . has ( fileName ) ) {
13281338 indicators . legacyConfigs . push ( fileName ) ;
13291339 }
13301340 }
13311341
13321342 for ( const fileName of lockfileAndWorkspaceFiles ) {
1333- if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1343+ if ( fileSet . has ( fileName ) ) {
13341344 indicators . lockfiles . push ( fileName ) ;
13351345 }
13361346 }
13371347
1338- if ( fs . existsSync ( path . join ( directory , 'package.json' ) ) ) {
1348+ if ( fileSet . has ( 'package.json' ) ) {
13391349 indicators . hasPackageJson = true ;
13401350 }
13411351
13421352 return indicators ;
13431353 }
13441354
1355+ // Safeguard: maximum 50 levels of traversal
1356+ // to avoid infinite loops
1357+ const maxTraversalIterations = 50 ;
1358+
13451359 function traverseUpwards ( startDirectory : string , workspaceFolder : string ) : DirectoryIndicators [ ] {
13461360 const candidates : DirectoryIndicators [ ] = [ ] ;
13471361 let directory : string | undefined = startDirectory ;
1362+ // Normalize workspace folder once since it comes from config
1363+ const normalizedWorkspace = path . normalize ( workspaceFolder ) ;
1364+
1365+ let iterations = 0 ;
13481366
1349- while ( directory !== undefined && directory . startsWith ( workspaceFolder ) ) {
1367+ while ( directory !== undefined && iterations < maxTraversalIterations ) {
1368+ // Check if we're still within workspace
1369+ if ( ! directory . startsWith ( normalizedWorkspace ) ) {
1370+ break ;
1371+ }
1372+
13501373 const indicators = collectProjectIndicators ( directory ) ;
13511374 candidates . push ( indicators ) ;
13521375
13531376 const parent = path . dirname ( directory ) ;
13541377 directory = parent !== directory ? parent : undefined ;
1378+ iterations ++ ;
13551379 }
13561380
13571381 return candidates ;
@@ -1367,7 +1391,7 @@ export namespace ESLint {
13671391 ? candidates . slice ( candidates . indexOf ( lockfileRoot ) ) . find ( c => c . flatConfigs . length > 0 )
13681392 : undefined ;
13691393
1370- const uppermostPackageJson = [ ... candidates ] . reverse ( ) . find ( c => c . hasPackageJson ) ;
1394+ const uppermostPackageJson = candidates . findLast ( c => c . hasPackageJson ) ;
13711395
13721396 // Priority 1: Flat config at or above lockfile root (best practice)
13731397 if ( lockfileRoot && flatConfigAtOrAboveLockfile ) {
0 commit comments