Craft CMS Vulnerable to Unauthorized Deletion of Destination Folders During Forced Moves
Published 2 Jul 2026 · Updated 10 Sept 2026
Affected packages
- Affected
- >=5.0.0-RC1 <5.9.21
- Fixed in
- 5.9.21
composer require craftcms/cms:^5.9.21- Affected
- >=4.0.0-RC1 <4.17.14
- Fixed in
- 4.17.14
composer require craftcms/cms:^4.17.14Details
We have identified an authorization issue in Craft CMS where a forced folder move can delete a conflicting destination folder without destination delete permission.
Description
Craft CMS’s craft\\controllers\\AssetsController::actionMoveFolder() supports moving an asset folder into a destination parent folder. If a folder with the same name already exists at the destination, the action can be called with force=true to overwrite the destination.
The permission checks for this action allow:
deleteAssets:<sourceVolumeUid>for the folder being movedcreateFolders:<destVolumeUid>for the destination parent foldersaveAssets:<destVolumeUid>for the destination parent folder
The action does not require deleteAssets on the destination volume or destination conflict folder. When force=true and a name conflict exists, the code deletes the destination folder to resolve the conflict.
$this->requireVolumePermissionByFolder('deleteAssets', $folderToMove);
$this->requireVolumePermissionByFolder('createFolders', $destinationFolder);
$this->requireVolumePermissionByFolder('saveAssets', $destinationFolder);*src/controllers/AssetsController.php:L751-L753*
Indexed destination conflicts are deleted via the Assets service:
$assets->deleteFoldersByIds($existingFolder->id);
*src/controllers/AssetsController.php:L798-L798*
Unindexed destination conflicts are deleted directly in the volume filesystem:
$targetVolume->deleteDirectory(rtrim($destinationFolder->path, '/') . '/' . $folderToMove->name);
*src/controllers/AssetsController.php:L815*
Impact
A user who cannot delete assets in a destination volume can still delete a destination folder and its contents by triggering a forced move into a conflicting name. This can cause asset loss, broken references in entries and fields that point to deleted assets, and operational disruption.
Is GHSA-3w32-23wj-rxg3 in any of your client repos?
Checking one package by hand is fine. Checking it across forty client projects is not. NPMscan reads the lockfiles in every GitHub repo you pick, finds each project with an affected craftcms/cms, and gives you the command that fixes it.
References
Advisory data from OSV.dev (opens OSV.dev in a new tab), refreshed daily.