@anonympins/fingerprint 0.3.2 → 0.3.4

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +1080 -834
  3. package/composer.json +39 -0
  4. package/index.js +5 -0
  5. package/package.json +31 -23
  6. package/phpunit.xml +21 -0
  7. package/public/fp.js +2 -0
  8. package/src/js/build-client.js +69 -0
  9. package/{fingerprint.builder.js → src/js/fingerprint.builder.js} +168 -175
  10. package/{fingerprint.client.js → src/js/fingerprint.client.js} +634 -538
  11. package/src/js/fingerprint.client.obfuscated.js +1 -0
  12. package/{fingerprint.js → src/js/fingerprint.js} +3733 -3294
  13. package/{library.js → src/js/library.js} +1729 -1729
  14. package/{problem-manager.js → src/js/problem-manager.js} +539 -522
  15. package/src/php/AutoTuner.php +155 -0
  16. package/src/php/Challenge/ChallengeUtils.php +306 -0
  17. package/src/php/Config/SecurityProfiles.php +267 -0
  18. package/src/php/DirectFingerprint.php +81 -0
  19. package/src/php/FingerprintBuilder.php +186 -0
  20. package/src/php/FingerprintClient.php +132 -0
  21. package/src/php/FingerprintEngine.php +863 -0
  22. package/src/php/Optimization/FunctionRegistry.php +63 -0
  23. package/src/php/Optimization/Optimization.php +256 -0
  24. package/src/php/Optimization/OptimizationOperators.php +305 -0
  25. package/src/php/Optimization/ProblemInitializers.php +53 -0
  26. package/src/php/ProblemManager.php +255 -0
  27. package/src/php/RequestContext.php +91 -0
  28. package/src/php/Store/IStore.php +42 -0
  29. package/src/php/Store/InMemoryStore.php +67 -0
  30. package/src/php/Store/StoreManager.php +36 -0
  31. package/src/php/Tests/ChallengeUtilsTest.php +82 -0
  32. package/src/php/Tests/FingerprintBuilderTest.php +58 -0
  33. package/src/php/Tests/FingerprintEngineTest.php +300 -0
  34. package/src/php/Tests/IpReputationTest.php +157 -0
  35. package/src/php/Tests/PowTest.php +40 -0
  36. package/src/php/Tests/ProblemManagerTest.php +295 -0
  37. package/src/php/Tests/RequestUtilsTest.php +81 -0
  38. package/src/php/Tests/problems.config.json +9 -0
  39. package/src/php/Utils/BigInt.php +145 -0
  40. package/src/php/Utils/BlockList.php +100 -0
  41. package/src/php/Utils/Logger.php +30 -0
  42. package/src/php/Utils/MaliciousPatterns.php +59 -0
  43. package/src/php/Utils/RequestUtils.php +962 -0
  44. package/fingerprint.client.obfuscated.js +0 -1
  45. /package/{mongodb-store.js → src/js/mongodb-store.js} +0 -0
  46. /package/{optimization.worker.js → src/js/optimization.worker.js} +0 -0
  47. /package/{pow.solver.inline.js → src/js/pow.solver.inline.js} +0 -0
  48. /package/{pow.solver.js → src/js/pow.solver.js} +0 -0
  49. /package/{pow.worker.js → src/js/pow.worker.js} +0 -0
  50. /package/{redis-store.js → src/js/redis-store.js} +0 -0
  51. /package/{sql-store.js → src/js/sql-store.js} +0 -0
