Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 65 additions & 0 deletions admin/css/webdecoy-admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,71 @@
color: #721c24;
}

/* Network-block badge (rows auto-synced from WebDecoy network intelligence). */
.webdecoy-badge-network {
background: #e7f0fb;
color: #1e50a0;
}

/* Actor-intel "Known" badge on the Detections list. */
.webdecoy-badge-known {
background: #fbeaea;
color: #a02222;
}

/* ==========================================================================
Actor Intel column (Detections)
========================================================================== */

.column-intel { width: 200px; }

.webdecoy-intel-locked {
color: #a7aaad;
font-size: 16px;
width: 16px;
height: 16px;
}

.webdecoy-intel-na,
.webdecoy-intel-new {
color: #646970;
}

.webdecoy-intel-known {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
}

.webdecoy-intel-meta {
font-size: 11px;
color: #646970;
}

.webdecoy-chip {
display: inline-block;
font-size: 10px;
line-height: 1.6;
padding: 1px 6px;
border-radius: 3px;
background: #f0f0f0;
color: #3c434a;
}

.webdecoy-chip-alert {
background: #fbeaea;
color: #a02222;
}

/* ==========================================================================
Post-CRITICAL moment notice
========================================================================== */

.webdecoy-critical-moment .button {
margin-top: 4px;
}

/* ==========================================================================
Filters & Actions Bar
========================================================================== */
Expand Down
10 changes: 9 additions & 1 deletion admin/partials/blocked-ips-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,15 @@
<em><?php esc_html_e('Never (permanent)', 'webdecoy'); ?></em>
<?php endif; ?>
</td>
<td><?php echo esc_html($block['created_by'] ?? 'system'); ?></td>
<td>
<?php if (($block['created_by'] ?? '') === 'webdecoy-network') : ?>
<span class="webdecoy-badge webdecoy-badge-network" title="<?php esc_attr_e('Auto-blocked from WebDecoy network threat intelligence', 'webdecoy'); ?>">
<?php esc_html_e('Network', 'webdecoy'); ?>
</span>
<?php else : ?>
<?php echo esc_html($block['created_by'] ?? 'system'); ?>
<?php endif; ?>
</td>
<td>
<a href="<?php echo esc_url(wp_nonce_url(add_query_arg('unblock', $block['ip_address']), 'webdecoy_unblock')); ?>"
class="button button-small"
Expand Down
76 changes: 75 additions & 1 deletion admin/partials/detections-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,27 @@

// Instantiate blocker once for the loop (performance optimization)
$blocker = new WebDecoy_Blocker();

// Actor intel column. ZERO external calls unless connected: unconnected installs
// render a static lock and make no request. When connected, resolve the unique
// IPs on THIS page in a single batched request (uncached IPs only; each cached
// 1h). On any failure the map is simply empty and cells fall back to em-dashes.
$wd_intel_connected = class_exists('WebDecoy_Cloud_Connect') && WebDecoy_Cloud_Connect::is_connected();
$wd_intel_map = [];
if ($wd_intel_connected && !empty($detections) && class_exists('WebDecoy_Actor_Intel')) {
$wd_intel_options = function_exists('webdecoy') ? webdecoy()->get_options() : [];
$wd_intel_key = isset($wd_intel_options['api_key']) ? (string) $wd_intel_options['api_key'] : '';
if ($wd_intel_key !== '') {
$wd_page_ips = [];
foreach ($detections as $wd_row) {
if (!empty($wd_row['ip_address'])) {
$wd_page_ips[] = (string) $wd_row['ip_address'];
}
}
$wd_intel_client = new WebDecoy_Actor_Intel($wd_intel_key);
$wd_intel_map = $wd_intel_client->lookup($wd_page_ips);
}
}
?>

