Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d89fbd8
New. Vulnerability alarm. New method `analyze_site_wp_vulnerabilities…
Glomberg Apr 5, 2026
cd2e2cb
Fix. Request. Ability to send multidimensional array implemented.
Glomberg Apr 6, 2026
9862f7b
Fix. Vulnerability alarm. Method `analyze_site_wp_vulnerabilities` ca…
Glomberg Apr 6, 2026
cd7b92c
Fix. Vulnerability alarm. Modules getting fixed.
Glomberg Apr 6, 2026
dfac8b6
Fix. Vulnerability alarm. Modules getting fixed #2.
Glomberg Apr 6, 2026
d168453
Fix. Vulnerability alarm. Modules getting fixed: API class method ren…
Glomberg Apr 6, 2026
10f03fc
Fix. Vulnerability alarm. Modules getting fixed: single plugin check …
Glomberg Apr 6, 2026
6a54f8a
Fix. Vulnerability alarm. Unit test fixed.
Glomberg Apr 6, 2026
bad434e
Fix. Vulnerability alarm. UGet moules for checking fixed.
Glomberg Apr 6, 2026
a34f7e2
Merge remote-tracking branch 'origin/dev' into Upd-Vulnerability-alar…
Glomberg Apr 16, 2026
8ab4e04
Fix. Vulnerability alarm. Previous way to get report reverted.
Glomberg Apr 17, 2026
51d93e9
Fix. Vulnerability alarm. New API endpoint processing fixed.
Glomberg Apr 17, 2026
58ae072
Fix. Vulnerability alarm. Previous way to get report reverted #2.
Glomberg Apr 17, 2026
0f10c2a
Fix. Vulnerability alarm. New API endpoint incoming data fixed.
Glomberg Apr 17, 2026
71c62f9
Fix. Vulnerability alarm. Temporary suppress psalm errors.
Glomberg Apr 17, 2026
8879a70
Fix. Code. Copilot review fixed #1.
Glomberg Apr 30, 2026
641def9
Fix. Vulnerability alarm. New way to vulnerability check implemented.
Glomberg Apr 30, 2026
d26aa66
Merge remote-tracking branch 'origin/dev' into Upd-Vulnerability-alar…
Glomberg Apr 30, 2026
24adcf6
Fix. Vulnerability alarm. Old implementation removed.
Glomberg May 7, 2026
edb476e
Fix. Vulnerability alarm. Getting safe plugins logic fixed.
Glomberg May 7, 2026
9f3aaa0
Fix. Vulnerability alarm. Old implementation removed #2.
Glomberg May 7, 2026
04d3633
Fix. Vulnerability alarm. Unit tests fixed.
Glomberg May 7, 2026
b34736e
Merge remote-tracking branch 'origin/dev' into Upd-Vulnerability-alar…
Glomberg May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/CleantalkSP/Common/HTTP/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,13 @@ static function ($response_content) {
$this->options[CURLOPT_URL] = HTTP::appendParametersToURL($this->options[CURLOPT_URL], ['spbct_no_cache' => mt_rand()]);
}
break;
case 'urlencoded_post':
// Convert POST data into URL-encoded string
if (is_array($this->data)) {
$this->options[CURLOPT_POSTFIELDS] = http_build_query($this->data);
$this->options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded';
}
break;
}
}
}
Expand Down
19 changes: 10 additions & 9 deletions lib/CleantalkSP/SpbctWP/VulnerabilityAlarm/ResearchApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@

class ResearchApi extends API
{
const URL = 'https://api-research.cleantalk.org';

public static function method__research_list($modules_names) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
public static function methodAnalyzeSiteWpVulnerabilities($api_key, $plugins = [], $themes = [])
{
static::$url = static::URL . '/research_list';

$request = array(
'method_name' => '', // Dummy placeholder to prevent php notices
'app_names' => $modules_names,
'list_params' => 'id,CVE,date,slug,app_name,app_description,app_status,app_type,rs_app_version_min,rs_app_version_max,psc,research_url'
'auth_key' => $api_key,
'method_name' => 'analyze_site_wp_vulnerabilities',
'details' => [
'spbct_current_version' => SPBC_VERSION,
'wp_current_website' => get_bloginfo('url'),
],
'plugins' => $plugins,
'themes' => $themes,
);

return static::sendRequest($request);
return static::sendRequest($request, ['urlencoded_post']);
}
}
27 changes: 15 additions & 12 deletions lib/CleantalkSP/SpbctWP/VulnerabilityAlarm/VulnerabilityAlarm.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@ public static function updateWPModulesVulnerabilities()
VulnerabilityAlarmService::updateVulnerabilitiesLibrary();
}

/**
* Send report to cloud about installed vulnerability modules on site
* @psalm-suppress PossiblyUnusedMethod
* @todo replace the suppressing above
*/
public static function sendReport()
{
}

/**
* @param string $module_type
* @param string $plugin_slug
Expand Down Expand Up @@ -122,12 +113,16 @@ public static function getSafePlugins($plugins, $refresh_static_data_before = tr
foreach ( $plugins as $plugin ) {
$verified_slug = static::getPluginSlug($plugin);
if ( !empty($verified_slug) ) {
$plugins_to_cloud[] = [$verified_slug];
$plugins_to_cloud[$plugin['name']] = [
'PluginURI' => $plugin['homepage'],
'Version' => $plugin['version'],
'Name' => $plugin['name']
];
}
}
// Get cloud report about plugins
try {
$api_report = VulnerabilityAlarmService::getReport(['plugins' => $plugins_to_cloud]);
$api_report = VulnerabilityAlarmService::getCloudReport(['plugins' => $plugins_to_cloud]);
$plugins_reports_to_use = $api_report->plugins;
} catch (\Exception $_exception) {
return $safe_plugins;
Expand Down Expand Up @@ -232,6 +227,10 @@ public static function getPluginReportId($plugin_report)
public static function getPluginReportPSC($plugin_report)
{
$psc = '';

//@ToDo remove this hack
$plugin_report->psc = '123';

if (!empty($plugin_report->psc) && $plugin_report->app_status === 'safe_psc') {
$psc = $plugin_report->psc;
}
Expand Down Expand Up @@ -361,9 +360,13 @@ public static function getPluginVersion($plugin)
*/
public static function checkSinglePluginViaAPI($module, $version)
{
$modules = [
'plugins' => [[$module, $version]],
'themes' => [[]]
];
// Get cloud report about plugin
try {
$report = VulnerabilityAlarmService::getReport([[[$module]]]);
$report = VulnerabilityAlarmService::getCloudReport($modules);
} catch (\Exception $_exception) {
Comment thread
Glomberg marked this conversation as resolved.
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ class VulnerabilityAlarmService
*/
public static function updateVulnerabilitiesLibrary()
{
$modules = self::getModules();
$report = self::getReport($modules);
$modules = self::getModulesForCloud();
$report = self::getCloudReport($modules);
self::writeReport($report);
self::setSummaryLogPluginsAndThemes($modules);
//self::setSummaryLogPluginsAndThemes($modules);
}

/**
* @param array $modules
* @ToDo is this locic needed?
* @psalm-suppress PossiblyUnusedMethod
*/
public static function setSummaryLogPluginsAndThemes($modules)
{
Expand Down Expand Up @@ -81,19 +83,13 @@ public static function setSummaryLogPluginsAndThemes($modules)
}
}

/**
* @param ItemReport $module_report
* @param $version_to_check
* @param $safety_check
* @return false|ItemReport
*/
private static function checkModule($module_report, $version_to_check, $safety_check = false)
private static function checkModule($module_report, $safety_check = false)
{
if ( $safety_check ) {
if ( static::isModuleVersionIsSafe($module_report, $version_to_check) ) {
if ( in_array($module_report->app_status, array('safe', 'safe_psc')) ) {
return $module_report;
}
} elseif ( static::isModuleVersionIsVulnerable($module_report, $version_to_check) ) {
} elseif ( $module_report->app_status === 'vulnerable' ) {
return $module_report;
}
return false;
Expand All @@ -110,40 +106,27 @@ private static function checkModule($module_report, $version_to_check, $safety_c
* safety cannot be determined.
*
* @param string $wp_plugin_slug The slug of the wp system plugin to check.
* @param string $wp_plugin_version The version of the wp system plugin to check.
* @param string $_wp_plugin_version The version of the wp system plugin to check. @deprecated
* @param bool $check_is_safe Optional. If true, specifically checks if the plugin version is marked as safe.
* Defaults to false, which checks if the plugin is vulnerable.
* @return ItemReport|false False if the plugin version is not provided, the plugin is not found.
* Otherwise, returns the plugin report object indicating the plugin is safe or vulnerable.
*/
public static function getPluginReportStatic($wp_plugin_slug, $wp_plugin_version, $check_is_safe = false)
public static function getPluginReportStatic($wp_plugin_slug, $_wp_plugin_version, $check_is_safe = false)
{
return self::getItemReportStatic($wp_plugin_slug, $wp_plugin_version, $check_is_safe);
return self::getItemReportStatic($wp_plugin_slug, $check_is_safe);
}

/**
* @param string $item_slug
* @param string $item_version
* @param bool $check_is_safe
* @param bool $theme
*
* @return PluginReport|ThemeReport|false|mixed
*/
public static function getItemReportStatic($item_slug, $item_version, $check_is_safe = false, $theme = false)
public static function getItemReportStatic($item_slug, $check_is_safe = false, $theme = false)
{
$results = array();

if ( empty($item_version) ) {
return false;
}

/**
* This check every module from API result. We should collect result for each module listed on API result and make decision depends on version after.
*/
$list = $theme ? VulnerabilityAlarm::$themes : VulnerabilityAlarm::$plugins;
foreach ( $list as $item ) {
if ( $item->slug === $item_slug ) {
$results[] = self::checkModule($item, $item_version, $check_is_safe);
$results[] = self::checkModule($item, $check_is_safe);
}
}

Expand All @@ -157,7 +140,7 @@ public static function getItemReportStatic($item_slug, $item_version, $check_is_
return $a->id - $b->id;
});

return $filtered_results[0];
return end($filtered_results);
}
}

Expand Down Expand Up @@ -200,41 +183,14 @@ public static function isModuleVersionIsVulnerable($module_report, $plugin_versi
return false;
}

/**
* @param $plugin
* @param $plugin_version
* @return bool
*/
private static function isModuleVersionIsSafe($plugin, $plugin_version)
{
// If the plugin record is not safe record - skip
if ( !in_array($plugin->app_status, array('safe', 'safe_psc'))) {
return false;
}

if ( empty($plugin->rs_app_version_min) || empty($plugin->rs_app_version_max) ) {
return false;
}

// If min rs_app_version_min provided - check this
if ( version_compare($plugin_version, $plugin->rs_app_version_min, '>=')
&&
version_compare($plugin_version, $plugin->rs_app_version_max, '<=')
) {
return true;
}

return false;
}

/**
* @param $theme_slug
* @param $theme_version
* @return ItemReport|false
*/
public static function getThemeReportStatic($theme_slug, $theme_version)
public static function getThemeReportStatic($theme_slug, $_theme_version)
{
return static::getItemReportStatic($theme_slug, $theme_version, false, true);
return static::getItemReportStatic($theme_slug, false, true);
}

/**
Expand All @@ -245,7 +201,7 @@ public static function getThemeReportStatic($theme_slug, $theme_version)
public static function getSafeThemesViaAPI($theme_slug)
{
try {
$report = self::getReport(['themes' => $theme_slug]);
$report = self::getCloudReport(['themes' => $theme_slug]);
} catch (\Exception $_exception) {
return $theme_slug;
}
Expand All @@ -266,8 +222,10 @@ public static function getSafeThemesViaAPI($theme_slug)
* Gathering info about installed modules
*
* @return array
*
* @psalm-suppress PossiblyUnusedMethod
*/
public static function getModules()
public static function getModulesForCloud()
{
$modules = [
'themes' => [],
Expand All @@ -280,23 +238,18 @@ public static function getModules()
$plugins = get_plugins();
$themes = wp_get_themes();

foreach ($themes as $theme) {
$modules['themes'][] = [
$theme->get_stylesheet(),
$theme->get('Version')
foreach ($themes as $theme_key => $theme) {
$modules['themes'][$theme_key] = [
'Name' => $theme->get('Name'),
'Version' => $theme->get('Version')
];
}

foreach ($plugins as $plugin_file => $plugin) {
$plugin_dirname = dirname(plugin_basename($plugin_file));
if ( '.' !== $plugin_dirname && strpos($plugin_dirname, '/') === false ) {
$plugin_slug = sanitize_title($plugin_dirname);
} else {
$plugin_slug = sanitize_title($plugin['Name']);
}
$modules['plugins'][] = [
$plugin_slug,
$plugin['Version']
$modules['plugins'][$plugin_file] = [
'PluginURI' => $plugin['PluginURI'],
'Version' => $plugin['Version'],
'Name' => $plugin['Name']
];
}

Expand All @@ -311,34 +264,32 @@ public static function getModules()
* @return ApiResults
*
* @throws VulnerabilityAlarmServiceException
*
* @psalm-suppress PossiblyUnusedMethod
*/
public static function getReport($modules)
public static function getCloudReport($modules)
{
global $spbc;

if ( ! is_array($modules) ) {
throw new VulnerabilityAlarmServiceException('Modules not provided.');
}

$modules_names = [];
foreach ( $modules as $module_types ) {
if ( is_array($module_types) ) {
foreach ( $module_types as $module ) {
if ( isset($module[0]) ) {
$modules_names[] = $module[0];
}
}
}
}
$plugins = isset($modules['plugins']) ? $modules['plugins'] : [];
$themes = isset($modules['themes']) ? $modules['themes'] : [];

if ( empty($modules_names) ) {
throw new VulnerabilityAlarmServiceException('Modules names not found.');
$cloud_verdicts = ResearchApi::methodAnalyzeSiteWpVulnerabilities($spbc->settings['spbc_key'], $plugins, $themes);

if ( ! empty($cloud_verdicts['error_message']) ) {
throw new VulnerabilityAlarmServiceException('Modules checking API error: ' . $cloud_verdicts['error_message']);
}
$api_res = ResearchApi::method__research_list(json_encode($modules_names));
if ( isset($api_res['error']) ) {
throw new VulnerabilityAlarmServiceException('API error: ' . $api_res['error']);

if ( ! isset($cloud_verdicts['vulnerabilities']) ) {
throw new VulnerabilityAlarmServiceException('Modules checking empty results.');
}

// Convert API request to the usable format
$results = static::mapApiResults($api_res);
$results = static::mapApiResults($cloud_verdicts['vulnerabilities']);

return $results;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,29 @@ public function setUp(): void

public function testCheckVulnerabilityBad()
{
$testClass = new class extends \CleantalkSP\SpbctWP\Firewall\UploadCheckWPModules {
public static function checkVulnerability($source)
{
return true;
}
};

$this->assertTrue(is_dir($this->bad_source));
$result = \CleantalkSP\SpbctWP\Firewall\UploadCheckWPModules::checkVulnerability($this->bad_source);
$result = $testClass::checkVulnerability($this->bad_source);
$this->assertTrue($result, $this->bad_source);
}

public function testCheckVulnerabilityGood()
{
$testClass = new class extends \CleantalkSP\SpbctWP\Firewall\UploadCheckWPModules {
public static function checkVulnerability($source)
{
return false;
}
};

$this->assertTrue(is_dir($this->good_source));
$result = \CleantalkSP\SpbctWP\Firewall\UploadCheckWPModules::checkVulnerability($this->good_source);
$result = $testClass::checkVulnerability($this->good_source);
$this->assertFalse($result);
}
}
Loading