Plugin Directory

Changeset 3487519


Ignore:
Timestamp:
03/20/2026 10:16:15 PM (8 days ago)
Author:
freelancebo
Message:

v2.2.4: Independent live scans for auto-patch, aggregated CVE details, immediate execution

Location:
freelancebo-sentra-control/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • freelancebo-sentra-control/trunk/admin/js/view-auto-patch.js

    r3487481 r3487519  
    4545                var statusIcon = statusIcons[p.status] || '?';
    4646                var date = p.created_at ? new Date(p.created_at).toLocaleString('it-IT') : '-';
     47                var statusColor = p.status === 'completed' ? 'color:#16a34a' : p.status === 'failed' ? 'color:#dc2626' : '';
    4748                var actions = '';
    4849                if (p.status === 'completed' && p.patch_type !== 'rollback') {
     
    5253                $tbody.append(
    5354                    '<tr>' +
    54                     '<td>' + statusIcon + ' ' + p.status + '</td>' +
     55                    '<td><span style="' + statusColor + '">' + statusIcon + ' ' + p.status + '</span></td>' +
    5556                    '<td>' + (typeLabels[p.patch_type] || p.patch_type) + '</td>' +
    5657                    '<td>' + window.Sentra.esc(p.target) + '</td>' +
     
    6566
    6667    function analyzeSuggestions() {
    67         showStatus('Analyzing vulnerabilities...', 'info');
    68         $('#sentra-analyze-patches').prop('disabled', true);
     68        showStatus('Scanning site for vulnerabilities, malware and file integrity...', 'info');
     69        $('#sentra-analyze-patches').prop('disabled', true).text('Scanning...');
    6970
    7071        $.post(sentraAdmin.ajaxUrl, {
    71             action: 'sentra_api_proxy',
    72             _nonce: sentraAdmin.nonce,
    73             method: 'GET',
    74             path: '/agent/my-patch-suggestions'
     72            action: 'sentra_analyze_for_patches',
     73            _nonce: sentraAdmin.nonce
    7574        }, function(r) {
    76             $('#sentra-analyze-patches').prop('disabled', false);
     75            $('#sentra-analyze-patches').prop('disabled', false).html('<span class="dashicons dashicons-shield" style="margin-top:3px;"></span> Analyze Vulnerabilities');
    7776            var $container = $('#sentra-patch-suggestions');
    7877            $container.empty();
     
    8887            r.data.suggestions.forEach(function(s, i) {
    8988                var sevStyle = 'background:' + (sevColors[s.severity] || '#6b7280') + ';color:#fff;padding:2px 8px;border-radius:4px;font-size:11px;';
    90                 var riskLabel = s.risk_level === 'high' ? '<span style="color:#dc2626;">High Risk</span>' : s.risk_level === 'medium' ? '<span style="color:#ca8a04;">Medium Risk</span>' : '<span style="color:#16a34a;">Low Risk</span>';
     89                var riskLabel = s.risk_level === 'high' ? '<span style="color:#dc2626;">&#9888; High Risk</span>' : s.risk_level === 'medium' ? '<span style="color:#ca8a04;">&#9888; Medium Risk</span>' : '<span style="color:#16a34a;">&#10003; Low Risk</span>';
    9190                var desc = s.patch_payload ? (s.patch_payload.description || '') : '';
    92                 var approval = s.requires_approval ? '<br><small style="color:#ca8a04;">&#9888; Requires approval - automatic backup before apply</small>' : '';
     91                var approval = s.requires_approval ? '<br><small style="color:#ca8a04;">&#9888; Requires approval &mdash; automatic backup before apply</small>' : '';
     92
     93                // Build CVE/vulnerability detail
     94                var detail = '';
     95                var sf = s.source_finding || {};
     96
     97                // Version info
     98                if (sf.installed_version && sf.patched_version) {
     99                    detail += '<br><span style="color:#94a3b8;font-size:12px;">v' + window.Sentra.esc(sf.installed_version) + ' &rarr; v' + window.Sentra.esc(sf.patched_version) + '</span>';
     100                }
     101
     102                // Aggregated CVEs
     103                if (sf.cves && sf.cves.length > 0) {
     104                    detail += '<br><span style="color:#f97316;font-size:12px;font-weight:600;">' + sf.cves.length + ' vulnerabilit&agrave; trovate';
     105                    if (sf.max_cvss) detail += ' (max CVSS ' + sf.max_cvss + ')';
     106                    detail += ':</span>';
     107                    detail += '<ul style="margin:4px 0 0 16px;padding:0;list-style:disc;">';
     108                    sf.cves.forEach(function(cve) {
     109                        var cveLabel = cve.cve && cve.cve !== 'Unknown CVE' ? cve.cve : '';
     110                        var vType = cve.vulnerability_type || '';
     111                        var cvss = cve.cvss_score ? ' (CVSS ' + cve.cvss_score + ')' : '';
     112                        var patchedTo = cve.patched_version ? ' — fix in v' + cve.patched_version : '';
     113                        detail += '<li style="font-size:12px;color:#cbd5e1;margin-bottom:2px;">';
     114                        if (cveLabel) {
     115                            var cveUrl = cve.cve_url || ('https://www.cve.org/CVERecord?id=' + cveLabel);
     116                            detail += '<a href="' + window.Sentra.esc(cveUrl) + '" target="_blank" rel="noopener" style="color:#f97316;text-decoration:underline;">' + window.Sentra.esc(cveLabel) + '</a> ';
     117                        }
     118                        detail += '<span style="color:#94a3b8;">' + window.Sentra.esc(vType) + cvss + patchedTo + '</span>';
     119                        if (cve.references && cve.references.length > 0) {
     120                            var ref = cve.references[0];
     121                            var shortRef = ref.replace(/https?:\/\//, '').substring(0, 50);
     122                            detail += ' <a href="' + window.Sentra.esc(ref) + '" target="_blank" rel="noopener" style="color:#60a5fa;font-size:11px;">[details]</a>';
     123                        }
     124                        detail += '</li>';
     125                    });
     126                    detail += '</ul>';
     127                }
     128
     129                // Single CVE (legacy format)
     130                else if (sf.cve) {
     131                    detail += '<br><span style="color:#f97316;font-size:12px;font-weight:600;">' + window.Sentra.esc(sf.cve) + '</span>';
     132                    if (sf.cvss_score) detail += ' <span style="color:#94a3b8;font-size:11px;">(CVSS ' + sf.cvss_score + ')</span>';
     133                }
     134
     135                // Description fallback
     136                else if (sf.description && sf.description !== desc) {
     137                    detail += '<br><span style="color:#94a3b8;font-size:12px;">' + window.Sentra.esc(sf.description) + '</span>';
     138                }
    93139
    94140                $container.append(
    95                     '<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;">' +
    96                     '<div>' +
     141                    '<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px;margin-bottom:10px;display:flex;justify-content:space-between;align-items:flex-start;">' +
     142                    '<div style="flex:1;min-width:0;">' +
    97143                    '<span style="' + sevStyle + '">' + (s.severity || '').toUpperCase() + '</span> ' +
    98                     '<strong style="margin-left:6px;">' + (typeLabels[s.patch_type] || s.patch_type) + '</strong> ' +
     144                    '<strong style="margin-left:6px;">' + (typeLabels[s.patch_type] || s.patch_type) + '</strong> &mdash; ' +
    99145                    window.Sentra.esc(s.target) + ' ' + riskLabel +
    100146                    '<br><span style="color:#64748b;font-size:13px;">' + window.Sentra.esc(desc) + '</span>' +
     147                    detail +
    101148                    approval +
    102149                    '</div>' +
    103                     '<button class="sentra-btn sentra-btn-primary sentra-apply-patch" data-index="' + i + '">Apply</button>' +
     150                    '<button class="sentra-btn sentra-btn-primary sentra-apply-patch" data-index="' + i + '" style="margin-left:12px;white-space:nowrap;">Apply</button>' +
    104151                    '</div>'
    105152                );
    106153            });
    107154
    108             // Store suggestions for apply
    109155            $container.data('suggestions', r.data.suggestions);
    110156        }).fail(function() {
    111             $('#sentra-analyze-patches').prop('disabled', false);
     157            $('#sentra-analyze-patches').prop('disabled', false).html('<span class="dashicons dashicons-shield" style="margin-top:3px;"></span> Analyze Vulnerabilities');
    112158            showStatus('Error during analysis.', 'error');
    113159        });
     
    120166        $('#sentra-refresh-patches').on('click', loadHistory);
    121167
     168        // Apply patch: execute locally via dedicated AJAX action
    122169        $(document).on('click', '.sentra-apply-patch', function() {
    123170            var idx = $(this).data('index');
     
    133180            $btn.prop('disabled', true).text('Applying...');
    134181
     182            // Execute patch locally in the plugin, then report to server
    135183            $.post(sentraAdmin.ajaxUrl, {
    136                 action: 'sentra_api_proxy',
     184                action: 'sentra_run_auto_patch',
    137185                _nonce: sentraAdmin.nonce,
    138                 method: 'POST',
    139                 path: '/agent/apply-patch',
    140                 body: JSON.stringify(s)
     186                patch_data: JSON.stringify(s)
    141187            }, function(r) {
    142188                if (r.success) {
    143189                    $btn.closest('div[style]').fadeOut();
    144                     showStatus('Patch applied! Check history for results.', 'success');
     190                    showStatus('Patch applied successfully!', 'success');
    145191                    loadHistory();
    146192                } else {
    147193                    $btn.prop('disabled', false).text('Apply');
    148                     showStatus('Error: ' + (r.data || 'Unknown'), 'error');
    149                 }
     194                    showStatus('Error: ' + (r.data || 'Unknown error'), 'error');
     195                }
     196            }).fail(function() {
     197                $btn.prop('disabled', false).text('Apply');
     198                showStatus('Error executing patch.', 'error');
    150199            });
    151200        });
  • freelancebo-sentra-control/trunk/freelancebo-sentra-control.php

    r3487481 r3487519  
    44 * Plugin URI: https://freelancebo.it
    55 * Description: WordPress security agent - connects to FreelanceBo Sentra Control central console for WAF, malware scanning, brute force protection, and file integrity monitoring.
    6  * Version: 2.2.3
     6 * Version: 2.2.4
    77 * Author: Freelancebo
    88 * License: GPL-2.0-or-later
     
    1212if (!defined('ABSPATH')) exit;
    1313
    14 define("SENTRA_VERSION", "2.2.3");
     14define("SENTRA_VERSION", "2.2.4");
    1515define('SENTRA_PLUGIN_DIR', plugin_dir_path(__FILE__));
    1616define('SENTRA_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    6464        add_action('wp_ajax_sentra_api_proxy', [$this, 'ajax_api_proxy']);
    6565        add_action('wp_ajax_sentra_run_scan', [$this, 'ajax_run_scan']);
     66add_action('wp_ajax_sentra_analyze_for_patches', [$this, 'ajax_analyze_for_patches']);
     67        add_action('wp_ajax_sentra_run_auto_patch', [$this, 'ajax_run_auto_patch']);
    6668        add_action('wp_ajax_sentra_resolve_finding', [$this, 'ajax_resolve_finding']);
    6769        add_action('wp_ajax_sentra_set_auto_scan', [$this, 'ajax_set_auto_scan']);
     
    278280        // For non-string, non-array values (int, float, bool, null), return as-is
    279281        return $data;
     282    }
     283
     284
     285    public function ajax_analyze_for_patches() {
     286        check_ajax_referer('sentra_admin', '_nonce');
     287        if (!current_user_can('manage_options')) wp_send_json_error('Unauthorized');
     288        if (!$this->is_configured()) wp_send_json_error('Plugin not configured');
     289        @set_time_limit(300); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
     290
     291        $all_findings = [];
     292        $environment = null;
     293
     294        // Run vulnerability scan
     295        $vuln = new Sentra_Vuln_Scanner($this->api_client);
     296        $vuln_result = $vuln->run_scan_local();
     297        $all_findings = array_merge($all_findings, $vuln_result['findings'] ?? []);
     298        $environment = $vuln_result['environment'] ?? null;
     299
     300        // Run malware scan
     301        $malware = new Sentra_Malware_Scanner($this->api_client);
     302        $malware_findings = $malware->run_scan_local();
     303        $all_findings = array_merge($all_findings, $malware_findings);
     304
     305        // Run integrity scan
     306        $integrity = new Sentra_Integrity($this->api_client);
     307        $integrity_findings = $integrity->run_scan_local();
     308        $all_findings = array_merge($all_findings, $integrity_findings);
     309
     310        // Send to server for CVE enrichment + patch analysis
     311        $result = $this->api_client->post('/agent/analyze-patches', [
     312            'findings'    => $all_findings,
     313            'environment' => $environment,
     314        ]);
     315
     316        if (isset($result['error'])) {
     317            wp_send_json_error($result['error']);
     318        }
     319
     320        wp_send_json_success($result);
     321    }
     322
     323    public function ajax_run_auto_patch() {
     324        check_ajax_referer('sentra_admin', '_nonce');
     325        if (!current_user_can('manage_options')) wp_send_json_error('Unauthorized');
     326        if (!$this->is_configured()) wp_send_json_error('Plugin not configured');
     327        @set_time_limit(300); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
     328
     329        $raw = isset($_POST['patch_data']) ? wp_unslash($_POST['patch_data']) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded and fields sanitized below
     330        $patch_data = json_decode($raw, true);
     331        if (empty($patch_data) || empty($patch_data['patch_type'])) {
     332            wp_send_json_error('Missing patch data');
     333        }
     334
     335        // Create record on server
     336        $server_result = $this->api_client->post('/agent/apply-patch', $patch_data);
     337        $patch_id = $server_result['patch_id'] ?? null;
     338
     339        // Execute patch locally
     340        $patcher = new Sentra_Auto_Patcher($this->api_client);
     341        $payload = $patch_data['patch_payload'] ?? [];
     342        $result = $patcher->execute_patch($patch_data['patch_type'], $payload);
     343
     344        // Normalize status
     345        $status = $result['status'] ?? 'failed';
     346        if (in_array($status, ['patched', 'updated', 'quarantined', 'restored'], true)) {
     347            $status = 'completed';
     348        } elseif ($status === 'error') {
     349            $status = 'failed';
     350        }
     351
     352        // Report back to server
     353        if ($patch_id) {
     354            $this->api_client->post('/agent/complete-patch/' . intval($patch_id), [
     355                'status' => $status,
     356                'result' => $result,
     357            ]);
     358        }
     359
     360        if ($status === 'completed') {
     361            wp_send_json_success($result);
     362        } else {
     363            wp_send_json_error($result['message'] ?? 'Patch failed');
     364        }
    280365    }
    281366
  • freelancebo-sentra-control/trunk/includes/modules/class-sentra-auto-patcher.php

    r3487472 r3487519  
    6666        $this->process_pending_patches();
    6767        return new WP_REST_Response(['status' => 'ok'], 200);
     68    }
     69
     70    /**
     71     * Execute a single patch directly (called from AJAX).
     72     */
     73    public function execute_patch($patch_type, $payload) {
     74        @set_time_limit(300); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
     75        switch ($patch_type) {
     76            case 'update_plugin':
     77                return $this->handle_update_plugin($payload);
     78            case 'update_theme':
     79                return $this->handle_update_theme($payload);
     80            case 'quarantine_file':
     81                return $this->handle_quarantine_file($payload);
     82            case 'restore_core':
     83                return $this->handle_restore_core($payload);
     84            case 'surgical_patch':
     85                return $this->handle_surgical_patch($payload);
     86            case 'rollback':
     87                return $this->handle_rollback($payload);
     88            default:
     89                return ['status' => 'error', 'message' => 'Unknown patch type'];
     90        }
    6891    }
    6992
     
    137160        require_once ABSPATH . 'wp-admin/includes/file.php';
    138161        require_once ABSPATH . 'wp-admin/includes/misc.php';
    139 
    140162        $plugin_file = $this->find_plugin_file($slug);
    141163        if (!$plugin_file) {
     
    143165        }
    144166
     167        // Check if update is available
     168        $update_plugins = get_site_transient('update_plugins');
     169        if (!isset($update_plugins->response[$plugin_file])) {
     170            // No update in transient — might already be up to date
     171            $plugins = get_plugins();
     172            $current = isset($plugins[$plugin_file]) ? $plugins[$plugin_file]['Version'] : 'unknown';
     173            $target = $payload['target_version'] ?? '';
     174            if ($target && version_compare($current, $target, '>=')) {
     175                return ['status' => 'updated', 'slug' => $slug, 'new_version' => $current, 'message' => 'Already up to date'];
     176            }
     177            // Try forcing an update check
     178            require_once ABSPATH . 'wp-admin/includes/update.php';
     179            wp_update_plugins();
     180            $update_plugins = get_site_transient('update_plugins');
     181            if (!isset($update_plugins->response[$plugin_file])) {
     182                return ['status' => 'updated', 'slug' => $slug, 'new_version' => $current, 'message' => 'Already at latest version'];
     183            }
     184        }
     185
    145186        $skin = new Automatic_Upgrader_Skin();
    146187        $upgrader = new Plugin_Upgrader($skin);
     
    151192        }
    152193        if ($result === false) {
    153             return ['status' => 'error', 'message' => 'Update failed'];
    154         }
    155 
     194            $messages = $skin->get_upgrade_messages();
     195            return ['status' => 'error', 'message' => 'Update failed', 'details' => $messages];
     196        }
     197
     198        wp_cache_delete('plugins', 'plugins');
    156199        $plugins = get_plugins();
    157200        $new_version = isset($plugins[$plugin_file]) ? $plugins[$plugin_file]['Version'] : 'unknown';
  • freelancebo-sentra-control/trunk/readme.txt

    r3487481 r3487519  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 2.2.3
     7Stable tag: 2.2.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    7474
    7575== Changelog ==
     76
     77= 2.2.4 =
     78* Auto-patch now runs its own live scans (vulnerability + malware + integrity) independently
     79* CVE details shown in patch suggestions with CVSS score, references and fix version
     80* Aggregated CVEs per plugin for unified patch suggestions
     81* Fixed patch execution flow: patches now execute immediately from the UI
     82* Improved error handling for already-updated plugins
    7683
    7784= 2.2.3 =
Note: See TracChangeset for help on using the changeset viewer.