Describe the bug
In apps/public-api/src/controllers/storage.controller.js, the uploaded file's original filename is used directly in the Supabase storage path after only replacing spaces:
const safeName = file.originalname.replace(/\s+/g, "_");
const filePath = `${project._id}/${randomUUID()}_${safeName}`;
The originalname field comes from the HTTP multipart upload and is entirely user-controlled. Characters such as ../, null bytes (%00), or Unicode directory separators are not stripped. While the random UUID prefix reduces the practical risk of path traversal, the unsanitized filename is sent verbatim to Supabase's storage API and is also returned to the client in the path response field.
A crafted filename like ../../secret.pdf or valid.pdf\x00.jpg may exploit permissive storage backends or confuse downstream filename parsers that use the stored path.
To Reproduce
- Send a
POST /api/storage/upload request with Content-Type: audio/webm and set the filename in the multipart header to ../../test.webm.
- Observe that the
path field in the response contains the unsanitized string (e.g., projectId/uuid_../../test.webm).
Expected behavior
The filename should be sanitized to safe characters before use in any path construction:
const safeName = file.originalname
.replace(/[^a-zA-Z0-9._-]/g, "_")
.replace(/\.{2,}/g, "_")
.substring(0, 100);
const filePath = `${project._id}/${randomUUID()}_${safeName}`;
Additional context
File: apps/public-api/src/controllers/storage.controller.js, function: uploadFile
Labels: bug, nsoc26, level2
Checklist:
Describe the bug
In
apps/public-api/src/controllers/storage.controller.js, the uploaded file's original filename is used directly in the Supabase storage path after only replacing spaces:The
originalnamefield comes from the HTTP multipart upload and is entirely user-controlled. Characters such as../, null bytes (%00), or Unicode directory separators are not stripped. While the random UUID prefix reduces the practical risk of path traversal, the unsanitized filename is sent verbatim to Supabase's storage API and is also returned to the client in thepathresponse field.A crafted filename like
../../secret.pdforvalid.pdf\x00.jpgmay exploit permissive storage backends or confuse downstream filename parsers that use the stored path.To Reproduce
POST /api/storage/uploadrequest withContent-Type: audio/webmand set the filename in the multipart header to../../test.webm.pathfield in the response contains the unsanitized string (e.g.,projectId/uuid_../../test.webm).Expected behavior
The filename should be sanitized to safe characters before use in any path construction:
Additional context
File:
apps/public-api/src/controllers/storage.controller.js, function:uploadFileLabels:
bug,nsoc26,level2Checklist: