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 changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
*** WebDecoy Bot Detection Changelog ***

= 2.2.0 - Unreleased =
= 2.2.0 - 2026-07-19 =
* Added: IP allowlist — IPs or CIDR ranges that bypass all detection and blocking (Settings → Blocking). Previously the check existed internally but had no way to configure it.
* Changed: Consolidated onto a single detector path (SDK detector + WordPress-signal wrapper) across front-end, forms, and WooCommerce; removed the unused legacy forms class and a dead scheduled task. No behavior change to detection.
* Added: Filter rules — write expression-based rules (e.g. `ip.tor or ip.abuse_score > 50`, `ip.country in ["CN","RU"] and req.path matches "^/wp-login"`) evaluated before scoring; block or throttle, with dry-run. New Settings → Rules tab with a rule builder and parse-on-save validation. Expression language is byte-for-byte compatible with @webdecoy/node.
Expand Down
17 changes: 16 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Donate link: https://webdecoy.com
Tags: security, bot detection, spam protection, woocommerce, firewall
Requires at least: 5.6
Tested up to: 6.8
Stable tag: 2.1.0
Stable tag: 2.2.0
Requires PHP: 7.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Expand Down Expand Up @@ -185,6 +185,21 @@ Privacy Policy: [https://www.jsdelivr.com/terms/privacy-policy-jsdelivr-net](htt

== Changelog ==

= 2.2.0 =
* Added: Tripwires — deterministic, zero-false-positive blocking of hidden honeypot paths (on by default)
* Added: Honeytoken — auto-injected invisible decoy link armed as a tripwire (on by default)
* Added: Filter rules — expression-based rules with an admin rule builder (ip.* / req.* fields)
* Added: IP enrichment (VPN/proxy/Tor, geo, ASN, abuse score) powering ip.* filter fields
* Added: wd_clearance enforcement loop — silent cookie minting + tripwire forwarding for rotation-proof device lockouts
* Added: Stealth-browser (F1) detection — catches automation that patches native browser functions
* Added: Deceptive tripwire responses — fake .env/wp-config/SQL/phpinfo with per-site canary credentials; canary logins flagged as critical
* Added: WordPress-native traps — fake vulnerable-plugin paths, optional XML-RPC trap, author-enumeration canary
* Added: WooCommerce honeytoken coupons — a hidden decoy coupon; applying it is a deterministic bot signal
* Added: IP allowlist (Settings → Blocking)
* Improved: Rate limiting runs as a rule — proper 429 + Retry-After + X-RateLimit-* headers, sliding-window algorithm, per-IP/route/user keying
* Improved: Resilient violation reporting (DB spool + cron retry, no page latency)
* Changed: Consolidated onto a single detector path; removed legacy dead code

= 2.1.0 =
* Added: JS execution verification — detects non-JS HTTP scrapers
* Added: Challenge token meta tag for premium page serve tracking
Expand Down
4 changes: 2 additions & 2 deletions webdecoy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: WebDecoy Bot Detection
* Plugin URI: https://webdecoy.com/wordpress
* Description: Protect your WordPress site from bots, spam, and carding attacks with WebDecoy's advanced threat detection.
* Version: 2.1.0
* Version: 2.2.0
* Requires at least: 5.6
* Requires PHP: 7.4
* Author: WebDecoy
Expand Down Expand Up @@ -41,7 +41,7 @@
}

// Plugin constants
define('WEBDECOY_VERSION', '2.1.0');
define('WEBDECOY_VERSION', '2.2.0');
define('WEBDECOY_PLUGIN_FILE', __FILE__);
define('WEBDECOY_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WEBDECOY_PLUGIN_URL', plugin_dir_url(__FILE__));
Expand Down Expand Up @@ -1113,7 +1113,7 @@
// latter, plus the raw cookie.
$headers = $this->collect_request_headers();
if (isset($_SERVER['HTTP_COOKIE'])) {
$headers['cookie'] = (string) wp_unslash($_SERVER['HTTP_COOKIE']);

Check failure on line 1116 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Detected usage of a non-sanitized input variable: $_SERVER['HTTP_COOKIE']
}

// Fetch IP enrichment only when a filter rule needs it (premium only).
Expand Down Expand Up @@ -1235,7 +1235,7 @@
],
];

$wpdb->insert($wpdb->prefix . 'webdecoy_detections', [

Check warning on line 1238 in webdecoy.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' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
'score' => 100,
Expand Down Expand Up @@ -1276,7 +1276,7 @@
),
];

