CVE-2026-55791 is a maximum-severity (CVSS 10.0) vulnerability in Craft CMS, the popular PHP content management system. Craft’s /actions/app/resource-js endpoint trusts the incoming Host / X-Forwarded-Host header to build the site’s base URL, then uses it to fetch and re-serve JavaScript. Because Craft ships with trustedHosts set to ['any'] by default, an unauthenticated attacker can poison that header to make the server fetch a malicious script from an attacker-controlled domain and hand it back to the browser as valid JavaScript. Behind a caching layer, this becomes web cache poisoning: the next administrator to log in loads the attacker’s script (stored XSS), which steals the CSRF token and silently installs a plugin — a 1-click path to remote code execution. It’s fixed in Craft CMS 4.18.0 and 5.10.0. Upgrade now.
What the Vulnerability Is
The root cause is an origin-validation error (CWE-346) that chains three weaknesses together:
- Permissive proxy trust. Craft’s default
GeneralConfig::$trustedHostsis['any']. That tells the underlying Yii2 framework to trust theHost/X-Forwarded-Hostheader on every request, even if your Nginx or Apache front end validated the realHost. An attacker who injectsX-Forwarded-Host: attacker.tldgets Craft to set its global$baseUrlto their domain. - A validation check that trusts the poisoned value. In
AppController::actionResourceJs(), Craft guards the remote fetch withstr_starts_with($url, $baseUrl). But$baseUrlis already attacker-controlled, so the check passes for the attacker’s own URL. Craft then fetches it with a Guzzle HTTP client that, unlike other fetchers in the codebase, allows redirects. - Forced JavaScript content type. Whatever comes back from the attacker’s server is returned to the browser via
asRaw()withContent-Type: application/javascript. The endpoint has effectively become an open proxy that serves attacker content as trusted first-party script.
The condition is reachable when assetManager.cacheSourcePaths is false. On its own, the flaw is a blind SSRF (useful for probing internal networks). But the devastating impact appears when a caching layer sits in front of Craft: the poisoned /actions/app/resource-js response gets cached, and when an authenticated administrator next opens the Control Panel, their browser executes the cached malicious JavaScript as stored XSS. That script reads window.Craft.csrfTokenValue and POSTs to /admin/actions/plugins/install-plugin, installing a plugin of the attacker’s choosing — remote code execution via session riding.
Why It Matters
- CVSS 10.0, no authentication to start the chain.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H— network vector, low complexity, no privileges, no user interaction, and a scope change from web app to server. - Insecure default does the work.
trustedHosts => ['any']is the out-of-the-box setting, so most installs are exposed without any misconfiguration on the operator’s part. - Caching layers amplify it. CDNs and reverse-proxy caches — standard on production sites — are exactly what turns a blind SSRF into a stored-XSS-to-RCE weapon that hits the highest-privilege user.
- RCE means full server control. Installing an arbitrary plugin lets an attacker run PHP on the host, read secrets, and pivot.
- Craft powers real production sites. It’s a mainstream PHP CMS used for business and agency sites; unpatched instances are attractive targets.
Am I Affected?
You are potentially exposed if all of these apply:
- You run Craft CMS in the affected range: 4.0.0-RC1 up to (not including) 4.18.0, or 5.0.0-RC1 up to (not including) 5.10.0.
- Your configuration leaves
trustedHostsat the permissive default (['any']) — the shipped default. assetManager.cacheSourcePathsisfalse.
The risk escalates from blind SSRF to full stored-XSS-to-RCE if a caching layer (CDN or reverse-proxy cache) sits in front of Craft, which is common in production. Check your Craft version in the Control Panel (Utilities → System Report) or via php craft version on the server.
What to Do About It: Step-by-Step
Step 1: Upgrade Craft to a fixed release — this is the real fix
Move to 4.18.0 (4.x branch) or 5.10.0 (5.x branch) or later:
composer require craftcms/cms:^5.10.0 --update-with-dependencies
php craft up
Use ^4.18.0 if you’re on the 4.x line. Run php craft migrate/all / php craft up after updating.
Step 2: Lock down trustedHosts regardless
Don’t rely on the default. In config/general.php, restrict trusted hosts to your real domain(s) instead of ['any']:
return [
'trustedHosts' => ['^example\.com$', '^www\.example\.com$'],
];
This is defense-in-depth against this and future host-header attacks.
Step 3: Enforce the real Host at the edge
Configure Nginx/Apache to reject requests whose Host doesn’t match your site, and to strip or ignore attacker-supplied X-Forwarded-Host unless it comes from a trusted proxy. Example Nginx guard:
if ($host !~* ^(example\.com|www\.example\.com)$) { return 421; }
proxy_set_header X-Forwarded-Host $host;
Step 4: Purge your cache after patching
If a CDN or reverse-proxy cache fronts the site, purge it fully to evict any already-poisoned /actions/app/resource-js responses.
Step 5: Hunt for exploitation
Search web-server/access logs for requests to the resource endpoint carrying suspicious host headers, and for unexpected plugin installs:
grep -Ei "/actions/app/resource-js" /var/log/nginx/access.log | grep -i "x-forwarded-host"
Review Craft’s installed plugins list and the project.yaml / plugin config for anything you didn’t install.
Step 6: If you find signs of compromise, treat it as RCE
Rotate the Craft security key and all secrets, review admin users, reinstall from known-good code, and restore content from a clean backup taken before the earliest suspicious request.
Quick-Win Checklist
- Confirmed the running Craft CMS version and branch.
- Upgraded to Craft 4.18.0 / 5.10.0 (or later).
- Set
trustedHoststo an explicit allow-list instead of['any']. - Enforced Host validation and controlled
X-Forwarded-Hostat the web-server/proxy edge. - Fully purged CDN / reverse-proxy caches after patching.
- Reviewed logs for poisoned host headers and the installed-plugins list for rogue entries.
- Rotated the security key and secrets if any compromise is suspected.