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
2 changes: 1 addition & 1 deletion admin/partials/detections-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

global $wpdb;

$table = $wpdb->prefix . 'webdecoy_detections';
$table = esc_sql($wpdb->prefix . 'webdecoy_detections');
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only admin list filtering via GET, no state change
$page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
$per_page = 50;
Expand Down
6 changes: 3 additions & 3 deletions admin/partials/statistics-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

global $wpdb;

$detections_table = $wpdb->prefix . 'webdecoy_detections';
$blocked_table = $wpdb->prefix . 'webdecoy_blocked_ips';
$checkout_table = $wpdb->prefix . 'webdecoy_checkout_attempts';
$detections_table = esc_sql($wpdb->prefix . 'webdecoy_detections');
$blocked_table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');
$checkout_table = esc_sql($wpdb->prefix . 'webdecoy_checkout_attempts');

// 30-day detection trend
$thirty_days_ago = gmdate('Y-m-d H:i:s', strtotime('-30 days'));
Expand Down
2 changes: 1 addition & 1 deletion includes/class-webdecoy-actor-feed.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public function intel_for(string $ip): ?array
public static function purge_feed_blocks(): int
{
global $wpdb;
$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');
$rows = $wpdb->get_col($wpdb->prepare(
"SELECT ip_address FROM {$table} WHERE created_by = %s",
self::CREATED_BY
Expand Down
5 changes: 5 additions & 0 deletions includes/class-webdecoy-actor-intel.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ class WebDecoy_Actor_Intel
/** @var string Plaintext API key. */
private $apiKey;

/**
* Constructor
*
* @param string $apiKey WebDecoy Cloud API key
*/
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
Expand Down
12 changes: 11 additions & 1 deletion includes/class-webdecoy-ai-referrals.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ class WebDecoy_AI_Referrals
/** Distinct platform and path pairs kept before new ones are dropped. */
private const MAX_ROWS = 5000;

/**
* The ai_referrals table name, prefixed and escaped for interpolation.
*/
public static function table(): string
{
global $wpdb;
return $wpdb->prefix . 'webdecoy_ai_referrals';
return esc_sql($wpdb->prefix . 'webdecoy_ai_referrals');
}

/**
Expand Down Expand Up @@ -103,6 +106,13 @@ public static function landingPath(string $uri): string
return mb_check_encoding($path, 'UTF-8') ? $path : '/';
}

/**
* Record one referral for a platform/landing-path pair, bounded to
* {@see self::MAX_ROWS} distinct pairs.
*
* @param string $platform Referring AI platform
* @param string $path Landing path the referral counted against
*/
private static function increment(string $platform, string $path): void
{
global $wpdb;
Expand Down
20 changes: 10 additions & 10 deletions includes/class-webdecoy-blocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public function block(string $ip, string $reason = '', ?int $duration_hours = nu
}
}

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$expires_at = null;
if ($duration_hours !== null && $duration_hours > 0) {
Expand Down Expand Up @@ -261,7 +261,7 @@ public function unblock(string $ip): bool
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$result = $wpdb->delete($table, ['ip_address' => $ip]);

Expand Down Expand Up @@ -292,7 +292,7 @@ public function is_blocked(string $ip): bool

global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

// First check for exact IP match (fastest)
$exact_blocked = $wpdb->get_var($wpdb->prepare(
Expand Down Expand Up @@ -344,7 +344,7 @@ public function get_blocked_ips(array $args = []): array
];

$args = wp_parse_args($args, $defaults);
$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$where = '1=1';
if (!$args['include_expired']) {
Expand Down Expand Up @@ -375,7 +375,7 @@ public function get_blocked_count(bool $include_expired = false): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$where = '1=1';
if (!$include_expired) {
Expand All @@ -395,7 +395,7 @@ public function get_block_info(string $ip): ?array
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$result = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table} WHERE ip_address = %s AND (expires_at IS NULL OR expires_at > %s)",
Expand All @@ -417,7 +417,7 @@ public function extend_block(string $ip, int $hours): bool
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$info = $this->get_block_info($ip);
if (!$info) {
Expand Down Expand Up @@ -449,7 +449,7 @@ public function clear_all(): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$count = $wpdb->query("DELETE FROM {$table}");

Expand All @@ -470,7 +470,7 @@ public function cleanup_expired(): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

return $wpdb->query($wpdb->prepare(
"DELETE FROM {$table} WHERE expires_at IS NOT NULL AND expires_at < %s",
Expand Down Expand Up @@ -740,7 +740,7 @@ public function get_stats(): array
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_blocked_ips';
$table = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');

$total = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
$active = $wpdb->get_var($wpdb->prepare(
Expand Down
6 changes: 3 additions & 3 deletions includes/class-webdecoy-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public function status($args, $assoc_args): void
$mode .= ' [forced by WEBDECOY_DEFAULT_MODE]';
}

$detections = $wpdb->prefix . 'webdecoy_detections';
$blocked = $wpdb->prefix . 'webdecoy_blocked_ips';
$detections = esc_sql($wpdb->prefix . 'webdecoy_detections');
$blocked = esc_sql($wpdb->prefix . 'webdecoy_blocked_ips');
$now = gmdate('Y-m-d H:i:s');

$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$detections}"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from $wpdb->prefix
Expand Down Expand Up @@ -284,7 +284,7 @@ public function logs($args, $assoc_args): void

\WP_CLI::confirm('Delete ALL recorded detections on this site?', $assoc_args);

$detections = $wpdb->prefix . 'webdecoy_detections';
$detections = esc_sql($wpdb->prefix . 'webdecoy_detections');
$deleted = $wpdb->query("DELETE FROM {$detections}"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from $wpdb->prefix
\WP_CLI::success(sprintf('Deleted %d detection%s.', (int) $deleted, (int) $deleted === 1 ? '' : 's'));
}
Expand Down
8 changes: 8 additions & 0 deletions includes/class-webdecoy-cloud-connect.php
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,14 @@ public static function connected_notice_message(string $org): string
return sprintf(__('Connected to WebDecoy Cloud (%s).', 'webdecoy'), $org) . ' ' . $tail;
}

/**
* Stash an admin notice for display on the next page load.
*
* @param string $type Notice type (e.g. 'success', 'error')
* @param string $message Notice text
* @param string $url Optional action link URL
* @param string $label Optional action link label
*/
private function set_notice(string $type, string $message, string $url = '', string $label = ''): void
{
set_transient(
Expand Down
3 changes: 3 additions & 0 deletions includes/class-webdecoy-cloud-policy.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ public static function clear(): void
delete_option(self::OPTION);
}

/**
* The connected organization id from the stored plugin options, if any.
*/
private static function organization_id(): string
{
$options = get_option('webdecoy_options', []);
Expand Down
16 changes: 16 additions & 0 deletions includes/class-webdecoy-decoy-response.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ private static function secret(): string
return $secret;
}

/**
* Derive a stable per-label canary value from the site secret.
*
* @param string $label Canary label
* @param int $len Length of the returned hex string
*/
private static function derive(string $label, int $len = 16): string
{
return substr(hash_hmac('sha256', $label, self::secret()), 0, $len);
Expand Down Expand Up @@ -204,6 +210,9 @@ public function served_canaries(string $path): array
return $this->decoy_for($path) === null ? [] : self::canaries();
}

/**
* Send a plain 404 response and stop execution.
*/
private function serve_404(): void
{
nocache_headers();
Expand All @@ -213,6 +222,13 @@ private function serve_404(): void
exit;
}

/**
* Send a 200 response with the given body and content type, and stop
* execution.
*
* @param string $body Response body
* @param string $type Content-Type value
*/
private function serve_body(string $body, string $type): void
{
nocache_headers();
Expand Down
8 changes: 4 additions & 4 deletions includes/class-webdecoy-detector.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_detections';
$table = esc_sql($wpdb->prefix . 'webdecoy_detections');

$wpdb->insert($table, [

Check warning on line 241 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
'ip_address' => $ip,
'user_agent' => $this->get_signal_collector()->getUserAgent(),
'score' => $result->getScore(),
Expand Down Expand Up @@ -271,9 +271,9 @@
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_detections';
$table = esc_sql($wpdb->prefix . 'webdecoy_detections');

return $wpdb->get_results($wpdb->prepare(

Check warning on line 276 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().

Check warning on line 276 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
"SELECT * FROM {$table} ORDER BY created_at DESC LIMIT %d",
$limit
), ARRAY_A) ?: [];
Expand All @@ -289,25 +289,25 @@
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_detections';
$table = esc_sql($wpdb->prefix . 'webdecoy_detections');
$since = gmdate('Y-m-d H:i:s', strtotime("-{$days} days"));

$total = $wpdb->get_var($wpdb->prepare(

Check warning on line 295 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().

Check warning on line 295 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
"SELECT COUNT(*) FROM {$table} WHERE created_at > %s",
$since
));

$by_level = $wpdb->get_results($wpdb->prepare(

Check warning on line 300 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().

Check warning on line 300 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
"SELECT threat_level, COUNT(*) as count FROM {$table} WHERE created_at > %s GROUP BY threat_level",
$since
), OBJECT_K);

$by_day = $wpdb->get_results($wpdb->prepare(

Check warning on line 305 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().

Check warning on line 305 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
"SELECT DATE(created_at) as date, COUNT(*) as count FROM {$table} WHERE created_at > %s GROUP BY DATE(created_at) ORDER BY date",
$since
), ARRAY_A);

$high_risk = $wpdb->get_var($wpdb->prepare(

Check warning on line 310 in includes/class-webdecoy-detector.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
"SELECT COUNT(*) FROM {$table} WHERE created_at > %s AND threat_level IN ('HIGH', 'CRITICAL')",
$since
));
Expand All @@ -331,7 +331,7 @@
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_detections';
$table = esc_sql($wpdb->prefix . 'webdecoy_detections');
$since = gmdate('Y-m-d H:i:s', strtotime("-{$days} days"));

return (int) $wpdb->get_var($wpdb->prepare(
Expand Down
5 changes: 5 additions & 0 deletions includes/class-webdecoy-honeytoken.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class WebDecoy_Honeytoken
/** @var bool */
private $rotate;

/**
* Constructor
*
* @param bool $rotate Whether to rotate the per-site secret on next use
*/
public function __construct(bool $rotate = false)
{
$this->rotate = $rotate;
Expand Down
5 changes: 5 additions & 0 deletions includes/class-webdecoy-ip-enrichment.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class WebDecoy_IP_Enrichment
/** @var int Request timeout in seconds (filterable). */
private $timeout;

/**
* Constructor
*
* @param string $apiKey WebDecoy Cloud API key
*/
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
Expand Down
14 changes: 7 additions & 7 deletions includes/class-webdecoy-rate-limiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public function check_and_increment(string $key): array
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$now = current_time('mysql', true);
$window_start_threshold = gmdate('Y-m-d H:i:s', strtotime("-{$this->window} seconds"));

Expand Down Expand Up @@ -126,7 +126,7 @@ public function increment(string $ip): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$now = current_time('mysql', true);
$window_start = gmdate('Y-m-d H:i:s', strtotime("-{$this->window} seconds"));

Expand Down Expand Up @@ -168,7 +168,7 @@ public function get_count(string $ip): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$window_start = gmdate('Y-m-d H:i:s', strtotime("-{$this->window} seconds"));

$count = $wpdb->get_var($wpdb->prepare(
Expand Down Expand Up @@ -202,7 +202,7 @@ public function get_reset_time(string $ip): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$window_start_threshold = gmdate('Y-m-d H:i:s', strtotime("-{$this->window} seconds"));

$window_start = $wpdb->get_var($wpdb->prepare(
Expand Down Expand Up @@ -231,7 +231,7 @@ public function reset(string $ip): bool
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');

return $wpdb->delete($table, ['ip_address' => $ip]) !== false;
}
Expand All @@ -245,7 +245,7 @@ public function cleanup(): int
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$threshold = gmdate('Y-m-d H:i:s', strtotime('-1 hour'));

return $wpdb->query($wpdb->prepare(
Expand Down Expand Up @@ -338,7 +338,7 @@ public function get_stats(): array
{
global $wpdb;

$table = $wpdb->prefix . 'webdecoy_rate_limits';
$table = esc_sql($wpdb->prefix . 'webdecoy_rate_limits');
$window_start = gmdate('Y-m-d H:i:s', strtotime("-{$this->window} seconds"));

$active_ips = $wpdb->get_var($wpdb->prepare(
Expand Down
3 changes: 3 additions & 0 deletions includes/class-webdecoy-updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
*/
class WebDecoy_Updater
{
/**
* Constructor
*/
public function __construct()
{
add_filter('pre_set_site_transient_update_plugins', [$this, 'check_for_updates']);
Expand Down
Loading
Loading