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 moved - `createFolders:<destVolumeUid>` for the destination parent folder - `saveAssets:<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. ```php $this->requireVolumePermissionByFolder('deleteAssets', $folderToMove); $this->requireVolumePermissionByFolder('createFolders', $destinationFolder); $this->requireVolumePermissionByFolder('saveAssets', $destinationFolder); ``` [*src/controllers/AssetsController.php:L751-L753*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L751-L753) Indexed destination conflicts are deleted via the Assets service: ```php $assets->deleteFoldersByIds($existingFolder->id); ``` [*src/controllers/AssetsController.php:L798-L798*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L798-L798) Unindexed destination conflicts are deleted directly in the volume filesystem: ```php $targetVolume->deleteDirectory(rtrim($destinationFolder->path, '/') . '/' . $folderToMove->name); ``` [*src/controllers/AssetsController.php:L815*](https://github.com/craftcms/cms/blob/5.x/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.
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 moved - `createFolders:<destVolumeUid>` for the destination parent folder - `saveAssets:<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. ```php $this->requireVolumePermissionByFolder('deleteAssets', $folderToMove); $this->requireVolumePermissionByFolder('createFolders', $destinationFolder); $this->requireVolumePermissionByFolder('saveAssets', $destinationFolder); ``` [*src/controllers/AssetsController.php:L751-L753*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L751-L753) Indexed destination conflicts are deleted via the Assets service: ```php $assets->deleteFoldersByIds($existingFolder->id); ``` [*src/controllers/AssetsController.php:L798-L798*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L798-L798) Unindexed destination conflicts are deleted directly in the volume filesystem: ```php $targetVolume->deleteDirectory(rtrim($destinationFolder->path, '/') . '/' . $folderToMove->name); ``` [*src/controllers/AssetsController.php:L815*](https://github.com/craftcms/cms/blob/5.x/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.
Update craftcms/cms to 5.9.21; craftcms/cms to 4.17.14 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanCraft CMS Vulnerable to Unauthorized Deletion of Destination Folders During Forced Moves affects craftcms/cms (composer), craftcms/cms (composer). Severity is high. 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 moved - `createFolders:<destVolumeUid>` for the destination parent folder - `saveAssets:<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. ```php $this->requireVolumePermissionByFolder('deleteAssets', $folderToMove); $this->requireVolumePermissionByFolder('createFolders', $destinationFolder); $this->requireVolumePermissionByFolder('saveAssets', $destinationFolder); ``` [*src/controllers/AssetsController.php:L751-L753*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L751-L753) Indexed destination conflicts are deleted via the Assets service: ```php $assets->deleteFoldersByIds($existingFolder->id); ``` [*src/controllers/AssetsController.php:L798-L798*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L798-L798) Unindexed destination conflicts are deleted directly in the volume filesystem: ```php $targetVolume->deleteDirectory(rtrim($destinationFolder->path, '/') . '/' . $folderToMove->name); ``` [*src/controllers/AssetsController.php:L815*](https://github.com/craftcms/cms/blob/5.x/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.
AI coding agents often install or upgrade packages automatically in composer. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| craftcms/cmscomposer | >=5.0.0-RC1,<5.9.21 | 5.9.21 |
| craftcms/cmscomposer | >=4.0.0-RC1,<4.17.14 | 4.17.14 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL GuardUpdate craftcms/cms to 5.9.21; craftcms/cms to 4.17.14 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanCraft CMS Vulnerable to Unauthorized Deletion of Destination Folders During Forced Moves affects craftcms/cms (composer), craftcms/cms (composer). Severity is high. 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 moved - `createFolders:<destVolumeUid>` for the destination parent folder - `saveAssets:<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. ```php $this->requireVolumePermissionByFolder('deleteAssets', $folderToMove); $this->requireVolumePermissionByFolder('createFolders', $destinationFolder); $this->requireVolumePermissionByFolder('saveAssets', $destinationFolder); ``` [*src/controllers/AssetsController.php:L751-L753*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L751-L753) Indexed destination conflicts are deleted via the Assets service: ```php $assets->deleteFoldersByIds($existingFolder->id); ``` [*src/controllers/AssetsController.php:L798-L798*](https://github.com/craftcms/cms/blob/5.x/src/controllers/AssetsController.php#L798-L798) Unindexed destination conflicts are deleted directly in the volume filesystem: ```php $targetVolume->deleteDirectory(rtrim($destinationFolder->path, '/') . '/' . $folderToMove->name); ``` [*src/controllers/AssetsController.php:L815*](https://github.com/craftcms/cms/blob/5.x/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.
AI coding agents often install or upgrade packages automatically in composer. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| craftcms/cmscomposer | >=5.0.0-RC1,<5.9.21 | 5.9.21 |
| craftcms/cmscomposer | >=4.0.0-RC1,<4.17.14 | 4.17.14 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL Guard