Changeset 3487519
- Timestamp:
- 03/20/2026 10:16:15 PM (8 days ago)
- Location:
- freelancebo-sentra-control/trunk
- Files:
-
- 4 edited
-
admin/js/view-auto-patch.js (modified) (6 diffs)
-
freelancebo-sentra-control.php (modified) (4 diffs)
-
includes/modules/class-sentra-auto-patcher.php (modified) (4 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
freelancebo-sentra-control/trunk/admin/js/view-auto-patch.js
r3487481 r3487519 45 45 var statusIcon = statusIcons[p.status] || '?'; 46 46 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' : ''; 47 48 var actions = ''; 48 49 if (p.status === 'completed' && p.patch_type !== 'rollback') { … … 52 53 $tbody.append( 53 54 '<tr>' + 54 '<td> ' + statusIcon + ' ' + p.status + '</td>' +55 '<td><span style="' + statusColor + '">' + statusIcon + ' ' + p.status + '</span></td>' + 55 56 '<td>' + (typeLabels[p.patch_type] || p.patch_type) + '</td>' + 56 57 '<td>' + window.Sentra.esc(p.target) + '</td>' + … … 65 66 66 67 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...'); 69 70 70 71 $.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 75 74 }, 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'); 77 76 var $container = $('#sentra-patch-suggestions'); 78 77 $container.empty(); … … 88 87 r.data.suggestions.forEach(function(s, i) { 89 88 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;">⚠ High Risk</span>' : s.risk_level === 'medium' ? '<span style="color:#ca8a04;">⚠ Medium Risk</span>' : '<span style="color:#16a34a;">✓ Low Risk</span>'; 91 90 var desc = s.patch_payload ? (s.patch_payload.description || '') : ''; 92 var approval = s.requires_approval ? '<br><small style="color:#ca8a04;">⚠ Requires approval - automatic backup before apply</small>' : ''; 91 var approval = s.requires_approval ? '<br><small style="color:#ca8a04;">⚠ Requires approval — 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) + ' → 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à 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 } 93 139 94 140 $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;">' + 97 143 '<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> — ' + 99 145 window.Sentra.esc(s.target) + ' ' + riskLabel + 100 146 '<br><span style="color:#64748b;font-size:13px;">' + window.Sentra.esc(desc) + '</span>' + 147 detail + 101 148 approval + 102 149 '</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>' + 104 151 '</div>' 105 152 ); 106 153 }); 107 154 108 // Store suggestions for apply109 155 $container.data('suggestions', r.data.suggestions); 110 156 }).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'); 112 158 showStatus('Error during analysis.', 'error'); 113 159 }); … … 120 166 $('#sentra-refresh-patches').on('click', loadHistory); 121 167 168 // Apply patch: execute locally via dedicated AJAX action 122 169 $(document).on('click', '.sentra-apply-patch', function() { 123 170 var idx = $(this).data('index'); … … 133 180 $btn.prop('disabled', true).text('Applying...'); 134 181 182 // Execute patch locally in the plugin, then report to server 135 183 $.post(sentraAdmin.ajaxUrl, { 136 action: 'sentra_ api_proxy',184 action: 'sentra_run_auto_patch', 137 185 _nonce: sentraAdmin.nonce, 138 method: 'POST', 139 path: '/agent/apply-patch', 140 body: JSON.stringify(s) 186 patch_data: JSON.stringify(s) 141 187 }, function(r) { 142 188 if (r.success) { 143 189 $btn.closest('div[style]').fadeOut(); 144 showStatus('Patch applied ! Check history for results.', 'success');190 showStatus('Patch applied successfully!', 'success'); 145 191 loadHistory(); 146 192 } else { 147 193 $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'); 150 199 }); 151 200 }); -
freelancebo-sentra-control/trunk/freelancebo-sentra-control.php
r3487481 r3487519 4 4 * Plugin URI: https://freelancebo.it 5 5 * 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. 36 * Version: 2.2.4 7 7 * Author: Freelancebo 8 8 * License: GPL-2.0-or-later … … 12 12 if (!defined('ABSPATH')) exit; 13 13 14 define("SENTRA_VERSION", "2.2. 3");14 define("SENTRA_VERSION", "2.2.4"); 15 15 define('SENTRA_PLUGIN_DIR', plugin_dir_path(__FILE__)); 16 16 define('SENTRA_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 64 64 add_action('wp_ajax_sentra_api_proxy', [$this, 'ajax_api_proxy']); 65 65 add_action('wp_ajax_sentra_run_scan', [$this, 'ajax_run_scan']); 66 add_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']); 66 68 add_action('wp_ajax_sentra_resolve_finding', [$this, 'ajax_resolve_finding']); 67 69 add_action('wp_ajax_sentra_set_auto_scan', [$this, 'ajax_set_auto_scan']); … … 278 280 // For non-string, non-array values (int, float, bool, null), return as-is 279 281 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 } 280 365 } 281 366 -
freelancebo-sentra-control/trunk/includes/modules/class-sentra-auto-patcher.php
r3487472 r3487519 66 66 $this->process_pending_patches(); 67 67 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 } 68 91 } 69 92 … … 137 160 require_once ABSPATH . 'wp-admin/includes/file.php'; 138 161 require_once ABSPATH . 'wp-admin/includes/misc.php'; 139 140 162 $plugin_file = $this->find_plugin_file($slug); 141 163 if (!$plugin_file) { … … 143 165 } 144 166 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 145 186 $skin = new Automatic_Upgrader_Skin(); 146 187 $upgrader = new Plugin_Upgrader($skin); … … 151 192 } 152 193 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'); 156 199 $plugins = get_plugins(); 157 200 $new_version = isset($plugins[$plugin_file]) ? $plugins[$plugin_file]['Version'] : 'unknown'; -
freelancebo-sentra-control/trunk/readme.txt
r3487481 r3487519 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 2.2. 37 Stable tag: 2.2.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 74 74 75 75 == 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 76 83 77 84 = 2.2.3 =
Note: See TracChangeset
for help on using the changeset viewer.