@@ -214,16 +214,47 @@ async function isPathAllowed(pathToCheck: string): Promise<boolean> {
214214 return isAllowed ;
215215}
216216
217+ /**
218+ * Check if a path is within a read-only directory
219+ * @param checkPath The path to check
220+ * @returns Promise<boolean> True if the path is read-only
221+ */
222+ async function isPathReadOnly ( checkPath : string ) : Promise < boolean > {
223+ const config = await configManager . getConfig ( ) ;
224+ const readOnlyDirs = config . readOnlyDirectories || [ ] ;
225+
226+ if ( readOnlyDirs . length === 0 ) {
227+ return false ; // No read-only directories configured
228+ }
229+
230+ const normalizedCheckPath = path . normalize ( checkPath ) . toLowerCase ( ) ;
231+
232+ for ( const dir of readOnlyDirs ) {
233+ const expandedDir = expandHome ( dir ) ;
234+ const normalizedDir = path . normalize ( expandedDir ) . toLowerCase ( ) ;
235+
236+ // Check if the path is within the read-only directory
237+ if ( normalizedCheckPath === normalizedDir ||
238+ normalizedCheckPath . startsWith ( normalizedDir + path . sep ) ) {
239+ return true ;
240+ }
241+ }
242+
243+ return false ;
244+ }
245+
217246/**
218247 * Validates a path to ensure it can be accessed or created.
219248 * For existing paths, returns the real path (resolving symlinks).
220249 * For non-existent paths, validates parent directories to ensure they exist.
250+ * For write operations, also checks if the path is read-only.
221251 *
222252 * @param requestedPath The path to validate
253+ * @param isWriteOperation Whether this is a write operation (default: false)
223254 * @returns Promise<string> The validated path
224- * @throws Error if the path or its parent directories don't exist or if the path is not allowed
255+ * @throws Error if the path or its parent directories don't exist or if the path is not allowed or read-only
225256 */
226- export async function validatePath ( requestedPath : string ) : Promise < string > {
257+ export async function validatePath ( requestedPath : string , isWriteOperation : boolean = false ) : Promise < string > {
227258 const validationOperation = async ( ) : Promise < string > => {
228259 // Expand home directory if present
229260 const expandedPath = expandHome ( requestedPath ) ;
@@ -243,6 +274,16 @@ export async function validatePath(requestedPath: string): Promise<string> {
243274 throw new Error ( `Path not allowed: ${ requestedPath } . Must be within one of these directories: ${ ( await getAllowedDirs ( ) ) . join ( ', ' ) } ` ) ;
244275 }
245276
277+ // Check if path is read-only for write operations
278+ if ( isWriteOperation && await isPathReadOnly ( absolute ) ) {
279+ capture ( 'server_path_validation_error' , {
280+ error : 'Path is read-only' ,
281+ operation : 'write'
282+ } ) ;
283+
284+ throw new Error ( `Path is read-only: ${ requestedPath } . This directory is protected from modifications.` ) ;
285+ }
286+
246287 // Check if path exists
247288 try {
248289 const stats = await fs . stat ( absolute ) ;
@@ -828,7 +869,7 @@ function splitLinesPreservingEndings(content: string): string[] {
828869}
829870
830871export async function writeFile ( filePath : string , content : string , mode : 'rewrite' | 'append' = 'rewrite' ) : Promise < void > {
831- const validPath = await validatePath ( filePath ) ;
872+ const validPath = await validatePath ( filePath , true ) ; // Mark as write operation
832873
833874 // Get file extension for telemetry
834875 const fileExtension = getFileExtension ( validPath ) ;
@@ -886,7 +927,7 @@ export async function readMultipleFiles(paths: string[]): Promise<MultiFileResul
886927}
887928
888929export async function createDirectory ( dirPath : string ) : Promise < void > {
889- const validPath = await validatePath ( dirPath ) ;
930+ const validPath = await validatePath ( dirPath , true ) ; // Creating directory is a write operation
890931 await fs . mkdir ( validPath , { recursive : true } ) ;
891932}
892933
@@ -897,8 +938,8 @@ export async function listDirectory(dirPath: string): Promise<string[]> {
897938}
898939
899940export async function moveFile ( sourcePath : string , destinationPath : string ) : Promise < void > {
900- const validSourcePath = await validatePath ( sourcePath ) ;
901- const validDestPath = await validatePath ( destinationPath ) ;
941+ const validSourcePath = await validatePath ( sourcePath , true ) ; // Source needs write permission (to delete)
942+ const validDestPath = await validatePath ( destinationPath , true ) ; // Destination needs write permission
902943 await fs . rename ( validSourcePath , validDestPath ) ;
903944}
904945
0 commit comments