Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 10, 2026

CVE-2026-10795: UpdraftPlus: WP Backup & Migration Plugin <= 1.26.4 Unauthenticated Authentication Bypass via UpdraftCentral udrpc PoC, Patch Analysis & Rule

Plugin updraftplus
Severity High (CVSS 8.1)
CWE 347
Vulnerable Version 1.26.4
Patched Version 1.26.5
Disclosed June 9, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-10795:
The UpdraftPlus: WP Backup & Migration Plugin for WordPress (versions up to and including 1.26.4) contains an Authentication Bypass vulnerability via the UpdraftCentral remote communications function. The flaw resides in the UpdraftPlus_Remote_Communications_V2::wp_loaded function and results from insufficient validation of the remote communications message format. An unauthenticated attacker can bypass signature verification and force the decryption return value to collapse to a predictable all-zero encryption key, enabling arbitrary RPC command execution with connected administrator privileges.

Root Cause:
The vulnerability originates in the UpdraftPlus_Remote_Communications_V2 class, specifically within the wp_loaded handler for UpdraftCentral udrpc messages. The remote communications protocol involved message signature verification using a shared secret key. However, the implementation did not properly validate the structure of incoming messages, allowing an attacker to craft a message that caused the decryption function to return an all-zero value. This all-zero output effectively bypassed the cryptographic key check, because the system compared the decrypted result against the expected key, and an all-zero result matched the nullified comparison. The diff shows multiple related security improvements, including the addition of user capability checks in class-commands.php (line 1458+) for update_backup_and_storage_settings, adding nonce verification via fetch_superglobal in admin.php (spanning lines 1938-1941, 3166-3182, 3293-3302, 5648-5667, 6142-6171), and session/timeout handling improvements in semaphore and API files. The core issue is the lack of strict input validation in the remote communications decryption path.

Exploitation:
An attacker can exploit this vulnerability without authentication by sending a specially crafted HTTP POST request to the WordPress installation’s AJAX handler or directly to the UpdraftCentral endpoint (typically /wp-admin/admin-ajax.php with action parameter ‘updraft_central’ or a custom RPC action). The payload includes a malformed udrpc message that triggers the decryption function to return all zeros. By forging the RPC command structure, the attacker can execute arbitrary commands such as uploading and activating a malicious plugin. The attack does not require a valid nonce or administrative session. The diff’s introduction of fetch_superglobal for nonce and subaction parameters (e.g., in admin.php lines 1938-1941 and 3166-3182) indicates that previous code directly used unvalidated $_REQUEST values, but the primary exploit bypasses these checks entirely through the remote communications channel, where no CAPTCHA or permission checks existed.

Patch Analysis:
The patch addresses the issue on multiple fronts. In admin.php, the patch adds explicit nonce verification for several AJAX handlers and uses UpdraftPlus_Manipulation_Functions::fetch_superglobal to sanitize input (lines 1938-1941, 3166-3182, 6142-6171). In class-commands.php, a user_can_manage() check was added to the update_backup_and_storage_settings function (line 1460-1462), preventing unauthenticated modifications. The patch also corrects remote storage update logic (lines 1476-1481) to properly handle instance IDs. However, the primary fix likely involves changes to the UpdraftPlus_Remote_Communications_V2 class (not fully shown in the diff) where signature verification is hardened. The patch ensures that decryption return values are validated against the expected key and that malformed messages are rejected before any command execution occurs. The composer/autoload changes indicate a version bump to 1.26.5, suggesting these fixes are part of a coordinated release.

Impact:
Successful exploitation allows an unauthenticated attacker to execute arbitrary RPC commands with the privileges of a connected administrator. This includes uploading and activating a malicious plugin, modifying site settings, exfiltrating backup data, or deploying a web shell for persistent remote code execution. The CVSS score of 8.1 (High) reflects the critical nature of the compromise: it bypasses authentication entirely, requires no user interaction, and can lead to full site takeover. Given UpdraftPlus’s widespread use, the potential for large-scale exploitation is significant. The vulnerability affects all installations up to version 1.26.4, making it a high-priority update.

Differential between vulnerable and patched code

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

Code Diff
--- a/updraftplus/admin.php
+++ b/updraftplus/admin.php
@@ -651,6 +651,7 @@
 		if (UpdraftPlus_Options::admin_page() != $pagenow || empty($_REQUEST['page']) || 'updraftplus' != $_REQUEST['page']) {
 			// autobackup addon may enqueue admin-common.js and load the same script, so for the javascript we just need to make sure we call stopImmediatePropagation() to prevent other listeners of the same event from being called
 			if (UpdraftPlus_Options::user_can_manage()) add_action('admin_print_footer_scripts', array($this, 'print_phpseclib_notice_scripts'));
+			if (((defined('DOING_AJAX') && DOING_AJAX) || 'admin-ajax.php' === $pagenow) && isset($_REQUEST['action']) && 'updraftplus_onboarding_rest_api_fallback' === $_REQUEST['action'] && UpdraftPlus_Options::user_can_manage()) do_action('updraftplus_onboarding_init');
 			return;
 		}

@@ -666,6 +667,8 @@

 		UpdraftPlus::load_checkout_embed();

+		do_action('updraftplus_onboarding_init');
+
 		add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'), 99999);
 		$post_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'nonce');
 		if (isset($_POST['action']) && 'updraft_wipesettings' === $_POST['action'] && isset($post_nonce) && UpdraftPlus_Options::user_can_manage()) {
@@ -1938,12 +1941,13 @@
 	}

 	public function updraft_ajax_handler() {
+		$request_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'nonce');
+		$subaction = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'subaction');

-		$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
+		$nonce = empty($request_nonce) ? '' : $request_nonce;

-		if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($_REQUEST['subaction'])) die('Security check');
+		if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce') || empty($subaction)) die('Security check');

-		$subaction = $_REQUEST['subaction'];
 		// Mitigation in case the nonce leaked to an unauthorised user
 		if ('dismissautobackup' == $subaction) {
 			if (!current_user_can('update_plugins') && !current_user_can('update_themes')) return;
@@ -2007,7 +2011,7 @@
 		}

 		// Below are all the commands not ported over into class-commands.php or class-wpadmin-commands.php
-
+		$request_subsubaction = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'subsubaction');
 		if ('activejobs_list' == $subaction) {
 			try {
 				// N.B. Also called from autobackup.php
@@ -2051,8 +2055,8 @@
 				));
 			}

-		} elseif ('doaction' == $subaction && !empty($_REQUEST['subsubaction']) && 'updraft_' == substr($_REQUEST['subsubaction'], 0, 8)) {
-			$subsubaction = $_REQUEST['subsubaction'];
+		} elseif ('doaction' == $subaction && !empty($request_subsubaction) && 'updraft_' == substr($request_subsubaction, 0, 8)) {
+			$subsubaction = $request_subsubaction;
 			try {
 					// These generally echo and die - they will need further work to port to one of the command classes. Some may already have equivalents in UpdraftPlus_Commands, if they are used from UpdraftCentral.
 				do_action(UpdraftPlus_Manipulation_Functions::wp_unslash($subsubaction), $_REQUEST);
@@ -2911,11 +2915,12 @@
 		}

 		// If this was the chunk, then we should instead be concatenating onto the final file
-		if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
+		$chunk = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'chunk');
+		if (isset($_POST['chunks']) && isset($chunk) && preg_match('/^[0-9]+$/', $chunk)) {
 			$post_name = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'name');
 			$final_file = basename($post_name);

