@@ -2,17 +2,41 @@ import fs from "fs";
22import ignore from "ignore" ;
33import path from "path" ;
44
5- const settingsPath = "~/.pulse-editor/settings.json" ;
6-
5+ // Define a safe root directory for projects. Can be overridden by env or configured as needed.
6+ const PROJECTS_ROOT = process . env . PROJECTS_ROOT ?? "/srv/projects" ;
7+
8+ // Utility to resolve and validate user-supplied uri inside PROJECTS_ROOT
9+ function getSafePath ( uri : string ) : string {
10+ // Prevent empty/undefined input
11+ if ( ! uri || typeof uri !== 'string' ) {
12+ throw new Error ( "Invalid project path" ) ;
13+ }
14+ // Resolve against the root directory
15+ const resolved = path . resolve ( PROJECTS_ROOT , uri ) ;
16+ // Use fs.realpathSync to follow symlinks
17+ let normalized ;
18+ try {
19+ normalized = fs . realpathSync ( resolved ) ;
20+ } catch {
21+ // If path does not exist yet (e.g., on creation), just use resolved.
22+ normalized = resolved ;
23+ }
24+ // Ensure the normalized path is inside the root
25+ if ( ! normalized . startsWith ( PROJECTS_ROOT ) ) {
26+ throw new Error ( "Access to paths outside projects root denied" ) ;
27+ }
28+ return normalized ;
29+ }
730// List all folders in a path
831async function handleListProjects ( uri : string ) {
9- const files = await fs . promises . readdir ( uri , { withFileTypes : true } ) ;
32+ const rootPath = getSafePath ( uri ) ;
33+ const files = await fs . promises . readdir ( rootPath , { withFileTypes : true } ) ;
1034 const folders = files
1135 . filter ( ( file ) => file . isDirectory ( ) )
1236 . map ( ( file ) => file . name )
1337 . map ( ( projectName ) => ( {
1438 name : projectName ,
15- ctime : fs . statSync ( path . join ( uri , projectName ) ) . ctime ,
39+ ctime : fs . statSync ( path . join ( rootPath , projectName ) ) . ctime ,
1640 } ) ) ;
1741
1842 return folders ;
@@ -23,7 +47,8 @@ async function listPathContent(
2347 options : any ,
2448 baseUri : string | undefined = undefined
2549) {
26- const files = await fs . promises . readdir ( uri , { withFileTypes : true } ) ;
50+ const rootPath = getSafePath ( uri ) ;
51+ const files = await fs . promises . readdir ( rootPath , { withFileTypes : true } ) ;
2752
2853 const promise : Promise < any > [ ] = files
2954 // Filter by file type
@@ -50,7 +75,7 @@ async function listPathContent(
5075 } )
5176 . map ( async ( file ) => {
5277 const name = file . name ;
53- const absoluteUri = path . join ( uri , name ) ;
78+ const absoluteUri = path . join ( rootPath , name ) ;
5479 if ( file . isDirectory ( ) ) {
5580 return {
5681 name : name ,
@@ -78,55 +103,54 @@ async function handleListPathContent(uri: string, options: any) {
78103}
79104
80105async function handleCreateProject ( uri : string ) {
81- // Create a folder at the uri
82- await fs . promises . mkdir ( uri ) ;
106+ // Create a folder at the validated path
107+ await fs . promises . mkdir ( getSafePath ( uri ) ) ;
83108}
84109
85110async function handleCreateFolder ( uri : string ) {
86- // Create a folder at the uri
87- await fs . promises . mkdir ( uri ) ;
111+ // Create a folder at the validated path
112+ await fs . promises . mkdir ( getSafePath ( uri ) ) ;
88113}
89114
90115async function handleCreateFile ( uri : string ) {
91- // Create a file at the uri
92- await fs . promises . writeFile ( uri , "" ) ;
116+ // Create a file at the validated path
117+ await fs . promises . writeFile ( getSafePath ( uri ) , "" ) ;
93118}
94119
95120async function handleRename ( oldUri : string , newUri : string ) {
96- await fs . promises . rename ( oldUri , newUri ) ;
121+ await fs . promises . rename ( getSafePath ( oldUri ) , getSafePath ( newUri ) ) ;
97122}
98123
99124async function handleDelete ( uri : string ) {
100- await fs . promises . rm ( uri , { recursive : true , force : true } ) ;
125+ await fs . promises . rm ( getSafePath ( uri ) , { recursive : true , force : true } ) ;
101126}
102127
103128async function handleHasPath ( uri : string ) {
104- return fs . existsSync ( uri ) ;
129+ return fs . existsSync ( getSafePath ( uri ) ) ;
105130}
106131
107132async function handleReadFile ( uri : string ) {
108- // Read the file at path
109- const data = await fs . promises . readFile ( uri , "utf-8" ) ;
133+ // Read the file at validated path
134+ const data = await fs . promises . readFile ( getSafePath ( uri ) , "utf-8" ) ;
110135
111136 return data ;
112137}
113138
114139async function handleWriteFile ( data : any , uri : string ) {
115- // Write the data at path
116- // await fs.promises.writeFile(path, data);
117- // create path if it doesn't exist
118- const dir = uri . split ( "/" ) . slice ( 0 , - 1 ) . join ( "/" ) ;
119-
140+ // Write the data at validated path
141+ const safePath = getSafePath ( uri ) ;
142+ // create parent directory if it doesn't exist
143+ const dir = path . dirname ( safePath ) ;
120144 if ( ! fs . existsSync ( dir ) ) {
121145 fs . mkdirSync ( dir , { recursive : true } ) ;
122146 }
123147
124- fs . writeFileSync ( uri , data ) ;
148+ fs . writeFileSync ( safePath , data ) ;
125149}
126150
127151async function handleCopyFiles ( from : string , to : string ) {
128- // Copy the files from the from path to the to path
129- await fs . promises . cp ( from , to , { recursive : true } ) ;
152+ // Copy the files from the validated from path to the validated to path
153+ await fs . promises . cp ( getSafePath ( from ) , getSafePath ( to ) , { recursive : true } ) ;
130154}
131155
132156async function handleLoadSettings ( ) {
0 commit comments