package/README.md CHANGED
@@ -1,835 +1,1081 @@
1
- # fingerprint
2
- [![CI](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
3
- [![Release](https://img.shields.io/github/v/release/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/releases)
4
- [![License](https://img.shields.io/github/license/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
5
- ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/anonympins/fingerprint)
6
- [![Watchers](https://img.shields.io/github/watchers/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/watchers)
7
-
8
- An HTTP(S) client mitigation and anti-bot protection library for both PHP and Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.
9
-
10
- ## How It Works
11
-
12
- This system identifies and slows down bots and automated scripts by evaluating the "suspicion" level of each incoming request. Instead of outright blocking, it imposes challenges with a difficulty proportional to the suspicion score, penalizing bots without significantly impacting legitimate users.
13
-
14
- The process unfolds in three steps:
15
-
16
- 1. **Identification & Fingerprinting**: A unique fingerprint is generated for each device. This combines a client-side browser fingerprint, server-side request headers, and the **JA3 fingerprint** from the TLS handshake, which reliably identifies the underlying HTTP client library (e.g., Chrome vs. a Python script). A `device_id` cookie is used to track the device over time.
17
- * **Advanced TLS Fingerprinting (JA4/JA4H)**: Beyond JA3, the system can leverage JA4/JA4H (if provided by a reverse proxy like Cloudflare or Akamai) for a more robust and modern TLS fingerprint, especially for HTTP/2 traffic.
18
- * **HTTP/2 Fingerprinting**: Analyzes HTTP/2 specific characteristics (settings frame, priority, window update) to identify client libraries.
19
- * **TCP/IP Fingerprinting**: If available (e.g., from a specialized reverse proxy), low-level TCP/IP stack characteristics (TTL, window size, options) are used for identification.
20
- * A `device_id` cookie is used to track the device over time.
21
- 2. **Suspicion Score Calculation**: Several indicators are analyzed to calculate a suspicion score:
22
- * **Header Anomalies**: Missing `User-Agent`, `Accept-Language`, etc.
23
- * **Device Behavior**: Rapid fingerprint changes (User-Agent rotation).
24
- * **IP Behavior**: An excessive number of different devices seen from the same IP, or a single device using a large number of IPs (proxy rotation).
25
- * **Inconsistency**: A low similarity score between the current fingerprint and the one initially associated with the `device_id` (cookie theft detection).
26
- * **Cross-Layer Inconsistency**: Mismatches between client-side data (e.g., OS reported by the browser) and server-side headers (e.g., `User-Agent`).
27
- * **Request Patterns**: Repetitive, rapid-fire, or sequential requests typical of scraping bots. The parameters for detecting these patterns (e.g., request velocity, burst detection) are dynamically adjusted by the auto-tuner for optimal performance.
28
- * **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
29
- * **TLS Fingerprint Spoofing**: Detects inconsistencies between the TLS fingerprint (JA3/JA4) and other HTTP headers (e.g., User-Agent), indicating an attempt to disguise the client.
30
- 3. **Dynamic Challenge**: If the suspicion score exceeds a certain threshold, a challenge is presented. The difficulty and type of challenge depend on the score:
31
- * **Low to Medium Suspicion**: A combined **CPU and Memory Proof-of-Work (PoW)** challenge is issued. The difficulty of both the CPU (hash calculation) and Memory (allocation and computation) components scales progressively with the suspicion score. For low scores, the memory challenge is negligible, making it primarily a CPU task.
32
- * **High Suspicion**: For the most suspicious requests, the system issues a high-difficulty combined CPU/Memory challenge or a "Useful Proof-of-Work" task (see below). The architecture allows for plugging in more complex challenges like CAPTCHAs if needed.
33
- * **New Devices**: To increase the cost for bots that simply clear their cookies, new (unseen) devices are systematically presented with a minimal, almost imperceptible challenge on their first visit. This behavior is enabled by default in `strict` and `ecommerce` profiles and can be configured with the `challengeNewDevices` option.
34
-
35
- Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges. The duration of this ticket is dynamic:
36
- - **Probationary Ticket**: If the request was moderately suspicious, a very short-lived "probationary" ticket (e.g., 30 seconds) is issued. This forces the client to be re-evaluated quickly, increasing security.
37
- - **Optimal TTL Ticket**: For less suspicious requests, a genetic algorithm calculates the optimal ticket duration, balancing security (shorter TTL for higher risk) and user experience (longer TTL for lower risk).
38
-
39
- For API clients, the challenge is delivered as a `404` JSON response, and the client library can automatically solve it and retry the original request.
40
-
41
- ## Features
42
-
43
- - **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
44
- - **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
45
- - **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
46
- - **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`.
47
- - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
48
- - **Bot Whitelisting**: Includes a DNS-based verification mechanism to reliably identify and whitelist legitimate crawlers like Googlebot and Bingbot, preventing them from being challenged. The results are cached for optimal performance.
49
- - **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust suspicion thresholds, weights, and behavioral pattern detection parameters, improving accuracy and reducing false positives over time. The tuner is hardened against data poisoning attempts.
50
- - **Optional WASM Acceleration**: The client-side library can be accelerated with a WebAssembly module for high-performance hashing. The build process handles this optionally, and the client gracefully falls back to a pure JavaScript implementation if WASM is unavailable.
51
- - **Hardened Security**: Protects against various attacks, including DoS via memory exhaustion, invalid nonce submission, and uses cryptographically secure randomness for all sensitive operations.
52
-
53
- ## Installation and Usage
54
-
55
- This library is available for both **Node.js** and **PHP**.
56
-
57
- * [PHP Quickstart](#php-quickstart)
58
- * [Node.js Quickstart](#nodejs-quickstart)
59
-
60
- ### Prerequisites
61
-
62
- * **PHP 7.4+**
63
- * The **GMP** extension (`php-gmp`) is required for handling the large-integer arithmetic used in cryptographic challenges.
64
- * **Composer** for package management.
65
-
66
- ---
67
-
68
- <a id="php-quickstart"></a>
69
-
70
- ## PHP Quickstart (Direct Integration)
71
-
72
- This guide shows the simplest way to integrate the library into any PHP application, without requiring a framework. It interacts directly with PHP's native functions and superglobals.
73
-
74
- ### Prerequisites
75
-
76
- * **PHP 7.4+**
77
- * The **GMP** extension (`php-gmp`) is required for handling the large-integer arithmetic used in cryptographic challenges.
78
- * **Composer** for package management.
79
-
80
- ### Installation
81
-
82
- Install the main library via Composer:
83
-
84
- ```bash
85
- composer require anonympins/fingerprint
86
- ```
87
-
88
- ### Configuration
89
-
90
- Define a secret key for signing Proof-of-Work tickets in your environment variables or your `.env` file. This is **required** for production environments.
91
-
92
- ```bash
93
- POW_SECRET="your_secret_key_of_at_least_32_characters"
94
- ```
95
-
96
- ### Integration Example
97
-
98
- This example shows how to protect an application's entry point (e.g., `index.php`) by calling the `protect()` method at the very beginning of your script.
99
-
100
- ```php
101
- <?php
102
-
103
- declare(strict_types=1);
104
-
105
- require_once __DIR__ . '/vendor/autoload.php';
106
-
107
- use Anonympins\Fingerprint\Config\SecurityProfiles;
108
- use Anonympins\Fingerprint\DirectFingerprint;
109
-
110
- // 1. Choose a security profile and customize it if necessary.
111
- $securityConfig = SecurityProfiles::createSecurityProfile('balanced', [
112
- // Enable verbose mode for development
113
- 'verbose' => true,
114
- ]);
115
-
116
- // IMPORTANT: For PHP environments, TLS fingerprinting (JA3/JA4) requires a reverse proxy
117
- // (like Nginx, HAProxy, or a cloud load balancer) to inspect the TLS handshake and
118
- // pass the fingerprint hashes to the PHP application via HTTP headers
119
- // (e.g., `X-JA3-Hash`, `X-JA4-Hash`).
120
-
121
- // 2. Create an instance of the DirectFingerprint protector.
122
- $protector = new DirectFingerprint($securityConfig);
123
-
124
- // 3. Protect the script.
125
- // This method will analyze the request. If it's suspicious, it will
126
- // send a challenge or block response and then call `exit()`.
127
- // If the request is allowed, it returns the fingerprint data.
128
- $fingerprint = $protector->protect();
129
-
130
- // --- If the script continues, the request was allowed ---
131
-
132
- $score = $fingerprint['score'] ?? 0;
133
-
134
- header('Content-Type: text/html; charset=utf-8');
135
- echo "<h1>Welcome to the protected page!</h1>";
136
- echo "<p>Your suspicion score was: " . round($score, 2) . "</p>";
137
-
138
- ?>
139
- ```
140
-
141
- <a id="nodejs-quickstart"></a>
142
- ## NodeJS Configuration
143
-
144
- ### Prerequisites for Node.js
145
-
146
- Ensure you have middleware for parsing cookies (like `cookie-parser`) and request bodies (like `express.json` and `express.urlencoded`) set up in your Express application *before* the `powMiddleware`.
147
-
148
- ### Configuration
149
- Define a secret key for signing PoW tickets in your environment variables.
150
-
151
- ```bash
152
- export POW_SECRET="your_secret_key_of_at_least_32_characters"
153
- ```
154
-
155
- ### Integration Example
156
-
157
- To simplify setup, `fingerprint` provides pre-configured security profiles for common use cases. You can use the `createSecurityProfile` helper to load a profile and optionally extend it with your own settings.
158
-
159
- Available profiles:
160
- - `balanced` (default): A general-purpose configuration suitable for most websites.
161
- - `strict`: A more aggressive configuration for sensitive applications.
162
- - `api`: Optimized for protecting API endpoints, with a higher sensitivity to request patterns.
163
- - `blog`: Tuned to detect content scraping and comment spam.
164
- - `ecommerce`: A strict profile focused on preventing inventory scalping, price scraping, and account takeover.
165
-
166
- ```javascript
167
- import express from 'express';
168
- import bodyParser from 'body-parser';
169
- import cookieParser from 'cookie-parser';
170
- import { powMiddleware, createSecurityProfile } from './fingerprint.js'; // Adjust the path
171
-
172
- const app = express();
173
- app.use(cookieParser());
174
- app.use(bodyParser.json());
175
- app.use(bodyParser.urlencoded({ extended: true }));
176
-
177
- // Array to store traffic analysis data for the auto-tuner.
178
- const trafficData = [];
179
-
180
- // 1. Choose a base profile (e.g., 'balanced', 'strict', 'api').
181
- // 2. (Optional) Define your custom overrides. These will be deeply merged with the base profile.
182
- const securityConfig = createSecurityProfile('api', {
183
- // Example of overriding a specific threshold from the 'balanced' profile.
184
- thresholds: {
185
- low: 25, // Make the initial challenge slightly harder.
186
- },
187
- // Example of adding a custom whitelisting rule.
188
- whitelist: [
189
- { type: 'path_allowlist', entries: ['/api/v1/public-stats'] }
190
- ],
191
- // The logger is required if you enable auto-tuning.
192
- logger: (log) => trafficData.push(log),
193
- autotuning: {
194
- trafficData: trafficData,
195
- interval: 1800000, // 30 minutes
196
- minDataPoints: 200,
197
- }
198
- });
199
-
200
- // Create an instance of the middleware with your security configuration.
201
- const powMiddlewareInstance = powMiddleware(securityConfig);
202
-
203
- // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
204
- // to correctly retrieve the client's IP.
205
- app.set('trust proxy', 1);
206
-
207
- // Apply the protection middleware to all routes or to specific ones.
208
- app.use(powMiddlewareInstance);
209
-
210
- app.get('/', (req, res) => {
211
- res.send('Welcome to the protected page!');
212
- });
213
-
214
- // Example of accessing the suspicion score in a subsequent middleware or route.
215
- // The `fingerprint` object is attached to the request object by the middleware.
216
- app.use((req, res, next) => {
217
- console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
218
- next();
219
- });
220
-
221
- app.listen(3000, () => console.log('Server started on port 3000'));
222
- ```
223
-
224
- ### Full Configuration Example
225
-
226
- If you prefer to define the entire configuration manually instead of using a profile, you can create a `securityConfig` object with all the parameters. All parameters are optional, but it is highly recommended to review and adjust them for your specific needs. The engine will warn you about any unknown keys in this configuration, helping you catch typos.
227
-
228
- ```javascript
229
- import { default_whitelist, default_analyzers } from './fingerprint.js';
230
-
231
- const app = express();
232
- app.use(cookieParser());
233
- app.use(bodyParser.json()); // For parsing application/json
234
- app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
235
-
236
- // Array to store traffic analysis data for the auto-tuner.
237
- // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
238
- const trafficData = [];
239
-
240
- // Configuration of weights and thresholds for calculating the suspicion score.
241
- // These values should be adjusted based on traffic and expected user behavior.
242
- const securityConfig = {
243
- weights: {
244
- historyScore: 0.3, // Penalizes IP rotation (proxy)
245
- rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
246
- headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
247
- requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
248
- inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
249
- behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
250
- honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
251
- crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
252
- timeInconsistencyScore: 0.9, // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
253
- tlsSpoofingScore: 0.8 // Penalizes mismatches between the TLS fingerprint (JA3/JA4) and the User-Agent (client spoofing)
254
- },
255
- thresholds: {
256
- low: 20, // Score from which a CPU challenge is issued
257
- medium: 45, // Score for a more difficult combined CPU/Memory challenge
258
- high: 75, // Score for a very difficult challenge
259
- block: 95, // Score above which the request is blocked outright (HTTP 404)
260
- },
261
- cpu: {
262
- minDifficultyBits: 8,
263
- maxDifficultyBits: 32,
264
- },
265
- // (Optional) Configure the duration (in milliseconds) for various temporary data.
266
- ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
267
- challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
268
- deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
269
- challengePagePath: './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
270
- verbose: process.env.NODE_ENV !== 'production', // Log detailed info in development, but not in production.
271
- patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
272
- velocityThreshold: 800, // ms between requests to be considered "fast"
273
- burstThreshold: 1500, // ms for identical requests to be a "burst"
274
- scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
275
- historySize: 10, // Number of requests to keep for pattern analysis
276
- minSamples: 5, // Minimum number of timings to collect before statistical analysis.
277
- regularityThreshold: 50, // Standard deviation (ms) below which behavior is "too regular".
278
- benfordThreshold: 0.15, // Benford's Law deviation threshold above which the distribution is "unnatural".
279
- patternWeight: 80, // Strong, one-time penalty when a pattern is detected.
280
- decayFactor: 0.9, // Factor by which the pattern score decreases over time.
281
- inactivityReset: 5000, // Time (ms) after which the pattern score is reset.
282
- },
283
- honeypot: {
284
- // List of field names that are traps for bots.
285
- // These should be hidden in forms for humans, or be URL parameters your app never uses.
286
- fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
287
- // List of URL paths that should never be accessed by a legitimate user.
288
- // A request to one of these paths will immediately flag the device as malicious.
289
- trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
290
- // Automatically detect common injection patterns. Can be a boolean or an array of specific types.
291
- // - `true`: Enables all available detections (default).
292
- // - `false`: Disables injection detection.
293
- // - `['sql', 'rce']`: Enables only SQL injection and Remote Command Execution detection.
294
- detectInjections: ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
295
- // (Optional) Plug in external analyzers. This allows you to extend detection with specialized libraries or custom logic.
296
- // Each function receives an object with all query and body data and should return `true` if a threat is detected.
297
- analyzers: [
298
- ...default_analyzers(), // Includes the default XSS analyzer.
299
-
300
- // Example 2: Enable a powerful WAF with ModSecurity and the OWASP Core Rule Set.
301
- // Requires `npm install modsecurity-nodejs` and downloading the OWASP CRS rules.
302
- // modsecurity_analyzer('/path/to/owasp-crs/crs-setup.conf'),
303
-
304
- // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
305
- (data) => {
306
- const spamKeywords = ['viagra', 'free money', 'crypto pump'];
307
- const dataString = JSON.stringify(data).toLowerCase();
308
- return spamKeywords.some(keyword => dataString.includes(keyword));
309
- }
310
- ]
311
- },
312
- // (Optional) Whitelisting configuration.
313
- whitelist: [
314
- // Option 1: Static IP Allowlist.
315
- // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
316
- // Useful for internal tools, trusted partners, or monitoring services.
317
- // This check is performed first for maximum efficiency.
318
- { type: 'allowlist', entries: [
319
- '192.168.1.100', // A specific internal IP
320
- '203.0.113.0/24', // A partner's network range
321
- '2001:db8::/32' // An IPv6 range
322
- ]},
323
- { type: 'hostname_allowlist', entries: [
324
- 'google.com', // A specific hostname
325
- ]},
326
- // Option 3: Host + Path Allowlist.
327
- // Bypasses checks for specific URL paths on specific hostnames. This is ideal for whitelisting
328
- // an API endpoint on one domain but not another. The entry is a combination of the host
329
- // header and the path. Supports wildcards (*) at the end of the path.
330
- { type: 'host_path_allowlist', entries: [
331
- 'web.primals.net/api/*', // All paths starting with /api2 on web.primal.net
332
- ]},
333
- // Option 3: Path Allowlist.
334
- // Bypasses checks for specific URL paths. This is useful for trusted API endpoints, webhooks, or static content paths
335
- // that don't need protection. Supports wildcards (*) at the end of an entry.
336
- { type: 'path_allowlist', entries: [
337
- '/api/v1/webhooks/trusted-source', // Exact path
338
- '/api/v2/public/*', // All paths starting with /api/v2/public/
339
- ]},
340
-
341
- // Allows a specific GraphQL query and all mutations•
342
- {type: 'graphql_operation_allowlist',entries: [
343
- 'query:GetPublicPosts',
344
- 'mutation:*']},
345
- // Option 2: DNS-verified bots (e.g., search engine crawlers).
346
- // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
347
- // The result is cached per IP to avoid repeated DNS lookups.
348
- // You can use the provided default list, which contains over 50 common bots, and extend it.
349
- ...default_whitelist(), // Use the defaults
350
- { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
351
- ],
352
- // Optional: Custom function to identify static resources
353
- isStaticResource: (req) => req.path.startsWith('/static/'),
354
- // Optional: Custom function to identify API requests
355
- isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json'),
356
- // The logger is required for auto-tuning. It collects data on requests.
357
- logger: (log) => trafficData.push(log),
358
- // (Optional) Configuration for the automatic threshold and pattern tuning.
359
- autotuning: {
360
- trafficData: trafficData, // The data source for the genetic algorithm.
361
- interval: 1800000, // Optimization cycle every 30 minutes (in ms).
362
- minDataPoints: 200, // Minimum requests before starting an optimization cycle.
363
- maxDataPoints: 20000 // Minimum requests before starting an optimization cycle.
364
- },
365
- // Enables problem solving for suspicious activity (configurable in problems.config.json)
366
- enableUsefulWork: true,
367
- usefulWorkConfigPath: './path/to/your/problems.config.json' // (Optional) Path to the useful work configuration.
368
- };
369
-
370
-
371
- // Create an instance of the middleware with your security configuration.
372
- const powMiddlewareInstance = powMiddleware(securityConfig);
373
- ```
374
-
375
-
376
- ---
377
-
378
- ## Advanced Behavioral Analysis
379
-
380
- The FingerprintEngine includes sophisticated behavioral analysis to detect non-human patterns. This analysis is performed by the `getRequestPatternScore` function, which is a stateful check that looks for repetitive or unnaturally fast requests from a single device.
381
-
382
- This function uses several configurable parameters to identify suspicious behavior:
383
-
384
- ### Core Pattern Detection
385
-
386
- These parameters form the basis of the request pattern analysis:
387
-
388
- * `velocityThreshold`: (Default: 800ms) Penalizes requests that are too fast to be humanly possible. If the time since the last request from a device is less than this value, the suspicion score increases.
389
- * `burstThreshold`: (Default: 1500ms) Adds a significant penalty for multiple identical requests (same path and query parameters) occurring in a very short time frame. This is a strong indicator of automated retries or brute-force attacks.
390
- * `scrapeThreshold`: (Default: 1000ms) Penalizes sequential requests to the same path but with different query parameters. This pattern is typical of scraping bots that iterate through pages or product IDs.
391
- * `sequenceLength`: (Default: 3) Detects repetitive sequences of requests (e.g., A -> B -> C -> A -> B -> C), which is a common pattern for scripted bots navigating a site.
392
-
393
- ### Statistical Analysis (Benford's Law)
394
-
395
- To counter more advanced bots that might try to randomize their request timings, the engine employs statistical analysis based on Benford's Law.
396
-
397
- * **How it works**: Benford's Law states that in many naturally occurring sets of numbers, the leading digit is more likely to be small. For example, the number 1 appears as the leading digit about 30% of the time, while 9 appears less than 5% of the time. The timings between a human's requests tend to follow this natural distribution, whereas a bot's randomized delays often do not.
398
-
399
- * `benfordMinSamples`: (Default: 15) The minimum number of request timings to collect before performing a Benford's Law test.
400
- * `benfordWeight`: (Default: 50) The weight applied to the suspicion score if the distribution of timings significantly deviates from Benford's Law.
401
-
402
- ### Configuration and Auto-Tuning
403
-
404
- All these parameters are part of the `patterns` object within the main security configuration and can be fine-tuned.
405
-
406
- ## Customizing the Challenge Page
407
-
408
- You can provide your own HTML template for the Proof-of-Work challenge page to maintain a consistent user experience with your brand.
409
-
410
- 1. **Configuration**: In your `securityConfig`, specify the path to your template file using the `challengePagePath` option.
411
-
412
- ```javascript
413
- const securityConfig = {
414
- // ... other options
415
- challengePagePath: './path/to/your/custom-challenge-page.html',
416
- };
417
- ```
418
-
419
- 2. **Template Placeholders**: Your HTML file **must** contain the following placeholders. The system will replace them with the dynamic JavaScript code required to run the challenge.
420
-
421
- * `<!-- FINGERPRINT_SOLVER_SCRIPT -->`: This will be replaced by the script that contains the logic for solving the CPU and memory challenges.
422
- * `<!-- FINGERPRINT_CHALLENGE_SCRIPT -->`: This will be replaced by the script that initiates the challenge with the specific parameters for the current request (nonce, difficulty, etc.).
423
- * `<!-- FINGERPRINT_TRAPS -->`: This will be replaced by hidden "honeypot" links designed to trap simple bots. This placeholder is crucial for an effective defense.
424
-
425
- #### Example Custom HTML Template
426
-
427
- Here is a basic example of what your `custom-challenge-page.html` could look like:
428
-
429
- ```html
430
- <!DOCTYPE html>
431
- <html lang="en">
432
- <head>
433
- <meta charset="UTF-8">
434
- <title>Security Verification</title>
435
- <style>
436
- body { font-family: sans-serif; text-align: center; padding-top: 50px; }
437
- h1 { color: #333; }
438
- </style>
439
- </head>
440
- <body>
441
- <h1>Please wait while we verify your connection...</h1>
442
- <div id="loader" style="margin:20px;">⚙️ Initializing verification...</div>
443
-
444
- <script><!-- FINGERPRINT_SOLVER_SCRIPT --></script>
445
- <script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script>
446
- <!-- FINGERPRINT_TRAPS -->
447
- </body>
448
- </html>
449
- ```
450
- ## Public API
451
-
452
- In addition to the main middleware, several functions are exported to allow for more advanced integrations.
453
-
454
- ### Main Functions
455
-
456
- #### `powMiddleware(securityConfig)`
457
- The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
458
-
459
- #### `configureStore(store)`
460
- Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
461
- The library provides ready-to-use adapters for popular datastores like **Redis**, **MongoDB**, and any **SQL database** supported by Knex.js. These adapters automatically handle the Time-To-Live (TTL) required for temporary data like challenge secrets.
462
-
463
- **Redis Example:**
464
-
465
- ```javascript
466
- import { configureStore } from './fingerprint.js';
467
- import { createRedisStore } from './redis-store.js';
468
- import Redis from 'ioredis';
469
-
470
- const redisClient = new Redis(process.env.REDIS_URL);
471
- const redisStore = createRedisStore(redisClient);
472
- configureStore(redisStore);
473
- ```
474
-
475
- **MongoDB Example:**
476
-
477
- ```javascript
478
- import { configureStore } from './fingerprint.js';
479
- import { createMongoDbStore } from './mongodb-store.js';
480
- import { MongoClient } from 'mongodb';
481
-
482
- const mongoClient = new MongoClient(process.env.MONGODB_URL);
483
-
484
- // It's recommended to connect before your application starts listening.
485
- await mongoClient.connect();
486
-
487
- const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
488
- configureStore(mongoStore);
489
-
490
- // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
491
- // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
492
- // Run this command in the mongo shell:
493
- // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
494
- ```
495
-
496
- **SQL Example (with Knex.js):**
497
-
498
- ```javascript
499
- import { configureStore } from './fingerprint.js';
500
- import { createSqlStore } from './sql-store.js';
501
- import knex from 'knex';
502
-
503
- const knexClient = knex({
504
- client: 'pg', // or 'mysql', 'sqlite3', etc.
505
- connection: process.env.DATABASE_URL,
506
- });
507
-
508
- const sqlStore = createSqlStore(knexClient, 'fingerprint_sessions'); // 'fingerprint_sessions' is the table name
509
- configureStore(sqlStore);
510
-
511
- // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
512
- // your table must have an `expiresAt` column. The store will handle cleanup of expired rows,
513
- // but you must create the table yourself.
514
- // Example schema for PostgreSQL:
515
- // CREATE TABLE fingerprint_sessions (
516
- // "key" VARCHAR(255) PRIMARY KEY,
517
- // "value" TEXT NOT NULL,
518
- // "expiresAt" TIMESTAMPTZ
519
- // );
520
- ```
521
-
522
- #### `identifyRequest(req, res)`
523
- An asynchronous function that returns an identification string for a given request, based on its suspicion level (`device:<id>`, `suspicious_medium:<ip>`, etc.). Useful for integration with a custom rate-limiter.
524
-
525
- ```javascript
526
- import { RateLimiterMemory } from 'rate-limiter-flexible';
527
- import { identifyRequest } from './fingerprint.js';
528
-
529
- const rateLimiter = new RateLimiterMemory({
530
- keyPrefix: 'rate_limit',
531
- points: 10,
532
- duration: 1,
533
- });
534
-
535
- app.use(async (req, res, next) => {
536
- try {
537
- const key = await identifyRequest(req, res);
538
- await rateLimiter.consume(key);
539
- next();
540
- } catch (err) {
541
- res.status(429).send('Too Many Requests');
542
- }
543
- });
544
- ```
545
-
546
- ### Utilities
547
-
548
- #### `isTicketValid(ip, ticket)`
549
- Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
550
-
551
- #### `FingerprintBuilder`
552
- A class for building granular server-side fingerprints.
553
-
554
- ```javascript
555
- const builder = new FingerprintBuilder();
556
- builder.add("ua", req.headers["user-agent"]);
557
- builder.add("os", req.headers["sec-ch-ua-platform"]);
558
- const fp = builder.toString(); // "os:hash1|ua:hash2"
559
- ```
560
-
561
- #### `getDeviceFingerprint()`
562
- *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
563
- This is the primary function for client-side identification.
564
-
565
- #### `generateRequestSignature(payload)`
566
- *Client-side function only.* Creates a signature for an outgoing request. It combines the device fingerprint with a hash of the request's `payload`. This can be used on the server-side to verify that a request comes from a recognized device and that its payload has not been trivially altered.
567
-
568
- ```javascript
569
- // On the client
570
- const signature = generateRequestSignature({ action: 'update', id: 123 });
571
- // Send signature in headers...
572
- ```
573
-
574
- #### `generateClientSideSignature(payload, secret)`
575
- *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
576
- **Security Note:** This function is powerful but should be used with caution. The `secret` must be managed securely. It is typically used with a temporary, single-use secret provided by the server for a specific action, rather than a long-lived shared secret embedded in the client-side code.
577
-
578
- ---
579
-
580
- ## Why Use the Client-Side Library? The Client + Server Synergy
581
-
582
- At first glance, client-side checks might seem redundant with server-side honeypots and analysis. In reality, they form two complementary and synergistic lines of defense.
583
-
584
- Imagine your server is a fortified castle:
585
-
586
- - **Server-Side Defense (the guards on the walls):** They inspect anyone who knocks on the gate. They are effective, but this means the enemy is already at your door, and your resources (guards) are mobilized for every interaction, legitimate or not.
587
- - **Client-Side Defense (scouts and traps in the forest):** They detect suspicious movements and neutralize threats *before* they even reach the castle walls. This saves the castle's resources for genuine visitors.
588
-
589
- The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
590
-
591
- 1. **Early Detection & Resource Savings:** A bot filling a client-side honeypot is flagged in its own browser. The server can then immediately block it based on the `X-Behavior-Metrics` header, saving CPU, memory, and bandwidth that would have been wasted processing a malicious request.
592
- 2. **Richer Behavioral Data:** The server cannot see how a user interacts with a page. The client-side library can detect non-human behavior (no mouse movement, instant form fills) that is impossible to spot from the server alone.
593
- 3. **More Robust Fingerprinting:** Server-side signals (IP, User-Agent) are easy to spoof. Client-side fingerprinting adds much stronger, hardware-based signals (Canvas, WebGL, CPU cores) that are significantly harder for bots to fake consistently.
594
-
595
- ### Strengths at a Glance
596
-
597
- | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
598
- | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
599
- | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
600
- | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
601
- | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
602
- | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
603
- | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
604
-
605
- In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
606
-
607
- ### Client-Side Integration: The `initializeClient` function
608
-
609
- To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
610
-
611
- ```javascript
612
- import { initializeClient } from './path/to/fingerprint.client.js';
613
-
614
- /**
615
- * Initializes all client-side protections.
616
- * This is the recommended way to set up the client-side library.
617
- */
618
- initializeClient({
619
- // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
620
- // Set to `false` to disable.
621
- mouse: true,
622
-
623
- // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
624
- // Set to `false` to disable.
625
- keystrokes: true,
626
-
627
- // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
628
- honeypots: ['email_confirm', 'user_nickname', 'website_url'],
629
-
630
- // (Optional) Path to the WebAssembly loader script (`fp.js`) for accelerated hashing.
631
- // If provided, the client will attempt to load the WASM module. If it fails or is not available,
632
- // it will gracefully fall back to the pure JavaScript implementation.
633
- wasmPath: '/fp.js',
634
-
635
- // (Optional) Enables automatic protection for `fetch` requests.
636
- // If the `fetch` object is present, the protection is active.
637
- fetch: {
638
- // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
639
- targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
640
-
641
- // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
642
- // solve the PoW in the background, and retry the original request with the solution.
643
- // This makes the protection seamless for API clients that use this library.
644
- handleChallenges: true
645
- }
646
- });
647
- ```
648
-
649
- ### Client-Side Behavioral Analysis
650
-
651
- The following functions, available in `fingerprint.client.js`, allow for proactive, client-side detection of bot-like behavior. They collect metrics on user interaction which can be sent to the server for more accurate suspicion scoring. The server-side logic to interpret these metrics (via the `X-Behavior-Metrics` header) would need to be implemented as part of a custom scoring extension.
652
-
653
- #### `startKeystrokeDynamicsTracker()`
654
- *Client-side function only.* Starts tracking the timing between keystrokes. The average latency between key presses is a strong behavioral indicator. Humans have a natural, somewhat variable typing rhythm, whereas bots often simulate keystrokes with a fixed, unnaturally consistent delay, or paste text instantly (zero latency).
655
-
656
- #### `startMouseEntropyTracker()`
657
- *Client-side function only.* Starts tracking mouse movements on the page. It calculates a simple entropy score based on movement patterns. Human mouse movements are typically chaotic, whereas bots often have linear or no movement at all. This should be called once when your application's main component mounts.
658
-
659
- #### `initializeHoneypots(fieldNames)`
660
- *Client-side function only.* Sets up "traps" on hidden form fields. If a script automatically fills one of these fields, it's immediately flagged as a bot on the client side.
661
-
662
- This provides a proactive, first-line defense against simple bots. By setting up traps directly in the browser, you can detect a bot the moment it interacts with a hidden field, rather than waiting for it to submit a form and consume server resources. This detection is then reported to the server via the `X-Behavior-Metrics` header, allowing for an immediate and efficient block.
663
-
664
- - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
665
-
666
- ### Advanced: Manual Wrapping with `protectedFetch`
667
-
668
- If you prefer not to modify global functions or need fine-grained control over which requests are protected, you can use the `protectedFetch` wrapper. You must use this function instead of the standard `fetch` for your API calls.
669
-
670
- - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
671
- - `X-Device-Fingerprint`: The client's device fingerprint.
672
- - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
673
-
674
- **Example:**
675
-
676
- ```javascript
677
- import {
678
- initializeClient,
679
- protectedFetch
680
- } from './path/to/fingerprint.client.js';
681
-
682
- // Start tracking user behavior as soon as the app loads.
683
- // Note: You still need to initialize the trackers even if you use protectedFetch manually.
684
- initializeClient({ fetch: false }); // Disables automatic fetch patching
685
-
686
- // Now, use protectedFetch for your specific API calls.
687
- async function submitForm(data) {
688
- const response = await protectedFetch('/api/submit-data', {
689
- method: 'POST',
690
- body: JSON.stringify(data),
691
- headers: {'Content-Type': 'application/json'}
692
- });
693
- }
694
- ```
695
-
696
- ## Advanced Features
697
-
698
- ### Architecture: `FingerprintEngine`
699
-
700
- The core logic of the library is encapsulated within the `FingerprintEngine` class. The `powMiddleware` is essentially a lightweight wrapper that adapts this engine for use with Express.js.
701
-
702
- The engine is responsible for:
703
- 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
704
- 2. Calculating the suspicion score using the configured weights.
705
- 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
706
-
707
- Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
708
-
709
- ### Manual Integration (outside Express.js)
710
-
711
- While `powMiddleware` is convenient for Express, you can use the `FingerprintEngine` directly in any Node.js server environment (e.g., native `http`, Fastify, Koa). This gives you full control over the request/response cycle.
712
-
713
- **For concrete examples with Koa and Fastify, see our [Framework Integration Guide](https://github.com/anonympins/fingerprint/blob/main/INTEGRATION.md).**
714
-
715
- The engine is a named export from the main module.
716
-
717
- ### Useful Proof-of-Work (`ProblemManager`)
718
-
719
- Instead of issuing a generic Proof-of-Work, the system can dispatch a "useful" computational problem to a suspicious client. This allows harnessing the client's CPU cycles to solve complex problems (like optimization tasks) over time. This feature is managed by the `ProblemManager` class, which is enabled via the `enableUsefulWork: true` flag in the security configuration. The state of these problems is persisted via the configured datastore, allowing a cluster of servers to collaborate on solving them.
720
-
721
- The `ProblemManager` reads its configuration asynchronously from `problems.config.json`, which defines the problems to be solved, the type of work units, and the initial state of the solutions.
722
-
723
- While you typically won't interact with it directly, its methods are exported and can be used for monitoring or manual administration. The main instance is exported as `problemManager`.
724
-
725
- #### `problemManager.dispatchWork(suspicionFactor)`
726
-
727
- Selects a problem and generates a work unit for a client. The difficulty of the task (e.g., number of iterations) is scaled based on the client's `suspicionFactor`.
728
-
729
- * **`suspicionFactor`** (`number`): A factor to adjust the difficulty of the work unit.
730
- * **Returns**: (`object|null`) An object containing the `problemId` and the `task` to be sent to the client, or `null` if no problems are available.
731
-
732
- #### `problemManager.integrateSolution(problemId, solutionData)`
733
-
734
- Integrates a solution returned by a client into the problem's state. If the new solution is better than the existing one, it is saved as the new best solution.
735
-
736
- * **`problemId`** (`string`): The ID of the problem being updated.
737
- * **`solutionData`** (`object`): The solution data returned by the client (e.g., `{ solution, energy }`).
738
-
739
- #### `problemManager.getBestSolutions([problemId])`
740
-
741
- Retrieves the best solution currently known for one or all problems. This is useful for creating an API endpoint to view the progress of the distributed computation.
742
-
743
- * **`problemId`** (`string`, optional): The ID of a specific problem.
744
- * **Returns**: (`object|Array<object>|null`)
745
- * If a `problemId` is provided, it returns an object with the best solution for that problem (`{ id, solution, score, lastUpdate }`).
746
- * If no `problemId` is provided, it returns an array of these objects for all problems.
747
-
748
- **Example: Creating an API endpoint to view solutions**
749
-
750
- ```javascript
751
- import { problemManager } from './fingerprint.js'; // Adjust path
752
-
753
- app.get('/api/problems/solutions', (req, res) => {
754
- const solutions = problemManager.getBestSolutions();
755
- res.json(solutions);
756
- });
757
-
758
- ```
759
-
760
- #### `getBestTuningSolution()`
761
-
762
- Returns the last best solution object found by the auto-tuner. This is particularly useful for "FinOps" or for auditing the tuner's performance, as it allows you to log the exact configuration that the genetic algorithm identified as optimal.
763
-
764
- * **Returns**: (`object|null`) The best solution object `{ solution, objectives }` or `null` if no tuning cycle has completed yet. The `solution` property contains the optimized `weights`, `thresholds`, and `patterns`, while `objectives` contains the performance scores (e.g., false positive/negative rates) for that solution.
765
-
766
- #### `problemManager.updateProblemPayload(problemId, newPayload)`
767
-
768
- Updates the payload (parameters) of a specific problem by its ID. This allows for dynamic adjustment of problem configurations without restarting the server. When the payload is updated, the problem's current best solution and energy are reset, forcing the system to find a new optimal solution for the modified problem.
769
-
770
- * **`problemId`** (`string`): The ID of the problem to update.
771
- * **`newPayload`** (`object`): The new payload object that will replace the existing one.
772
- * **Returns**: (`boolean`) `true` if the update was successful, `false` otherwise.
773
-
774
- **Example: Changing the number of facilities for `facility_location_challenge`**
775
-
776
- ---
777
-
778
- ## NodeJS raw integration
779
- **Workflow:**
780
-
781
- 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
782
- 2. **Build the `requestContext`**: On each request, manually create a context object. It must include `clientIp`, `path`, `cookies`, `query`, `headers`, and mock `rawReq`/`rawRes` objects for cookie handling.
783
- 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
784
- 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
785
-
786
- **Example with native Node.js `http` server:**
787
-
788
- ```javascript
789
- import http from 'http';
790
- import { FingerprintEngine } from './fingerprint.js'; // Adjust path
791
-
792
- const securityConfig = { /* ... your config ... */ };
793
- const engine = new FingerprintEngine(securityConfig);
794
-
795
- const server = http.createServer(async (req, res) => {
796
- // 1. Manually build the context
797
- const requestContext = {
798
- clientIp: req.socket.remoteAddress,
799
- path: req.url.split('?')[0],
800
- cookies: {}, // Parse cookies from req.headers.cookie
801
- query: Object.fromEntries(new URL(req.url, `http://${req.headers.host}`).searchParams),
802
- headers: req.headers,
803
- rawReq: req, // Pass the raw request
804
- rawRes: res, // Pass the raw response for cookie setting
805
- };
806
-
807
- // 2. Process and get a decision
808
- const decision = await engine.processRequest(requestContext);
809
-
810
- // The decision object now contains the score and the raw suspicion vector.
811
- // You can use it for logging or custom logic.
812
- console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
813
-
814
- // 3. Act on the decision
815
- if (decision.action === 'challenge') {
816
- res.writeHead(decision.status, { 'Content-Type': 'text/html' });
817
- res.end(decision.body);
818
- } else if (decision.action === 'redirect') {
819
- // The engine sets the cookie directly on `res` via `rawRes`
820
- res.writeHead(302, { 'Location': decision.path });
821
- res.end();
822
- } else { // 'next'
823
- res.writeHead(200, { 'Content-Type': 'text/plain' });
824
- res.end('Welcome to the protected page!');
825
- }
826
- });
827
-
828
- server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
829
- ```
830
-
831
- ---
832
-
833
- ## License
834
-
1
+ # fingerprint
2
+ [![CI](https://img.shields.io/github/actions/workflow/status/anonympins/fingerprint/ci.yml)](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
3
+ [![Release](https://img.shields.io/github/v/release/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/releases)
4
+ [![License](https://img.shields.io/github/license/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
5
+ ![GitHub commit activity](https://img.shields.io/github/commit-activity/w/anonympins/fingerprint)
6
+ [![Watchers](https://img.shields.io/github/watchers/anonympins/fingerprint)](https://github.com/anonympins/fingerprint/watchers)
7
+
8
+ An HTTP(S) client mitigation and anti-bot protection library for both PHP and Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.
9
+
10
+ ## Installation and Usage
11
+
12
+ This library is available for both **Node.js** and **PHP**.
13
+
14
+ * [PHP Quickstart](#php-quickstart)
15
+ * [Node.js Quickstart](#nodejs-quickstart)
16
+
17
+ ## How It Works
18
+
19
+ This system identifies and slows down bots and automated scripts by evaluating the "suspicion" level of each incoming request. Instead of outright blocking, it imposes challenges with a difficulty proportional to the suspicion score, penalizing bots without significantly impacting legitimate users.
20
+
21
+ The process unfolds in three steps:
22
+
23
+ 1. **Identification & Fingerprinting**: A unique fingerprint is generated for each device. This combines a client-side browser fingerprint, server-side request headers, and the **JA3 fingerprint** from the TLS handshake, which reliably identifies the underlying HTTP client library (e.g., Chrome vs. a Python script). A `device_id` cookie is used to track the device over time.
24
+ * **Advanced TLS Fingerprinting (JA4/JA4H)**: Beyond JA3, the system can leverage JA4/JA4H (if provided by a reverse proxy like Cloudflare or Akamai) for a more robust and modern TLS fingerprint, especially for HTTP/2 traffic.
25
+ * **HTTP/2 Fingerprinting**: Analyzes HTTP/2 specific characteristics (settings frame, priority, window update) to identify client libraries.
26
+ * **TCP/IP Fingerprinting**: If available (e.g., from a specialized reverse proxy), low-level TCP/IP stack characteristics (TTL, window size, options) are used for identification.
27
+ * A `device_id` cookie is used to track the device over time.
28
+ 2. **Suspicion Score Calculation**: Several indicators are analyzed to calculate a suspicion score:
29
+ * **Header Anomalies**: Missing `User-Agent`, `Accept-Language`, etc.
30
+ * **Device Behavior**: Rapid fingerprint changes (User-Agent rotation).
31
+ * **IP Behavior**: An excessive number of different devices seen from the same IP, or a single device using a large number of IPs (proxy rotation).
32
+ * **Inconsistency**: A low similarity score between the current fingerprint and the one initially associated with the `device_id` (cookie theft detection).
33
+ * **Cross-Layer Inconsistency**: Mismatches between client-side data (e.g., OS reported by the browser) and server-side headers (e.g., `User-Agent`).
34
+ * **Request Patterns**: Repetitive, rapid-fire, or sequential requests typical of scraping bots. The parameters for detecting these patterns (e.g., request velocity, burst detection) are dynamically adjusted by the auto-tuner for optimal performance.
35
+ * **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
36
+ * **TLS Fingerprint Spoofing**: Detects inconsistencies between the TLS fingerprint (JA3/JA4) and other HTTP headers (e.g., User-Agent), indicating an attempt to disguise the client.
37
+ 3. **Dynamic Challenge**: If the suspicion score exceeds a certain threshold, a challenge is presented. The difficulty and type of challenge depend on the score:
38
+ * **Low to Medium Suspicion**: A combined **CPU and Memory Proof-of-Work (PoW)** challenge is issued. The difficulty of both the CPU (hash calculation) and Memory (allocation and computation) components scales progressively with the suspicion score. For low scores, the memory challenge is negligible, making it primarily a CPU task.
39
+ * **High Suspicion**: For the most suspicious requests, the system issues a high-difficulty combined CPU/Memory challenge or a "Useful Proof-of-Work" task (see below). The architecture allows for plugging in more complex challenges like CAPTCHAs if needed.
40
+ * **New Devices**: To increase the cost for bots that simply clear their cookies, new (unseen) devices are systematically presented with a minimal, almost imperceptible challenge on their first visit. This behavior is enabled by default in `strict` and `ecommerce` profiles and can be configured with the `challengeNewDevices` option.
41
+
42
+ Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges. The duration of this ticket is dynamic:
43
+ - **Probationary Ticket**: If the request was moderately suspicious, a very short-lived "probationary" ticket (e.g., 30 seconds) is issued. This forces the client to be re-evaluated quickly, increasing security.
44
+ - **Optimal TTL Ticket**: For less suspicious requests, a genetic algorithm calculates the optimal ticket duration, balancing security (shorter TTL for higher risk) and user experience (longer TTL for lower risk).
45
+
46
+ For API clients, the challenge is delivered as a `404` JSON response, and the client library can automatically solve it and retry the original request.
47
+
48
+ ## Features
49
+
50
+ - **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
51
+ - **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
52
+ - **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
53
+ - **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`.
54
+ - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
55
+ - **Bot Whitelisting**: Includes a DNS-based verification mechanism to reliably identify and whitelist legitimate crawlers like Googlebot and Bingbot, preventing them from being challenged. The results are cached for optimal performance.
56
+ - **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust suspicion thresholds, weights, and behavioral pattern detection parameters, improving accuracy and reducing false positives over time. The tuner is hardened against data poisoning attempts.
57
+ - **Optional WASM Acceleration**: The client-side library can be accelerated with a WebAssembly module for high-performance hashing. The build process handles this optionally, and the client gracefully falls back to a pure JavaScript implementation if WASM is unavailable.
58
+ - **Hardened Security**: Protects against various attacks, including DoS via memory exhaustion, invalid nonce submission, and uses cryptographically secure randomness for all sensitive operations.
59
+
60
+ ### Prerequisites
61
+
62
+ * **PHP 7.4+**
63
+ * The **BCMath** extension (`php-bcmath`) is required. It is included by default in most PHP installations.
64
+ * **Composer** for package management.
65
+ * The **GMP** extension (`php-gmp`) is highly recommended for performance. If not available, the library will fall back to a slower BCMath-based implementation for cryptographic operations.
66
+
67
+ ---
68
+
69
+ <a id="php-quickstart"></a>
70
+
71
+ ## PHP Quickstart (Direct Integration)
72
+
73
+ This guide shows the simplest way to integrate the library into any PHP application, without requiring a framework. It interacts directly with PHP's native functions and superglobals.
74
+
75
+ ### Prerequisites
76
+
77
+ * **PHP 7.4+**
78
+ * The **BCMath** extension (`php-bcmath`) is required. It is included by default in most PHP installations.
79
+ * **Composer** for package management.
80
+ * The **GMP** extension (`php-gmp`) is highly recommended for performance. If not available, the library will fall back to a slower BCMath-based implementation for cryptographic operations.
81
+
82
+ ### Installation
83
+
84
+ Install the main library via Composer:
85
+
86
+ ```bash
87
+ composer require anonympins/fingerprint
88
+ ```
89
+
90
+ ### Configuration
91
+
92
+ Define a secret key for signing Proof-of-Work tickets in your environment variables or your `.env` file. This is **required** for production environments.
93
+
94
+ ```bash
95
+ POW_SECRET="your_secret_key_of_at_least_32_characters"
96
+ ```
97
+
98
+ ### Integration Example
99
+
100
+ This example shows how to protect an application's entry point (e.g., `index.php`) by calling the `protect()` method at the very beginning of your script.
101
+
102
+ ```php
103
+ <?php
104
+
105
+ declare(strict_types=1);
106
+
107
+ require_once __DIR__ . '/vendor/autoload.php';
108
+
109
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
110
+ use Anonympins\Fingerprint\DirectFingerprint;
111
+
112
+ // 1. Choose a security profile and customize it if necessary.
113
+ $securityConfig = SecurityProfiles::createSecurityProfile('balanced', [
114
+ // Enable verbose mode for development
115
+ 'verbose' => true,
116
+ ]);
117
+
118
+ /*
119
+ * IMPORTANT: Unlike Node.js, standard PHP environments (like PHP-FPM) cannot directly access
120
+ * the raw TLS handshake to compute JA3/JA4 fingerprints.
121
+ * To enable robust TLS fingerprinting in PHP, you must use a reverse proxy (like Nginx,
122
+ * HAProxy, or a cloud load balancer) configured to extract the fingerprint and pass it
123
+ * to your application via an HTTP header (e.g., `X-JA3-Hash`). The library is already
124
+ * built to consume these headers automatically.
125
+ */
126
+
127
+ // 2. Create an instance of the DirectFingerprint protector.
128
+ $protector = new DirectFingerprint($securityConfig);
129
+
130
+ // 3. Protect the script.
131
+ // This method will analyze the request. If it's suspicious, it will
132
+ // send a challenge or block response and then call `exit()`.
133
+ // If the request is allowed, it returns the fingerprint data.
134
+ $fingerprint = $protector->protect();
135
+
136
+ // --- If the script continues, the request was allowed ---
137
+
138
+ $score = $fingerprint['score'] ?? 0;
139
+
140
+ header('Content-Type: text/html; charset=utf-8');
141
+ echo "<h1>Welcome to the protected page!</h1>";
142
+ echo "<p>Your suspicion score was: " . round($score, 2) . "</p>";
143
+
144
+ ?>
145
+ ```
146
+
147
+ ### Full Configuration Example (PHP)
148
+
149
+ If you prefer to define the entire configuration manually instead of using a profile, you can create a `$securityConfig` array with all the parameters. All parameters are optional, but it is highly recommended to review and adjust them for your specific needs. The engine will warn you about any unknown keys in this configuration, helping you catch typos.
150
+
151
+ ```php
152
+ <?php
153
+
154
+ use Anonympins\Fingerprint\Utils\DefaultWhitelist;
155
+
156
+ // Array to store traffic analysis data for the auto-tuner.
157
+ // In a real application, this could be a more robust logging system.
158
+ $trafficData = [];
159
+
160
+ // Configuration of weights and thresholds for calculating the suspicion score.
161
+ // These values should be adjusted based on traffic and expected user behavior.
162
+ $securityConfig = [
163
+ 'weights' => [
164
+ 'historyScore' => 0.3, // Penalizes IP rotation (proxy)
165
+ 'rotationScore' => 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
166
+ 'headerAnomalyScore' => 0.1, // Penalizes abnormal headers (missing UA, etc.)
167
+ 'requestPatternScore' => 0.6,// Penalizes bot-like request sequences (scraping, etc.)
168
+ 'inconsistencyScore' => 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
169
+ 'behaviorScore' => 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
170
+ 'honeypotScore' => 1.0, // Strongly penalizes bots filling hidden form fields
171
+ 'crossLayerInconsistencyScore' => 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
172
+ 'timeInconsistencyScore' => 0.9, // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
173
+ 'tlsSpoofingScore' => 0.8, // Penalizes mismatches between the TLS fingerprint (JA3/JA4) and the User-Agent (client spoofing)
174
+ 'botScore' => 1.0, // Penalizes explicit bot markers from the client
175
+ 'clientHintsInconsistencyScore' => 0.7, // Penalizes mismatches between User-Agent and Client-Hints versions
176
+ 'cookieDroppingScore' => 0.9, // Penalizes clients that appear to be intentionally dropping cookies
177
+ 'threatIntelScore' => 0.4, // Penalizes requests from known malicious IPs (proxies, Tor, etc.)
178
+ ],
179
+ 'thresholds' => [
180
+ 'low' => 20, // Score from which a CPU challenge is issued
181
+ 'medium' => 45, // Score for a more difficult combined CPU/Memory challenge
182
+ 'high' => 75, // Score for a very difficult challenge
183
+ 'block' => 95, // Score above which the request is blocked outright (HTTP 403)
184
+ ],
185
+ 'cpu' => [
186
+ 'minDifficultyBits' => 8,
187
+ 'maxDifficultyBits' => 24,
188
+ ],
189
+ // (Optional) Configure the duration (in milliseconds) for various temporary data.
190
+ 'ticketMaxAge' => 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
191
+ 'challengeTtl' => 300000, // 5 minutes. Time during which a challenge nonce is valid.
192
+ 'deviceIdCookieMaxAge' => null, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
193
+ 'challengePagePath' => './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
194
+ 'verbose' => ($_ENV['APP_ENV'] ?? 'production') !== 'production', // Log detailed info in development.
195
+ 'patterns' => [ // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
196
+ 'historySize' => 10, // Number of requests to keep for pattern analysis
197
+ 'minSamples' => 5, // Minimum number of timings to collect before statistical analysis.
198
+ 'regularityThreshold' => 50, // Standard deviation (ms) below which behavior is "too regular".
199
+ 'benfordThreshold' => 0.15, // Benford's Law deviation threshold above which the distribution is "unnatural".
200
+ 'patternWeight' => 80, // Strong, one-time penalty when a pattern is detected.
201
+ 'decayFactor' => 0.9, // Factor by which the pattern score decreases over time.
202
+ 'inactivityReset' => 5000, // Time (ms) after which the pattern score is reset.
203
+ ],
204
+ 'honeypot' => [
205
+ // List of field names that are traps for bots.
206
+ 'fields' => ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
207
+ // List of URL paths that should never be accessed by a legitimate user.
208
+ 'trapUrls' => ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
209
+ // Automatically detect common injection patterns. Can be a boolean or an array of specific types.
210
+ 'detectInjections' => ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
211
+ // (Optional) Plug in external analyzers. Each must be a callable that receives request data
212
+ // and returns `true` if a threat is detected.
213
+ 'analyzers' => [
214
+ // Example: A custom function to detect specific keywords (e.g., for anti-spam).
215
+ function ($data) {
216
+ $spamKeywords = ['viagra', 'free money', 'crypto pump'];
217
+ $dataString = strtolower(json_encode($data));
218
+ foreach ($spamKeywords as $keyword) {
219
+ if (str_contains($dataString, $keyword)) {
220
+ return true;
221
+ }
222
+ }
223
+ return false;
224
+ }
225
+ ]
226
+ ],
227
+ // (Optional) Whitelisting configuration.
228
+ 'whitelist' => [
229
+ // Option 1: Static IP Allowlist (IPs or CIDR ranges).
230
+ [
231
+ 'type' => 'allowlist',
232
+ 'entries' => [
233
+ '192.168.1.100', // A specific internal IP
234
+ '203.0.113.0/24', // A partner's network range
235
+ '2001:db8::/32', // An IPv6 range
236
+ ]
237
+ ],
238
+ // Option 2: Host + Path Allowlist (supports wildcards).
239
+ [
240
+ 'type' => 'host_path_allowlist',
241
+ 'entries' => [
242
+ 'api.yourdomain.com/v1/webhooks/*', // All paths under /v1/webhooks on a specific host
243
+ ]
244
+ ],
245
+ // Option 3: Path Allowlist (supports wildcards).
246
+ [
247
+ 'type' => 'path_allowlist',
248
+ 'entries' => [
249
+ '/api/v2/public-stats', // Exact path
250
+ '/callbacks/trusted-source/*', // All paths under /callbacks/trusted-source/
251
+ ]
252
+ ],
253
+ // Option 4: GraphQL Operation Allowlist (supports wildcards).
254
+ [
255
+ 'type' => 'graphql_operation_allowlist',
256
+ 'entries' => [
257
+ 'query:GetPublicPosts', // A specific query
258
+ 'mutation:*' // All mutations
259
+ ]
260
+ ],
261
+ // Option 5: DNS-verified bots (e.g., search engine crawlers).
262
+ // You can use the provided default list and extend it.
263
+ ...DefaultWhitelist::getRules(), // Use the defaults
264
+ ['userAgent' => 'MyCustomBot', 'hostnameSuffix' => '.my-bot-verifier.com'], // Add a custom bot
265
+ ],
266
+ // (Optional) Custom function to identify API requests. Must be a callable.
267
+ 'isApiRequest' => function (RequestContext $context) {
268
+ return str_starts_with($context->path, '/api/') ||
269
+ str_contains($context->getHeader('accept') ?? '', 'application/json');
270
+ },
271
+ // The logger is required for auto-tuning. It must be a callable.
272
+ 'logger' => function ($log) use (&$trafficData) {
273
+ $trafficData[] = $log;
274
+ },
275
+ // (Optional) Configuration for the automatic threshold and pattern tuning.
276
+ 'autotuning' => [
277
+ 'trafficData' => &$trafficData, // Pass the data source by reference.
278
+ 'interval' => 1800, // Optimization cycle every 30 minutes (in seconds for a cron job).
279
+ 'minDataPoints' => 200,
280
+ 'maxDataPoints' => 20000,
281
+ 'savePath' => './security-config.optimized.json' // (Optional) Save the best config found.
282
+ ],
283
+ // Enables "Useful Proof-of-Work" for suspicious activity.
284
+ 'enableUsefulWork' => true,
285
+ // Provide either a path to a JSON file or the configuration as an array.
286
+ 'usefulWorkConfigPath' => './path/to/your/problems.config.json', // (Optional)
287
+ // Or provide the configuration directly as an array.
288
+ // 'usefulWorkConfig' => [ /* ... your problem definitions ... */ ]
289
+ ];
290
+
291
+ ```
292
+ ## TLS Fingerprinting (JA3/JA4) with Nginx and Apache
293
+
294
+ Unlike Node.js, which can directly inspect the TLS handshake, a standard PHP environment (such as PHP-FPM) runs behind a web server (Nginx, Apache) that terminates the TLS connection. Consequently, the PHP script lacks direct access to the low-level information required to calculate the JA3 fingerprint.
295
+
296
+ If you want a **better protection**, the standard solution is to delegate this calculation to the front-end web server (or a reverse proxy like HAProxy) and pass the result to PHP via an HTTP header. The library is designed to automatically detect and utilize these headers.
297
+
298
+ ### Automatic Detection in the Library
299
+
300
+ The PHP `RequestContext` class automatically looks for the following headers.
301
+
302
+ Once these headers are present, the `FingerprintEngine` incorporates them into the composite device fingerprint, providing the same level of robustness as the Node.js version.
303
+
304
+ ---
305
+
306
+ ### Configuration with Nginx
307
+
308
+ Nginx is the simplest and most common solution. It requires your Nginx instance to be compiled with the `ngx_http_ssl_ja3_module` module. Many modern Nginx builds or distribution-provided packages include it. Here is an example configuration:
309
+
310
+ ```nginx
311
+ http {
312
+ # ... other http configurations ...
313
+
314
+ # Declare a variable to store the JA3 fingerprint.
315
+ # Nginx automatically populates $ssl_ja3_hash if the module is active.
316
+ map $ssl_ja3_hash $ja3_hash {
317
+ default $ssl_ja3_hash;
318
+ }
319
+
320
+ server {
321
+ listen 443 ssl http2;
322
+ server_name yourdomain.com;
323
+
324
+ # ... SSL configuration (certificates, etc.) ...
325
+ ssl_certificate /path/to/your/fullchain.pem;
326
+ ssl_certificate_key /path/to/your/privkey.pem;
327
+
328
+ location / {
329
+ # ... your application configuration ...
330
+ try_files $uri $uri/ /index.php?$query_string;
331
+ }
332
+
333
+ location ~ \.php$ {
334
+ include fastcgi_params;
335
+ fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust for your PHP version
336
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
337
+
338
+ # Add the JA3 fingerprint as a FastCGI parameter.
339
+ # PHP will make it available in $_SERVER['HTTP_X_JA3_HASH'].
340
+ fastcgi_param HTTP_X_JA3_HASH $ja3_hash;
341
+ }
342
+ }
343
+ }
344
+ ```
345
+
346
+ After reloading the Nginx configuration, the `X-JA3-Hash` header will be automatically available to your PHP application.
347
+
348
+ ---
349
+
350
+ ### Configuration with Apache
351
+
352
+ For Apache, obtaining the JA3 fingerprint is less straightforward because there is no standard module as widely available as the one for Nginx.
353
+
354
+ #### Option 1: `mod_ssl_ja3` module (Recommended)
355
+
356
+ The best approach is to use a third-party module like `mod_ssl_ja3`. You will need to compile and load it into your Apache configuration. Once the module is installed and enabled, you can add the JA3 header to your requests using the `RequestHeader` directive in your Virtual Host configuration:
357
+
358
+ ```apache
359
+ <VirtualHost *:443>
360
+ ServerName yourdomain.com
361
+ # ... SSL configuration ...
362
+
363
+ # The JA3_HASH environment variable is provided by mod_ssl_ja3
364
+ RequestHeader set X-JA3-Hash "%{JA3_HASH}e"
365
+
366
+ # ... your PHP application configuration ...
367
+ </VirtualHost>
368
+ ```
369
+
370
+ #### Option 2: Using a Reverse Proxy in front of Apache
371
+
372
+ If you cannot compile modules for Apache, a very robust alternative is to place another service in front to handle TLS termination. **HAProxy** is an excellent choice for this, as it can calculate the JA3 hash natively and add it as a header before forwarding the request (via plain HTTP) to Apache.
373
+
374
+ This architecture is common in high-performance environments and offers great flexibility.
375
+
376
+ ---
377
+
378
+ <a id="nodejs-quickstart"></a>
379
+ ## NodeJS Quickstart
380
+
381
+ ### Prerequisites for Node.js
382
+
383
+ Ensure you have middleware for parsing cookies (like `cookie-parser`) and request bodies (like `express.json` and `express.urlencoded`) set up in your Express application *before* the `powMiddleware`.
384
+
385
+ ### Configuration
386
+ Define a secret key for signing PoW tickets in your environment variables.
387
+
388
+ ```bash
389
+ export POW_SECRET="your_secret_key_of_at_least_32_characters"
390
+ ```
391
+
392
+ ### Integration Example
393
+
394
+ To simplify setup, `fingerprint` provides pre-configured security profiles for common use cases. You can use the `createSecurityProfile` helper to load a profile and optionally extend it with your own settings.
395
+
396
+ Available profiles:
397
+ - `balanced` (default): A general-purpose configuration suitable for most websites.
398
+ - `strict`: A more aggressive configuration for sensitive applications.
399
+ - `api`: Optimized for protecting API endpoints, with a higher sensitivity to request patterns.
400
+ - `blog`: Tuned to detect content scraping and comment spam.
401
+ - `ecommerce`: A strict profile focused on preventing inventory scalping, price scraping, and account takeover.
402
+
403
+ ```javascript
404
+ import express from 'express';
405
+ import bodyParser from 'body-parser';
406
+ import cookieParser from 'cookie-parser';
407
+ import { powMiddleware, createSecurityProfile } from '@anonympins/fingerprint'; // Adjust the path
408
+
409
+ const app = express();
410
+ app.use(cookieParser());
411
+ app.use(bodyParser.json());
412
+ app.use(bodyParser.urlencoded({ extended: true }));
413
+
414
+ // Array to store traffic analysis data for the auto-tuner.
415
+ const trafficData = [];
416
+
417
+ // 1. Choose a base profile (e.g., 'balanced', 'strict', 'api').
418
+ // 2. (Optional) Define your custom overrides. These will be deeply merged with the base profile.
419
+ const securityConfig = createSecurityProfile('api', {
420
+ // Example of overriding a specific threshold from the 'balanced' profile.
421
+ thresholds: {
422
+ low: 25, // Make the initial challenge slightly harder.
423
+ },
424
+ // Example of adding a custom whitelisting rule.
425
+ whitelist: [
426
+ { type: 'path_allowlist', entries: ['/api/v1/public-stats'] }
427
+ ],
428
+ // The logger is required if you enable auto-tuning.
429
+ logger: (log) => trafficData.push(log),
430
+ autotuning: {
431
+ trafficData: trafficData,
432
+ interval: 1800000, // 30 minutes
433
+ minDataPoints: 200,
434
+ savePath: './security-config.optimized.json' // (Optional) Save the best config found.
435
+ },
436
+ });
437
+
438
+ // Create an instance of the middleware with your security configuration.
439
+ const powMiddlewareInstance = powMiddleware(securityConfig);
440
+
441
+ // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
442
+ // to correctly retrieve the client's IP.
443
+ app.set('trust proxy', 1);
444
+
445
+ // Apply the protection middleware to all routes or to specific ones.
446
+ app.use(powMiddlewareInstance);
447
+
448
+ app.get('/', (req, res) => {
449
+ res.send('Welcome to the protected page!');
450
+ });
451
+
452
+ // Example of accessing the suspicion score in a subsequent middleware or route.
453
+ // The `fingerprint` object is attached to the request object by the middleware.
454
+ app.use((req, res, next) => {
455
+ console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
456
+ next();
457
+ });
458
+
459
+ app.listen(3000, () => console.log('Server started on port 3000'));
460
+ ```
461
+
462
+ ### Full Configuration Example
463
+
464
+ If you prefer to define the entire configuration manually instead of using a profile, you can create a `securityConfig` object with all the parameters. All parameters are optional, but it is highly recommended to review and adjust them for your specific needs. The engine will warn you about any unknown keys in this configuration, helping you catch typos.
465
+
466
+ ```javascript
467
+ import { powMiddleware, default_whitelist, default_analyzers } from '@anonympins/fingerprint';
468
+
469
+ const app = express();
470
+ app.use(cookieParser());
471
+ app.use(bodyParser.json()); // For parsing application/json
472
+ app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
473
+
474
+ // Array to store traffic analysis data for the auto-tuner.
475
+ // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
476
+ const trafficData = [];
477
+
478
+ // Configuration of weights and thresholds for calculating the suspicion score.
479
+ // These values should be adjusted based on traffic and expected user behavior.
480
+ const securityConfig = {
481
+ weights: {
482
+ historyScore: 0.3, // Penalizes IP rotation (proxy)
483
+ rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
484
+ headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
485
+ requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
486
+ inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
487
+ behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
488
+ honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
489
+ crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
490
+ timeInconsistencyScore: 0.9, // Strongly penalizes large time gaps between client metric collection and server reception (replay attack),
491
+ tlsSpoofingScore: 0.8, // Penalizes mismatches between the TLS fingerprint (JA3/JA4) and the User-Agent (client spoofing)
492
+ clientHintsInconsistencyScore: 0.7 // Penalizes mismatches between User-Agent and Client-Hints versions
493
+ },
494
+ thresholds: {
495
+ low: 20, // Score from which a CPU challenge is issued
496
+ medium: 45, // Score for a more difficult combined CPU/Memory challenge
497
+ high: 75, // Score for a very difficult challenge
498
+ block: 95, // Score above which the request is blocked outright (HTTP 404)
499
+ },
500
+ cpu: {
501
+ minDifficultyBits: 8,
502
+ maxDifficultyBits: 32,
503
+ },
504
+ // (Optional) Configure the duration (in milliseconds) for various temporary data.
505
+ ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
506
+ challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
507
+ deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
508
+ challengePagePath: './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
509
+ verbose: process.env.NODE_ENV !== 'production', // Log detailed info in development, but not in production.
510
+ patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
511
+ velocityThreshold: 800, // ms between requests to be considered "fast"
512
+ burstThreshold: 1500, // ms for identical requests to be a "burst"
513
+ scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
514
+ historySize: 10, // Number of requests to keep for pattern analysis
515
+ minSamples: 5, // Minimum number of timings to collect before statistical analysis.
516
+ regularityThreshold: 50, // Standard deviation (ms) below which behavior is "too regular".
517
+ benfordThreshold: 0.15, // Benford's Law deviation threshold above which the distribution is "unnatural".
518
+ patternWeight: 80, // Strong, one-time penalty when a pattern is detected.
519
+ decayFactor: 0.9, // Factor by which the pattern score decreases over time.
520
+ inactivityReset: 5000, // Time (ms) after which the pattern score is reset.
521
+ },
522
+ honeypot: {
523
+ // List of field names that are traps for bots.
524
+ // These should be hidden in forms for humans, or be URL parameters your app never uses.
525
+ fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
526
+ // List of URL paths that should never be accessed by a legitimate user.
527
+ // A request to one of these paths will immediately flag the device as malicious.
528
+ trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
529
+ // Automatically detect common injection patterns. Can be a boolean or an array of specific types.
530
+ // - `true`: Enables all available detections (default).
531
+ // - `false`: Disables injection detection.
532
+ // - `['sql', 'rce']`: Enables only SQL injection and Remote Command Execution detection.
533
+ detectInjections: ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
534
+ // (Optional) Plug in external analyzers. This allows you to extend detection with specialized libraries or custom logic.
535
+ // Each function receives an object with all query and body data and should return `true` if a threat is detected.
536
+ analyzers: [
537
+ ...default_analyzers(), // Includes the default XSS analyzer.
538
+
539
+ // Example 2: Enable a powerful WAF with ModSecurity and the OWASP Core Rule Set.
540
+ // Requires `npm install modsecurity-nodejs` and downloading the OWASP CRS rules.
541
+ // modsecurity_analyzer('/path/to/owasp-crs/crs-setup.conf'),
542
+
543
+ // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
544
+ (data) => {
545
+ const spamKeywords = ['viagra', 'free money', 'crypto pump'];
546
+ const dataString = JSON.stringify(data).toLowerCase();
547
+ return spamKeywords.some(keyword => dataString.includes(keyword));
548
+ }
549
+ ]
550
+ },
551
+ // (Optional) Whitelisting configuration.
552
+ whitelist: [
553
+ // Option 1: Static IP Allowlist.
554
+ // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
555
+ // Useful for internal tools, trusted partners, or monitoring services.
556
+ // This check is performed first for maximum efficiency.
557
+ { type: 'allowlist', entries: [
558
+ '192.168.1.100', // A specific internal IP
559
+ '203.0.113.0/24', // A partner's network range
560
+ '2001:db8::/32', // An IPv6 range
561
+ '2a01:e0a:129:57c0::1' // A specific IPv6 address
562
+ ]},
563
+ { type: 'hostname_allowlist', entries: [
564
+ 'google.com', // A specific hostname
565
+ ]},
566
+ // Option 3: Host + Path Allowlist.
567
+ // Bypasses checks for specific URL paths on specific hostnames. This is ideal for whitelisting
568
+ // an API endpoint on one domain but not another. The entry is a combination of the host
569
+ // header and the path. Supports wildcards (*) at the end of the path.
570
+ { type: 'host_path_allowlist', entries: [
571
+ 'web.primals.net/api/*', // All paths starting with /api2 on web.primal.net
572
+ ]},
573
+ // Option 3: Path Allowlist.
574
+ // Bypasses checks for specific URL paths. This is useful for trusted API endpoints, webhooks, or static content paths
575
+ // that don't need protection. Supports wildcards (*) at the end of an entry.
576
+ { type: 'path_allowlist', entries: [
577
+ '/api/v1/webhooks/trusted-source', // Exact path
578
+ '/api/v2/public/*', // All paths starting with /api/v2/public/
579
+ ]},
580
+
581
+ // Allows a specific GraphQL query and all mutations•
582
+ {type: 'graphql_operation_allowlist',entries: [
583
+ 'query:GetPublicPosts',
584
+ 'mutation:*']},
585
+ // Option 2: DNS-verified bots (e.g., search engine crawlers).
586
+ // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
587
+ // The result is cached per IP to avoid repeated DNS lookups.
588
+ // You can use the provided default list, which contains over 50 common bots, and extend it.
589
+ ...default_whitelist(), // Use the defaults
590
+ { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
591
+ ],
592
+ // Optional: Custom function to identify static resources
593
+ isStaticResource: (req) => req.path.startsWith('/static/'),
594
+ // Optional: Custom function to identify API requests
595
+ isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json'),
596
+ // The logger is required for auto-tuning. It collects data on requests.
597
+ logger: (log) => trafficData.push(log),
598
+ // (Optional) Configuration for the automatic threshold and pattern tuning.
599
+ autotuning: {
600
+ trafficData: trafficData, // The data source for the genetic algorithm.
601
+ interval: 1800000, // Optimization cycle every 30 minutes (in ms).
602
+ minDataPoints: 200, // Minimum requests before starting an optimization cycle.
603
+ maxDataPoints: 20000, // Maximum log entries to keep in memory.
604
+ savePath: './security-config.optimized.json' // (Optional) Save the best config found.
605
+ },
606
+ // Enables problem solving for suspicious activity (configurable in problems.config.json)
607
+ enableUsefulWork: true,
608
+ // (Optional) Path to the useful work configuration.
609
+ usefulWorkConfigPath: './path/to/your/problems.config.json',
610
+ // or usefulWorkConfig: [ /* ... your problem definitions ... */ ],
611
+ // (Optional) Enable "dry run" mode. The engine will calculate scores and log intended actions
612
+ // but will never actually block or challenge a request. Useful for testing new configs in production.
613
+ dryRun: false,
614
+ };
615
+
616
+
617
+ // Create an instance of the middleware with your security configuration.
618
+ const powMiddlewareInstance = powMiddleware(securityConfig);
619
+ ```
620
+
621
+
622
+ ---
623
+
624
+ ## Advanced Behavioral Analysis
625
+
626
+ The `FingerprintEngine` includes sophisticated behavioral analysis to detect non-human patterns. This analysis is performed by the `getRequestPatternScore` function, which is a stateful check that looks for repetitive or unnaturally fast requests from a single device.
627
+
628
+ This function uses several configurable parameters to identify suspicious behavior:
629
+
630
+ ### Statistical Analysis (Regularity and Benford's Law)
631
+
632
+ To counter more advanced bots that might try to randomize their request timings, the engine employs statistical analysis.
633
+
634
+ * **Regularity Detection**: The system also calculates the standard deviation of the time intervals between requests. A very low standard deviation indicates an unnaturally regular, "cron-like" behavior, which is a strong signal of automation.
635
+ * **Benford's Law Analysis**: Benford's Law states that in many naturally occurring sets of numbers, the leading digit is more likely to be small. The timings between a human's requests tend to follow this natural distribution, whereas a bot's randomized delays often do not. The engine penalizes distributions that violate this law.
636
+
637
+ ### Core Pattern Detection Parameters
638
+
639
+ These parameters form the basis of the statistical request pattern analysis:
640
+
641
+ * `regularityThreshold`: (Default: 50ms) The standard deviation in milliseconds below which the request timing is considered "too regular" and robotic.
642
+ * `benfordThreshold`: (Default: 0.15) The deviation score from Benford's Law above which the timing distribution is considered "unnatural".
643
+ * `patternWeight`: (Default: 80) A strong, one-time penalty applied to the suspicion score if either a regularity or Benford's Law anomaly is detected.
644
+
645
+ * `benfordMinSamples`: (Default: 15) The minimum number of request timings to collect before performing a Benford's Law test.
646
+ * `benfordWeight`: (Default: 50) The weight applied to the suspicion score if the distribution of timings significantly deviates from Benford's Law.
647
+
648
+ ## Customizing the Challenge Page
649
+
650
+ You can provide your own HTML template for the Proof-of-Work challenge page to maintain a consistent user experience with your brand.
651
+
652
+ 1. **Configuration**: In your `securityConfig`, specify the path to your template file using the `challengePagePath` option.
653
+
654
+ ```javascript
655
+ const securityConfig = {
656
+ // ... other options
657
+ challengePagePath: './path/to/your/custom-challenge-page.html',
658
+ };
659
+ ```
660
+
661
+ 2. **Template Placeholders**: Your HTML file **must** contain the following placeholders. The system will replace them with the dynamic JavaScript code required to run the challenge.
662
+
663
+ * `<!-- FINGERPRINT_SOLVER_SCRIPT -->`: This will be replaced by the script that contains the logic for solving the CPU and memory challenges.
664
+ * `<!-- FINGERPRINT_CHALLENGE_SCRIPT -->`: This will be replaced by the script that initiates the challenge with the specific parameters for the current request (nonce, difficulty, etc.).
665
+ * `<!-- FINGERPRINT_TRAPS -->`: This will be replaced by hidden "honeypot" links designed to trap simple bots. This placeholder is crucial for an effective defense.
666
+
667
+ #### Example Custom HTML Template
668
+
669
+ Here is a basic example of what your `custom-challenge-page.html` could look like:
670
+
671
+ ```html
672
+ <!DOCTYPE html>
673
+ <html lang="en">
674
+ <head>
675
+ <meta charset="UTF-8">
676
+ <title>Security Verification</title>
677
+ <style>
678
+ body { font-family: sans-serif; text-align: center; padding-top: 50px; }
679
+ h1 { color: #333; }
680
+ </style>
681
+ </head>
682
+ <body>
683
+ <h1>Please wait while we verify your connection...</h1>
684
+ <div id="loader" style="margin:20px;">⚙️ Initializing verification...</div>
685
+
686
+ <script><!-- FINGERPRINT_SOLVER_SCRIPT --></script>
687
+ <script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script>
688
+ <!-- FINGERPRINT_TRAPS -->
689
+ </body>
690
+ </html>
691
+ ```
692
+ ## Public API
693
+
694
+ In addition to the main middleware, several functions are exported to allow for more advanced integrations.
695
+
696
+ ### Main Functions
697
+
698
+ #### `powMiddleware(securityConfig)`
699
+ The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
700
+
701
+ #### `configureStore(store)`
702
+ Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
703
+ The library provides ready-to-use adapters for popular datastores like **Redis**, **MongoDB**, and any **SQL database** supported by Knex.js. These adapters automatically handle the Time-To-Live (TTL) required for temporary data like challenge secrets.
704
+
705
+ **Redis Example:**
706
+
707
+ ```javascript
708
+ import { configureStore } from '@anonympins/fingerprint';
709
+ import { createRedisStore } from './redis-store.js';
710
+ import Redis from 'ioredis';
711
+
712
+ const redisClient = new Redis(process.env.REDIS_URL);
713
+ const redisStore = createRedisStore(redisClient);
714
+ configureStore(redisStore);
715
+ ```
716
+
717
+ **MongoDB Example:**
718
+
719
+ ```javascript
720
+ import { configureStore } from './fingerprint.js';
721
+ import { createMongoDbStore } from './mongodb-store.js';
722
+ import { MongoClient } from 'mongodb';
723
+
724
+ const mongoClient = new MongoClient(process.env.MONGODB_URL);
725
+
726
+ // It's recommended to connect before your application starts listening.
727
+ await mongoClient.connect();
728
+
729
+ const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
730
+ configureStore(mongoStore);
731
+
732
+ // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
733
+ // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
734
+ // Run this command in the mongo shell:
735
+ // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
736
+ ```
737
+
738
+ **SQL Example (with Knex.js):**
739
+
740
+ ```javascript
741
+ import { configureStore } from './fingerprint.js';
742
+ import { createSqlStore } from './sql-store.js';
743
+ import knex from 'knex';
744
+
745
+ const knexClient = knex({
746
+ client: 'pg', // or 'mysql', 'sqlite3', etc.
747
+ connection: process.env.DATABASE_URL,
748
+ });
749
+
750
+ const sqlStore = createSqlStore(knexClient, 'fingerprint_sessions'); // 'fingerprint_sessions' is the table name
751
+ configureStore(sqlStore);
752
+
753
+ // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
754
+ // your table must have an `expiresAt` column. The store will handle cleanup of expired rows,
755
+ // but you must create the table yourself.
756
+ // Example schema for PostgreSQL:
757
+ // CREATE TABLE fingerprint_sessions (
758
+ // "key" VARCHAR(255) PRIMARY KEY,
759
+ // "value" TEXT NOT NULL,
760
+ // "expiresAt" TIMESTAMPTZ
761
+ // );
762
+ ```
763
+
764
+ #### `identifyRequest(req, res)`
765
+ An asynchronous function that returns an identification string for a given request, based on its suspicion level (`device:<id>`, `suspicious_medium:<ip>`, etc.). Useful for integration with a custom rate-limiter.
766
+
767
+ ```javascript
768
+ import { RateLimiterMemory } from 'rate-limiter-flexible';
769
+ import { identifyRequest } from './fingerprint.js';
770
+
771
+ const rateLimiter = new RateLimiterMemory({
772
+ keyPrefix: 'rate_limit',
773
+ points: 10,
774
+ duration: 1,
775
+ });
776
+
777
+ app.use(async (req, res, next) => {
778
+ try {
779
+ const key = await identifyRequest(req, res);
780
+ await rateLimiter.consume(key);
781
+ next();
782
+ } catch (err) {
783
+ res.status(429).send('Too Many Requests');
784
+ }
785
+ });
786
+ ```
787
+
788
+ ### Utilities
789
+
790
+ #### `isTicketValid(ip, ticket)`
791
+ Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
792
+
793
+ #### `FingerprintBuilder`
794
+ A class for building granular server-side fingerprints.
795
+
796
+ ```javascript
797
+ const builder = new FingerprintBuilder();
798
+ builder.add("ua", req.headers["user-agent"]);
799
+ builder.add("os", req.headers["sec-ch-ua-platform"]);
800
+ const fp = builder.toString(); // "os:hash1|ua:hash2"
801
+ ```
802
+
803
+ #### `getDeviceFingerprint()`
804
+ *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
805
+ This is the primary function for client-side identification.
806
+
807
+ #### `generateRequestSignature(payload)`
808
+ *Client-side function only.* Creates a signature for an outgoing request. It combines the device fingerprint with a hash of the request's `payload`. This can be used on the server-side to verify that a request comes from a recognized device and that its payload has not been trivially altered.
809
+
810
+ ```javascript
811
+ // On the client
812
+ const signature = generateRequestSignature({ action: 'update', id: 123 });
813
+ // Send signature in headers...
814
+ ```
815
+
816
+ #### `generateClientSideSignature(payload, secret)`
817
+ *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
818
+ **Security Note:** This function is powerful but should be used with caution. The `secret` must be managed securely. It is typically used with a temporary, single-use secret provided by the server for a specific action, rather than a long-lived shared secret embedded in the client-side code.
819
+
820
+ ---
821
+
822
+ ## Why Use the Client-Side Library? The Client + Server Synergy
823
+
824
+ At first glance, client-side checks might seem redundant with server-side honeypots and analysis. In reality, they form two complementary and synergistic lines of defense.
825
+
826
+ Imagine your server is a fortified castle:
827
+
828
+ - **Server-Side Defense (the guards on the walls):** They inspect anyone who knocks on the gate. They are effective, but this means the enemy is already at your door, and your resources (guards) are mobilized for every interaction, legitimate or not.
829
+ - **Client-Side Defense (scouts and traps in the forest):** They detect suspicious movements and neutralize threats *before* they even reach the castle walls. This saves the castle's resources for genuine visitors.
830
+
831
+ The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
832
+
833
+ 1. **Early Detection & Resource Savings:** A bot filling a client-side honeypot is flagged in its own browser. The server can then immediately block it based on the `X-Behavior-Metrics` header, saving CPU, memory, and bandwidth that would have been wasted processing a malicious request.
834
+ 2. **Richer Behavioral Data:** The server cannot see how a user interacts with a page. The client-side library can detect non-human behavior (no mouse movement, instant form fills) that is impossible to spot from the server alone.
835
+ 3. **More Robust Fingerprinting:** Server-side signals (IP, User-Agent) are easy to spoof. Client-side fingerprinting adds much stronger, hardware-based signals (Canvas, WebGL, CPU cores) that are significantly harder for bots to fake consistently.
836
+
837
+ ### Strengths at a Glance
838
+
839
+ | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
840
+ | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
841
+ | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
842
+ | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
843
+ | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
844
+ | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
845
+ | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
846
+
847
+ In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
848
+
849
+ ### Client-Side Integration: The `initializeClient` function
850
+
851
+ To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
852
+
853
+ ```javascript
854
+ import { initializeClient } from '@anonympins/fingerprint/client';
855
+
856
+ /**
857
+ * Initializes all client-side protections.
858
+ * This is the recommended way to set up the client-side library.
859
+ */
860
+ initializeClient({
861
+ // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
862
+ // Set to `false` to disable.
863
+ mouse: true,
864
+
865
+ // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
866
+ // Set to `false` to disable.
867
+ keystrokes: true,
868
+
869
+ // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
870
+ honeypots: ['email_confirm', 'user_nickname', 'website_url'],
871
+
872
+ // (Optional) An array of signed trap URLs provided by the server. The client will
873
+ // dynamically inject these into the DOM to trap bots that parse the live DOM.
874
+ trapUrls: ['/backups/db.sql.gz?sig=...', '/.env?sig=...'],
875
+
876
+ // (Optional) Path to the WebAssembly loader script (`fp.js`) for accelerated hashing.
877
+ // If provided, the client will attempt to load the WASM module. If it fails or is not available,
878
+ // it will gracefully fall back to the pure JavaScript implementation.
879
+ wasmPath: '/fp.js',
880
+
881
+ // (Optional) Enables automatic protection for `fetch` requests.
882
+ // If the `fetch` object is present, the protection is active.
883
+ fetch: {
884
+ // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
885
+ targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
886
+
887
+ // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
888
+ // solve the PoW in the background, and retry the original request with the solution.
889
+ // This makes the protection seamless for API clients that use this library.
890
+ handleChallenges: true
891
+ }
892
+ });
893
+ ```
894
+
895
+ ### Client-Side Behavioral Analysis
896
+
897
+ The following functions, available in `fingerprint.client.js`, allow for proactive, client-side detection of bot-like behavior. They collect metrics on user interaction which can be sent to the server for more accurate suspicion scoring. The server-side logic to interpret these metrics (via the `X-Behavior-Metrics` header) would need to be implemented as part of a custom scoring extension.
898
+
899
+ #### `startKeystrokeDynamicsTracker()`
900
+ *Client-side function only.* Starts tracking the timing between keystrokes. The average latency between key presses is a strong behavioral indicator. Humans have a natural, somewhat variable typing rhythm, whereas bots often simulate keystrokes with a fixed, unnaturally consistent delay, or paste text instantly (zero latency).
901
+
902
+ #### `startMouseEntropyTracker()`
903
+ *Client-side function only.* Starts tracking mouse movements on the page. It calculates a simple entropy score based on movement patterns. Human mouse movements are typically chaotic, whereas bots often have linear or no movement at all. This should be called once when your application's main component mounts.
904
+
905
+ #### `initializeHoneypots(fieldNames)`
906
+ *Client-side function only.* Sets up "traps" on hidden form fields. If a script automatically fills one of these fields, it's immediately flagged as a bot on the client side.
907
+
908
+ This provides a proactive, first-line defense against simple bots. By setting up traps directly in the browser, you can detect a bot the moment it interacts with a hidden field, rather than waiting for it to submit a form and consume server resources. This detection is then reported to the server via the `X-Behavior-Metrics` header, allowing for an immediate and efficient block.
909
+
910
+ - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
911
+
912
+ ### Advanced: Manual Wrapping with `protectedFetch`
913
+
914
+ If you prefer not to modify global functions or need fine-grained control over which requests are protected, you can use the `protectedFetch` wrapper. You must use this function instead of the standard `fetch` for your API calls.
915
+
916
+ - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
917
+ - `X-Device-Fingerprint`: The client's device fingerprint.
918
+ - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
919
+
920
+ **Example:**
921
+
922
+ ```javascript
923
+ import {
924
+ initializeClient,
925
+ protectedFetch
926
+ } from '@anonympins/fingerprint/client';
927
+
928
+ // Start tracking user behavior as soon as the app loads.
929
+ // Note: You still need to initialize the trackers even if you use protectedFetch manually.
930
+ initializeClient({ fetch: false }); // Disables automatic fetch patching
931
+
932
+ // Now, use protectedFetch for your specific API calls.
933
+ async function submitForm(data) {
934
+ const response = await protectedFetch('/api/submit-data', {
935
+ method: 'POST',
936
+ body: JSON.stringify(data),
937
+ headers: {'Content-Type': 'application/json'}
938
+ });
939
+ }
940
+ ```
941
+
942
+ ## Advanced Features
943
+
944
+ ### Architecture: `FingerprintEngine`
945
+
946
+ The core logic of the library is encapsulated within the `FingerprintEngine` class. The `powMiddleware` is essentially a lightweight wrapper that adapts this engine for use with Express.js.
947
+
948
+ The engine is responsible for:
949
+ 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
950
+ 2. Calculating the suspicion score using the configured weights.
951
+ 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
952
+
953
+ Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
954
+
955
+ ### Manual Integration (outside Express.js)
956
+
957
+ While `powMiddleware` is convenient for Express, you can use the `FingerprintEngine` directly in any Node.js server environment (e.g., native `http`, Fastify, Koa). This gives you full control over the request/response cycle.
958
+
959
+ **For concrete examples with Koa and Fastify, see our [Framework Integration Guide](https://github.com/anonympins/fingerprint/blob/main/INTEGRATION.md).**
960
+
961
+ The engine is a named export from the main module.
962
+
963
+ ### Useful Proof-of-Work (`ProblemManager`)
964
+
965
+ Instead of issuing a generic Proof-of-Work, the system can dispatch a "useful" computational problem to a suspicious client. This allows harnessing the client's CPU cycles to solve complex problems (like optimization tasks) over time. This feature is managed by the `ProblemManager` class, which is enabled via the `enableUsefulWork: true` flag in the security configuration. The state of these problems is persisted via the configured datastore, allowing a cluster of servers to collaborate on solving them.
966
+
967
+ The `ProblemManager` reads its configuration asynchronously from `problems.config.json`, which defines the problems to be solved, the type of work units, and the initial state of the solutions.
968
+
969
+ While you typically won't interact with it directly, its methods are exported and can be used for monitoring or manual administration. The main instance is exported as `problemManager`.
970
+
971
+ #### `problemManager.dispatchWork(suspicionFactor)`
972
+
973
+ Selects a problem and generates a work unit for a client. The difficulty of the task (e.g., number of iterations) is scaled based on the client's `suspicionFactor`.
974
+
975
+ * **`suspicionFactor`** (`number`): A factor to adjust the difficulty of the work unit.
976
+ * **Returns**: (`object|null`) An object containing the `problemId` and the `task` to be sent to the client, or `null` if no problems are available.
977
+
978
+ #### `problemManager.integrateSolution(problemId, solutionData)`
979
+
980
+ Integrates a solution returned by a client into the problem's state. If the new solution is better than the existing one, it is saved as the new best solution.
981
+
982
+ * **`problemId`** (`string`): The ID of the problem being updated.
983
+ * **`solutionData`** (`object`): The solution data returned by the client (e.g., `{ solution, energy }`).
984
+
985
+ #### `problemManager.getBestSolutions([problemId])`
986
+
987
+ Retrieves the best solution currently known for one or all problems. This is useful for creating an API endpoint to view the progress of the distributed computation.
988
+
989
+ * **`problemId`** (`string`, optional): The ID of a specific problem.
990
+ * **Returns**: (`object|Array<object>|null`)
991
+ * If a `problemId` is provided, it returns an object with the best solution for that problem (`{ id, solution, score, lastUpdate }`).
992
+ * If no `problemId` is provided, it returns an array of these objects for all problems.
993
+
994
+ **Example: Creating an API endpoint to view solutions**
995
+
996
+ ```javascript
997
+ import { problemManager } from '@anonympins/fingerprint'; // Adjust path
998
+
999
+ app.get('/api/problems/solutions', (req, res) => {
1000
+ const solutions = problemManager.getBestSolutions();
1001
+ res.json(solutions);
1002
+ });
1003
+
1004
+ ```
1005
+
1006
+ #### `getBestTuningSolution()`
1007
+
1008
+ Returns the last best solution object found by the auto-tuner. This is particularly useful for "FinOps" or for auditing the tuner's performance, as it allows you to log the exact configuration that the genetic algorithm identified as optimal.
1009
+
1010
+ * **Returns**: (`object|null`) The best solution object `{ solution, objectives }` or `null` if no tuning cycle has completed yet. The `solution` property contains the optimized `weights`, `thresholds`, and `patterns`, while `objectives` contains the performance scores (e.g., false positive/negative rates) for that solution.
1011
+
1012
+ #### `problemManager.updateProblemPayload(problemId, newPayload)`
1013
+
1014
+ Updates the payload (parameters) of a specific problem by its ID. This allows for dynamic adjustment of problem configurations without restarting the server. When the payload is updated, the problem's current best solution and energy are reset, forcing the system to find a new optimal solution for the modified problem.
1015
+
1016
+ * **`problemId`** (`string`): The ID of the problem to update.
1017
+ * **`newPayload`** (`object`): The new payload object that will replace the existing one.
1018
+ * **Returns**: (`boolean`) `true` if the update was successful, `false` otherwise.
1019
+
1020
+ **Example: Changing the number of facilities for `facility_location_challenge`**
1021
+
1022
+ ---
1023
+
1024
+ ## NodeJS raw integration
1025
+ **Workflow:**
1026
+
1027
+ 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
1028
+ 2. **Build the `requestContext`**: On each request, manually create a context object. It must include `clientIp`, `path`, `cookies`, `query`, `headers`, and mock `rawReq`/`rawRes` objects for cookie handling.
1029
+ 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
1030
+ 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
1031
+
1032
+ **Example with native Node.js `http` server:**
1033
+
1034
+ ```javascript
1035
+ import http from 'http';
1036
+ import { FingerprintEngine } from '@anonympins/fingerprint'; // Adjust path
1037
+
1038
+ const securityConfig = { /* ... your config ... */ };
1039
+ const engine = new FingerprintEngine(securityConfig);
1040
+
1041
+ const server = http.createServer(async (req, res) => {
1042
+ // 1. Manually build the context
1043
+ const requestContext = {
1044
+ clientIp: req.socket.remoteAddress,
1045
+ path: req.url.split('?')[0],
1046
+ cookies: {}, // Parse cookies from req.headers.cookie
1047
+ query: Object.fromEntries(new URL(req.url, `http://${req.headers.host}`).searchParams),
1048
+ headers: req.headers,
1049
+ rawReq: req, // Pass the raw request
1050
+ rawRes: res, // Pass the raw response for cookie setting
1051
+ };
1052
+
1053
+ // 2. Process and get a decision
1054
+ const decision = await engine.processRequest(requestContext);
1055
+
1056
+ // The decision object now contains the score and the raw suspicion vector.
1057
+ // You can use it for logging or custom logic.
1058
+ console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
1059
+
1060
+ // 3. Act on the decision
1061
+ if (decision.action === 'challenge') {
1062
+ res.writeHead(decision.status, { 'Content-Type': 'text/html' });
1063
+ res.end(decision.body);
1064
+ } else if (decision.action === 'redirect') {
1065
+ // The engine sets the cookie directly on `res` via `rawRes`
1066
+ res.writeHead(302, { 'Location': decision.path });
1067
+ res.end();
1068
+ } else { // 'next'
1069
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
1070
+ res.end('Welcome to the protected page!');
1071
+ }
1072
+ });
1073
+
1074
+ server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
1075
+ ```
1076
+
1077
+ ---
1078
+
1079
+ ## License
1080
+
835
1081
  This project is licensed under the MIT License. See the `LICENSE` file for more details.