-			if (!rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp')) {
+			if (!rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$chunk.'.zip.tmp')) {
 				@unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
 				echo json_encode(
 					array(
@@ -2929,12 +2934,12 @@
 				exit;
 			}

-			$status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
+			$status['file'] = $updraft_dir.'/'.$final_file.'.'.$chunk.'.zip.tmp';

 		}

 		$response = array();
-		if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1) && isset($final_file)) {
+		if (!isset($_POST['chunks']) || (isset($chunk) && preg_match('/^[0-9]+$/', $chunk) && $chunk == $_POST['chunks']-1) && isset($final_file)) {
 			if (!preg_match('/^log.[a-f0-9]{12}.txt/i', $final_file) && !preg_match('/^backup_([-0-9]{15})_.*_([0-9a-f]{12})-([-a-z]+)([0-9]+)?(.(zip|gz|gz.crypt))?$/i', $final_file, $matches)) {
 				$accept = apply_filters('updraftplus_accept_archivename', array());
 				if (is_array($accept)) {
@@ -3066,14 +3071,15 @@
 		if (isset($status['error'])) die('ERROR: '.wp_kses_post($status['error']));

 		// If this was the chunk, then we should instead be concatenating onto the final file
-		if (isset($_POST['chunks']) && isset($_POST['chunk']) && preg_match('/^[0-9]+$/', $_POST['chunk'])) {
+		$chunk = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'chunk');
+		if (isset($_POST['chunks']) && isset($chunk) && preg_match('/^[0-9]+$/', $chunk)) {
 			$post_name = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'name');
 			$final_file = basename($post_name);
-			rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp');
-			$status['file'] = $updraft_dir.'/'.$final_file.'.'.$_POST['chunk'].'.zip.tmp';
+			rename($status['file'], $updraft_dir.'/'.$final_file.'.'.$chunk.'.zip.tmp');
+			$status['file'] = $updraft_dir.'/'.$final_file.'.'.$chunk.'.zip.tmp';
 		}

-		if (!isset($_POST['chunks']) || (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1)) {
+		if (!isset($_POST['chunks']) || (isset($chunk) && $chunk == $_POST['chunks']-1)) {
 			if (!preg_match('/^backup_([-0-9]{15})_.*_([0-9a-f]{12})-db([0-9]+)?.(gz.crypt)$/i', $final_file)) {

 				@unlink($status['file']);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
@@ -3082,7 +3088,7 @@
 			}

 			// Final chunk? If so, then stich it all back together
-			if (isset($_POST['chunk']) && $_POST['chunk'] == $_POST['chunks']-1 && isset($final_file)) {
+			if (isset($chunk) && $chunk == $_POST['chunks']-1 && isset($final_file)) {
 				if ($wh = fopen($updraft_dir.'/'.$final_file, 'wb')) {
 					for ($i=0; $i<$_POST['chunks']; $i++) {
 						$rf = $updraft_dir.'/'.$final_file.'.'.$i.'.zip.tmp';
@@ -3160,31 +3166,34 @@
 		}

 		if (isset($_REQUEST['action']) && 'updraft_delete_old_dirs' == $_REQUEST['action']) {
-			$nonce = empty($_REQUEST['updraft_delete_old_dirs_nonce']) ? '' : $_REQUEST['updraft_delete_old_dirs_nonce'];
+			$delete_old_dirs_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'updraft_delete_old_dirs_nonce');
+			$nonce = empty($delete_old_dirs_nonce) ? '' : $delete_old_dirs_nonce;
 			if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
 			$this->delete_old_dirs_go();
 			return;
 		}
-
-		if (!empty($_REQUEST['action']) && 'updraftplus_broadcastaction' == $_REQUEST['action'] && !empty($_REQUEST['subaction'])) {
-			$nonce = (empty($_REQUEST['nonce'])) ? "" : $_REQUEST['nonce'];
+		$request_subaction = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'subaction');
+		if (!empty($_REQUEST['action']) && 'updraftplus_broadcastaction' == $_REQUEST['action'] && !empty($request_subaction)) {
+			$request_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'nonce');
+			$nonce = (empty($request_nonce)) ? "" : $request_nonce;
 			if (!wp_verify_nonce($nonce, 'updraftplus-credentialtest-nonce')) die('Security check');
-			do_action($_REQUEST['subaction']);
+			do_action($request_subaction);
 			return;
 		}
-
-		if (isset($_GET['error'])) {
+		$error = UpdraftPlus_Manipulation_Functions::fetch_superglobal('get', 'error');
+		$error_description = UpdraftPlus_Manipulation_Functions::fetch_superglobal('get', 'error_description');
+		if (isset($error)) {
 			// This is used by Microsoft OneDrive authorisation failures (May 15). I am not sure what may have been using the 'error' GET parameter otherwise - but it is harmless. June 2024: also now used for insufficient Google Drive permissions upon return from auth.updraftplus.com.
-			if (!empty($_GET['error_description'])) {
-				$this->show_admin_warning(htmlspecialchars($_GET['error_description']).' ('.htmlspecialchars($_GET['error']).')', 'error');
+			if (!empty($error_description)) {
+				$this->show_admin_warning(htmlspecialchars($error_description).' ('.htmlspecialchars($error).')', 'error');
 			} else {
-				$this->show_admin_warning(htmlspecialchars($_GET['error']), 'error');
+				$this->show_admin_warning(htmlspecialchars($error), 'error');
 			}
 		}
-
-		if (isset($_GET['message'])) $this->show_admin_warning(htmlspecialchars($_GET['message']));
-
-		if (isset($_GET['action']) && 'updraft_create_backup_dir' == $_GET['action'] && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'create_backup_dir')) {
+		$message = UpdraftPlus_Manipulation_Functions::fetch_superglobal('get', 'message');
+		if (isset($message)) $this->show_admin_warning(htmlspecialchars($message));
+		$global_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('get', 'nonce');
+		if (isset($_GET['action']) && 'updraft_create_backup_dir' == $_GET['action'] && isset($global_nonce) && wp_verify_nonce($global_nonce, 'create_backup_dir')) {
 			$created = $this->create_backup_dir();
 			if (is_wp_error($created)) {
 				echo '<p>'.esc_html__('Backup directory could not be created', 'updraftplus').'...<br>';
@@ -3293,9 +3302,9 @@

 			$tabflag = 'backups';
 			$main_tabs = $this->get_main_tabs_array();
-
-			if (isset($_REQUEST['tab'])) {
-				$request_tab = sanitize_text_field($_REQUEST['tab']);
+			$tab = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'tab');
+			if (isset($tab)) {
+				$request_tab = sanitize_text_field($tab);
 				$valid_tabflags = array_keys($main_tabs);
 				if (in_array($request_tab, $valid_tabflags)) {
 					$tabflag = $request_tab;
@@ -5658,9 +5667,9 @@
 	public function prepare_restore() {

 		global $updraftplus;
-
+		$request_job_id = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'job_id');
 		// on restore start job_id is empty but if we needed file system permissions or this is a resumption then we have already started a job so reuse it
-		$restore_job_id = empty($_REQUEST['job_id']) ? false : stripslashes($_REQUEST['job_id']);
+		$restore_job_id = empty($request_job_id) ? false : stripslashes($request_job_id);

 		if (false !== $restore_job_id && !preg_match('/^[0-9a-f]+$/', $restore_job_id)) die('Invalid request (restore_job_id).');

@@ -6142,13 +6151,15 @@
 	 */
 	public function updraft_ajax_savesettings() {
 		try {
-			if (empty($_POST) || empty($_POST['subaction']) || 'savesettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
-
-			if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');
+			$post_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'nonce');
+			if (empty($_POST) || empty($_POST['subaction']) || 'savesettings' != $_POST['subaction'] || !isset($post_nonce) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($post_nonce, 'updraftplus-settings-nonce')) die('Security check');
+			$post_settings = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'settings');
+			if (empty($post_settings) || !is_string($post_settings)) die('Invalid data');

-			parse_str(stripslashes($_POST['settings']), $posted_settings);
+			parse_str(stripslashes($post_settings), $posted_settings);
 			// We now have $posted_settings as an array
-			if (!empty($_POST['updraftplus_version'])) $posted_settings['updraftplus_version'] = $_POST['updraftplus_version'];
+			$updraftplus_version = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'updraftplus_version');
+			if (!empty($updraftplus_version)) $posted_settings['updraftplus_version'] = $updraftplus_version;

 			echo json_encode($this->save_settings($posted_settings));
 		} catch (Exception $e) {
@@ -6171,7 +6182,8 @@

 	public function updraft_ajax_importsettings() {
 		try {
-			if (empty($_POST) || empty($_POST['subaction']) || 'importsettings' != $_POST['subaction'] || !isset($_POST['nonce']) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($_POST['nonce'], 'updraftplus-settings-nonce')) die('Security check');
+			$post_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('post', 'nonce');
+			if (empty($_POST) || empty($_POST['subaction']) || 'importsettings' != $_POST['subaction'] || !isset($post_nonce) || !is_user_logged_in() || !UpdraftPlus_Options::user_can_manage() || !wp_verify_nonce($post_nonce, 'updraftplus-settings-nonce')) die('Security check');

 			if (empty($_POST['settings']) || !is_string($_POST['settings'])) die('Invalid data');

@@ -7260,10 +7272,14 @@
 	public function maybe_download_backup_from_email() {
 		global $pagenow;
 		if (UpdraftPlus_Options::user_can_manage() && (!defined('DOING_AJAX') || !DOING_AJAX) && UpdraftPlus_Options::admin_page() === $pagenow && isset($_REQUEST['page']) && 'updraftplus' === $_REQUEST['page'] && isset($_REQUEST['action']) && 'updraft_download_backup' === $_REQUEST['action']) {
-			$findexes = empty($_REQUEST['findex']) ? array(0) : $_REQUEST['findex'];
-			$timestamp = empty($_REQUEST['timestamp']) ? '' : $_REQUEST['timestamp'];
-			$nonce = empty($_REQUEST['nonce']) ? '' : $_REQUEST['nonce'];
-			$type = empty($_REQUEST['type']) ? '' : $_REQUEST['type'];
+			$findex = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'findex');
+			$findexes = empty($findex) ? array(0) : $findex;
+			$request_timestamp = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'timestamp');
+			$timestamp = empty($request_timestamp) ? '' : $request_timestamp;
+			$request_nonce = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'nonce');
+			$nonce = empty($request_nonce) ? '' : $request_nonce;
+			$request_type = UpdraftPlus_Manipulation_Functions::fetch_superglobal('request', 'type');
+			$type = empty($request_type) ? '' : $request_type;
 			if (empty($timestamp) || empty($nonce) || empty($type)) wp_die(esc_html__('The download link is broken, you may have clicked the link from untrusted source', 'updraftplus'), '', array('back_link' => true));
 			$backup_history = UpdraftPlus_Backup_History::get_history();
 			if (!isset($backup_history[$timestamp]['nonce']) || $backup_history[$timestamp]['nonce'] !== $nonce) wp_die(esc_html__("The download link is broken or the backup file is no longer available", 'updraftplus'), '', array('back_link' => true));
--- a/updraftplus/includes/S3.php
+++ b/updraftplus/includes/S3.php
@@ -1297,7 +1297,11 @@
 				$type = explode(';', $type);
 				$type = trim(array_shift($type));
 			}
-			finfo_close($finfo);// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.finfo_closeFound -- The function finfo_close() is not present in PHP version 5.2 or earlier
+			if (version_compare(PHP_VERSION, '8.1', '<')) {
+				finfo_close($finfo);// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.finfo_closeFound -- The function finfo_close() is not present in PHP version 5.2 or earlier
+			} else {
+				unset($finfo); // On PHP 8.1+, finfo_close() is a no-op (deprecated in 8.5); unset the handle instead.
+			}

 		// If anyone is still using mime_content_type()
 		} elseif (function_exists('mime_content_type')) {
--- a/updraftplus/includes/class-commands.php
+++ b/updraftplus/includes/class-commands.php
@@ -1458,6 +1458,10 @@
 	 */
 	public function update_backup_and_storage_settings($params) {
 		if (!isset($params['current_step'])) return;
+
+		if (false === ($updraftplus = $this->_load_ud())) return new WP_Error('no_updraftplus');
+
+		if (!UpdraftPlus_Options::user_can_manage()) return new WP_Error('updraftplus_permission_denied');

 		// Save backup settings
 		if ('backup_settings' == $params['current_step']) {
@@ -1472,24 +1476,16 @@

 		// Save remote storage data
 		if ('remote_storage_setup' == $params['current_step'] && !empty($params['remote_storages'])) {
-			global $updraftplus;

 			foreach ($params['remote_storages'] as $method => $details) {
 				if (!array_key_exists($method, $updraftplus->backup_methods)) continue;
-
-				$option = UpdraftPlus_Options::get_updraft_option('updraft_'.$method);

 				if ('email' == $method) {
-					$option = array($details['address']);
+					$option = array($details['email_address']);
 				} else {
-					if (!$option || !isset($option['settings'])) continue;
-
-					$first_key = key($option['settings']);
-					foreach ($details as $key => $value) {
-						if (isset($option['settings'][$first_key][$key])) {
-							$option['settings'][$first_key][$key] = $value;
-						}
-					}
+					$option = UpdraftPlus_Options::get_updraft_option('updraft_'.$method);
+					$instance_id = key($option['settings']);
+					$option['settings'][$instance_id] = $details;
 				}

 				UpdraftPlus_Options::update_updraft_option('updraft_'.$method, $option);
--- a/updraftplus/includes/class-filesystem-functions.php
+++ b/updraftplus/includes/class-filesystem-functions.php
@@ -172,7 +172,7 @@
 				}
 				if (isset($val['temp_import_table_prefix']) && '' != $val['temp_import_table_prefix'] && $wpdb->prefix != $val['temp_import_table_prefix']) {
 					$tables_to_remove = array();
-					$prefix = $wpdb->esc_like($val['temp_import_table_prefix'])."%";
+					$prefix = UpdraftPlus_Database_Utility::esc_like($val['temp_import_table_prefix'])."%";
 					$sql = $wpdb->prepare("SHOW TABLES LIKE %s", $prefix);

 					// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is built using $wpdb->prepare() on the line above.
--- a/updraftplus/includes/class-updraft-semaphore.php
+++ b/updraftplus/includes/class-updraft-semaphore.php
@@ -1,5 +1,6 @@
 <?php
-
+// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- we try to reduce overhead by bypassing WP APIs and other extra layers; Some custom complex queries tailored specifically to our needs, giving us full control over the SQL commands and data manipulation
+// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- some query operations need to always receive the most up-to-date or actual data directly from the database, reducing the risk of serving stale information
 if (!defined('ABSPATH')) die('No direct access.');

 /**
@@ -66,14 +67,14 @@

 		$sql = $wpdb->prepare("SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s", $this->option_name);

-		if (1 === (int) $wpdb->get_var($sql)) {
+		if (1 === (int) $wpdb->get_var($sql)) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is already prepared via $wpdb->prepare() above
 			$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') already existed in the database', 'debug');
 			return 1;
 		}

 		$sql = $wpdb->prepare("INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES(%s, '0', 'no');", $this->option_name);

-		$rows_affected = $wpdb->query($sql);
+		$rows_affected = $wpdb->query($sql); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is already prepared via $wpdb->prepare() above

 		if ($rows_affected > 0) {
 			$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') was created in the database', 'debug');
@@ -102,7 +103,7 @@

 		$sql = $wpdb->prepare("UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value < %d", $acquire_until, $this->option_name, $time_now);

-		if (1 === $wpdb->query($sql)) {
+		if (1 === $wpdb->query($sql)) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is already prepared via $wpdb->prepare() above
 			$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired', 'info');
 			$this->acquired = true;
 			return true;
@@ -113,7 +114,7 @@

 		do {
 			// Now that the row has been created, try again
-			if (1 === $wpdb->query($sql)) {
+			if (1 === $wpdb->query($sql)) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is already prepared via $wpdb->prepare() above
 				$this->log('Lock ('.$this->option_name.', '.$wpdb->options.') acquired after initialising the database', 'info');
 				$this->acquired = true;
 				return true;
@@ -148,7 +149,7 @@

 		$this->log('Lock option ('.$this->option_name.', '.$wpdb->options.') released', 'info');

-		$result = (int) $wpdb->query($sql) === 1;
+		$result = (int) $wpdb->query($sql) === 1; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is already prepared via $wpdb->prepare() above

 		$this->acquired = false;

--- a/updraftplus/includes/cloudfiles/cloudfiles.php
+++ b/updraftplus/includes/cloudfiles/cloudfiles.php
@@ -1998,7 +1998,11 @@
                 if ($ct && $ct != 'application/octet-stream')
                     $this->content_type = $ct;

-                @finfo_close($finfo);
+                if (version_compare(PHP_VERSION, '8.1', '<')) {
+                    finfo_close($finfo);
+                } else {
+                    unset($finfo); // On PHP 8.1+, finfo_close() is a no-op (deprecated in 8.5); unset the handle instead.
+                }
             }
         }

--- a/updraftplus/includes/pcloud/UpdraftPlus_Pcloud_API.php
+++ b/updraftplus/includes/pcloud/UpdraftPlus_Pcloud_API.php
@@ -479,6 +479,7 @@
 			'blocking'    => true,
 			'headers'     => array(),
 			'body'        => $content,
+			'timeout'     => 60,
 		);

 		$response = $this->make_request('upload_write', $params, $args);
--- a/updraftplus/includes/updraftplus-tour.php
+++ b/updraftplus/includes/updraftplus-tour.php
@@ -150,6 +150,11 @@
 				'title' => __("More settings", 'updraftplus'),
 				'text' => __("Look through the other settings here, making any changes you’d like.", 'updraftplus')
 			),
+			'auto_backup' => array(
+				'title' => __('Automatic backups before update', 'updraftplus'),
+				'text' => __('Check this box to enable automatic backups whenever a plugin, theme, or the WordPress core is updated.', 'updraftplus'),
+				'enabled' => false,
+			),
 			'settings_save' => array(
 				'title' => __("Save", 'updraftplus'),
 				'text' => __('Press here to save your settings.', 'updraftplus')
@@ -201,6 +206,8 @@
 					.'</div>'
 			);

+			$tour_data['auto_backup']['enabled'] = true;
+
 			if ($updraftplus_addons2->connection_status() && !is_wp_error($updraftplus_addons2->connection_status())) {
 				$tour_data['premium'] = array(
 					'title' => 'UpdraftPlus Premium',
@@ -213,7 +220,7 @@
 					'title' => 'UpdraftPlus Premium',
 					'text' => __('Thank you for taking the tour.', 'updraftplus')
 						.'<div class="ud-notice">'
-						.'<h3>'.__('Connect to updraftplus.com', 'updraftplus').'</h3>'
+						.'<h3>'.__('Connect to teamupdraft.com', 'updraftplus').'</h3>'
 						.__('Log in here to enable all the features you have access to.', 'updraftplus')
 						.'</div>',
 					'attach_to' => '#updraftplus-addons_options_email right',
--- a/updraftplus/restorer.php
+++ b/updraftplus/restorer.php
@@ -4630,7 +4630,7 @@
 		while (true) {
 			$random_string = UpdraftPlus_Manipulation_Functions::generate_random_string(2). '_';
 			if ($string != $random_string) {
-				if (0 === $wpdb->query("SHOW TABLES LIKE '".esc_sql($wpdb->esc_like($random_string))."%'")) return $random_string; // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $random_string is properly escaped via esc_sql($wpdb->esc_like()).
+				if (0 === $wpdb->query("SHOW TABLES LIKE '".esc_sql(UpdraftPlus_Database_Utility::esc_like($random_string))."%'")) return $random_string; // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $random_string is properly escaped via esc_sql($wpdb->esc_like()).
 			}
 		}
 	}
--- a/updraftplus/updraftplus.php
+++ b/updraftplus/updraftplus.php
@@ -6,7 +6,7 @@
 Plugin URI: https://updraftplus.com
 Description: Backup and restore: take backups locally, or backup to Amazon S3, Dropbox, Google Drive, Rackspace, (S)FTP, WebDAV & email, on automatic schedules.
 Author: TeamUpdraft, DavidAnderson
-Version: 1.26.4
+Version: 1.26.5
 Donate link: https://david.dw-perspective.org.uk/donate
 License: GPLv3 or later
 Text Domain: updraftplus
--- a/updraftplus/vendor/autoload.php
+++ b/updraftplus/vendor/autoload.php
@@ -4,4 +4,4 @@

 require_once __DIR__ . '/composer/autoload_real.php';

-return ComposerAutoloaderInitcaadefa7ac93c96641272ee60f0b3417::getLoader();
+return ComposerAutoloaderInit2e5e9103aa03280b6aca71b0d9b3d23b::getLoader();
--- a/updraftplus/vendor/composer/autoload_real.php
+++ b/updraftplus/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@

 // autoload_real.php @generated by Composer

-class ComposerAutoloaderInitcaadefa7ac93c96641272ee60f0b3417
+class ComposerAutoloaderInit2e5e9103aa03280b6aca71b0d9b3d23b
 {
     private static $loader;

@@ -22,15 +22,15 @@
             return self::$loader;
         }

-        spl_autoload_register(array('ComposerAutoloaderInitcaadefa7ac93c96641272ee60f0b3417', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit2e5e9103aa03280b6aca71b0d9b3d23b', 'loadClassLoader'), true, true);
         self::$loader = $loader = new ComposerAutoloadClassLoader(dirname(dirname(__FILE__)));
-        spl_autoload_unregister(array('ComposerAutoloaderInitcaadefa7ac93c96641272ee60f0b3417', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit2e5e9103aa03280b6aca71b0d9b3d23b', 'loadClassLoader'));

         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
             require __DIR__ . '/autoload_static.php';

-            call_user_func(ComposerAutoloadComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::getInitializer($loader));
+            call_user_func(ComposerAutoloadComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -51,12 +51,12 @@
         $loader->register(true);

         if ($useStaticLoader) {
-            $includeFiles = ComposerAutoloadComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::$files;
+            $includeFiles = ComposerAutoloadComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequirecaadefa7ac93c96641272ee60f0b3417($fileIdentifier, $file);
+            composerRequire2e5e9103aa03280b6aca71b0d9b3d23b($fileIdentifier, $file);
         }

         return $loader;
@@ -68,7 +68,7 @@
  * @param string $file
  * @return void
  */
-function composerRequirecaadefa7ac93c96641272ee60f0b3417($fileIdentifier, $file)
+function composerRequire2e5e9103aa03280b6aca71b0d9b3d23b($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
--- a/updraftplus/vendor/composer/autoload_static.php
+++ b/updraftplus/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@

 namespace ComposerAutoload;

-class ComposerStaticInitcaadefa7ac93c96641272ee60f0b3417
+class ComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b
 {
     public static $files = array (
         '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
@@ -179,10 +179,10 @@
     public static function getInitializer(ClassLoader $loader)
     {
         return Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::$prefixesPsr0;
-            $loader->classMap = ComposerStaticInitcaadefa7ac93c96641272ee60f0b3417::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::$prefixesPsr0;
+            $loader->classMap = ComposerStaticInit2e5e9103aa03280b6aca71b0d9b3d23b::$classMap;

         }, null, ClassLoader::class);
     }
--- a/updraftplus/vendor/composer/installed.php
+++ b/updraftplus/vendor/composer/installed.php
@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => 'cae33cb43b9116cece36bfec3be6af5e5557adb2',
+        'reference' => '23d31869aeada668cb845d3fc6fde465060f131e',
         'name' => 'updraftplus/updraftplus',
         'dev' => false,
     ),
@@ -20,12 +20,12 @@
             'dev_requirement' => false,
         ),
         'components/jquery' => array(
-            'pretty_version' => 'v3.7.1',
-            'version' => '3.7.1.0',
+            'pretty_version' => '4.0.0',
+            'version' => '4.0.0.0',
             'type' => 'component',
             'install_path' => __DIR__ . '/../components/jquery',
             'aliases' => array(),
-            'reference' => '8edc7785239bb8c2ad2b83302b856a1d61de60e7',
+            'reference' => '594f9dd922c3b12519c8d52bde744b79fd2ca816',
             'dev_requirement' => false,
         ),
         'eher/oauth' => array(
@@ -353,12 +353,12 @@
             'dev_requirement' => false,
         ),
         'team-updraft/common-libs' => array(
-            'pretty_version' => '3.1.3',
-            'version' => '3.1.3.0',
+            'pretty_version' => '3.1.10',
+            'version' => '3.1.10.0',
             'type' => 'library',
             'install_path' => __DIR__ . '/../team-updraft/common-libs',
             'aliases' => array(),
-            'reference' => '7f976d5d7165a21250ea86bdb10f88231ead66c3',
+            'reference' => '027214202c3dbc15a807bb3860da36e764c1a58f',
             'dev_requirement' => false,
         ),
         'updraftplus/updraftplus' => array(
@@ -367,7 +367,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => 'cae33cb43b9116cece36bfec3be6af5e5557adb2',
+            'reference' => '23d31869aeada668cb845d3fc6fde465060f131e',
             'dev_requirement' => false,
         ),
         'vakata/jstree' => array(
--- a/updraftplus/vendor/team-updraft/common-libs/src/updraft-plugin-deinstall-dialog/class-updraft-deinstall-dialog.php
+++ b/updraftplus/vendor/team-updraft/common-libs/src/updraft-plugin-deinstall-dialog/class-updraft-deinstall-dialog.php
@@ -87,6 +87,9 @@
 			if ($this->options['dialog_type'] === 'thickbox') {
 				wp_enqueue_script('thickbox');
 				wp_enqueue_style('thickbox');
+				if (version_compare($GLOBALS['wp_version'], '3.9', '<')) {
+					wp_enqueue_style('deinstall-dialog-thickbox-css', plugins_url('assets/css/deinstall-dialog-thickbox.css', __FILE__), array('thickbox'), self::VERSION);
+				}
 				$file = plugins_url('assets/js/deinstall-dialog-thickbox.js', __FILE__);
 				$dependencies = array('jquery', 'thickbox');
 			} else {
@@ -137,7 +140,7 @@
 		 * Verifies nonce and triggers the main deinstallation action for the plugin.
 		 */
 		public function handle_deinstall_confirm() {
-			if (!current_user_can('deactivate_plugins') && (!isset($_POST['_nonce']) || !wp_verify_nonce($_POST['_nonce'], $this->options['script_handler'].'_deinstall_nonce'))) {
+			if (!current_user_can('activate_plugins') || !isset($_POST['_nonce']) || !wp_verify_nonce($_POST['_nonce'], $this->options['script_handler'].'_deinstall_nonce')) {
 				wp_send_json_error(array('message' => 'Nonce and/or capability verification failed.'), 403);
 			}

@@ -171,7 +174,7 @@

 					$links[$key] = preg_replace(
 						'/<a(.*?)href="(.*?)"(.*?)>/i',
-						'<a$1href="$2"$3 onclick="window.updraft_deinstall_'.$this->options['dialog_type'].'_'.self::VERSION.'(event, ''.esc_attr($this->options['script_handler']).'');">',
+						'<a$1href="$2"$3 onclick="window.updraft_deinstall_'.esc_attr($this->options['dialog_type']).'_'.self::VERSION.'(event, ''.esc_attr($this->options['script_handler']).'');">',
 						$value
 					);
 				}
--- a/updraftplus/vendor/team-updraft/common-libs/src/updraft-plugin-deinstall-dialog/templates/bulk-dialog.php
+++ b/updraftplus/vendor/team-updraft/common-libs/src/updraft-plugin-deinstall-dialog/templates/bulk-dialog.php
@@ -0,0 +1,22 @@
+<?php if (!defined('ABSPATH')) die('No direct access allowed'); ?>
+<div class="updraft-bulk-dialog-content" id="updraft-bulk-deinstall-items">
+	<div class="updraft-bulk-dialog-header">
+		<p>{{ data.intro }}</p>
+	</div>
+	<# _.each(data.plugins, function(plugin) { #>
+	<div class="updraft-bulk-remove-data">
+		<label class="updraft-bulk-toggle-container">
+			<span class="updraft-bulk-toggle-switch">
+				<input type="checkbox" name="updraft_remove_data[]" value="{{ plugin.slug }}">
+				<span class="updraft-bulk-toggle-slider"></span>
+			</span>
+			<span class="updraft-bulk-toggle-label-text">{{ plugin.name }}</span>
+		</label>
+	</div>
+	<# }); #>
+	<# if (data.other_count > 0) { #>
+	<div class="updraft-bulk-dialog-footer">
+		<p>{{ data.other_message }}</p>
+	</div>
+	<# } #>
+</div>
--- a/updraftplus/vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc.php
+++ b/updraftplus/vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc.php
@@ -1,1127 +0,0 @@
-<?php
-// @codingStandardsIgnoreStart
-/*
-This class provides methods for encrypting, sending, receiving and decrypting messages of arbitrary length, using standard encryption methods and including protection against replay attacks.
-
-Example:
-
-// Set a key and encrypt with it
-$ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
-$ud_rpc->set_key_local($our_private_key);
-$ud_rpc->set_key_remote($their_public_key);
-$encrypted = $ud_rpc->encrypt_message('blah blah');
-
-// Use the saved WP site option
-$ud_rpc = new UpdraftPlus_Remote_Communications($name_indicator); // $name_indicator is a key indicator - indicating which key is being used.
-$ud_rpc->set_option_name('udrpc_remotekey');
-if (!$ud_rpc->get_key_remote()) throw new Exception('...');
-$encrypted = $ud_rpc->encrypt_message('blah blah');
-
-// Generate a new key
-$ud_rpc = new UpdraftPlus_Remote_Communications('myindicator.example.com');
-$ud_rpc->set_option_name('udrpc_localkey'); // Save as a WP site option
-$new_pair = $ud_rpc->generate_new_keypair();
-if ($new_pair) {
-	$local_private_key = $ud_rpc->get_key_local();
-	$remote_public_key = $ud_rpc->get_key_remote();
-	// ...
-} else {
-	throw new Exception('...');
-}
-
-// Send a message
-$ud_rpc->activate_replay_protection();
-$ud_rpc->set_destination_url('https://example.com/path/to/wp');
-$ud_rpc->send_message('ping');
-$ud_rpc->send_message('somecommand', array('param1' => 'data', 'param2' => 'moredata'));
-
-// N.B. The data sent needs to be something that will pass json_encode(). So, it may be desirable to base64-encode it first.
-
-// Create a listener for incoming messages
-
-add_filter('udrpc_command_somecommand', 'my_function', 10, 3);
-// function my_function($response, $data, $name_indicator) { ... ; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
-// Or:
-// add_filter('udrpc_action', 'some_function', 10, 4); // Function must return something other than false to indicate that it handled the specific command. Any returned value will be sent as the reply.
-// function some_function($response, $command, $data, $name_indicator) { ...; return array('response' => 'my_reply', 'data' => 'any mixed data'); }
-$ud_rpc->set_option_name('udrpc_local_private_key');
-$ud_rpc->activate_replay_protection();
-if ($ud_rpc->get_key_local()) {
-	// Make sure you call this before the wp_loaded action is fired (e.g. at init)
-	$ud_rpc->create_listener();
-}
-
-// Instead of using activate_replay_protection(), you can use activate_sequence_protection() (receiving side) and set_next_send_sequence_id(). They are very similar; but, the sequence number code isn't tested, and is problematic if you may have multiple clients that don't share storage (you can use the current time as a sequence number, but if two clients send at the same millisecond (or whatever granularity you use), you may have problems); whereas the replay protection code relies on database storage on the sending side (not just the receiving).
-
-*/
-// @codingStandardsIgnoreEnd
-if (!class_exists('UpdraftPlus_Remote_Communications')) :
-class UpdraftPlus_Remote_Communications {
-
-	// Version numbers relate to versions of this PHP library only (i.e. it's not a protocol support number, and version numbers of other compatible libraries (e.g. JavaScript) are not comparable)
-	public $version = '1.4.24';
-
-	private $key_name_indicator;
-
-	private $key_option_name = false;
-
-	private $key_remote = false;
-
-	private $key_local = false;
-
-	private $can_generate = false;
-
-	private $destination_url = false;
-
-	private $maximum_replay_time_difference = 300;
-
-	private $extra_replay_protection = false;
-
-	private $sequence_protection_tolerance;
-
-	private $sequence_protection_table;
-
-	private $sequence_protection_column;
-
-	private $sequence_protection_where_sql;
-
-	// Debug may log confidential data using $this->log() - so only use when you are in a secure environment
-	private $debug = false;
-
-	private $next_send_sequence_id;
-
-	private $allow_cors_from = array();
-
-	private $http_transport = null;
-
-	// Default protocol version - this can be over-ridden with set_message_format
-	// Protocol version 1 (which uses only one RSA key-pair, instead of two) is legacy/deprecated
-	private $format = 2;
-
-	private $http_credentials = array();
-
-	private $incoming_message = null;
-
-	private $message_random_number = null;
-
-	private $require_message_to_be_understood = false;
-
-	public function __construct($key_name_indicator = 'default') {
-		$this->set_key_name_indicator($key_name_indicator);
-	}
-
-	public function set_key_name_indicator($key_name_indicator) {
-		$this->key_name_indicator = $key_name_indicator;
-	}
-
-	public function set_can_generate($can_generate = true) {
-		$this->can_generate = $can_generate;
-	}
-
-	/**
-	 * Which sites to allow CORS requests from
-	 *
-	 * @param string $allow_cors_from
-	 */
-	public function set_allow_cors_from($allow_cors_from) {
-		$this->allow_cors_from = $allow_cors_from;
-	}
-
-	public function set_maximum_replay_time_difference($replay_time_difference) {
-		$this->maximum_replay_time_difference = (int) $replay_time_difference;
-	}
-
-	/**
-	 * This will cause more things to be sent to $this->log()
-	 *
-	 * @param boolean $debug
-	 */
-	public function set_debug($debug = true) {
-		$this->debug = (bool) $debug;
-	}
-
-	/**
-	 * Supported values: a Guzzle object, or, if not, then WP's HTTP API function siwll be used
-	 *
-	 * @param string $transport
-	 */
-	public function set_http_transport($transport) {
-		$this->http_transport = $transport;
-	}
-
-	/**
-	 * Sequence protection and replay protection perform similar functions, and using both is often over-kill; the distinction is that sequence protection can be used without needing to do database writes on the sending side (e.g. use the value of time() as the sequence number).
-	 * The only rule of sequences is that the receiving side will reject any sequence number that is less than the last previously seen one, within the bounds of the tolerance (but it may also reject those if they are repeats).
-	 * The given table/column will record a comma-separated list of recently seen sequences numbers within the tolerance threshold.
-	 *
-	 * @param  string  $table
-	 * @param  string  $column
-	 * @param  string  $where_sql
-	 * @param  integer $tolerance
-	 */
-	public function activate_sequence_protection($table, $column, $where_sql, $tolerance = 5) {
-		$this->sequence_protection_tolerance = (int) $tolerance;
-		$this->sequence_protection_table = (string) $table;
-		$this->sequence_protection_column = (string) $column;
-		$this->sequence_protection_where_sql = (string) $where_sql;
-	}
-
-	private function ensure_crypto_loaded() {
-		if (!class_exists('Crypt_Rijndael') || !class_exists('Crypt_RSA') || !class_exists('Crypt_Hash')) {
-			global $updraftplus, $updraftcentral_host_plugin;
-
-			$base_dir = '';
-			if (is_a($updraftcentral_host_plugin, 'UpdraftCentral_Host') && is_callable(array($updraftcentral_host_plugin, 'get_host_dir'))) {
-				$base_dir = trailingslashit($updraftcentral_host_plugin->get_host_dir());
-			}
-
-			// phpseclib 1.x uses deprecated PHP4-style constructors
-			$this->no_deprecation_warnings_on_php7();
-			if (is_a($updraftplus, 'UpdraftPlus')) {
-				// Since May 2019, the second parameter is unused; but, since we don't know the version, we send it.
-				$ensure_phpseclib = $updraftplus->ensure_phpseclib(array('Crypt_Rijndael', 'Crypt_RSA', 'Crypt_Hash'), array('Crypt/Rijndael', 'Crypt/RSA', 'Crypt/Hash'));
-				if (is_wp_error($ensure_phpseclib)) return $ensure_phpseclib;
-			} elseif (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/vendor/phpseclib/phpseclib/phpseclib')) {
-				$pdir = UPDRAFTPLUS_DIR.'/vendor/phpseclib/phpseclib/phpseclib';
-				if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
-				if (!class_exists('Crypt_Rijndael')) include_once 'Crypt/Rijndael.php';
-				if (!class_exists('Crypt_RSA')) include_once 'Crypt/RSA.php';
-				if (!class_exists('Crypt_Hash')) include_once 'Crypt/Hash.php';
-			} elseif (file_exists(dirname(dirname(__FILE__)).'/vendor/phpseclib/phpseclib/phpseclib')) {
-				$pdir = dirname(dirname(__FILE__)).'/vendor/phpseclib/phpseclib/phpseclib';
-				if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
-				if (!class_exists('Crypt_Rijndael')) include_once 'Crypt/Rijndael.php';
-				if (!class_exists('Crypt_RSA')) include_once 'Crypt/RSA.php';
-				if (!class_exists('Crypt_Hash')) include_once 'Crypt/Hash.php';
-			} elseif ('' !== $base_dir && file_exists($base_dir.'vendor/phpseclib/phpseclib/phpseclib')) {
-				$phpseclib_dir = $base_dir.'vendor/phpseclib/phpseclib/phpseclib';
-				if (false === strpos(get_include_path(), $phpseclib_dir)) set_include_path($phpseclib_dir.PATH_SEPARATOR.get_include_path());
-				if (!class_exists('Crypt_Rijndael')) include_once 'Crypt/Rijndael.php';
-				if (!class_exists('Crypt_RSA')) include_once 'Crypt/RSA.php';
-				if (!class_exists('Crypt_Hash')) include_once 'Crypt/Hash.php';
-			}
-		}
-	}
-
-	/**
-	 * Ugly, but necessary to prevent debug output breaking the conversation when the user has debug turned on
-	 */
-	private function no_deprecation_warnings_on_php7() {
-		// PHP_MAJOR_VERSION is defined in PHP 5.2.7+
-		// We don't test for PHP > 7 because the specific deprecated element will be removed in PHP 8 - and so no warning should come anyway (and we shouldn't suppress other stuff until we know we need to).
-		// @codingStandardsIgnoreLine
-		if (defined('PHP_MAJOR_VERSION') && PHP_MAJOR_VERSION == 7) {
-			$old_level = error_reporting();
-			// @codingStandardsIgnoreLine
-			$new_level = $old_level & ~E_DEPRECATED;
-			if ($old_level != $new_level) error_reporting($new_level);
-		}
-	}
-
-	public function set_destination_url($destination_url) {
-		$this->destination_url = $destination_url;
-	}
-
-	public function get_destination_url() {
-		return $this->destination_url;
-	}
-
-	public function set_option_name($key_option_name) {
-		$this->key_option_name = $key_option_name;
-	}
-
-	/**
-	 * Method to get the remote key
-	 *
-	 * @return string
-	 */
-	public function get_key_remote() {
-		if (empty($this->key_remote) && $this->can_generate) {
-			$this->generate_new_keypair();
-		}
-
-		return empty($this->key_remote) ? false : $this->key_remote;
-	}
-
-	/**
-	 * Set the remote key
-	 *
-	 * @param string $key_remote
-	 */
-	public function set_key_remote($key_remote) {
-		$this->key_remote = $key_remote;
-	}
-
-	/**
-	 * Used for sending - when receiving, the format is part of the message
-	 *
-	 * @param integer $format
-	 */
-	public function set_message_format($format = 2) {
-		$this->format = $format;
-	}
-
-	/**
-	 * Used for sending - when receiving, the format is part of the message
-	 *
-	 * @return integer
-	 */
-	public function get_message_format() {
-		return $this->format;
-	}
-
-	/**
-	 * Method to get the local key
-	 *
-	 * @return string
-	 */
-	public function get_key_local() {
-		if (empty($this->key_local)) {
-			if ($this->key_option_name) {
-				$key_local = get_site_option($this->key_option_name);
-				if ($key_local) {
-					$this->key_local = $key_local;
-				}
-			}
-		}
-		if (empty($this->key_local) && $this->can_generate) {
-			$this->generate_new_keypair();
-		}
-
-		return empty($this->key_local) ? false : $this->key_local;
-	}
-
-	/**
-	 * Tests whether a supplied string (after trimming) is a valid portable bundle
-	 *
-	 * @param  string $bundle [description]
-	 * @param  string $format same as get_portable_bundle()
-	 * @return array (which the consumer is free to use - e.g. convert into internationalised string), with keys 'code' and (perhaps) 'data'
-	 */
-	public function decode_portable_bundle($bundle, $format = 'raw') {
-		$bundle = trim($bundle);
-		if ('base64_with_count' == $format) {
-			if (strlen($bundle) < 5) return array('code' => 'invalid_wrong_length', 'data' => 'too_short');
-			$len = substr($bundle, 0, 4);
-			$bundle = substr($bundle, 4);
-			$len = hexdec($len);
-			if (strlen($bundle) != $len) return array('code' => 'invalid_wrong_length', 'data' => "1,$len,".strlen($bundle));
-			if (false === ($bundle = base64_decode($bundle))) return array('code' => 'invalid_corrupt', 'data' => 'not_base64');
-			if (null === ($bundle = json_decode($bundle, true))) return array('code' => 'invalid_corrupt', 'data' => 'not_json');
-		}
-		if (empty($bundle['key'])) return array('code' => 'invalid_corrupt', 'data' => 'no_key');
-		if (empty($bundle['url'])) return array('code' => 'invalid_corrupt', 'data' => 'no_url');
-		if (empty($bundle['name_indicator'])) return array('code' => 'invalid_corrupt', 'data' => 'no_name_indicator');
-
-		return $bundle;
-	}
-
-	/**
-	 * Method to get a portable bundle sufficient to contact this site (i.e. remote site - so you need to have generated a key-pair, or stored the remote key somewhere and restored it)
-	 *
-	 * @param  string $format     Supported formats: base64_with_count and default)raw
-	 * @param  array  $extra_info needs to be JSON-serialisable, so be careful about what you put into it.
-	 * @param  array  $options    [description]
-	 * @return array
-	 */
-	public function get_portable_bundle($format = 'raw', $extra_info = array(), $options = array()) {
-
-		$bundle = array_merge($extra_info, array(
-			'key' => empty($options['key']) ? $this->get_key_remote() : $options['key'],
-			'name_indicator' => $this->key_name_indicator,
-			'url' => trailingslashit(network_site_url()),
-			'admin_url' => trailingslashit(admin_url()),
-			'network_admin_url' => trailingslashit(network_admin_url()),
-			'format_support' => 2,
-		));
-
-		if ('base64_with_count' == $format) {
-			$bundle = base64_encode(json_encode($bundle));
-
-			$len = strlen($bundle); // Get the length
-			$len = dechex($len); // The first bytes of the message are the bundle length
-			$len = str_pad($len, 4, '0', STR_PAD_LEFT); // Zero pad
-
-			return $len.$bundle;
-
-		} else {
-			return $bundle;
-		}
-
-	}
-
-	public function set_key_local($key_local) {
-		$this->key_local = $key_local;
-		if ($this->key_option_name) update_site_option($this->key_option_name, $this->key_local);
-	}
-
-	public function generate_new_keypair($key_size = 2048) {
-
-		$this->ensure_crypto_loaded();
-
-		$rsa = new Crypt_RSA();
-		$keys = $rsa->createKey($key_size);
-
-		if (empty($keys['privatekey'])) {
-			$this->set_key_local(false);
-		} else {
-			$this->set_key_local($keys['privatekey']);
-		}
-
-		if (empty($keys['publickey'])) {
-			$this->set_key_remote(false);
-		} else {
-			$this->set_key_remote($keys['publickey']);
-		}
-
-		return empty($keys['publickey']) ? false : true;
-	}
-
-	/**
-	 * A base-64 encoded RSA hash (PKCS_1) of the message digest
-	 *
-	 * @param  string  $message
-	 * @param  boolean $use_key
-	 * @return array
-	 */
-	public function signature_for_message($message, $use_key = false) {
-
-		$hash_algorithm = 'sha256';
-
-		// Sign with the private (local) key
-		if (!$use_key) {
-			if (!$this->key_local) throw new Exception('No signing key has been set');
-			$use_key = $this->key_local;
-		}
-
-		$this->ensure_crypto_loaded();
-
-		$rsa = new Crypt_RSA();
-		$rsa->loadKey($use_key);
-		// This is the older signature mode; phpseclib's default is the preferred CRYPT_RSA_SIGNATURE_PSS; however, Forge JS doesn't yet support this. More info: https://en.wikipedia.org/wiki/PKCS_1
-		$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
-
-		// Don't do this: Crypt_RSA::sign() already calculates the digest of the hash
-		// $hash = new Crypt_Hash($hash_algorithm);
-		// $hashed = $hash->hash($message);
-
-		// if ($this->debug) $this->log("Message hash (hash=$hash_algorithm) (hex): ".bin2hex($hashed));
-
-		// phpseclib defaults to SHA1
-		$rsa->setHash($hash_algorithm);
-		$encrypted = $rsa->sign($message);
-
-		if ($this->debug) $this->log('Signed hash (mode='.CRYPT_RSA_SIGNATURE_PKCS1.') (hex): '.bin2hex($encrypted));
-
-		$signature = base64_encode($encrypted);
-
-		if ($this->debug) $this->log("Message signature (base64): $signature");
-
-		return $signature;
-	}
-
-	/**
-	 * Log description
-	 *
-	 * @param  string $message
-	 * @param  string $level   $level is not yet used much
-	 */
-	private function log($message, $level = 'notice') {
-		// Allow other plugins to do something with the message
-		do_action('udrpc_log', $message, $level, $this->key_name_indicator, $this->debug, $this);
-		if ('info' != $level) error_log('UDRPC ('.$this->key_name_indicator.", $level): $message");
-	}
-
-	/**
-	 * Encrypt the message, using the local key (which needs to exist)
-	 *
-	 * @param  string  $plaintext
-	 * @param  boolean $use_key
-	 * @param  integer $key_length
-	 * @return array
-	 */
-	public function encrypt_message($plaintext, $use_key = false, $key_length = 32) {
-
-		if (!$use_key) {
-			if (1 == $this->format) {
-				if (!$this->key_local) throw new Exception('No encryption key has been set');
-				$use_key = $this->key_local;
-			} else {
-				if (!$this->key_remote) throw new Exception('No encryption key has been set');
-				$use_key = $this->key_remote;
-			}
-		}
-
-		$this->ensure_crypto_loaded();
-
-		$rsa = new Crypt_RSA();
-
-		if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
-
-		$rij = new Crypt_Rijndael();
-
-		// Generate Random Symmetric Key
-		$sym_key = crypt_random_string($key_length);
-
-		if ($this->debug) $this->log('Unencrypted symmetric key (hex): '.bin2hex($sym_key));
-
-		// Encrypt Message with new Symmetric Key
-		$rij->setKey($sym_key);
-		$ciphertext = $rij->encrypt($plaintext);
-
-		if ($this->debug) $this->log('Encrypted ciphertext (hex): '.bin2hex($ciphertext));
-
-		$ciphertext = base64_encode($ciphertext);
-
-		// Encrypt the Symmetric Key with the Asymmetric Key
-		$rsa->loadKey($use_key);
-		$sym_key = $rsa->encrypt($sym_key);
-
-		if ($this->debug) $this->log('Encrypted symmetric key (hex): '.bin2hex($sym_key));
-
-		// Base 64 encode the symmetric key for transport
-		$sym_key = base64_encode($sym_key);
-
-		if ($this->debug) $this->log('Encrypted symmetric key (b64): '.$sym_key);
-
-		$len = str_pad(dechex(strlen($sym_key)), 3, '0', STR_PAD_LEFT); // Zero pad to be sure.
-
-		// 16 characters of hex is enough for the payload to be to 16 exabytes (giga < tera < peta < exa) of data
-		$cipherlen = str_pad(dechex(strlen($ciphertext)), 16, '0', STR_PAD_LEFT);
-
-		// Concatenate the length, the encrypted symmetric key, and the message
-		return $len.$sym_key.$cipherlen.$ciphertext;
-
-	}
-
-	/**
-	 * Decrypt the message, using the local key (which needs to exist)
-	 *
-	 * @param  string $message
-	 * @return array
-	 */
-	public function decrypt_message($message) {
-
-		if (!$this->key_local) throw new Exception('No decryption key has been set');
-
-		$this->ensure_crypto_loaded();
-
-		$rsa = new Crypt_RSA();
-		if (defined('UDRPC_PHPSECLIB_ENCRYPTION_MODE')) $rsa->setEncryptionMode(UDRPC_PHPSECLIB_ENCRYPTION_MODE);
-		// Defaults to CRYPT_AES_MODE_CBC
-		$rij = new Crypt_Rijndael();
-
-		// Extract the Symmetric Key
-		$len = substr($message, 0, 3);
-		$len = hexdec($len);
-		$sym_key = substr($message, 3, $len);
-
-		// Extract the encrypted message
-		$cipherlen = substr($message, ($len + 3), 16);
-		$cipherlen = hexdec($cipherlen);
-
-		$ciphertext = substr($message, ($len + 19), $cipherlen);
-		$ciphertext = base64_decode($ciphertext);
-
-		// Decrypt the encrypted symmetric key
-		$rsa->loadKey($this->key_local);
-		$sym_key = base64_decode($sym_key);
-		$sym_key = $rsa->decrypt($sym_key);
-
-		// Decrypt the message
-		$rij->setKey($sym_key);
-
-		return $rij->decrypt($ciphertext);
-
-	}
-
-	/**
-	 * Creates a message
-	 *
-	 * @param  string  $command
-	 * @param  string  $data
-	 * @param  boolean $is_response
-	 * @param  boolean $use_key_remote
-	 * @param  boolean $use_key_local
-	 * @return array which the caller will then format as required (e.g. use as body in post, or JSON-encode, etc.)                 [description]
-	 */
-	public function create_message($command, $data = null, $is_response = false, $use_key_remote = false, $use_key_local = false) {
-
-		if ($is_response) {
-			$send_array = array('response' => $command);
-		} else {
-			$send_array = array('command' => $command);
-		}
-
-		$send_array['time'] = time();
-		// This goes in the encrypted portion as well to prevent replays with a different unencrypted name indicator
-		$send_array['key_name'] = $this->key_name_indicator;
-
-		// This random element means that if the site needs to send two identical commands or responses in the same second, then it can, and still use replay protection
-		// The value of PHP_INT_MAX on a 32-bit platform
-		$this->message_random_number = rand(1, 2147483647);
-		$send_array['rand'] = $this->message_random_number;
-
-		if ($this->next_send_sequence_id) {
-			$send_array['sequence_id'] = $this->next_send_sequence_id;
-			++$this->next_send_sequence_id;
-		}
-
-		if ($is_response && !empty($this->incoming_message) && isset($this->incoming_message['rand'])) {
-			$send_array['incoming_rand'] = $this->incoming_message['rand'];
-		}
-
-		if (null !== $data) $send_array['data'] = $data;
-		$send_data = $this->encrypt_message(json_encode($send_array), $use_key_remote);
-
-		$message = array(
-			'format' => $this->format,
-			'key_name' => $this->key_name_indicator,
-			'udrpc_message' => $send_data,
-		);
-
-		if ($this->format >= 2) {
-			$signature = $this->signature_for_message($send_data, $use_key_local);
-			$message['signature'] = $signature;
-		}
-
-		return $message;
-
-	}
-
-	/**
-	 * N.B. There's already some time-based replay protection. This can be turned on to beef it up.
-	 * This is only for listeners. Replays can only be detection if transients are working on the WP site (which by default only means that the option table is working).
-	 *
-	 * @param  boolean $activate
-	 */
-	public function activate_replay_protection($activate = true) {
-		$this->extra_replay_protection = (bool) $activate;
-	}
-
-	public function set_next_send_sequence_id($id) {
-		$this->next_send_sequence_id = $id;
-	}
-
-	/**
-	 * Set_http_credentials
-	 *
-	 * @param string $credentials should be an array with entries for 'username' and 'password'
-	 */
-	public function set_http_credentials($credentials) {
-		$this->http_credentials = $credentials;
-	}
-
-	/**
-	 * This needs only to return an array with keys body and response - where response is also an array, with key 'code' (the HTTP status code)
-	 * The $post_options array support these keys: timeout, body,
-	 * Public, to allow short-circuiting of the library's own encoding/decoding (e.g. for acting as a proxy for a message already encrypted elsewhere)
-	 *
-	 * @param  array $post_options
-	 * @return array
-	 */
-	public function http_post($post_options) {
-		global $wp_version;
-		include ABSPATH.WPINC.'/version.php';
-		$http_credentials = $this->http_credentials;
-
-		if (is_a($this->http_transport, 'GuzzleHttpClient')) {
-
-			// https://guzzle.readthedocs.org/en/5.3/clients.html
-
-			$client = $this->http_transport;
-
-			$guzzle_options = array(
-				'form_params' => $post_options['body'],
-				'headers' => array(
-					'User-Agent' => 'WordPress/'.$wp_version.'; class-udrpc.php-Guzzle/'.$this->version.'; '.get_bloginfo('url'),
-				),
-				'exceptions' => false,
-				'timeout' => $post_options['timeout'],
-			);
-
-			if (!class_exists('WP_HTTP_Proxy')) include_once ABSPATH.WPINC.'/class-http.php';
-			$proxy = new WP_HTTP_Proxy();
-			if ($proxy->is_enabled()) {
-				$user = $proxy->username();
-				$pass = $proxy->password();
-				$host = $proxy->host();
-				$port = (int) $proxy->port();
-				if (empty($port)) $port = 8080;
-				if (!empty($host) && $proxy->send_through_proxy($this->destination_url)) {
-					$proxy_auth = '';
-					if (!empty($user)) {
-						$proxy_auth = $user;
-						if (!empty($pass)) $proxy_auth .= ':'.$pass;
-						$proxy_auth .= '@';
-					}
-					$guzzle_options['proxy'] = array(
-						'http' => "http://{$proxy_auth}$host:$port",
-						'https' => "http://{$proxy_auth}$host:$port",
-					);
-				}
-			}
-
-			if (defined('UDRPC_GUZZLE_SSL_VERIFY')) {
-				$verify = UDRPC_GUZZLE_SSL_VERIFY;
-			} elseif (file_exists(ABSPATH.WPINC.'/certificates/ca-bundle.crt')) {
-				$verify = ABSPATH.WPINC.'/certificates/ca-bundle.crt';
-			} else {
-				$verify = true;
-			}
-
-			$guzzle_options['verify'] = apply_filters('udrpc_guzzle_verify', $verify);
-
-			if (!empty($http_credentials['username'])) {
-
-				$authentication_method = empty($http_credentials['authentication_method']) ? 'basic' : $http_credentials['authentication_method'];
-
-				$password = empty($http_credentials['password']) ? '' : $http_credentials['password'];
-
-				$guzzle_options['auth'] = array(
-					$http_credentials['username'],
-					$password,
-					$authentication_method,
-				);
-
-			}
-
-			$response = $client->post($this->destination_url, apply_filters('udrpc_guzzle_options', $guzzle_options, $this));
-
-			$formatted_response = array(
-				'response' => array(
-					'code' => $response->getStatusCode(),
-				),
-				'body' => $response->getBody(),
-			);
-
-			return $formatted_response;
-
-		} else {
-

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