CVE-2026-50283 Overview
Craft CMS contains an authorization flaw in AssetsController::actionReplaceFile that allows authenticated users to delete assets in volumes where they lack delete permission. The issue affects versions 5.0.0-RC1 through 5.9.20 and 4.0.0-RC1 through 4.17.13. When a request supplies both assetId and sourceAssetId, the controller resolves the permission check against the target asset only, skipping enforcement on the source asset. Craft then copies the source file into the target and deletes the source asset, resulting in broken content references and data loss. The vulnerability is tracked as [CWE-639] Authorization Bypass Through User-Controlled Key. Fixes are available in versions 4.17.14 and 5.9.21.
Critical Impact
An authenticated user with replace permission in one volume can delete arbitrary assets in another volume without holding delete permission, causing content loss.
Affected Products
- Craft CMS versions 5.0.0-RC1 through 5.9.20
- Craft CMS versions 4.0.0-RC1 through 4.17.13
- Fixed in Craft CMS 4.17.14 and 5.9.21
Discovery Timeline
- 2026-07-01 - CVE-2026-50283 published to NVD
- 2026-07-02 - Last updated in NVD database
Technical Details for CVE-2026-50283
Vulnerability Analysis
The flaw resides in AssetsController::actionReplaceFile(), which supports replacing a target asset file using another existing asset as the source. The action loads two objects from user-supplied identifiers: assetId becomes $assetToReplace and sourceAssetId becomes $sourceAsset. Before the patch, permission enforcement used the expression ($assetToReplace ?: $sourceAsset), which returns the first truthy value.
When both identifiers are supplied, the expression resolves to $assetToReplace, so Craft checks replaceFiles and replacePeerFiles only against the target volume. The source asset volume is never validated. Craft then copies the source file into the target and deletes the original source asset, though no deleteAssets permission check is performed against the source volume.
Root Cause
The root cause is missing authorization on a user-controlled object reference [CWE-639]. Using the null coalescing behavior on two independently controlled asset IDs collapses two distinct permission scopes into a single check. The deletion side effect on the source asset has no matching authorization gate.
Attack Vector
An authenticated user with replaceFiles permission in any volume can send a crafted request to the asset replace endpoint containing both assetId (in their own volume) and sourceAssetId (referencing an asset in a restricted volume). Craft deletes the referenced source asset without validating delete permission on the source volume.
// Patch applied in src/controllers/AssetsController.php
// Before (vulnerable):
// $this->requireVolumePermissionByAsset('replaceFiles', $assetToReplace ?: $sourceAsset);
// $this->requirePeerVolumePermissionByAsset('replacePeerFiles', $assetToReplace ?: $sourceAsset);
// After (fixed): both assets are independently checked
if ($assetToReplace) {
$this->requireVolumePermissionByAsset('replaceFiles', $assetToReplace);
$this->requirePeerVolumePermissionByAsset('replacePeerFiles', $assetToReplace);
}
if ($sourceAsset) {
$this->requireVolumePermissionByAsset('replaceFiles', $sourceAsset);
$this->requirePeerVolumePermissionByAsset('replacePeerFiles', $sourceAsset);
}
// Handle the Element Action
if ($assetToReplace !== null && $uploadedFile) {
Source: GitHub Commit 2c2579c
Detection Methods for CVE-2026-50283
Indicators of Compromise
- POST requests to the assets replace-file endpoint containing both assetId and sourceAssetId parameters referencing distinct volumes.
- Craft CMS audit logs showing asset deletions initiated by users who lack deleteAssets permission on the source volume.
- Unexpected broken asset references in entries, categories, or Matrix blocks following asset replace actions.
Detection Strategies
- Enable and review Craft CMS activity and asset audit logs, focusing on replace-file actions where the acting user has no delete rights in the affected volume.
- Inspect web server logs for HTTP requests to /actions/assets/replace-file that include both assetId and sourceAssetId fields in the POST body.
- Correlate asset deletion events with the volumes and permission sets of the acting user identity.
Monitoring Recommendations
- Alert on repeated replace-file requests originating from low-privilege authenticated accounts.
- Monitor for spikes in asset deletion events across sensitive volumes containing production media.
- Track drift between the running Craft CMS version and the fixed releases 4.17.14 and 5.9.21.
How to Mitigate CVE-2026-50283
Immediate Actions Required
- Upgrade Craft CMS to version 4.17.14 or 5.9.21 immediately.
- Audit recent asset deletions and replace-file activity to identify any unauthorized removals.
- Review and restrict replaceFiles and replacePeerFiles permissions to trusted user groups only.
Patch Information
Craft CMS resolved the issue in versions 4.17.14 and 5.9.21. The fix, referenced in commit 2c2579c7f1030872423f268d0c8b48377101961d, splits the single conditional permission check into two independent checks that validate replaceFiles and replacePeerFiles permissions against both the target and the source assets. Additional hardening in src/elements/Asset.php tightens the canMoveTo logic to require saveAssets, deleteAssets, savePeerAssets, and deletePeerAssets permissions on the destination volume. See the Craft CMS Security Advisory GHSA-qh45-9g5p-m2v4.
Workarounds
- Revoke replaceFiles permission from any user group that does not require it until the upgrade is applied.
- Restrict asset volumes so low-privilege users cannot enumerate sourceAssetId values in other volumes.
- Place the Craft CMS control panel behind an authenticated reverse proxy or IP allowlist to limit exposure.
# Upgrade Craft CMS via Composer to a patched release
composer require craftcms/cms:^5.9.21 --update-with-dependencies
# or for the 4.x branch
composer require craftcms/cms:^4.17.14 --update-with-dependencies
# Clear caches and run pending migrations
php craft clear-caches/all
php craft migrate/all
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