$wpdb->insert($wpdb->prefix . 'webdecoy_detections', [

Check warning on line 1279 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Use of a direct database call is discouraged.
'ip_address' => $violation->ip,
'user_agent' => $violation->userAgent ?? '',
'score' => $confidence,
Expand Down Expand Up @@ -1340,7 +1340,7 @@
*/
private function is_challenge_verified(string $ip): bool
{
$cookie = isset($_COOKIE['webdecoy_verified']) ? sanitize_text_field($_COOKIE['webdecoy_verified']) : '';

Check failure on line 1343 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_COOKIE['webdecoy_verified'] not unslashed before sanitization. Use wp_unslash() or similar
if (empty($cookie)) {
return false;
}
Expand Down Expand Up @@ -1414,9 +1414,9 @@
'metadata' => $result->getMetadata(),
];

$wpdb->insert($table, [

Check warning on line 1417 in webdecoy.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' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field($_SERVER['HTTP_USER_AGENT']) : '',

Check failure on line 1419 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_SERVER['HTTP_USER_AGENT'] not unslashed before sanitization. Use wp_unslash() or similar
'score' => $result->getScore(),
'threat_level' => $result->getThreatLevel(),
'source' => 'wordpress_plugin',
Expand Down Expand Up @@ -1516,7 +1516,7 @@
$ip = $this->get_client_ip();

global $wpdb;
$wpdb->insert($wpdb->prefix . 'webdecoy_detections', [

Check warning on line 1519 in webdecoy.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' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
'score' => 100,
Expand Down Expand Up @@ -1646,7 +1646,7 @@
{
$honeypot_name = 'webdecoy_hp_' . $context;

if (isset($_POST[$honeypot_name]) && !empty($_POST[$honeypot_name])) {

Check failure on line 1649 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Processing form data without nonce verification.

Check failure on line 1649 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Processing form data without nonce verification.
// Honeypot triggered - definitely a bot
$ip = $this->get_client_ip();
$blocker = new WebDecoy_Blocker();
Expand Down Expand Up @@ -2110,7 +2110,7 @@
}

// Get detection data
$detection_json = isset($_POST['detection']) ? wp_unslash($_POST['detection']) : '';

Check failure on line 2113 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

Detected usage of a non-sanitized input variable: $_POST['detection']
$detection = json_decode($detection_json, true);

if (!$detection || !is_array($detection)) {
Expand Down Expand Up @@ -2205,7 +2205,7 @@
$threat_level = 'LOW';
}

$wpdb->insert($table, [

Check warning on line 2208 in webdecoy.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' => $user_agent,
'score' => $score,
Expand Down Expand Up @@ -2282,7 +2282,7 @@
return;
}

$api_key = sanitize_text_field($_POST['api_key'] ?? '');

Check failure on line 2285 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_POST['api_key'] not unslashed before sanitization. Use wp_unslash() or similar

// Decrypt if encrypted
if (!empty($api_key) && $this->is_encrypted($api_key)) {
Expand Down Expand Up @@ -2357,8 +2357,8 @@
return;
}

$ip = sanitize_text_field($_POST['ip'] ?? '');

Check failure on line 2360 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_POST['ip'] not unslashed before sanitization. Use wp_unslash() or similar
$reason = sanitize_text_field($_POST['reason'] ?? '');

Check failure on line 2361 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_POST['reason'] not unslashed before sanitization. Use wp_unslash() or similar
$duration = intval($_POST['duration'] ?? 24);

if (empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
Expand All @@ -2384,7 +2384,7 @@
return;
}

$ip = sanitize_text_field($_POST['ip'] ?? '');

Check failure on line 2387 in webdecoy.php

View workflow job for this annotation

GitHub Actions / PHP Coding Standards

$_POST['ip'] not unslashed before sanitization. Use wp_unslash() or similar

if (empty($ip)) {
wp_send_json_error(['message' => __('Invalid IP address.', 'webdecoy')]);
Expand Down Expand Up @@ -2784,7 +2784,7 @@
return $transient;
}

$transient->response[WEBDECOY_PLUGIN_BASENAME] = (object) [

Check failure on line 2787 in webdecoy.php

View workflow job for this annotation

GitHub Actions / Static Analysis

Access to an undefined property object::$response.
'slug' => 'webdecoy',
'plugin' => WEBDECOY_PLUGIN_BASENAME,
'new_version' => $update_info['version'],
Expand Down
Loading