@@ -567,6 +567,68 @@ const platformConfirmationRequiredPaths = (
567567 : [ ]
568568) . concat ( allPlatformPatterns ) . map ( p => glob . parse ( p ) ) ;
569569
570+ /**
571+ * Validates that a path doesn't contain suspicious characters that could be used
572+ * to bypass security checks on Windows (e.g., NTFS Alternate Data Streams, invalid chars).
573+ * Throws an error if the path is suspicious.
574+ */
575+ export function assertPathIsSafe ( fsPath : string , _isWindows = isWindows ) : void {
576+ if ( fsPath . includes ( '\0' ) ) {
577+ throw new Error ( `Path contains null bytes: ${ fsPath } ` ) ;
578+ }
579+
580+ if ( ! _isWindows ) {
581+ return ;
582+ }
583+
584+ // Check for NTFS Alternate Data Streams (ADS)
585+ const colonIndex = fsPath . indexOf ( ':' , 2 ) ;
586+ if ( colonIndex !== - 1 ) {
587+ throw new Error ( `Path contains invalid characters (alternate data stream): ${ fsPath } ` ) ;
588+ }
589+
590+ // Check for invalid Windows filename characters
591+ const invalidChars = / [ < > " | ? * ] / ;
592+ const pathAfterDrive = fsPath . length > 2 ? fsPath . substring ( 2 ) : fsPath ;
593+ if ( invalidChars . test ( pathAfterDrive ) ) {
594+ throw new Error ( `Path contains invalid characters: ${ fsPath } ` ) ;
595+ }
596+
597+ // Check for named pipes or device paths
598+ if ( fsPath . startsWith ( '\\\\.' ) || fsPath . startsWith ( '\\\\?' ) ) {
599+ throw new Error ( `Path is a reserved device path: ${ fsPath } ` ) ;
600+ }
601+
602+ const reserved = / ^ ( C O N | P R N | A U X | N U L | C O M [ 1 - 9 ] | L P T [ 1 - 9 ] ) ( \. | $ ) / i;
603+
604+ // Check for trailing dots and spaces on path components (Windows quirk)
605+ const parts = fsPath . split ( '\\' ) ;
606+ for ( const part of parts ) {
607+ if ( part . length === 0 ) {
608+ continue ;
609+ }
610+
611+ // Reserved device names. Would error on edit, but fail explicitly
612+ if ( reserved . test ( part ) ) {
613+ throw new Error ( `Reserved device name in path: ${ fsPath } ` ) ;
614+ }
615+
616+ // Check for trailing dots or spaces
617+ if ( part . endsWith ( '.' ) || part . endsWith ( ' ' ) ) {
618+ throw new Error ( `Path contains invalid trailing characters: ${ fsPath } ` ) ;
619+ }
620+
621+ // Check for 8.3 short filename pattern
622+ const tildeIndex = part . indexOf ( '~' ) ;
623+ if ( tildeIndex !== - 1 ) {
624+ const afterTilde = part . substring ( tildeIndex + 1 ) ;
625+ if ( afterTilde . length > 0 && / ^ \d / . test ( afterTilde ) ) {
626+ throw new Error ( `Path appears to use short filename format (8.3 names): ${ fsPath } . Please use the full path.` ) ;
627+ }
628+ }
629+ }
630+ }
631+
570632const enum ConfirmationCheckResult {
571633 NoConfirmation ,
572634 NoPermissions ,
@@ -612,6 +674,8 @@ function makeUriConfirmationChecker(configuration: IConfigurationService, worksp
612674 let ok = true ;
613675 let fsPath = uri . fsPath ;
614676
677+ assertPathIsSafe ( fsPath ) ;
678+
615679 if ( platformConfirmationRequiredPaths . some ( p => p ( fsPath ) ) ) {
616680 return ConfirmationCheckResult . SystemFile ;
617681 }
@@ -635,6 +699,8 @@ function makeUriConfirmationChecker(configuration: IConfigurationService, worksp
635699 if ( uri . scheme === Schemas . file ) {
636700 try {
637701 const linked = await realpath ( uri . fsPath ) ;
702+ assertPathIsSafe ( linked ) ;
703+
638704 if ( linked !== uri . fsPath ) {
639705 toCheck . push ( URI . file ( linked ) ) ;
640706 }
0 commit comments