CVE-2026-50284
Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets
Craft CMS is a content management system (CMS). In versions 5.0.0-RC1 through 5.9.21 and 4.0.0-RC1 through 4.17.14, theAssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of the uploader's assigned privileges. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies. This issue has been fixed in versions 4.17.15 and 5.9.22.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- High
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.1
Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets
Craft CMS is a content management system (CMS). In versions 5.0.0-RC1 through 5.9.21 and 4.0.0-RC1 through 4.17.14, theAssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of the uploader's assigned privileges. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies. This issue has been fixed in versions 4.17.15 and 5.9.22.
- Record
CVE-2026-50284- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
EUVDEUVD-2026-41208
7.1
Craft CMS is a content management system (CMS). In versions 5.0.0-RC1 through 5.9.21 and 4.0.0-RC1 through 4.17.14, theAssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of the uploader's assigned privileges. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies. This issue has been fixed in versions 4.17.15 and 5.9.22.
- Record
EUVD-2026-41208- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7h62-6v23-v8fm
7.1
Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets
Summary
AssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of who uploaded them. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies.
This is the same bug class that was just fixed in actionMoveFolder as GHSA-3w32-23wj-rxg3 (commit 05c2042, Apr 23 2026); the fix added requireVolumePermissionByFolder('deletePeerAssets', …) and savePeerAssets checks to the move endpoint but did not propagate to the delete-folder endpoint.
Details
src/controllers/AssetsController.php:552-569:
public function actionDeleteFolder(): Response
{
$this->requireAcceptsJson();
$folderId = $this->request->getRequiredBodyParam('folderId');
$assets = Craft::$app->getAssets();
$folder = $assets->getFolderById($folderId);
if (!$folder) {
throw new BadRequestHttpException('The folder cannot be found');
}
// Check if it's possible to delete objects in the target volume.
$this->requireVolumePermissionByFolder('deleteAssets', $folder); // <-- only checks deleteAssets
$assets->deleteFoldersByIds($folderId);
return $this->asSuccess();
}
requireVolumePermissionByFolder() (src/controllers/AssetsControllerTrait.php:75-88) only resolves to a single requirePermission('deleteAssets:<vol-uid>') call. The peer-equivalent helper (requirePeerVolumePermissionByAsset) is never invoked because there is no folder-level peer helper that iterates the folder's contents.
Assets::deleteFoldersByIds() (src/services/Assets.php:311-349) then enumerates the folder + every descendant folder, queries every asset under those IDs, and calls Craft::$app->getElements()->deleteElement($asset, true) directly:
$assetQuery = Asset::find()->folderId($allFolderIds);
$elementService = Craft::$app->getElements();
foreach (Db::each($assetQuery) as $asset) {
$asset->keepFileOnDelete = !$deleteDir;
$elementService->deleteElement($asset, true);
}
This bypasses Asset::canDelete() (src/elements/Asset.php:1515-1536):
public function canDelete(User $user): bool
{
if ($this->isFolder) { return false; }
if (parent::canDelete($user)) { return true; }
$volume = $this->getVolume();
if (Assets::isTempUploadFs($volume->getFs())) { return true; }
if ($this->uploaderId !== $user->id) {
return $user->can("deletePeerAssets:$volume->uid"); // <-- never reached on cascade delete
}
return $user->can("deleteAssets:$volume->uid");
}
Compare to actionDeleteAsset (src/controllers/AssetsController.php:579-613), which correctly does:
$this->requireVolumePermissionByAsset('deleteAssets', $asset);
$this->requirePeerVolumePermissionByAsset('deletePeerAssets', $asset);
The fix that landed in 05c2042 for actionMoveFolder (src/controllers/AssetsController.php:733-765) added both savePeerAssets and deletePeerAssets requireVolumePermissionByFolder checks to mirror the per-asset pattern, but the same hardening was not applied to actionDeleteFolder or actionRenameFolder (which also calls deleteFoldersByIds indirectly through later logic).
The asymmetry between the two endpoints demonstrates the missing check.
Impact
- Integrity / availability of other users' assets on any volume where the attacker has
deleteAssetsbut notdeletePeerAssets: the attacker can permanently delete peer-owned files (and their parent folder structure) on the underlying filesystem, with no recovery via Craft's UI. - The Craft permission model explicitly distinguishes "delete your own assets"…
- Record
GHSA-7h62-6v23-v8fm- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
NVD
7.1
Craft CMS is a content management system (CMS). In versions 5.0.0-RC1 through 5.9.21 and 4.0.0-RC1 through 4.17.14, theAssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of the uploader's assigned privileges. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies. This issue has been fixed in versions 4.17.15 and 5.9.22.
- Record
CVE-2026-50284- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-862
- Updated
- 2026-11-02
OSV
7.1
Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets
Craft CMS is a content management system (CMS). In versions 5.0.0-RC1 through 5.9.21 and 4.0.0-RC1 through 4.17.14, theAssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of the uploader's assigned privileges. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies. This issue has been fixed in versions 4.17.15 and 5.9.22.
- Record
CVE-2026-50284- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
OSVGHSA-7h62-6v23-v8fm
7.1
Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets
Summary
AssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of who uploaded them. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies.
This is the same bug class that was just fixed in actionMoveFolder as GHSA-3w32-23wj-rxg3 (commit 05c2042, Apr 23 2026); the fix added requireVolumePermissionByFolder('deletePeerAssets', …) and savePeerAssets checks to the move endpoint but did not propagate to the delete-folder endpoint.
Details
src/controllers/AssetsController.php:552-569:
public function actionDeleteFolder(): Response
{
$this->requireAcceptsJson();
$folderId = $this->request->getRequiredBodyParam('folderId');
$assets = Craft::$app->getAssets();
$folder = $assets->getFolderById($folderId);
if (!$folder) {
throw new BadRequestHttpException('The folder cannot be found');
}
// Check if it's possible to delete objects in the target volume.
$this->requireVolumePermissionByFolder('deleteAssets', $folder); // <-- only checks deleteAssets
$assets->deleteFoldersByIds($folderId);
return $this->asSuccess();
}
requireVolumePermissionByFolder() (src/controllers/AssetsControllerTrait.php:75-88) only resolves to a single requirePermission('deleteAssets:<vol-uid>') call. The peer-equivalent helper (requirePeerVolumePermissionByAsset) is never invoked because there is no folder-level peer helper that iterates the folder's contents.
Assets::deleteFoldersByIds() (src/services/Assets.php:311-349) then enumerates the folder + every descendant folder, queries every asset under those IDs, and calls Craft::$app->getElements()->deleteElement($asset, true) directly:
$assetQuery = Asset::find()->folderId($allFolderIds);
$elementService = Craft::$app->getElements();
foreach (Db::each($assetQuery) as $asset) {
$asset->keepFileOnDelete = !$deleteDir;
$elementService->deleteElement($asset, true);
}
This bypasses Asset::canDelete() (src/elements/Asset.php:1515-1536):
public function canDelete(User $user): bool
{
if ($this->isFolder) { return false; }
if (parent::canDelete($user)) { return true; }
$volume = $this->getVolume();
if (Assets::isTempUploadFs($volume->getFs())) { return true; }
if ($this->uploaderId !== $user->id) {
return $user->can("deletePeerAssets:$volume->uid"); // <-- never reached on cascade delete
}
return $user->can("deleteAssets:$volume->uid");
}
Compare to actionDeleteAsset (src/controllers/AssetsController.php:579-613), which correctly does:
$this->requireVolumePermissionByAsset('deleteAssets', $asset);
$this->requirePeerVolumePermissionByAsset('deletePeerAssets', $asset);
The fix that landed in 05c2042 for actionMoveFolder (src/controllers/AssetsController.php:733-765) added both savePeerAssets and deletePeerAssets requireVolumePermissionByFolder checks to mirror the per-asset pattern, but the same hardening was not applied to actionDeleteFolder or actionRenameFolder (which also calls deleteFoldersByIds indirectly through later logic).
The asymmetry between the two endpoints demonstrates the missing check.
Impact
- Integrity / availability of other users' assets on any volume where the attacker has
deleteAssetsbut notdeletePeerAssets: the attacker can permanently delete peer-owned files (and their parent folder structure) on the underlying filesystem, with no recovery via Craft's UI. - The Craft permission model explicitly distinguishes "delete your own assets"…
- Record
GHSA-7h62-6v23-v8fm- CVSS 4.0
- 7.1 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N - Weakness
- CWE-862
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
craftcms/cmsPackagist |
5.0.0-RC1 up to 5.9.22 | 5.9.22 |
craftcms/cmsPackagist |
4.0.0-RC1 up to 4.17.15 | 4.17.15 |
craftcms/cmsPackagist |
5.0.0-RC1 up to 5.9.22, plus listed versions 5.0.05.0.15.0.25.0.35.0.4 Show 45 moreShow less5.0.55.0.65.1.05.1.15.1.105.1.25.1.35.1.45.1.55.1.65.1.75.1.85.1.95.2.05.2.0-beta.15.2.0-beta.25.2.0-beta.35.2.0-beta.45.2.0-beta.55.2.0-beta.65.2.15.2.105.2.25.2.35.2.45.2.4.15.2.55.2.65.2.75.2.85.2.95.3.05.3.0-beta.15.3.0-beta.25.3.0.15.3.0.25.3.0.35.3.15.3.25.3.35.3.45.3.55.3.65.4.05.4.0.1 |
5.9.22 |
craftcms/cmsPackagist |
4.0.0-RC1 up to 4.17.15, plus listed versions 4.0.04.0.0-RC24.0.0-RC34.0.0.14.0.1 Show 45 moreShow less4.0.24.0.34.0.44.0.54.0.5.14.0.5.24.0.64.1.04.1.0.14.1.0.24.1.14.1.24.1.34.1.44.1.4.14.10.04.10.0-beta.14.10.0-beta.24.10.14.10.24.10.34.10.44.10.54.10.64.10.74.10.84.11.04.11.0.14.11.0.24.11.14.11.24.11.34.11.44.11.54.12.04.12.14.12.24.12.34.12.44.12.4.14.12.54.12.64.12.6.14.12.74.12.8 |
4.17.15 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-50284 is a high vulnerability (CVSS 7.1) in Craftcms Cms, published July 1, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.39%, the 32nd percentile. Fixed in craftcms/cms 5.9.22.
References
github.com/craftcms/cms/security/advisories/GHSA-7h62-6v23-v8fm
github.com/craftcms/cms/commit/b4e08977f0c9bdf002a77f9f6d1346cd55ac0598
nvd.nist.gov/vuln/detail/CVE-2026-50284
github.com/advisories/GHSA-7h62-6v23-v8fm
github.com/CVEProject/cvelistV5/tree/main/cves/2026/50xxx/CVE-2026-50284.json
github.com/craftcms/cms
Left out here: 314 versions and the rest of 2 source descriptions. The JSON record has them.