@@ -294,6 +294,30 @@ const wakaTimeExportSchema = z.object({
294294 ) ,
295295} ) ;
296296
297+ const FILE_ID_PATTERN = / ^ [ A - Z a - z 0 - 9 _ - ] { 1 , 128 } $ / ;
298+
299+ function assertSafeFileId ( fileId : unknown ) : void {
300+ if ( typeof fileId !== "string" || ! FILE_ID_PATTERN . test ( fileId ) ) {
301+ throw handleApiError (
302+ 400 ,
303+ `Rejected import with invalid fileId: ${ JSON . stringify ( fileId ) } ` ,
304+ "Invalid upload identifier." ,
305+ ) ;
306+ }
307+ }
308+
309+ function assertWithinUserDir ( userTempDir : string , targetPath : string ) : void {
310+ const base = path . resolve ( userTempDir ) ;
311+ const resolved = path . resolve ( targetPath ) ;
312+ if ( resolved !== base && ! resolved . startsWith ( base + path . sep ) ) {
313+ throw handleApiError (
314+ 400 ,
315+ `Path traversal detected: ${ resolved } escapes ${ base } ` ,
316+ "Invalid upload path." ,
317+ ) ;
318+ }
319+ }
320+
297321async function handleChunkUpload ( formData : any [ ] , userId : string ) {
298322 const fileId = formData . find ( ( p ) => p . name === "fileId" ) ?. data . toString ( ) ;
299323 const chunkIndex = parseInt (
@@ -316,6 +340,8 @@ async function handleChunkUpload(formData: any[], userId: string) {
316340 ) ;
317341 }
318342
343+ assertSafeFileId ( fileId ) ;
344+
319345 const MAX_CHUNK_SIZE = 100 * 1024 * 1024 ;
320346 if ( chunk . data && chunk . data . length > MAX_CHUNK_SIZE ) {
321347 throw handleApiError (
@@ -336,6 +362,7 @@ async function handleChunkUpload(formData: any[], userId: string) {
336362
337363 const userTempDir = path . join ( tmpdir ( ) , "ziit-chunks" , userId ) ;
338364 const chunksDir = path . join ( userTempDir , fileId ) ;
365+ assertWithinUserDir ( userTempDir , chunksDir ) ;
339366 mkdirSync ( chunksDir , { recursive : true } ) ;
340367
341368 let job = activeJobs . get ( fileId ) ;
@@ -387,6 +414,7 @@ async function handleChunkUpload(formData: any[], userId: string) {
387414}
388415
389416async function processFileInBackground ( fileId : string , userId : string ) {
417+ assertSafeFileId ( fileId ) ;
390418 const job = activeJobs . get ( fileId ) ;
391419 if ( ! job || job . fileId !== fileId ) {
392420 throw handleApiError (
@@ -403,6 +431,8 @@ async function processFileInBackground(fileId: string, userId: string) {
403431 const userTempDir = path . join ( tmpdir ( ) , "ziit-chunks" , userId ) ;
404432 const chunksDir = path . join ( userTempDir , fileId ) ;
405433 const combinedFilePath = path . join ( userTempDir , `${ fileId } -combined.json` ) ;
434+ assertWithinUserDir ( userTempDir , chunksDir ) ;
435+ assertWithinUserDir ( userTempDir , combinedFilePath ) ;
406436
407437 try {
408438 handleLog (
0 commit comments