Harden local file storage driver path resolution (#17783)

## Summary

- Normalize all file paths with `path.resolve` instead of `join` to
properly handle `..` segments in file path inputs
- Add `assertPathIsWithinStorage` guard on all write, delete, move,
copy, and existence-check operations
- Introduce `ACCESS_DENIED` exception code with i18n-ready user-friendly
message
- Read path already had realpath-based validation; updated its error
code to `ACCESS_DENIED` for consistency

## Test plan

- [x] Typecheck passes
- [x] Lint passes
- [x] Manual: verify file upload/download still works with valid paths
- [x] Manual: verify `../` in file paths is rejected with ACCESS_DENIED


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Etienne <etiennejouan@users.noreply.github.com>
This commit is contained in:
Félix Malfait
2026-02-09 10:54:10 +01:00
committed by GitHub
parent 15f09736b2
commit ece265c6e4
30 changed files with 614 additions and 246 deletions
@@ -429,7 +429,7 @@ export class DevSeederDataService {
const filePath = join(sampleFilesDir, filename);
const fileBuffer = await readFile(filePath);
await this.fileStorageService.writeFile({
await this.fileStorageService.writeFileLegacy({
file: fileBuffer,
name: filename,
folder: `workspace-${workspaceId}/attachment`,
@@ -68,7 +68,7 @@ export class CreateFrontComponentActionHandlerService extends WorkspaceMigration
applicationUniversalIdentifier: string;
builtComponentPath: string;
}): Promise<void> {
const builtExists = await this.fileStorageService.checkFileExists_v2({
const builtExists = await this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltFrontComponent,
@@ -40,13 +40,13 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
const [sourceExists, builtExists] = await Promise.all([
this.fileStorageService.checkFileExists_v2({
this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: logicFunction.sourceHandlerPath,
}),
this.fileStorageService.checkFileExists_v2({
this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
@@ -59,7 +59,7 @@ export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationR
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
await this.fileStorageService.delete_v2({
await this.fileStorageService.delete({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
@@ -73,13 +73,13 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
applicationUniversalIdentifier: string;
}): Promise<void> {
const [sourceExists, builtExists] = await Promise.all([
this.fileStorageService.checkFileExists_v2({
this.fileStorageService.checkFileExists({
workspaceId: flatLogicFunction.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: flatLogicFunction.sourceHandlerPath,
}),
this.fileStorageService.checkFileExists_v2({
this.fileStorageService.checkFileExists({
workspaceId: flatLogicFunction.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,