@anonympins/fingerprint 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +1784 -1738
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +489 -99
- package/src/js/fingerprint.utils.js +252 -2
- package/src/js/pow.worker.js +2 -2
- package/src/js/tests/cross_parity.test.js +3 -3
- package/src/js/tests/dns-circuit-breaker.test.js +6 -6
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.builder.test.js +76 -78
- package/src/js/tests/fingerprint.client.init.test.js +78 -0
- package/src/js/tests/fingerprint.client.test.js +16 -17
- package/src/js/tests/fingerprint.test.js +37 -12
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
- package/src/js/tests/library.test.js +94 -95
- package/src/js/tests/obfuscation.test.js +8 -8
- package/src/js/tests/quicFingerprint.test.js +47 -0
- package/src/js/tests/tcpFingerprint.test.js +4 -4
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/js/upow-model-task.js +155 -112
- package/src/php/Challenge/ChallengeUtils.php +392 -86
- package/src/php/Config/SecurityProfiles.php +79 -61
- package/src/php/DirectFingerprint.php +55 -8
- package/src/php/FingerprintBuilder.php +13 -13
- package/src/php/FingerprintClient.php +6 -6
- package/src/php/FingerprintEngine.php +2122 -1690
- package/src/php/Ja3AnomalyDetector.php +22 -22
- package/src/php/Optimization/FunctionRegistry.php +4 -4
- package/src/php/Optimization/Optimization.php +6 -6
- package/src/php/RequestContext.php +11 -11
- package/src/php/Store/InMemoryStore.php +96 -66
- package/src/php/Store/MongoDbStore.php +2 -2
- package/src/php/Store/RedisStore.php +1 -1
- package/src/php/Store/StoreManager.php +48 -35
- package/src/php/Tests/ChallengeUtilsTest.php +31 -0
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintClientTest.php +70 -70
- package/src/php/Tests/PowTest.php +37 -39
- package/src/php/Tests/QuicFingerprintTest.php +115 -53
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/BlockList.php +13 -13
- package/src/php/Utils/Logger.php +19 -1
- package/src/php/Utils/MaliciousPatterns.php +6 -6
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +362 -1
- package/src/php/Utils/TLSClientHelloParser.php +385 -369
- package/src/php/WordPress/WpDbStore.php +7 -7
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1121 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
- package/src/php/WordPress/package.php +80 -101
- package/src/php/WordPress/readme.txt +21 -5
- package/src/php/bin/auto-tune.php +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin Name: Anonympins Bot Mitigation with Proof-of-Work
|
|
4
|
+
* Plugin URI: https://github.com/anonympins/fingerprint
|
|
5
|
+
* Description: High-performance client-side anti-bot protection and Proof-of-Work challenge verification for WordPress.
|
|
6
|
+
* Version: 0.8.0
|
|
7
|
+
* Author: anonympins
|
|
8
|
+
* Requires at least: 5.9
|
|
9
|
+
* Requires PHP: 8.0
|
|
10
|
+
* License: MIT
|
|
11
|
+
* Text Domain: anonympins-bot-mitigation-pow
|
|
12
|
+
* Domain Path: /languages
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare(strict_types=1);
|
|
16
|
+
|
|
17
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
18
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
19
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
20
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
21
|
+
use Anonympins\Fingerprint\WordPress\WpDbStore;
|
|
22
|
+
|
|
23
|
+
if (!defined('ABSPATH')) {
|
|
24
|
+
exit;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!defined('ANONYMPINS_BOT_MITIGATION_VERSION')) {
|
|
28
|
+
define('ANONYMPINS_BOT_MITIGATION_VERSION', '0.8.0');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// HONEYPOT TRAPS FOR THIRD-PARTY ENVIRONMENTS (NON-WORDPRESS)
|
|
33
|
+
// =============================================================================
|
|
34
|
+
$fingerprint_foreign_env_trap_urls = [
|
|
35
|
+
'/phpmyadmin', '/pma', '/adminer', '/mysql', '/dbadmin', '/phpinfo',
|
|
36
|
+
'/.env', '/artisan', '/telescope', '/_profiler', '/config/database.php', '/vendor/',
|
|
37
|
+
'/actuator', '/actuator/health', '/actuator/gateway', '/api-docs', '/swagger-ui',
|
|
38
|
+
'/.flask', '/django_admin', '/__pycache__',
|
|
39
|
+
'/.npmrc', '/package.json', '/package-lock.json',
|
|
40
|
+
'/elmah.axd', '/trace.axd', '/cgi-bin/',
|
|
41
|
+
'/.git', '/.svn', '/.aws', '/.kube', '/docker-compose', '/serverless'
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
$fingerprint_foreign_env_honeypot_fields = [
|
|
45
|
+
'_token',
|
|
46
|
+
'csrfmiddlewaretoken',
|
|
47
|
+
'authenticity_token',
|
|
48
|
+
'form_build_id',
|
|
49
|
+
'form_id',
|
|
50
|
+
'j_username',
|
|
51
|
+
'j_password',
|
|
52
|
+
'__VIEWSTATE',
|
|
53
|
+
'__EVENTVALIDATION',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// DEFAULT SECURITY PROFILES CONFIGURATION BY CONTEXT
|
|
58
|
+
// =============================================================================
|
|
59
|
+
$fingerprint_security_profiles = [
|
|
60
|
+
'api' => [
|
|
61
|
+
'profile' => 'api',
|
|
62
|
+
'overrides' => [
|
|
63
|
+
'verbose' => false,
|
|
64
|
+
'challengeNewDevices' => false,
|
|
65
|
+
'honeypot' => [
|
|
66
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
67
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
68
|
+
'detectInjections' => true,
|
|
69
|
+
],
|
|
70
|
+
],
|
|
71
|
+
],
|
|
72
|
+
'admin' => [
|
|
73
|
+
'profile' => 'strict',
|
|
74
|
+
'overrides' => [
|
|
75
|
+
'verbose' => true,
|
|
76
|
+
'challengeNewDevices' => true,
|
|
77
|
+
'honeypot' => [
|
|
78
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
79
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
80
|
+
'detectInjections' => true,
|
|
81
|
+
],
|
|
82
|
+
],
|
|
83
|
+
],
|
|
84
|
+
'frontend' => [
|
|
85
|
+
'profile' => 'blog',
|
|
86
|
+
'overrides' => [
|
|
87
|
+
'verbose' => false,
|
|
88
|
+
'challengeNewDevices' => false,
|
|
89
|
+
'honeypot' => [
|
|
90
|
+
'fields' => $fingerprint_foreign_env_honeypot_fields,
|
|
91
|
+
'trapUrls' => $fingerprint_foreign_env_trap_urls,
|
|
92
|
+
'detectInjections' => true,
|
|
93
|
+
],
|
|
94
|
+
],
|
|
95
|
+
],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Retrieves configurations merged with settings persisted in WordPress.
|
|
100
|
+
*/
|
|
101
|
+
function fingerprint_get_effective_profiles(array $defaultProfiles): array {
|
|
102
|
+
$saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
|
|
103
|
+
if (!is_array($saved) || empty($saved)) {
|
|
104
|
+
return $defaultProfiles;
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
'frontend' => SecurityProfiles::deepMerge($defaultProfiles['frontend'], $saved['frontend'] ?? []),
|
|
108
|
+
'admin' => SecurityProfiles::deepMerge($defaultProfiles['admin'], $saved['admin'] ?? []),
|
|
109
|
+
'api' => SecurityProfiles::deepMerge($defaultProfiles['api'], $saved['api'] ?? []),
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Retrieves the configured settings for Sandbox / Simulation mode.
|
|
115
|
+
*/
|
|
116
|
+
function fingerprint_get_sandbox_config(): array {
|
|
117
|
+
$saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
|
|
118
|
+
$sandbox = is_array($saved) && isset($saved['sandbox']) && is_array($saved['sandbox']) ? $saved['sandbox'] : [];
|
|
119
|
+
|
|
120
|
+
return [
|
|
121
|
+
'enabled' => !empty($sandbox['enabled']),
|
|
122
|
+
'audit_only' => !empty($sandbox['audit_only']),
|
|
123
|
+
'log_requests' => !empty($sandbox['log_requests']),
|
|
124
|
+
'add_headers' => !isset($sandbox['add_headers']) || !empty($sandbox['add_headers']),
|
|
125
|
+
'ip_filter' => isset($sandbox['ip_filter']) ? trim((string)$sandbox['ip_filter']) : '',
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 1. PSR-4 autoloader for the Fingerprint engine.
|
|
130
|
+
if (!class_exists(DirectFingerprint::class)) {
|
|
131
|
+
$fingerprint_composer_paths = [
|
|
132
|
+
__DIR__ . '/vendor/autoload.php',
|
|
133
|
+
dirname(__DIR__, 2) . '/vendor/autoload.php',
|
|
134
|
+
dirname(__DIR__, 3) . '/vendor/autoload.php',
|
|
135
|
+
(defined('ABSPATH') ? ABSPATH . 'vendor/autoload.php' : ''),
|
|
136
|
+
];
|
|
137
|
+
foreach ($fingerprint_composer_paths as $fingerprint_composer_path) {
|
|
138
|
+
if (!empty($fingerprint_composer_path) && file_exists($fingerprint_composer_path)) {
|
|
139
|
+
require_once $fingerprint_composer_path;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
spl_autoload_register(function (string $class): void {
|
|
145
|
+
$prefix = 'Anonympins\\Fingerprint\\';
|
|
146
|
+
$len = strlen($prefix);
|
|
147
|
+
if (strncmp($prefix, $class, $len) !== 0) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
$relativeClass = substr($class, $len);
|
|
152
|
+
|
|
153
|
+
if (str_starts_with($relativeClass, 'WordPress\\')) {
|
|
154
|
+
$localClass = substr($relativeClass, strlen('WordPress\\'));
|
|
155
|
+
$localFile = __DIR__ . '/' . str_replace('\\', '/', $localClass) . '.php';
|
|
156
|
+
if (file_exists($localFile)) {
|
|
157
|
+
require_once $localFile;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
$candidateDirs = [
|
|
163
|
+
__DIR__ . '/src/',
|
|
164
|
+
__DIR__ . '/includes/',
|
|
165
|
+
__DIR__ . '/',
|
|
166
|
+
dirname(__DIR__) . '/',
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
$fileRelative = str_replace('\\', '/', $relativeClass) . '.php';
|
|
170
|
+
foreach ($candidateDirs as $dir) {
|
|
171
|
+
$fullPath = $dir . $fileRelative;
|
|
172
|
+
if (file_exists($fullPath)) {
|
|
173
|
+
require_once $fullPath;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 2. Plugin activation: Create the SQL cache table.
|
|
181
|
+
register_activation_hook(__FILE__, function () {
|
|
182
|
+
$store = new WpDbStore();
|
|
183
|
+
$store->ensureTable();
|
|
184
|
+
|
|
185
|
+
if (!wp_next_scheduled('fingerprint_prune_expired_entries')) {
|
|
186
|
+
wp_schedule_event(time(), 'hourly', 'fingerprint_prune_expired_entries');
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// 3. Deactivation: Clean up the WP-Cron.
|
|
191
|
+
register_deactivation_hook(__FILE__, function () {
|
|
192
|
+
wp_clear_scheduled_hook('fingerprint_prune_expired_entries');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 4. WP-Cron background task: Purge expired keys from the database.
|
|
196
|
+
add_action('fingerprint_prune_expired_entries', function () {
|
|
197
|
+
$store = new WpDbStore();
|
|
198
|
+
$store->pruneExpired();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// 5. Admin Notices
|
|
202
|
+
add_action('admin_notices', function () {
|
|
203
|
+
if (!is_ssl()) {
|
|
204
|
+
echo '<div class="notice notice-warning is-dismissible">';
|
|
205
|
+
echo '<p><strong>' . esc_html__('[Anonympins Bot Mitigation Security Warning]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
|
|
206
|
+
esc_html__('Your website is currently running on unencrypted HTTP. In this mode, modern web browsers disable the native Web Cryptography API (crypto.subtle) for security reasons, forcing a JavaScript fallback simulation that is slower and vulnerable to Man-in-the-Middle (MitM) attacks. We strongly recommend deploying a TLS/SSL certificate and enforcing HTTPS to ensure the cryptographic integrity of Proof-of-Work computations and identity protection.', 'anonympins-bot-mitigation-pow') .
|
|
207
|
+
'</p>';
|
|
208
|
+
echo '</div>';
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
add_action('admin_notices', function () {
|
|
213
|
+
$sandbox = fingerprint_get_sandbox_config();
|
|
214
|
+
if ($sandbox['enabled'] && current_user_can('manage_options')) {
|
|
215
|
+
$modeDesc = !empty($sandbox['audit_only'])
|
|
216
|
+
? esc_html__('Audit-Only dry-run is active: visitors will not be blocked or challenged.', 'anonympins-bot-mitigation-pow')
|
|
217
|
+
: esc_html__('Enforced test mode: suspicion analysis and PoW challenges are actively triggered.', 'anonympins-bot-mitigation-pow');
|
|
218
|
+
|
|
219
|
+
echo '<div class="notice notice-warning is-dismissible">';
|
|
220
|
+
echo '<p><strong>' . esc_html__('[Anonympins Bot Mitigation Sandbox Active]', 'anonympins-bot-mitigation-pow') . '</strong> : ' .
|
|
221
|
+
esc_html__('Sandbox Mode is currently ENABLED.', 'anonympins-bot-mitigation-pow') . ' ' . esc_html($modeDesc) .
|
|
222
|
+
' <a href="' . esc_url(admin_url('options-general.php?page=anonympins-bot-mitigation#tab-sandbox')) . '">' . esc_html__('Configure Sandbox', 'anonympins-bot-mitigation-pow') . '</a></p>';
|
|
223
|
+
echo '</div>';
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Records a challenge event in the store's circular buffer.
|
|
229
|
+
*/
|
|
230
|
+
function fingerprint_record_challenge_event(array $event): void {
|
|
231
|
+
$store = new WpDbStore();
|
|
232
|
+
$history = $store->get('fingerprint_challenges_log');
|
|
233
|
+
if (!is_array($history)) {
|
|
234
|
+
$history = [];
|
|
235
|
+
}
|
|
236
|
+
array_unshift($history, $event);
|
|
237
|
+
if (count($history) > 50) {
|
|
238
|
+
$history = array_slice($history, 0, 50);
|
|
239
|
+
}
|
|
240
|
+
$store->set('fingerprint_challenges_log', $history, 3600);
|
|
241
|
+
$store->set('fingerprint_latest_challenge', $event, 3600);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
$GLOBALS['fingerprint_current_evaluation'] = null;
|
|
245
|
+
|
|
246
|
+
// 6. Security interception at the earliest point in the WordPress lifecycle.
|
|
247
|
+
add_action('plugins_loaded', function () use ($fingerprint_security_profiles) {
|
|
248
|
+
if ((defined('WP_CLI') && WP_CLI) || (defined('DOING_CRON') && DOING_CRON)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
253
|
+
$rawUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '/';
|
|
254
|
+
$requestUri = !empty($rawUri) ? $rawUri : '/';
|
|
255
|
+
$isRestApi = defined('REST_REQUEST') && REST_REQUEST;
|
|
256
|
+
|
|
257
|
+
if (preg_match('/\.(ico|png|jpg|jpeg|gif|webp|svg|css|js|woff|woff2|ttf)$/i', (string)wp_parse_url($requestUri, PHP_URL_PATH))) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
261
|
+
$isAdmin = is_admin() || (isset($_SERVER['PHP_SELF']) && str_contains(sanitize_text_field(wp_unslash($_SERVER['PHP_SELF'])), 'wp-login.php')) || str_contains($requestUri, 'wp-login.php');
|
|
262
|
+
|
|
263
|
+
if (str_contains($requestUri, 'page=anonympins-bot-mitigation') || str_contains($requestUri, 'page=fingerprint-settings') || str_contains($requestUri, '/wp-json/fingerprint/v1/')) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
$store = new WpDbStore();
|
|
268
|
+
$store->ensureTable();
|
|
269
|
+
StoreManager::configureStore($store);
|
|
270
|
+
|
|
271
|
+
$effectiveProfiles = fingerprint_get_effective_profiles($fingerprint_security_profiles);
|
|
272
|
+
$configs = apply_filters('fingerprint_security_profiles', $effectiveProfiles);
|
|
273
|
+
|
|
274
|
+
if ($isRestApi || str_starts_with($requestUri, '/wp-json/')) {
|
|
275
|
+
$contextConfig = $configs['api'] ?? $fingerprint_security_profiles['api'];
|
|
276
|
+
} elseif ($isAdmin) {
|
|
277
|
+
$contextConfig = $configs['admin'] ?? $fingerprint_security_profiles['admin'];
|
|
278
|
+
} else {
|
|
279
|
+
$contextConfig = $configs['frontend'] ?? $fingerprint_security_profiles['frontend'];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
$sandboxConfig = fingerprint_get_sandbox_config();
|
|
283
|
+
$isSandboxActive = false;
|
|
284
|
+
|
|
285
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
286
|
+
$clientIp = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
|
|
287
|
+
|
|
288
|
+
if ($sandboxConfig['enabled']) {
|
|
289
|
+
if (!empty($sandboxConfig['ip_filter'])) {
|
|
290
|
+
$targetIps = array_filter(array_map('trim', explode(',', $sandboxConfig['ip_filter'])));
|
|
291
|
+
if (in_array($clientIp, $targetIps, true)) {
|
|
292
|
+
$isSandboxActive = true;
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
$isSandboxActive = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if ($isSandboxActive) {
|
|
300
|
+
$contextConfig['overrides']['sandboxMode'] = true;
|
|
301
|
+
if (!empty($sandboxConfig['audit_only'])) {
|
|
302
|
+
$contextConfig['overrides']['dryRun'] = true;
|
|
303
|
+
}
|
|
304
|
+
if ($sandboxConfig['log_requests']) {
|
|
305
|
+
$contextConfig['overrides']['verbose'] = true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
$contextConfig['overrides']['onDecision'] = function (array $decision, \Anonympins\Fingerprint\RequestContext $context) use ($isSandboxActive, $sandboxConfig, $requestUri, $clientIp, $contextConfig) {
|
|
310
|
+
$score = (float)($decision['score'] ?? 0.0);
|
|
311
|
+
$thresholdLow = (float)($contextConfig['overrides']['thresholds']['low'] ?? 20.0);
|
|
312
|
+
$thresholdBlock = (float)($contextConfig['overrides']['thresholds']['block'] ?? 95.0);
|
|
313
|
+
|
|
314
|
+
$action = $decision['intendedAction'] ?? ($decision['action'] ?? 'next');
|
|
315
|
+
$actionTaken = 'allow';
|
|
316
|
+
if ($action === 'block' || $score >= $thresholdBlock) {
|
|
317
|
+
$actionTaken = 'block';
|
|
318
|
+
} elseif ($action === 'challenge' || ($decision['action'] ?? '') === 'challenge' || $score >= $thresholdLow) {
|
|
319
|
+
$actionTaken = 'challenge';
|
|
320
|
+
} elseif ($action === 'redirect') {
|
|
321
|
+
$actionTaken = 'challenge_solved';
|
|
322
|
+
} elseif (!empty($decision['intendedAction']) && $decision['intendedAction'] !== 'next') {
|
|
323
|
+
$actionTaken = (string)$decision['intendedAction'];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if ($isSandboxActive && !empty($sandboxConfig['audit_only'])) {
|
|
327
|
+
if ($actionTaken !== 'allow') {
|
|
328
|
+
$actionTaken = 'audit_sim (' . $actionTaken . ')';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
$isChallengedOrSuspect = ($actionTaken === 'challenge' || $actionTaken === 'block' || $actionTaken === 'challenge_solved' || str_starts_with($actionTaken, 'audit_sim'));
|
|
333
|
+
|
|
334
|
+
$payload = [
|
|
335
|
+
'id' => uniqid('ch_', true),
|
|
336
|
+
'timestamp' => time(),
|
|
337
|
+
'uri' => $requestUri,
|
|
338
|
+
'ip' => !empty($clientIp) ? $clientIp : ($context->clientIp ?? 'unknown'),
|
|
339
|
+
'suspicionScore' => round($score, 1),
|
|
340
|
+
'action' => $actionTaken,
|
|
341
|
+
'suspicionVector' => $decision['vector'] ?? [],
|
|
342
|
+
'sandbox' => $isSandboxActive,
|
|
343
|
+
];
|
|
344
|
+
$GLOBALS['fingerprint_current_evaluation'] = $payload;
|
|
345
|
+
|
|
346
|
+
if ($isSandboxActive && $sandboxConfig['add_headers'] && !headers_sent()) {
|
|
347
|
+
header('X-Fingerprint-Sandbox: active');
|
|
348
|
+
header('X-Fingerprint-Mode: ' . (!empty($sandboxConfig['audit_only']) ? 'audit-only' : 'enforce'));
|
|
349
|
+
header('X-Fingerprint-Score: ' . (string)round($score, 1));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if ($sandboxConfig['log_requests']) {
|
|
353
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
354
|
+
error_log(sprintf('[Fingerprint Challenge Log] Target: %s | IP: %s | Action: %s | Score: %s', $requestUri, $payload['ip'], $actionTaken, $payload['suspicionScore']));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if ($isChallengedOrSuspect) {
|
|
358
|
+
fingerprint_record_challenge_event($payload);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
$securityConfig = SecurityProfiles::createSecurityProfile($contextConfig['profile'], $contextConfig['overrides'] ?? []);
|
|
363
|
+
$guard = new DirectFingerprint($securityConfig);
|
|
364
|
+
$guard->protect();
|
|
365
|
+
}, 0);
|
|
366
|
+
|
|
367
|
+
// =============================================================================
|
|
368
|
+
// CLIENT TELEMETRY LOADING & INITIALIZATION
|
|
369
|
+
// =============================================================================
|
|
370
|
+
add_action('wp_enqueue_scripts', 'fingerprint_enqueue_client_telemetry');
|
|
371
|
+
add_action('login_enqueue_scripts', 'fingerprint_enqueue_client_telemetry');
|
|
372
|
+
|
|
373
|
+
function fingerprint_enqueue_client_telemetry(): void {
|
|
374
|
+
global $fingerprint_foreign_env_honeypot_fields, $fingerprint_foreign_env_trap_urls;
|
|
375
|
+
|
|
376
|
+
$scriptUrl = plugin_dir_url(__FILE__) . 'assets/fingerprint.client.js';
|
|
377
|
+
$scriptPath = plugin_dir_path(__FILE__) . 'assets/fingerprint.client.js';
|
|
378
|
+
|
|
379
|
+
if (!file_exists($scriptPath)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
$version = filemtime($scriptPath) ?: ANONYMPINS_BOT_MITIGATION_VERSION;
|
|
384
|
+
wp_enqueue_script('fingerprint-client-telemetry', $scriptUrl, [], (string)$version, false);
|
|
385
|
+
|
|
386
|
+
$clientConfig = [
|
|
387
|
+
'mouse' => true,
|
|
388
|
+
'keystrokes' => true,
|
|
389
|
+
'clicks' => true,
|
|
390
|
+
'touches' => true,
|
|
391
|
+
'motion' => true,
|
|
392
|
+
'rendering' => true,
|
|
393
|
+
'phantomTraps' => true,
|
|
394
|
+
'honeypots' => array_values($fingerprint_foreign_env_honeypot_fields),
|
|
395
|
+
'trapUrls' => array_values($fingerprint_foreign_env_trap_urls),
|
|
396
|
+
'fetch' => [
|
|
397
|
+
'handleChallenges' => true,
|
|
398
|
+
],
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
$inlineInit = 'if (window.ClientLibrary && typeof window.ClientLibrary.initializeClient === "function") {'
|
|
402
|
+
. ' window.ClientLibrary.initializeClient(' . wp_json_encode($clientConfig) . ');'
|
|
403
|
+
. '};';
|
|
404
|
+
|
|
405
|
+
wp_add_inline_script('fingerprint-client-telemetry', $inlineInit);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// =============================================================================
|
|
409
|
+
// SUSPICION VECTOR INJECTION (VIA WP_ENQUEUE_SCRIPT & WP_ADD_INLINE_SCRIPT)
|
|
410
|
+
// =============================================================================
|
|
411
|
+
add_action('wp_enqueue_scripts', 'fingerprint_inject_client_suspicion_event', 20);
|
|
412
|
+
add_action('admin_enqueue_scripts', 'fingerprint_inject_client_suspicion_event', 20);
|
|
413
|
+
add_action('login_enqueue_scripts', 'fingerprint_inject_client_suspicion_event', 20);
|
|
414
|
+
|
|
415
|
+
function fingerprint_inject_client_suspicion_event(): void {
|
|
416
|
+
$eval = $GLOBALS['fingerprint_current_evaluation'] ?? null;
|
|
417
|
+
$sandbox = fingerprint_get_sandbox_config();
|
|
418
|
+
if (!$eval || (!current_user_can('manage_options') && empty($sandbox['enabled']))) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
$jsonPayload = wp_json_encode($eval);
|
|
423
|
+
$script = 'window.__FINGERPRINT_VECTOR__ = ' . $jsonPayload . '; try { document.dispatchEvent(new CustomEvent("fingerprint:suspicion", { detail: window.__FINGERPRINT_VECTOR__ })); } catch(e) {}';
|
|
424
|
+
|
|
425
|
+
wp_register_script('anonympins-suspicion-event', false, [], ANONYMPINS_BOT_MITIGATION_VERSION, true);
|
|
426
|
+
wp_enqueue_script('anonympins-suspicion-event');
|
|
427
|
+
wp_add_inline_script('anonympins-suspicion-event', $script);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// =============================================================================
|
|
431
|
+
// SPECIAL SANDBOX ROUTES: REST & SERVER-SENT EVENTS (SSE)
|
|
432
|
+
// =============================================================================
|
|
433
|
+
add_action('rest_api_init', function () {
|
|
434
|
+
register_rest_route('fingerprint/v1', '/sandbox/telemetry', [
|
|
435
|
+
'methods' => 'GET',
|
|
436
|
+
'callback' => 'fingerprint_rest_sandbox_telemetry',
|
|
437
|
+
'permission_callback' => function () {
|
|
438
|
+
return current_user_can('manage_options') || !empty(fingerprint_get_sandbox_config()['enabled']);
|
|
439
|
+
},
|
|
440
|
+
]);
|
|
441
|
+
|
|
442
|
+
register_rest_route('fingerprint/v1', '/sandbox/sse', [
|
|
443
|
+
'methods' => 'GET',
|
|
444
|
+
'callback' => 'fingerprint_rest_sandbox_sse',
|
|
445
|
+
'permission_callback' => function () {
|
|
446
|
+
return current_user_can('manage_options') || !empty(fingerprint_get_sandbox_config()['enabled']);
|
|
447
|
+
},
|
|
448
|
+
]);
|
|
449
|
+
|
|
450
|
+
register_rest_route('fingerprint/v1', '/sandbox/clear-challenges', [
|
|
451
|
+
'methods' => 'POST',
|
|
452
|
+
'callback' => 'fingerprint_rest_sandbox_clear_challenges',
|
|
453
|
+
'permission_callback' => function () {
|
|
454
|
+
return current_user_can('manage_options');
|
|
455
|
+
},
|
|
456
|
+
]);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
function fingerprint_rest_sandbox_telemetry(\WP_REST_Request $request): \WP_REST_Response {
|
|
460
|
+
$store = new WpDbStore();
|
|
461
|
+
$history = $store->get('fingerprint_challenges_log');
|
|
462
|
+
if (!is_array($history)) {
|
|
463
|
+
$history = [];
|
|
464
|
+
}
|
|
465
|
+
$latest = !empty($history) ? $history[0] : null;
|
|
466
|
+
|
|
467
|
+
return new \WP_REST_Response([
|
|
468
|
+
'latest' => $latest ?: null,
|
|
469
|
+
'history' => $history,
|
|
470
|
+
'total' => count($history),
|
|
471
|
+
], 200);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function fingerprint_rest_sandbox_clear_challenges(): \WP_REST_Response {
|
|
475
|
+
$store = new WpDbStore();
|
|
476
|
+
$store->delete('fingerprint_challenges_log');
|
|
477
|
+
$store->delete('fingerprint_latest_challenge');
|
|
478
|
+
return new \WP_REST_Response(['cleared' => true], 200);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function fingerprint_rest_sandbox_sse(): void {
|
|
482
|
+
if (function_exists('apache_setenv')) {
|
|
483
|
+
apache_setenv('no-gzip', '1');
|
|
484
|
+
}
|
|
485
|
+
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.ob_end_clean_ob_end_clean
|
|
486
|
+
if (ob_get_level() > 0) {
|
|
487
|
+
ob_end_clean();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
header('Content-Type: text/event-stream; charset=utf-8');
|
|
491
|
+
header('Cache-Control: no-cache, no-transform');
|
|
492
|
+
header('Connection: keep-alive');
|
|
493
|
+
header('X-Accel-Buffering: no');
|
|
494
|
+
|
|
495
|
+
$store = new WpDbStore();
|
|
496
|
+
$lastSeenId = '';
|
|
497
|
+
|
|
498
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
499
|
+
if (!empty($_SERVER['HTTP_LAST_EVENT_ID'])) {
|
|
500
|
+
$lastSeenId = sanitize_text_field(wp_unslash($_SERVER['HTTP_LAST_EVENT_ID']));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
$endTime = time() + 25;
|
|
504
|
+
while (time() < $endTime) {
|
|
505
|
+
$history = $store->get('fingerprint_challenges_log');
|
|
506
|
+
if (is_array($history) && !empty($history)) {
|
|
507
|
+
$newEvents = [];
|
|
508
|
+
foreach ($history as $ev) {
|
|
509
|
+
if (!empty($ev['id']) && (string)$ev['id'] === $lastSeenId) {
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
$newEvents[] = $ev;
|
|
513
|
+
}
|
|
514
|
+
if (!empty($newEvents)) {
|
|
515
|
+
$newEvents = array_reverse($newEvents);
|
|
516
|
+
foreach ($newEvents as $ev) {
|
|
517
|
+
$lastSeenId = (string)$ev['id'];
|
|
518
|
+
echo "id: " . esc_attr($lastSeenId) . "\n";
|
|
519
|
+
echo "event: challenge\n";
|
|
520
|
+
echo 'data: ' . wp_json_encode($ev) . "\n\n";
|
|
521
|
+
}
|
|
522
|
+
if (flush() && ob_get_level() > 0) {
|
|
523
|
+
ob_flush();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
echo ": ping\n\n";
|
|
528
|
+
if (flush() && ob_get_level() > 0) {
|
|
529
|
+
ob_flush();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
usleep(400000); // 400ms
|
|
533
|
+
}
|
|
534
|
+
exit;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// =============================================================================
|
|
538
|
+
// ADMINISTRATION INTERFACE, SETTINGS & METRICS INSPECTION
|
|
539
|
+
// =============================================================================
|
|
540
|
+
add_action('admin_menu', function () {
|
|
541
|
+
add_options_page(
|
|
542
|
+
__('Anonympins Bot Mitigation', 'anonympins-bot-mitigation-pow'),
|
|
543
|
+
__('Anonympins Bot Mitigation', 'anonympins-bot-mitigation-pow'),
|
|
544
|
+
'manage_options',
|
|
545
|
+
'anonympins-bot-mitigation',
|
|
546
|
+
'fingerprint_render_admin_page'
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Registers the admin page JS script via admin_enqueue_scripts (required by WP).
|
|
552
|
+
*/
|
|
553
|
+
add_action('admin_enqueue_scripts', function (string $hook) {
|
|
554
|
+
if (strpos($hook, 'anonympins-bot-mitigation') === false) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
wp_register_script('anonympins-admin-settings', false, [], ANONYMPINS_BOT_MITIGATION_VERSION, true);
|
|
559
|
+
wp_enqueue_script('anonympins-admin-settings');
|
|
560
|
+
|
|
561
|
+
$adminScript = '
|
|
562
|
+
function fingerprintSwitchTab(evt, tabId) {
|
|
563
|
+
evt.preventDefault();
|
|
564
|
+
document.querySelectorAll(".fingerprint-tab-content").forEach(function(t) { t.style.display = "none"; });
|
|
565
|
+
document.querySelectorAll(".nav-tab-wrapper a").forEach(function(n) { n.classList.remove("nav-tab-active"); });
|
|
566
|
+
document.getElementById(tabId).style.display = "block";
|
|
567
|
+
evt.currentTarget.classList.add("nav-tab-active");
|
|
568
|
+
if (window.location.hash !== "#" + tabId && history.pushState) {
|
|
569
|
+
history.pushState(null, null, "#" + tabId);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
574
|
+
const hash = window.location.hash;
|
|
575
|
+
if (hash) {
|
|
576
|
+
const targetId = hash.replace("#", "");
|
|
577
|
+
const targetContent = document.getElementById(targetId);
|
|
578
|
+
const targetLink = document.querySelector(".nav-tab-wrapper a[href=\'" + hash + "\']");
|
|
579
|
+
if (targetContent && targetLink) {
|
|
580
|
+
document.querySelectorAll(".fingerprint-tab-content").forEach(function(t) { t.style.display = "none"; });
|
|
581
|
+
document.querySelectorAll(".nav-tab-wrapper a").forEach(function(n) { n.classList.remove("nav-tab-active"); });
|
|
582
|
+
targetContent.style.display = "block";
|
|
583
|
+
targetLink.classList.add("nav-tab-active");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
let sseSource = null;
|
|
589
|
+
let challengesHistory = [];
|
|
590
|
+
let selectedChallengeId = null;
|
|
591
|
+
const wpRestNonce = ' . wp_json_encode(wp_create_nonce('wp_rest')) . ';
|
|
592
|
+
|
|
593
|
+
function fingerprintToggleSSE() {
|
|
594
|
+
const statusEl = document.getElementById("sse-connection-status");
|
|
595
|
+
const btnText = document.getElementById("sse-btn-text");
|
|
596
|
+
if (sseSource) {
|
|
597
|
+
sseSource.close();
|
|
598
|
+
sseSource = null;
|
|
599
|
+
statusEl.textContent = ' . wp_json_encode(__('SSE Disconnected', 'anonympins-bot-mitigation-pow')) . ';
|
|
600
|
+
statusEl.style.color = "#646970";
|
|
601
|
+
btnText.textContent = ' . wp_json_encode(__('Start Real-Time SSE Stream', 'anonympins-bot-mitigation-pow')) . ';
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
statusEl.textContent = ' . wp_json_encode(__('Connecting to SSE stream...', 'anonympins-bot-mitigation-pow')) . ';
|
|
606
|
+
statusEl.style.color = "#2271b1";
|
|
607
|
+
sseSource = new EventSource(' . wp_json_encode(rest_url('fingerprint/v1/sandbox/sse')) . ' + "?_wpnonce=" + wpRestNonce);
|
|
608
|
+
|
|
609
|
+
sseSource.onopen = function() {
|
|
610
|
+
statusEl.textContent = ' . wp_json_encode(__('SSE Live Connected', 'anonympins-bot-mitigation-pow')) . ';
|
|
611
|
+
statusEl.style.color = "#007017";
|
|
612
|
+
btnText.textContent = ' . wp_json_encode(__('Stop Stream', 'anonympins-bot-mitigation-pow')) . ';
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
sseSource.addEventListener("challenge", function(e) {
|
|
616
|
+
try {
|
|
617
|
+
const newChallenge = JSON.parse(e.data);
|
|
618
|
+
if (!challengesHistory.some(function(c) { return c.id === newChallenge.id; })) {
|
|
619
|
+
challengesHistory.unshift(newChallenge);
|
|
620
|
+
if (challengesHistory.length > 50) { challengesHistory.pop(); }
|
|
621
|
+
selectedChallengeId = newChallenge.id;
|
|
622
|
+
renderChallengesUI();
|
|
623
|
+
}
|
|
624
|
+
} catch(err) {}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
sseSource.onerror = function() {
|
|
628
|
+
statusEl.textContent = ' . wp_json_encode(__('SSE Reconnecting...', 'anonympins-bot-mitigation-pow')) . ';
|
|
629
|
+
statusEl.style.color = "#d63638";
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function fingerprintFetchTelemetry() {
|
|
634
|
+
fetch(' . wp_json_encode(rest_url('fingerprint/v1/sandbox/telemetry')) . ', {
|
|
635
|
+
headers: { "X-WP-Nonce": wpRestNonce }
|
|
636
|
+
})
|
|
637
|
+
.then(function(res) { return res.json(); })
|
|
638
|
+
.then(function(data) {
|
|
639
|
+
if (data && Array.isArray(data.history)) {
|
|
640
|
+
challengesHistory = data.history;
|
|
641
|
+
renderChallengesUI();
|
|
642
|
+
}
|
|
643
|
+
})
|
|
644
|
+
.catch(function(err) { console.error(err); });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function fingerprintClearFeed() {
|
|
648
|
+
if (!confirm(' . wp_json_encode(__('Clear the live challenged visitors feed?', 'anonympins-bot-mitigation-pow')) . ')) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
fetch(' . wp_json_encode(rest_url('fingerprint/v1/sandbox/clear-challenges')) . ', {
|
|
652
|
+
method: "POST",
|
|
653
|
+
headers: { "X-WP-Nonce": wpRestNonce }
|
|
654
|
+
})
|
|
655
|
+
.then(function() {
|
|
656
|
+
challengesHistory = [];
|
|
657
|
+
selectedChallengeId = null;
|
|
658
|
+
renderChallengesUI();
|
|
659
|
+
})
|
|
660
|
+
.catch(function(err) { console.error(err); });
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function renderChallengesUI() {
|
|
664
|
+
const tableBody = document.getElementById("live-challenges-tbody");
|
|
665
|
+
const countEl = document.getElementById("live-challenges-count");
|
|
666
|
+
countEl.textContent = challengesHistory.length;
|
|
667
|
+
|
|
668
|
+
if (!challengesHistory.length) {
|
|
669
|
+
tableBody.innerHTML = "<tr><td colspan=\"6\" style=\"text-align:center;color:#646970;padding:16px;\"><em>" + ' . wp_json_encode(__('No challenged visitors recorded yet.', 'anonympins-bot-mitigation-pow')) . ' + "</em></td></tr>";
|
|
670
|
+
renderDetailView(null);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
let rowsHtml = "";
|
|
675
|
+
challengesHistory.forEach(function(item, index) {
|
|
676
|
+
const isSelected = item.id === selectedChallengeId || (!selectedChallengeId && index === 0);
|
|
677
|
+
if (isSelected && !selectedChallengeId) { selectedChallengeId = item.id; }
|
|
678
|
+
|
|
679
|
+
const date = item.timestamp ? new Date(item.timestamp * 1000).toLocaleTimeString() : "--:--:--";
|
|
680
|
+
const score = item.suspicionScore !== undefined ? Math.round(item.suspicionScore) : 0;
|
|
681
|
+
const scoreColor = score >= 75 ? "#d63638" : (score >= 40 ? "#dba617" : "#2271b1");
|
|
682
|
+
const actionBadge = (item.action || "challenge").toUpperCase();
|
|
683
|
+
const actionBg = actionBadge.indexOf("BLOCK") !== -1 ? "#d63638" : (actionBadge.indexOf("AUDIT") !== -1 ? "#72aee6" : "#dba617");
|
|
684
|
+
|
|
685
|
+
rowsHtml += "<tr style=\"cursor:pointer;background:" + (isSelected ? "#f0f6fc" : "transparent") + ";\" onclick=\"fingerprintSelectChallenge(\'" + item.id + "\')\">" +
|
|
686
|
+
"<td><b>" + date + "</b></td>" +
|
|
687
|
+
"<td><code>" + escapeHtml(item.ip || "unknown") + "</code></td>" +
|
|
688
|
+
"<td style=\"max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\" title=\"" + escapeHtml(item.uri || "/") + "\"><code>" + escapeHtml(item.uri || "/") + "</code></td>" +
|
|
689
|
+
"<td><span style=\"font-weight:bold;color:" + scoreColor + ";\">" + score + "</span></td>" +
|
|
690
|
+
"<td><span style=\"display:inline-block;padding:2px 6px;border-radius:3px;font-size:11px;font-weight:bold;color:#fff;background:" + actionBg + ";\">" + escapeHtml(actionBadge) + "</span></td>" +
|
|
691
|
+
"<td><button type=\"button\" class=\"button button-small button-secondary\">" + (isSelected ? "▶ View" : "Inspect") + "</button></td>" +
|
|
692
|
+
"</tr>";
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
tableBody.innerHTML = rowsHtml;
|
|
696
|
+
const selectedItem = challengesHistory.find(function(c) { return c.id === selectedChallengeId; }) || challengesHistory[0];
|
|
697
|
+
renderDetailView(selectedItem);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function renderDetailView(item) {
|
|
701
|
+
const detailScore = document.getElementById("live-score-val");
|
|
702
|
+
const detailSub = document.getElementById("live-score-sub");
|
|
703
|
+
const breakdownEl = document.getElementById("live-vector-breakdown");
|
|
704
|
+
|
|
705
|
+
if (!item) {
|
|
706
|
+
detailScore.textContent = "--";
|
|
707
|
+
detailScore.style.color = "#2271b1";
|
|
708
|
+
detailSub.textContent = ' . wp_json_encode(__('Select an entry from the list to inspect its vector.', 'anonympins-bot-mitigation-pow')) . ';
|
|
709
|
+
breakdownEl.innerHTML = "<em>" + ' . wp_json_encode(__('No entry selected.', 'anonympins-bot-mitigation-pow')) . ' + "</em>";
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const score = item.suspicionScore !== undefined ? Math.round(item.suspicionScore) : 0;
|
|
714
|
+
detailScore.textContent = score;
|
|
715
|
+
detailScore.style.color = score >= 75 ? "#d63638" : (score >= 40 ? "#dba617" : "#2271b1");
|
|
716
|
+
detailSub.innerHTML = "<b>IP:</b> " + escapeHtml(item.ip || "unknown") + "<br><b>Target:</b> " + escapeHtml(item.uri || "/") + "<br><b>Action:</b> " + escapeHtml(item.action || "challenge");
|
|
717
|
+
|
|
718
|
+
const vector = item.suspicionVector || {};
|
|
719
|
+
const keys = Object.keys(vector);
|
|
720
|
+
if (keys.length === 0) {
|
|
721
|
+
breakdownEl.innerHTML = "<em>" + ' . wp_json_encode(__('No indicators recorded for this entry.', 'anonympins-bot-mitigation-pow')) . ' + "</em>";
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
let html = "<table style=\"width:100%;border-collapse:collapse;font-size:12px;\">";
|
|
725
|
+
keys.forEach(function(k) {
|
|
726
|
+
html += "<tr style=\"border-bottom:1px solid #f0f0f1;\"><td style=\"padding:3px 6px;\"><b>" + escapeHtml(k) + "</b></td><td style=\"padding:3px 6px;text-align:right;\"><code>" + escapeHtml(JSON.stringify(vector[k])) + "</code></td></tr>";
|
|
727
|
+
});
|
|
728
|
+
html += "</table>";
|
|
729
|
+
breakdownEl.innerHTML = html;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function fingerprintSelectChallenge(id) {
|
|
733
|
+
selectedChallengeId = id;
|
|
734
|
+
renderChallengesUI();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function escapeHtml(str) {
|
|
738
|
+
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
document.addEventListener("DOMContentLoaded", function() {
|
|
742
|
+
fingerprintFetchTelemetry();
|
|
743
|
+
});';
|
|
744
|
+
|
|
745
|
+
wp_add_inline_script('anonympins-admin-settings', $adminScript);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
function fingerprint_render_admin_page(): void {
|
|
749
|
+
if (!current_user_can('manage_options')) {
|
|
750
|
+
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'anonympins-bot-mitigation-pow'));
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
global $fingerprint_security_profiles, $wpdb;
|
|
754
|
+
|
|
755
|
+
if (isset($_POST['fingerprint_save_settings']) && check_admin_referer('fingerprint_settings_nonce', 'fingerprint_nonce')) {
|
|
756
|
+
$saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
|
|
757
|
+
if (!is_array($saved)) {
|
|
758
|
+
$saved = [];
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
$saved['frontend']['profile'] = isset($_POST['frontend_profile']) ? sanitize_text_field(wp_unslash($_POST['frontend_profile'])) : 'blog';
|
|
762
|
+
$saved['admin']['profile'] = isset($_POST['admin_profile']) ? sanitize_text_field(wp_unslash($_POST['admin_profile'])) : 'strict';
|
|
763
|
+
$saved['api']['profile'] = isset($_POST['api_profile']) ? sanitize_text_field(wp_unslash($_POST['api_profile'])) : 'api';
|
|
764
|
+
|
|
765
|
+
$saved['frontend']['overrides']['thresholds'] = [
|
|
766
|
+
'low' => max(1, isset($_POST['frontend_threshold_low']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_low'])) : 20),
|
|
767
|
+
'medium' => max(5, isset($_POST['frontend_threshold_medium']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_medium'])) : 45),
|
|
768
|
+
'high' => max(10, isset($_POST['frontend_threshold_high']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_high'])) : 75),
|
|
769
|
+
'block' => max(20, isset($_POST['frontend_threshold_block']) ? (int)sanitize_text_field(wp_unslash($_POST['frontend_threshold_block'])) : 95),
|
|
770
|
+
];
|
|
771
|
+
|
|
772
|
+
$saved['frontend']['overrides']['challengeNewDevices'] = !empty($_POST['frontend_challenge_new']);
|
|
773
|
+
$saved['frontend']['overrides']['verbose'] = !empty($_POST['frontend_verbose']);
|
|
774
|
+
$saved['frontend']['overrides']['honeypot']['detectInjections'] = !empty($_POST['detect_injections']);
|
|
775
|
+
|
|
776
|
+
foreach ($_POST as $postKey => $postVal) {
|
|
777
|
+
if (str_starts_with((string)$postKey, 'weight_')) {
|
|
778
|
+
$wKey = sanitize_key(substr((string)$postKey, 7));
|
|
779
|
+
$val = (float)sanitize_text_field(wp_unslash($postVal));
|
|
780
|
+
$saved['frontend']['overrides']['weights'][$wKey] = max(0.0, min(2.0, $val));
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
update_option('anonympins_security_options', $saved);
|
|
785
|
+
echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Settings updated successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (isset($_POST['fingerprint_save_sandbox']) && check_admin_referer('fingerprint_sandbox_nonce', 'fingerprint_nonce_sandbox')) {
|
|
789
|
+
$saved = get_option('anonympins_security_options', get_option('fingerprint_security_options', []));
|
|
790
|
+
if (!is_array($saved)) {
|
|
791
|
+
$saved = [];
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
$saved['sandbox'] = [
|
|
795
|
+
'enabled' => !empty($_POST['sandbox_enabled']),
|
|
796
|
+
'audit_only' => !empty($_POST['sandbox_audit_only']),
|
|
797
|
+
'log_requests' => !empty($_POST['sandbox_log_requests']),
|
|
798
|
+
'add_headers' => !empty($_POST['sandbox_add_headers']),
|
|
799
|
+
'ip_filter' => isset($_POST['sandbox_ip_filter']) ? sanitize_text_field(wp_unslash($_POST['sandbox_ip_filter'])) : '',
|
|
800
|
+
];
|
|
801
|
+
|
|
802
|
+
update_option('anonympins_security_options', $saved);
|
|
803
|
+
echo '<div class="notice notice-success is-dismissible"><p><strong>' . esc_html__('Sandbox settings updated successfully.', 'anonympins-bot-mitigation-pow') . '</strong></p></div>';
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (isset($_POST['fingerprint_clear_store']) && check_admin_referer('fingerprint_clear_nonce', 'fingerprint_nonce_clear')) {
|
|
807
|
+
$store = new WpDbStore();
|
|
808
|
+
$store->clear();
|
|
809
|
+
echo '<div class="notice notice-info is-dismissible"><p>' . esc_html__('SQL cache table ($wpdb) cleared successfully.', 'anonympins-bot-mitigation-pow') . '</p></div>';
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
$sandboxConfig = fingerprint_get_sandbox_config();
|
|
813
|
+
$effective = fingerprint_get_effective_profiles($fingerprint_security_profiles);
|
|
814
|
+
$frontendConfig = SecurityProfiles::createSecurityProfile($effective['frontend']['profile'], $effective['frontend']['overrides'] ?? []);
|
|
815
|
+
|
|
816
|
+
$tableName = esc_sql($wpdb->prefix . 'fingerprint_store');
|
|
817
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
|
|
818
|
+
$totalRows = (int)$wpdb->get_var("SELECT COUNT(*) FROM `{$tableName}`");
|
|
819
|
+
$now = time();
|
|
820
|
+
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
|
|
821
|
+
$expiredRows = (int)$wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM `{$tableName}` WHERE expires_at IS NOT NULL AND expires_at < %d", $now));
|
|
822
|
+
|
|
823
|
+
$prometheusRaw = MetricsManager::getPrometheusMetrics($frontendConfig);
|
|
824
|
+
?>
|
|
825
|
+
<div class="wrap">
|
|
826
|
+
<h1><span class="dashicons dashicons-shield-alt" style="font-size:32px;vertical-align:middle;margin-right:8px;"></span> Anonympins Bot Mitigation with Proof-of-Work</h1>
|
|
827
|
+
<p class="description"><?php esc_html_e('Client-side behavioral and cryptographic anti-bot protection without third-party CAPTCHA for WordPress.', 'anonympins-bot-mitigation-pow'); ?></p>
|
|
828
|
+
|
|
829
|
+
<div style="background:#fff;border-left:4px solid #2271b1;padding:12px 18px;margin:18px 0;box-shadow:0 1px 1px rgba(0,0,0,.04);">
|
|
830
|
+
<p style="margin:4px 0;">
|
|
831
|
+
<strong><?php esc_html_e('Official documentation reference:', 'anonympins-bot-mitigation-pow'); ?></strong>
|
|
832
|
+
<a href="https://github.com/anonympins/fingerprint/blob/main/doc/full_options.md" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:8px;">
|
|
833
|
+
<span class="dashicons dashicons-external" style="vertical-align:middle;"></span> <?php esc_html_e('View Full Configuration Options on GitHub', 'anonympins-bot-mitigation-pow'); ?>
|
|
834
|
+
</a>
|
|
835
|
+
<a href="https://github.com/anonympins/fingerprint" target="_blank" rel="noopener noreferrer" class="button button-secondary" style="margin-left:4px;">
|
|
836
|
+
<span class="dashicons dashicons-admin-plugins" style="vertical-align:middle;"></span> <?php esc_html_e('GitHub Repository', 'anonympins-bot-mitigation-pow'); ?>
|
|
837
|
+
</a>
|
|
838
|
+
</p>
|
|
839
|
+
</div>
|
|
840
|
+
|
|
841
|
+
<!-- STATISTICS & STATUS DASHBOARD -->
|
|
842
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));gap:16px;margin-bottom:24px;">
|
|
843
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
844
|
+
<h3 style="margin-top:0;"><?php esc_html_e('HTTPS Status', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
845
|
+
<?php if (is_ssl()): ?>
|
|
846
|
+
<p style="color:#007017;font-weight:bold;font-size:16px;">
|
|
847
|
+
<span class="dashicons dashicons-yes-alt"></span> <?php esc_html_e('Active (WebCrypto Subtle available)', 'anonympins-bot-mitigation-pow'); ?>
|
|
848
|
+
</p>
|
|
849
|
+
<?php else: ?>
|
|
850
|
+
<p style="color:#d63638;font-weight:bold;font-size:16px;">
|
|
851
|
+
<span class="dashicons dashicons-warning"></span> <?php esc_html_e('Insecure (Unencrypted HTTP)', 'anonympins-bot-mitigation-pow'); ?>
|
|
852
|
+
</p>
|
|
853
|
+
<?php endif; ?>
|
|
854
|
+
</div>
|
|
855
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
856
|
+
<h3 style="margin-top:0;"><?php esc_html_e('SQL Cache ($wpdb)', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
857
|
+
<p style="font-size:22px;margin:0;font-weight:600;"><?php echo esc_html((string)$totalRows); ?> <span style="font-size:14px;color:#646970;font-weight:normal;"><?php esc_html_e('keys in database', 'anonympins-bot-mitigation-pow'); ?></span></p>
|
|
858
|
+
<small style="color:#8c8f94;"><?php
|
|
859
|
+
/* translators: %d: number of expired keys awaiting cron purge */
|
|
860
|
+
echo esc_html(sprintf(__(' %d expired keys awaiting cron purge', 'anonympins-bot-mitigation-pow'), $expiredRows));
|
|
861
|
+
?></small>
|
|
862
|
+
</div>
|
|
863
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
864
|
+
<h3 style="margin-top:0;"><?php esc_html_e('Active Profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
865
|
+
<p style="margin:0;">
|
|
866
|
+
<?php esc_html_e('Frontend:', 'anonympins-bot-mitigation-pow'); ?> <strong><?php echo esc_html($effective['frontend']['profile']); ?></strong><br>
|
|
867
|
+
<?php esc_html_e('Admin:', 'anonympins-bot-mitigation-pow'); ?> <strong><?php echo esc_html($effective['admin']['profile']); ?></strong><br>
|
|
868
|
+
<?php esc_html_e('REST API:', 'anonympins-bot-mitigation-pow'); ?> <strong><?php echo esc_html($effective['api']['profile']); ?></strong>
|
|
869
|
+
</p>
|
|
870
|
+
</div>
|
|
871
|
+
<div style="background:#fff;padding:16px;border-radius:4px;border:1px solid #ccd0d4;">
|
|
872
|
+
<h3 style="margin-top:0;"><?php esc_html_e('Sandbox Mode', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
873
|
+
<?php if ($sandboxConfig['enabled']): ?>
|
|
874
|
+
<p style="color:#dba617;font-weight:bold;font-size:16px;margin:0;">
|
|
875
|
+
<span class="dashicons dashicons-warning"></span> <?php esc_html_e('Active (Simulation)', 'anonympins-bot-mitigation-pow'); ?>
|
|
876
|
+
</p>
|
|
877
|
+
<small style="color:#646970;"><?php echo !empty($sandboxConfig['audit_only']) ? esc_html__('Audit-Only: non-blocking observation.', 'anonympins-bot-mitigation-pow') : esc_html__('Testing: challenges & mitigations active.', 'anonympins-bot-mitigation-pow'); ?></small>
|
|
878
|
+
<?php else: ?>
|
|
879
|
+
<p style="color:#007017;font-weight:bold;font-size:16px;margin:0;">
|
|
880
|
+
<span class="dashicons dashicons-yes-alt"></span> <?php esc_html_e('Production (Enforced)', 'anonympins-bot-mitigation-pow'); ?>
|
|
881
|
+
</p>
|
|
882
|
+
<small style="color:#646970;"><?php esc_html_e('Bot protection active across all visitors.', 'anonympins-bot-mitigation-pow'); ?></small>
|
|
883
|
+
<?php endif; ?>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
|
|
887
|
+
<!-- NAVIGATION TABS -->
|
|
888
|
+
<h2 class="nav-tab-wrapper">
|
|
889
|
+
<a href="#tab-settings" class="nav-tab nav-tab-active" onclick="fingerprintSwitchTab(event, 'tab-settings')"><?php esc_html_e('Settings & Profiles', 'anonympins-bot-mitigation-pow'); ?></a>
|
|
890
|
+
<a href="#tab-sandbox" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-sandbox')"><?php esc_html_e('Sandbox / Test Mode', 'anonympins-bot-mitigation-pow'); ?></a>
|
|
891
|
+
<a href="#tab-metrics" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-metrics')"><?php esc_html_e('Metrics & Weights View', 'anonympins-bot-mitigation-pow'); ?></a>
|
|
892
|
+
<a href="#tab-prometheus" class="nav-tab" onclick="fingerprintSwitchTab(event, 'tab-prometheus')"><?php esc_html_e('Prometheus Stream', 'anonympins-bot-mitigation-pow'); ?></a>
|
|
893
|
+
</h2>
|
|
894
|
+
|
|
895
|
+
<div id="tab-settings" class="fingerprint-tab-content" style="background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
896
|
+
<form method="post" action="">
|
|
897
|
+
<?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
|
|
898
|
+
<h3><?php esc_html_e('1. Target Security Profiles', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
899
|
+
<table class="form-table">
|
|
900
|
+
<tr>
|
|
901
|
+
<th scope="row"><label for="frontend_profile"><?php esc_html_e('Visitors & Frontend', 'anonympins-bot-mitigation-pow'); ?></label></th>
|
|
902
|
+
<td>
|
|
903
|
+
<select name="frontend_profile" id="frontend_profile">
|
|
904
|
+
<option value="blog" <?php selected($effective['frontend']['profile'], 'blog'); ?>><?php esc_html_e('Blog (Optimal for content, smooth UX)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
905
|
+
<option value="balanced" <?php selected($effective['frontend']['profile'], 'balanced'); ?>><?php esc_html_e('Balanced (General purpose)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
906
|
+
<option value="strict" <?php selected($effective['frontend']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Maximum protection)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
907
|
+
<option value="ecommerce" <?php selected($effective['frontend']['profile'], 'ecommerce'); ?>><?php esc_html_e('E-commerce (Anti-scraping / scalping)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
908
|
+
</select>
|
|
909
|
+
</td>
|
|
910
|
+
</tr>
|
|
911
|
+
<tr>
|
|
912
|
+
<th scope="row"><label for="admin_profile"><?php esc_html_e('Administration Area (wp-login / wp-admin)', 'anonympins-bot-mitigation-pow'); ?></label></th>
|
|
913
|
+
<td>
|
|
914
|
+
<select name="admin_profile" id="admin_profile">
|
|
915
|
+
<option value="strict" <?php selected($effective['admin']['profile'], 'strict'); ?>><?php esc_html_e('Strict (Recommended to secure logins)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
916
|
+
<option value="balanced" <?php selected($effective['admin']['profile'], 'balanced'); ?>><?php esc_html_e('Balanced', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
917
|
+
</select>
|
|
918
|
+
</td>
|
|
919
|
+
</tr>
|
|
920
|
+
<tr>
|
|
921
|
+
<th scope="row"><label for="api_profile"><?php esc_html_e('REST API (/wp-json/)', 'anonympins-bot-mitigation-pow'); ?></label></th>
|
|
922
|
+
<td>
|
|
923
|
+
<select name="api_profile" id="api_profile">
|
|
924
|
+
<option value="api" <?php selected($effective['api']['profile'], 'api'); ?>><?php esc_html_e('API (JSON challenges & machine PoW)', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
925
|
+
<option value="strict" <?php selected($effective['api']['profile'], 'strict'); ?>><?php esc_html_e('Strict', 'anonympins-bot-mitigation-pow'); ?></option>
|
|
926
|
+
</select>
|
|
927
|
+
</td>
|
|
928
|
+
</tr>
|
|
929
|
+
</table>
|
|
930
|
+
<hr>
|
|
931
|
+
<h3><?php esc_html_e('2. Action Thresholds (Frontend)', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
932
|
+
<table class="form-table">
|
|
933
|
+
<tr>
|
|
934
|
+
<th scope="row"><?php esc_html_e('Suspicion Score Thresholds (0 - 100)', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
935
|
+
<td>
|
|
936
|
+
<label><?php esc_html_e('Low (Initial challenge):', 'anonympins-bot-mitigation-pow'); ?>
|
|
937
|
+
<input type="number" name="frontend_threshold_low" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['low'] ?? 20)); ?>" min="1" max="50" style="width:80px;">
|
|
938
|
+
</label><br><br>
|
|
939
|
+
<label><?php esc_html_e('Medium (Hardened challenge):', 'anonympins-bot-mitigation-pow'); ?>
|
|
940
|
+
<input type="number" name="frontend_threshold_medium" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['medium'] ?? 45)); ?>" min="10" max="75" style="width:80px;">
|
|
941
|
+
</label><br><br>
|
|
942
|
+
<label><?php esc_html_e('High (Heavy Proof-of-Work):', 'anonympins-bot-mitigation-pow'); ?>
|
|
943
|
+
<input type="number" name="frontend_threshold_high" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['high'] ?? 75)); ?>" min="30" max="95" style="width:80px;">
|
|
944
|
+
</label><br><br>
|
|
945
|
+
<label><?php esc_html_e('Block (Immediate 403 Forbidden):', 'anonympins-bot-mitigation-pow'); ?>
|
|
946
|
+
<input type="number" name="frontend_threshold_block" value="<?php echo esc_attr((string)($frontendConfig['thresholds']['block'] ?? 95)); ?>" min="50" max="100" style="width:80px;">
|
|
947
|
+
</label>
|
|
948
|
+
</td>
|
|
949
|
+
</tr>
|
|
950
|
+
<tr>
|
|
951
|
+
<th scope="row"><?php esc_html_e('Advanced Behaviors', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
952
|
+
<td>
|
|
953
|
+
<label>
|
|
954
|
+
<input type="checkbox" name="frontend_challenge_new" value="1" <?php checked(!empty($frontendConfig['challengeNewDevices'])); ?>>
|
|
955
|
+
<?php esc_html_e('Automatically challenge unknown new devices (without device_id cookie)', 'anonympins-bot-mitigation-pow'); ?>
|
|
956
|
+
</label><br>
|
|
957
|
+
<label>
|
|
958
|
+
<input type="checkbox" name="detect_injections" value="1" <?php checked(!empty($frontendConfig['honeypot']['detectInjections'])); ?>>
|
|
959
|
+
<?php esc_html_e('Enable recursive WAF inspection (SQL, NoSQL, Log4j, Traversal in GET/POST)', 'anonympins-bot-mitigation-pow'); ?>
|
|
960
|
+
</label><br>
|
|
961
|
+
<label>
|
|
962
|
+
<input type="checkbox" name="frontend_verbose" value="1" <?php checked(!empty($frontendConfig['verbose'])); ?>>
|
|
963
|
+
<?php esc_html_e('Verbose mode in PHP logs (Debugging)', 'anonympins-bot-mitigation-pow'); ?>
|
|
964
|
+
</label>
|
|
965
|
+
</td>
|
|
966
|
+
</tr>
|
|
967
|
+
</table>
|
|
968
|
+
<?php submit_button(esc_html__('Save Changes', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings'); ?>
|
|
969
|
+
</form>
|
|
970
|
+
<hr>
|
|
971
|
+
<form method="post" action="" style="margin-top:16px;">
|
|
972
|
+
<?php wp_nonce_field('fingerprint_clear_nonce', 'fingerprint_nonce_clear'); ?>
|
|
973
|
+
<p>
|
|
974
|
+
<strong><?php esc_html_e('Store Maintenance:', 'anonympins-bot-mitigation-pow'); ?></strong>
|
|
975
|
+
<button type="submit" name="fingerprint_clear_store" class="button button-secondary" onclick="return confirm('<?php echo esc_js(__('Purge all stored sessions and nonces?', 'anonympins-bot-mitigation-pow')); ?>');">
|
|
976
|
+
<?php esc_html_e('Clear SQL cache table ($wpdb)', 'anonympins-bot-mitigation-pow'); ?>
|
|
977
|
+
</button>
|
|
978
|
+
</p>
|
|
979
|
+
</form>
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<div id="tab-sandbox" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
983
|
+
<h3><?php esc_html_e('Sandbox Mode & Dry-Run Configuration', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
984
|
+
<form method="post" action="">
|
|
985
|
+
<?php wp_nonce_field('fingerprint_sandbox_nonce', 'fingerprint_nonce_sandbox'); ?>
|
|
986
|
+
<table class="form-table">
|
|
987
|
+
<tr>
|
|
988
|
+
<th scope="row"><?php esc_html_e('Sandbox Activation', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
989
|
+
<td>
|
|
990
|
+
<label>
|
|
991
|
+
<input type="checkbox" name="sandbox_enabled" value="1" <?php checked($sandboxConfig['enabled']); ?>>
|
|
992
|
+
<strong><?php esc_html_e('Enable Sandbox Mode', 'anonympins-bot-mitigation-pow'); ?></strong>
|
|
993
|
+
</label>
|
|
994
|
+
</td>
|
|
995
|
+
</tr>
|
|
996
|
+
<tr>
|
|
997
|
+
<th scope="row"><?php esc_html_e('Behavior & Enforcement', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
998
|
+
<td>
|
|
999
|
+
<label>
|
|
1000
|
+
<input type="checkbox" name="sandbox_audit_only" value="1" <?php checked($sandboxConfig['audit_only']); ?>>
|
|
1001
|
+
<strong><?php esc_html_e('Audit Only / Dry-Run (Never block or challenge visitors)', 'anonympins-bot-mitigation-pow'); ?></strong>
|
|
1002
|
+
</label><br><br>
|
|
1003
|
+
<label>
|
|
1004
|
+
<input type="checkbox" name="sandbox_add_headers" value="1" <?php checked($sandboxConfig['add_headers']); ?>>
|
|
1005
|
+
<?php esc_html_e('Add diagnostic HTTP headers (X-Fingerprint-Sandbox: active)', 'anonympins-bot-mitigation-pow'); ?>
|
|
1006
|
+
</label><br><br>
|
|
1007
|
+
<label>
|
|
1008
|
+
<input type="checkbox" name="sandbox_log_requests" value="1" <?php checked($sandboxConfig['log_requests']); ?>>
|
|
1009
|
+
<?php esc_html_e('Log evaluated scores and suspicious attempts to PHP error_log', 'anonympins-bot-mitigation-pow'); ?>
|
|
1010
|
+
</label>
|
|
1011
|
+
</td>
|
|
1012
|
+
</tr>
|
|
1013
|
+
<tr>
|
|
1014
|
+
<th scope="row"><label for="sandbox_ip_filter"><?php esc_html_e('Test IP Whitelist Filter', 'anonympins-bot-mitigation-pow'); ?></label></th>
|
|
1015
|
+
<td>
|
|
1016
|
+
<?php
|
|
1017
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
1018
|
+
$currentAdminIp = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
|
|
1019
|
+
?>
|
|
1020
|
+
<input type="text" name="sandbox_ip_filter" id="sandbox_ip_filter" value="<?php echo esc_attr($sandboxConfig['ip_filter']); ?>" class="regular-text" placeholder="e.g. 192.168.1.100, 203.0.113.42">
|
|
1021
|
+
<?php if (!empty($currentAdminIp)): ?>
|
|
1022
|
+
<button type="button" class="button button-secondary button-small" onclick="document.getElementById('sandbox_ip_filter').value = '<?php echo esc_js($currentAdminIp); ?>';">
|
|
1023
|
+
<?php esc_html_e('Use my IP', 'anonympins-bot-mitigation-pow'); ?> (<?php echo esc_html($currentAdminIp); ?>)
|
|
1024
|
+
</button>
|
|
1025
|
+
<?php endif; ?>
|
|
1026
|
+
</td>
|
|
1027
|
+
</tr>
|
|
1028
|
+
</table>
|
|
1029
|
+
<?php submit_button(esc_html__('Save Sandbox Settings', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_sandbox'); ?>
|
|
1030
|
+
</form>
|
|
1031
|
+
<hr>
|
|
1032
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
|
|
1033
|
+
<h3 style="margin:0;"><span class="dashicons dashicons-shield" style="vertical-align:text-bottom;"></span> <?php esc_html_e('Live Challenged Visitors & Suspicion Monitor', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
1034
|
+
<div>
|
|
1035
|
+
<span style="font-weight:600;color:#646970;"><?php esc_html_e('Recent Challenges:', 'anonympins-bot-mitigation-pow'); ?></span>
|
|
1036
|
+
<span id="live-challenges-count" style="display:inline-block;padding:2px 8px;background:#2271b1;color:#fff;border-radius:10px;font-weight:bold;font-size:12px;">0</span>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div style="background:#f6f7f7;padding:16px;border:1px solid #c3c4c7;border-radius:4px;margin-bottom:15px;">
|
|
1040
|
+
<div style="display:flex;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap;">
|
|
1041
|
+
<button type="button" class="button button-primary" id="btn-toggle-sse" onclick="fingerprintToggleSSE();">
|
|
1042
|
+
<span class="dashicons dashicons-controls-play" style="vertical-align:middle;"></span> <span id="sse-btn-text"><?php esc_html_e('Start Real-Time SSE Stream', 'anonympins-bot-mitigation-pow'); ?></span>
|
|
1043
|
+
</button>
|
|
1044
|
+
<button type="button" class="button button-secondary" onclick="fingerprintFetchTelemetry();">
|
|
1045
|
+
<span class="dashicons dashicons-update" style="vertical-align:middle;"></span> <?php esc_html_e('Poll Now (REST)', 'anonympins-bot-mitigation-pow'); ?>
|
|
1046
|
+
</button>
|
|
1047
|
+
<button type="button" class="button button-secondary" onclick="fingerprintClearFeed();">
|
|
1048
|
+
<span class="dashicons dashicons-trash" style="vertical-align:middle;"></span> <?php esc_html_e('Clear Feed', 'anonympins-bot-mitigation-pow'); ?>
|
|
1049
|
+
</button>
|
|
1050
|
+
<span id="sse-connection-status" style="font-weight:bold;color:#646970;"></span>
|
|
1051
|
+
</div>
|
|
1052
|
+
<div style="display:grid;grid-template-columns: 1.4fr 1fr;gap:16px;">
|
|
1053
|
+
<div style="background:#fff;padding:12px;border:1px solid #dcdcde;border-radius:4px;overflow-x:auto;">
|
|
1054
|
+
<h4 style="margin:0 0 8px 0;"><?php esc_html_e('Challenged Visitors Stream', 'anonympins-bot-mitigation-pow'); ?></h4>
|
|
1055
|
+
<table class="wp-list-table widefat fixed striped" style="font-size:12px;">
|
|
1056
|
+
<thead>
|
|
1057
|
+
<tr>
|
|
1058
|
+
<th style="width:75px;"><?php esc_html_e('Time', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1059
|
+
<th style="width:110px;"><?php esc_html_e('IP', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1060
|
+
<th><?php esc_html_e('URI', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1061
|
+
<th style="width:50px;"><?php esc_html_e('Score', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1062
|
+
<th style="width:90px;"><?php esc_html_e('Action', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1063
|
+
<th style="width:70px;"></th>
|
|
1064
|
+
</tr>
|
|
1065
|
+
</thead>
|
|
1066
|
+
<tbody id="live-challenges-tbody">
|
|
1067
|
+
<tr><td colspan="6" style="text-align:center;color:#646970;padding:16px;"><em><?php esc_html_e('Awaiting incoming challenges...', 'anonympins-bot-mitigation-pow'); ?></em></td></tr>
|
|
1068
|
+
</tbody>
|
|
1069
|
+
</table>
|
|
1070
|
+
</div>
|
|
1071
|
+
<div style="background:#fff;padding:14px;border:1px solid #dcdcde;border-radius:4px;">
|
|
1072
|
+
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px;">
|
|
1073
|
+
<h4 style="margin:0;"><?php esc_html_e('Inspection & Suspicion Vector', 'anonympins-bot-mitigation-pow'); ?></h4>
|
|
1074
|
+
<div id="live-score-val" style="font-size:28px;font-weight:bold;color:#2271b1;line-height:1;">--</div>
|
|
1075
|
+
</div>
|
|
1076
|
+
<div id="live-score-sub" style="font-size:12px;color:#646970;background:#f6f7f7;padding:8px;border-radius:3px;margin-bottom:10px;">
|
|
1077
|
+
<?php esc_html_e('Select an entry from the list to inspect its vector.', 'anonympins-bot-mitigation-pow'); ?>
|
|
1078
|
+
</div>
|
|
1079
|
+
<div id="live-vector-breakdown" style="font-family:monospace;font-size:12px;max-height:220px;overflow-y:auto;color:#2c3338;">
|
|
1080
|
+
<em><?php esc_html_e('No entry selected.', 'anonympins-bot-mitigation-pow'); ?></em>
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
|
|
1087
|
+
<div id="tab-metrics" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
1088
|
+
<h3><?php esc_html_e('Behavioral & Transport Indicator Weights', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
1089
|
+
<form method="post" action="">
|
|
1090
|
+
<?php wp_nonce_field('fingerprint_settings_nonce', 'fingerprint_nonce'); ?>
|
|
1091
|
+
<table class="wp-list-table widefat fixed striped">
|
|
1092
|
+
<thead>
|
|
1093
|
+
<tr>
|
|
1094
|
+
<th style="width:280px;"><?php esc_html_e('Indicator / Metric', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1095
|
+
<th style="width:120px;"><?php esc_html_e('Current Weight', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1096
|
+
<th><?php esc_html_e('Description & Detection Role', 'anonympins-bot-mitigation-pow'); ?></th>
|
|
1097
|
+
</tr>
|
|
1098
|
+
</thead>
|
|
1099
|
+
<tbody>
|
|
1100
|
+
<?php foreach ($frontendConfig['weights'] as $indicator => $weight): ?>
|
|
1101
|
+
<tr>
|
|
1102
|
+
<td><code><?php echo esc_html($indicator); ?></code></td>
|
|
1103
|
+
<td><input type="number" step="0.05" min="0" max="2.0" name="weight_<?php echo esc_attr($indicator); ?>" value="<?php echo esc_attr((string)$weight); ?>" style="width:75px;"></td>
|
|
1104
|
+
<td><?php esc_html_e('Behavioral suspicion indicator.', 'anonympins-bot-mitigation-pow'); ?></td>
|
|
1105
|
+
</tr>
|
|
1106
|
+
<?php endforeach; ?>
|
|
1107
|
+
</tbody>
|
|
1108
|
+
</table>
|
|
1109
|
+
<p style="margin-top:16px;">
|
|
1110
|
+
<?php submit_button(esc_html__('Update Metric Weights', 'anonympins-bot-mitigation-pow'), 'primary', 'fingerprint_save_settings', false); ?>
|
|
1111
|
+
</p>
|
|
1112
|
+
</form>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
<div id="tab-prometheus" class="fingerprint-tab-content" style="display:none;background:#fff;padding:20px;border:1px solid #ccd0d4;border-top:none;">
|
|
1116
|
+
<h3><?php esc_html_e('Real-Time Prometheus Metrics', 'anonympins-bot-mitigation-pow'); ?></h3>
|
|
1117
|
+
<textarea readonly style="width:100%;height:380px;font-family:monospace;background:#f6f7f7;padding:12px;"><?php echo esc_textarea($prometheusRaw); ?></textarea>
|
|
1118
|
+
</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
<?php
|
|
1121
|
+
}
|