WordPress Plugin Submission Review Seems to Have Failed Badly With ConvertPro
Earlier this week the team running the WordPress Plugin Directory were touting how great things are going. They proclaimed that the “WordPress Ecosystem is Growing,” basing that on “plugin submissions hav[ing] doubled in 2025.” They didn’t mention how much usage those plugins have, which might have something to do with the pretty bleak numbers. A recently introduced plugin to the directory highlights that there are other problems that the team seems to be blind to with what they are responsible for.
Fake Install Count?
We are in the process of reviewing WordPress plugins used by our customers to see if they contain any third-party libraries we still need to add detection to for to our Plugin Security Scorecard. That led us to coming across the plugin ConvertPro. Or more accurately, one instance of it. The WordPress Plugin Directory listing for it seems rather odd. The plugin is at version 1.0.0 and has no reviews, yet it has 20,000+ installs:
Maybe version 1.0.0 isn’t the initial release? That couldn’t be it. As the changelog says that version is the initial release. The development log shows that the first version submitted was on March 30. And yet here is the download chart that shows downloads before that:
The total downloads, 6,675, isn’t even a third of the installs. The downloads since release are significantly below that number. So what is going on here?
The answer might be that there is an existing commercial plugin with the same name Convert Pro and the same slug.
From what little has been made public, the team used to restrict reusing slugs from existing commercial plugins. One reason for doing that might be that you can end up with highly inaccurate stats for plugins, which is our best guess as to where the 20,000+ install count comes from.
Missing Security and Tracking Without Consent
The plugin directory team’s post stated that:
Remember, the main security issues stem from lack of sanitization, escaping, and nonce usage.
While looking into the code of the plugin, we noticed a pretty glaring lack of nonce usage that is part of a larger security issue and a tracking issue in violation of the directory’s guidelines.
After activating the plugin, this notice is shown on the backend of WordPress:
The two buttons link to URLs that lack a nonce even though they should:
http://example.com/wp-admin/plugins.php?convertpro_tracker_optin=true
http://example.com/wp-admin/plugins.php?convertpro_tracker_optout=true
Under the hood, things are worse. The related code, which is supposed to opt in or out of tracking done by the plugin, runs during admin_init, which makes it accessible to even those not logged in to WordPress:
160 | add_action('admin_init', array($this, 'handle_optin_optout')); |
The code doesn’t include any security checks, so anyone should be able to opt in or out of tracking done by the plugin:
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 | public function handle_optin_optout() { if (isset($_GET[$this->client->slug . '_tracker_optin']) && $_GET[$this->client->slug . '_tracker_optin'] == 'true') { $this->optin(); wp_redirect(remove_query_arg($this->client->slug . '_tracker_optin')); exit; } if (isset($_GET[$this->client->slug . '_tracker_optout']) && $_GET[$this->client->slug . '_tracker_optout'] == 'true') { $this->optin(); wp_redirect(remove_query_arg($this->client->slug . '_tracker_optout')); exit; } } |
Meaning the developer or someone else could opt a website in to tracking that the people running the website didn’t want. That turns out to not really being an issue because clicking the button to opt out of tracking causes the same function be called, which opts in to tracking:
public function optin()
{
update_option($this->client->slug . '_allow_tracking', 'yes');
update_option($this->client->slug . '_tracking_notice', 'hide');
$this->clear_schedule_event();
$this->schedule_event();
$this->send_tracking_data();
}
There is an opt out function in the file, but it is never called.
The tracking without consent is in violation of one of the WordPress Plugin Directory’s guidelines:
7. Plugins may not track users without their consent.
In the interest of protecting user privacy, plugins may not contact external servers without explicit and authorized consent. This is commonly done via an ‘opt in’ method, requiring registration with a service or a checkbox within the plugin settings. Documentation on how any user data is collected, and used, should be included in the plugin’s readme, preferably with a clearly stated privacy policy.
Some examples of prohibited tracking include:
- Automated collection of user data without explicit confirmation from the user.
If you click the “what we collect” link in the notice, the notice is expanded to this:
The Learn more link is to https://finestics.com/privacy-policy/. The domain name finestics.com isn’t even currently registered.
The team’s handling of tracking without consent has been an issue, including with popular plugins from companies the team’s members are connected to.
More Missing Security
Checking the code run during admin_init is a basic part of a security review, since issues there often lead to serious vulnerabilities. The same is true of AJAX accessible functions. There are also problems on that front.
In the file /includes/function.php, the plugin registers four functions to be AJAX accessible when the request is coming from someone logged in to WordPress as well those not logged in. Here is one of those:
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | add_action('wp_ajax_convertpro_get_chart_data', 'convertpro_get_chart_data'); add_action('wp_ajax_nopriv_convertpro_get_chart_data', 'convertpro_get_chart_data'); function convertpro_get_views($test_id, $variation_id, $range = 7) { global $wpdb; $table_name = $wpdb->prefix . 'convertpro_interactions'; $views_query = ""; $views_placeholders = []; $views_query .= "SELECT COUNT(*) FROM {$table_name} WHERE splittest_id = %d AND variation_id = %d"; $views_placeholders[] = $test_id; $views_placeholders[] = $variation_id; if ($range != 'all') { $views_query .= " AND updated_at <= NOW() AND updated_at >= DATE_SUB(NOW(), INTERVAL %s DAY)"; $views_placeholders[] = intval($range); } $views_query = $wpdb->prepare( $views_query,// phpcs:ignore $views_placeholders ); return $wpdb->get_var($views_query);// phpcs:ignore } |
There is no nonce check there.
Also missing there is something the plugin directory team concerningly failed to mention as a main security issue, a capability check. That is despite missing capability checks being a common issue, including with widely exploited vulnerabilities. The functionality is only intended to be accessed through the plugin’s admin page, which is limited to users with the manage_options capability. So there shouldn’t be an AJAX registration for those not logged in and there should be a capability check.
Missing Unintall Method
The plugin also is missing an uninstall method.
Review Failed?
The plugin directory team largely operates in the dark, so we don’t know what they actually did here. Did they review a very different plugin than the one that was submitted in March? Did they review any plugin? Who knows. That shouldn’t be the case. WordPress is supposed to be an open source project. According to the Executive Director of WordPress, the project is supposed to be centering transparency, which hasn’t yet wit this team. The theme team, by comparison, is public with their review process.