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
@@ -180,7 +180,7 @@ export class BackfillApplicationPackageFilesCommand extends ActiveOrSuspendedWor
toDelete: false,
};
const packageJsonFile = await this.fileStorageService.writeFile_v2({
const packageJsonFile = await this.fileStorageService.writeFile({
sourceFile: packageJsonContent,
mimeType: undefined,
fileFolder: FileFolder.Dependencies,
@@ -190,7 +190,7 @@ export class BackfillApplicationPackageFilesCommand extends ActiveOrSuspendedWor
settings: dependencyFileSettings,
});
const yarnLockFile = await this.fileStorageService.writeFile_v2({
const yarnLockFile = await this.fileStorageService.writeFile({
sourceFile: layer.yarnLock,
mimeType: undefined,
fileFolder: FileFolder.Dependencies,
@@ -245,13 +245,13 @@ export class MigrateWorkflowCodeStepsCommand extends ActiveOrSuspendedWorkspaces
await fs.mkdir(builtTempDir, { recursive: true });
await fs.mkdir(sourceTempDir, { recursive: true });
const builtSources = await this.fileStorageService.readFolder(
const builtSources = await this.fileStorageService.readFolderLegacy(
oldPaths.built,
);
await this.writeSourcesToLocalFolder(builtSources as Sources, builtTempDir);
const sourceSources = await this.fileStorageService.readFolder(
const sourceSources = await this.fileStorageService.readFolderLegacy(
oldPaths.source,
);
const flattened =
@@ -272,7 +272,7 @@ export class MigrateWorkflowCodeStepsCommand extends ActiveOrSuspendedWorkspaces
const builtTempDir = join(tempRoot, 'built');
const sourceTempDir = join(tempRoot, 'source');
await this.fileStorageService.uploadFolder_v2({
await this.fileStorageService.uploadFolder({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
@@ -280,7 +280,7 @@ export class MigrateWorkflowCodeStepsCommand extends ActiveOrSuspendedWorkspaces
localPath: builtTempDir,
});
await this.fileStorageService.uploadFolder_v2({
await this.fileStorageService.uploadFolder({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
@@ -486,8 +486,12 @@ export class SeedWorkflowV1_16Command extends ActiveOrSuspendedWorkspacesMigrati
);
try {
await this.fileStorageService.delete({ folderPath: OLD_BUILT_FOLDER });
await this.fileStorageService.delete({ folderPath: OLD_SOURCE_FOLDER });
await this.fileStorageService.deleteLegacy({
folderPath: OLD_BUILT_FOLDER,
});
await this.fileStorageService.deleteLegacy({
folderPath: OLD_SOURCE_FOLDER,
});
this.logger.log(
`Cleaned old file storage: ${OLD_BUILT_FOLDER}, ${OLD_SOURCE_FOLDER}`,
);
@@ -516,7 +520,10 @@ export class SeedWorkflowV1_16Command extends ActiveOrSuspendedWorkspacesMigrati
},
};
await this.fileStorageService.writeFolder(builtSources, builtFolder);
await this.fileStorageService.writeFolder(sourceSources, sourceFolder);
await this.fileStorageService.writeFolderLegacy(builtSources, builtFolder);
await this.fileStorageService.writeFolderLegacy(
sourceSources,
sourceFolder,
);
}
}