GitHub Advisoryadvisory · Packagist

GHSA-7h62-6v23-v8fm Craft CMS: Missing peer-permission check in `AssetsController::actionDeleteFolder` allows deletion of other users' assets

HIGH Published 2026-07-02 Modified 2026-07-02 Official page ↗
Aliases
CVE-2026-50284
Ecosystem
Packagist
Packages
craftcms/cms
Severity
High
CVSS vector
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
Weaknesses
CWE-862
Reviewed
2026-07-02

Affected packages

• Packagist craftcms/cms: >= 5.0.0-RC1, < 5.9.22 → fixed in 5.9.22 • Packagist craftcms/cms: >= 4.0.0-RC1, < 4.17.15 → fixed in 4.17.15

Description

Summary `AssetsController::actionDeleteFolder()` only requires the `deleteAssets:` permission for the target folder. It never enforces `deletePeerAssets:`, 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`: ```php 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); // deleteFoldersByIds($folderId); return $this->asSuccess(); } ``` `requireVolumePermissionByFolder()` (`src/controllers/AssetsControllerTrait.php:75-88`) only resolves to a single `requirePermission('deleteAssets:')` 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: ```php $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`): ```php 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"); // can("deleteAssets:$volume->uid"); } ``` Compare to `actionDeleteAsset` (`src/controllers/AssetsController.php:579-613`), which correctly does: ```php $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 `deleteAssets` but not `deletePeerAssets`: 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" (`deleteAssets`) from "delete other users' assets" (`deletePeerAssets`) precisely so administrators can grant the former without the latter on shared volumes — this finding renders that distinction unenforceable for any user given folder-delete rights. • No information disclosure or remote code execution; impact is bounded to the affected volume's contents. • Does not require any non-default configuration: the affected endpoint is enabled by default and only requires that an administrator has split `deleteAssets` from `deletePeerAssets` (the documented, supported permission model).

Related

Alias · CVE (1)

  • CVE-2026-50284 Craft CMS is a content management system (CMS). vulnerability

Weakness · CWE (1)

  • CWE-862 Missing Authorization weakness

References