{"record":{"id":"e3ee4a3a8359e123","repo":"octobercms/october","slug":"editor-lang-filesystem-error-deleting-directory","errorCode":null,"errorMessage":"editor::lang.filesystem.error_deleting_directory","messagePattern":"editor::lang\\.filesystem\\.error_deleting_directory","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"error","filePath":"modules/editor/traits/FileSystemFunctions.php","lineNumber":225,"sourceCode":"\n            if (is_file($originalFullPath)) {\n                if (!@File::move($originalFullPath, $newFullPath)) {\n                    throw new ApplicationException(Lang::get(\n                        'editor::lang.filesystem.error_moving_file',\n                        ['file' => $basename]\n                    ));\n                }\n            }\n            elseif (is_dir($originalFullPath)) {\n                if (!@File::copyDirectory($originalFullPath, $newFullPath)) {\n                    throw new ApplicationException(Lang::get(\n                        'editor::lang.filesystem.error_moving_directory',\n                        ['dir' => $basename]\n                    ));\n                }\n\n                if (strpos($originalFullPath, '../') !== false) {\n                    throw new ApplicationException(Lang::get(\n                        'editor::lang.filesystem.error_deleting_directory',\n                        ['dir' => $basename]\n                    ));\n                }\n\n                if (strpos($originalFullPath, $safeDir) !== 0) {\n                    throw new ApplicationException(Lang::get(\n                        'editor::lang.filesystem.error_deleting_directory',\n                        ['dir' => $basename]\n                    ));\n                }\n\n                if (!@File::deleteDirectory($originalFullPath)) {\n                    throw new ApplicationException(Lang::get(\n                        'editor::lang.filesystem.error_deleting_directory',\n                        ['dir' => $basename]\n                    ));\n                }","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/editor/traits/FileSystemFunctions.php#L207-L243","documentation":"Thrown by editorMoveFilesOrDirectories after a directory's copyDirectory() SUCCEEDED but the original path contains '../', so the code refuses to delete the source (lines 224-229). Message: 'Error deleting the original directory :dir'. This is defense-in-depth: every source already passed validateFileSystemPath (which rejects '..'), so it is only reachable when $basePath itself was built with '../' segments — i.e., a custom extension supplying an unnormalized base path. Note the consequence: content now exists in BOTH places because the copy already ran.","triggerScenarios":"A custom Editor\\Classes\\ExtensionBase subclass whose getAssetsPath() (or equivalent) returns a path containing '..' (e.g. base_path().'/../shared/assets'), combined with command_onAssetMove of a directory. Standard CMS/Tailor extensions, whose base paths are normalized theme directories, cannot trigger it.","commonSituations":"Bespoke editor extensions mounting asset roots outside the theme via relative '..' paths; refactors that assemble base paths from config without realpath(); copied example code for Tailor content extensions with sloppy path joins.","solutions":["Normalize the base path before calling editorMoveFilesOrDirectories: $basePath = realpath($basePath)","If already hit, the destination copy is complete — delete the original directory manually after verifying the copy","Audit custom editor extensions for '..' in their resolved base paths","Prefer an absolute, symlink-free base path (or a symlink) instead of relative traversal segments"],"exampleFix":"// before (custom extension)\n$base = base_path().'/../shared/assets'; // '..' leaks into originalFullPath\n$this->editorMoveFilesOrDirectories($base, $src, $dst);\n\n// after\n$base = realpath(base_path().'/../shared/assets'); // normalized, no '../'","handlingStrategy":"validation","validationCode":"// In custom extensions, before calling editorMoveFilesOrDirectories\n$basePath = realpath($basePath);\nif ($basePath === false || strpos($basePath, '../') !== false) {\n    throw new \\UnexpectedValueException('Base path must be a normalized absolute directory');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always realpath() the base path in custom editor extensions","Never compose asset roots with '..' segments — use absolute paths or symlinks","If hit, the copy succeeded: verify the destination then remove the original manually"],"tags":["filesystem","path-traversal","security","custom-extension","editor","winter-cms"],"backgroundTag":"path-traversal-blocked","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}