@@ -3,8 +3,30 @@ import ignore from "ignore";
33import path from "path" ;
44
55// Define a safe root directory for projects. Can be overridden by env or configured as needed.
6+ // All incoming URIs will be resolved and validated to ensure they don't escape this root.
7+ const SAFE_ROOT = path . resolve (
8+ process . env . PLATFORM_API_ROOT ?? "/pulse-editor" ,
9+ ) ;
610
7- const settingsPath = path . join ( "/pulse-editor" , "settings.json" ) ;
11+ const settingsPath = path . join ( SAFE_ROOT , "settings.json" ) ;
12+
13+ function safeResolve ( uri : string ) : string {
14+ if ( ! uri || typeof uri !== "string" ) {
15+ throw new Error ( "Invalid path" ) ;
16+ }
17+
18+ // Canonicalize the SAFE_ROOT once for this function
19+ const rootPath = path . resolve ( SAFE_ROOT ) ;
20+ // Combine and normalize the user input relative to the safe root
21+ const candidate = path . resolve ( SAFE_ROOT , uri ) ;
22+
23+ // Check that candidate is strictly under rootPath (or equal to rootPath)
24+ if ( candidate === rootPath || candidate . startsWith ( rootPath + path . sep ) ) {
25+ return candidate ;
26+ }
27+
28+ throw new Error ( "Can only access paths within the project home directory." ) ;
29+ }
830
931export async function handlePlatformAPIRequest (
1032 data : {
@@ -110,10 +132,9 @@ export async function handlePlatformAPIRequest(
110132 }
111133}
112134
113-
114135// List all folders in a path
115136async function handleListProjects ( uri : string ) {
116- const rootPath = uri ;
137+ const rootPath = safeResolve ( uri ) ;
117138 const files = await fs . promises . readdir ( rootPath , { withFileTypes : true } ) ;
118139 const folders = files
119140 . filter ( ( file ) => file . isDirectory ( ) )
@@ -131,7 +152,7 @@ async function listPathContent(
131152 options : any ,
132153 baseUri : string | undefined = undefined ,
133154) {
134- const rootPath = uri ;
155+ const rootPath = safeResolve ( uri ) ;
135156 const files = await fs . promises . readdir ( rootPath , { withFileTypes : true } ) ;
136157
137158 const promise : Promise < any > [ ] = files
@@ -188,12 +209,14 @@ async function handleListPathContent(uri: string, options: any) {
188209
189210async function handleCreateProject ( uri : string ) {
190211 // Create a folder at the validated path
191- await fs . promises . mkdir ( uri ) ;
212+ const safe = safeResolve ( uri ) ;
213+ await fs . promises . mkdir ( safe , { recursive : true } ) ;
192214}
193215
194216async function handleDeleteProject ( uri : string ) {
195217 // Delete the folder at the validated path
196- await fs . promises . rm ( uri , { recursive : true , force : true } ) ;
218+ const safe = safeResolve ( uri ) ;
219+ await fs . promises . rm ( safe , { recursive : true , force : true } ) ;
197220}
198221
199222async function handleUpdateProject (
@@ -203,51 +226,59 @@ async function handleUpdateProject(
203226 ctime ?: Date ;
204227 } ,
205228) {
206- const newUri = path . join ( path . dirname ( uri ) , updatedInfo . name ) ;
207- await fs . promises . rename ( uri , newUri ) ;
229+ const safeOld = safeResolve ( uri ) ;
230+ const newPathCandidate = path . join ( path . dirname ( safeOld ) , updatedInfo . name ) ;
231+ const safeNew = safeResolve ( newPathCandidate ) ;
232+ await fs . promises . rename ( safeOld , safeNew ) ;
208233}
209234
210235async function handleCreateFolder ( uri : string ) {
211236 // Create a folder at the validated path
212- await fs . promises . mkdir ( uri ) ;
237+ const safe = safeResolve ( uri ) ;
238+ await fs . promises . mkdir ( safe , { recursive : true } ) ;
213239}
214240
215241async function handleCreateFile ( uri : string ) {
216242 // Create a file at the validated path
217- await fs . promises . writeFile ( uri , "" ) ;
243+ const safe = safeResolve ( uri ) ;
244+ // ensure parent exists
245+ await fs . promises . mkdir ( path . dirname ( safe ) , { recursive : true } ) ;
246+ await fs . promises . writeFile ( safe , "" ) ;
218247}
219248
220249async function handleRename ( oldUri : string , newUri : string ) {
221- await fs . promises . rename (
222- oldUri ,
223- newUri ,
224- ) ;
250+ const safeOld = safeResolve ( oldUri ) ;
251+ const safeNew = safeResolve ( newUri ) ;
252+ await fs . promises . rename ( safeOld , safeNew ) ;
225253}
226254
227255async function handleDelete ( uri : string ) {
228- await fs . promises . rm ( uri , {
256+ const safe = safeResolve ( uri ) ;
257+ await fs . promises . rm ( safe , {
229258 recursive : true ,
230259 force : true ,
231260 } ) ;
232261}
233262
234263async function handleHasPath ( uri : string ) {
235- return fs . existsSync ( uri ) ;
264+ try {
265+ const safe = safeResolve ( uri ) ;
266+ return fs . existsSync ( safe ) ;
267+ } catch ( err ) {
268+ return false ;
269+ }
236270}
237271
238272async function handleReadFile ( uri : string ) {
239273 // Read the file at validated path
240- const data = await fs . promises . readFile (
241- uri ,
242- "utf-8" ,
243- ) ;
244-
274+ const safe = safeResolve ( uri ) ;
275+ const data = await fs . promises . readFile ( safe , "utf-8" ) ;
245276 return data ;
246277}
247278
248279async function handleWriteFile ( data : any , uri : string ) {
249280 // Write the data at validated path
250- const safePath = uri ;
281+ const safePath = safeResolve ( uri ) ;
251282 // create parent directory if it doesn't exist
252283 const dir = path . dirname ( safePath ) ;
253284 if ( ! fs . existsSync ( dir ) ) {
@@ -259,13 +290,9 @@ async function handleWriteFile(data: any, uri: string) {
259290
260291async function handleCopyFiles ( from : string , to : string ) {
261292 // Copy the files from the validated from path to the validated to path
262- await fs . promises . cp (
263- from ,
264- to ,
265- {
266- recursive : true ,
267- } ,
268- ) ;
293+ const safeFrom = safeResolve ( from ) ;
294+ const safeTo = safeResolve ( to ) ;
295+ await fs . promises . cp ( safeFrom , safeTo , { recursive : true } ) ;
269296}
270297
271298async function handleLoadSettings ( ) {
0 commit comments