<div class="wrap">
Expand Down Expand Up @@ -241,6 +262,7 @@ class="button <?php echo empty($active_range) ? 'button-primary' : ''; ?>">
<th class="column-score"><?php esc_html_e('Score', 'webdecoy'); ?></th>
<th class="column-level"><?php esc_html_e('Level', 'webdecoy'); ?></th>
<th class="column-mitre"><?php esc_html_e('MITRE Tactic', 'webdecoy'); ?></th>
<th class="column-intel"><?php esc_html_e('Actor Intel', 'webdecoy'); ?></th>
<th class="column-source"><?php esc_html_e('Source', 'webdecoy'); ?></th>
<th class="column-ua"><?php esc_html_e('User Agent', 'webdecoy'); ?></th>
<th class="column-actions"><?php esc_html_e('Actions', 'webdecoy'); ?></th>
Expand Down Expand Up @@ -330,6 +352,58 @@ class="button <?php echo empty($active_range) ? 'button-primary' : ''; ?>">
<span class="webdecoy-mitre-na">—</span>
<?php endif; ?>
</td>
<td class="column-intel">
<?php if (!$wd_intel_connected) : ?>
<span class="webdecoy-intel-locked dashicons dashicons-lock" title="<?php esc_attr_e('Connect to WebDecoy Cloud to see actor intelligence', 'webdecoy'); ?>"></span>
<?php else :
$wd_ip = (string) $detection['ip_address'];
$wd_entry = isset($wd_intel_map[$wd_ip]) && is_array($wd_intel_map[$wd_ip]) ? $wd_intel_map[$wd_ip] : null;
if ($wd_entry === null) : ?>
<span class="webdecoy-intel-na">—</span>
<?php elseif (empty($wd_entry['known'])) : ?>
<span class="webdecoy-intel-new" title="<?php esc_attr_e('Not yet seen elsewhere on the WebDecoy network', 'webdecoy'); ?>"><?php esc_html_e('New', 'webdecoy'); ?></span>
<?php else :
$wd_count = (int) $wd_entry['network_detections'];
$wd_first = (int) $wd_entry['first_seen'];
?>
<span class="webdecoy-intel-known">
<span class="webdecoy-badge webdecoy-badge-known" title="<?php esc_attr_e('Known across the WebDecoy network', 'webdecoy'); ?>"><?php esc_html_e('Known', 'webdecoy'); ?></span>
<?php if ($wd_count > 0) : ?>
<span class="webdecoy-intel-meta">
<?php
/* translators: %s: number of detections across the network */
echo esc_html(sprintf(_n('%s detection', '%s detections', $wd_count, 'webdecoy'), number_format_i18n($wd_count)));
?>
</span>
<?php endif; ?>
<?php if ($wd_first > 0) : ?>
<span class="webdecoy-intel-meta" title="<?php echo esc_attr(date_i18n(get_option('date_format'), $wd_first)); ?>">
<?php
/* translators: %s: human-readable time difference, e.g. "3 days" */
echo esc_html(sprintf(__('first seen %s ago', 'webdecoy'), human_time_diff($wd_first, time())));
?>
</span>
<?php endif; ?>
<?php if (empty($wd_entry['redacted'])) : ?>
<?php if (!empty($wd_entry['tor'])) : ?>
<span class="webdecoy-chip webdecoy-chip-alert" title="<?php esc_attr_e('Tor exit node', 'webdecoy'); ?>"><?php esc_html_e('Tor', 'webdecoy'); ?></span>
<?php endif; ?>
<?php if (!empty($wd_entry['vpn'])) : ?>
<span class="webdecoy-chip" title="<?php esc_attr_e('VPN or proxy', 'webdecoy'); ?>"><?php esc_html_e('VPN', 'webdecoy'); ?></span>
<?php endif; ?>
<?php if ($wd_entry['abuse_score'] !== null && (int) $wd_entry['abuse_score'] > 0) : ?>
<span class="webdecoy-chip webdecoy-chip-alert" title="<?php esc_attr_e('AbuseIPDB confidence score', 'webdecoy'); ?>">
<?php
/* translators: %d: AbuseIPDB confidence score 0-100 */
echo esc_html(sprintf(__('Abuse %d', 'webdecoy'), (int) $wd_entry['abuse_score']));
?>
</span>
<?php endif; ?>
<?php endif; ?>
</span>
<?php endif; ?>
<?php endif; ?>
</td>
<td>
<?php echo esc_html(str_replace('_', ' ', ucfirst($detection['source']))); ?>
</td>
Expand Down Expand Up @@ -359,7 +433,7 @@ class="button button-small">
</td>
</tr>
<tr class="webdecoy-detail-row" style="display:none;">
<td colspan="9">
<td colspan="10">
<div class="webdecoy-detection-detail">
<strong><?php esc_html_e('User Agent:', 'webdecoy'); ?></strong>
<code><?php echo esc_html($detection['user_agent']); ?></code>
Expand Down
2 changes: 1 addition & 1 deletion admin/partials/settings-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,7 @@
</p>

<p class="webdecoy-connected-actions">
<a href="<?php echo esc_url(WebDecoy_Cloud_Connect::billing_url()); ?>" class="button button-primary" target="_blank" rel="noopener">
<a href="<?php echo esc_url(WebDecoy_Cloud_Connect::wp_upgrade_url('wp_pro')); ?>" class="button button-primary" target="_blank" rel="noopener">
<?php esc_html_e('Upgrade', 'webdecoy'); ?>
</a>
<button type="submit" form="webdecoy-disconnect-form" class="button button-secondary">
Expand Down
4 changes: 4 additions & 0 deletions includes/class-webdecoy-activator.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public static function deactivate(): void
wp_clear_scheduled_hook('webdecoy_cleanup_expired');
wp_clear_scheduled_hook('webdecoy_sync_blocked_ips');
wp_clear_scheduled_hook('webdecoy_sync_entitlements');
wp_clear_scheduled_hook('webdecoy_sync_actor_feed');

// Flush rewrite rules
flush_rewrite_rules();
Expand Down Expand Up @@ -260,12 +261,15 @@ public static function uninstall(): void
delete_option('webdecoy_options');
delete_option('webdecoy_db_version');
delete_option('webdecoy_entitlements');
delete_option('webdecoy_actor_feed_cursor');
delete_option('webdecoy_critical_moment_last');

// Clear scheduled events
wp_clear_scheduled_hook('webdecoy_cleanup_expired');
wp_clear_scheduled_hook('webdecoy_sync_blocked_ips');
wp_clear_scheduled_hook('webdecoy_flush_violations');
wp_clear_scheduled_hook('webdecoy_sync_entitlements');
wp_clear_scheduled_hook('webdecoy_sync_actor_feed');
}
}

Expand Down
Loading
Loading