@anonympins/fingerprint 0.2.2 → 0.2.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.
package/README.md CHANGED
@@ -1,651 +1,727 @@
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 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
- 2. **Suspicion Score Calculation**: Several indicators are analyzed to calculate a suspicion score:
18
- * **Header Anomalies**: Missing `User-Agent`, `Accept-Language`, etc.
19
- * **Device Behavior**: Rapid fingerprint changes (User-Agent rotation).
20
- * **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).
21
- * **Inconsistency**: A low similarity score between the current fingerprint and the one initially associated with the `device_id` (cookie theft detection).
22
- * **Cross-Layer Inconsistency**: Mismatches between client-side data (e.g., OS reported by the browser) and server-side headers (e.g., `User-Agent`).
23
- * **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.
24
- * **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
25
- 3. **Dynamic Challenge**: If the suspicion score exceeds a certain threshold, a challenge is presented to the user. The difficulty and type of challenge depend on the score:
26
- * **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.
27
- * **High Suspicion**: For the most suspicious requests, the system issues a high-difficulty combined CPU/Memory challenge. The architecture allows for plugging in more complex challenges like CAPTCHAs if needed.
28
- * **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, even if their suspicion score is low.
29
-
30
- 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:
31
- - **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.
32
- - **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).
33
-
34
- 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.
35
-
36
- ## Features
37
-
38
- - **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
39
- - **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
40
- - **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
41
- - **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`. The datastore must support setting a Time-To-Live (TTL) for challenge secrets.
42
- - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
43
- - **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.
44
- - **Automatic Parameter Tuning**: Includes a genetic algorithm-based optimizer (`startThresholdAutoTuning`) that analyzes real traffic to dynamically adjust not only suspicion thresholds (`low`, `medium`, `high`) but also the parameters for behavioral pattern detection, improving accuracy and reducing false positives over time.
45
-
46
- ## Installation and Usage
47
-
48
- This module is designed for a Node.js environment.
49
-
50
- ### Prerequisites
51
-
52
- 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`.
53
-
54
- ### Configuration
55
-
56
- Define a secret key for signing PoW tickets in your environment variables.
57
-
58
- ```bash
59
- export POW_SECRET="your_secret_key_of_at_least_32_characters"
60
- ```
61
-
62
- ### Integration Example
63
-
64
- The `powMiddleware` requires a configuration object defining the weights of suspicion indicators and the challenge trigger thresholds.
65
-
66
- All following `securityConfig` parameters are optional.
67
-
68
- ```javascript
69
- import express from 'express';
70
- import bodyParser from 'body-parser';
71
- import cookieParser from 'cookie-parser';
72
- import { powMiddleware, default_whitelist, default_analyzers } from './fingerprint.js'; // Adjust the path
73
-
74
- const app = express();
75
- app.use(cookieParser());
76
- app.use(bodyParser.json()); // For parsing application/json
77
- app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
78
-
79
- // Array to store traffic analysis data for the auto-tuner.
80
- // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
81
- const trafficData = [];
82
-
83
- // Configuration of weights and thresholds for calculating the suspicion score.
84
- // These values should be adjusted based on traffic and expected user behavior.
85
- const securityConfig = {
86
- weights: {
87
- historyScore: 0.3, // Penalizes IP rotation (proxy)
88
- rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
89
- headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
90
- requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
91
- inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
92
- behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
93
- honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
94
- crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
95
- timeInconsistencyScore: 0.9 // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
96
- },
97
- thresholds: {
98
- low: 20, // Score from which a CPU challenge is issued
99
- medium: 45, // Score for a more difficult combined CPU/Memory challenge
100
- high: 75, // Score for a very difficult challenge
101
- block: 95, // Score above which the request is blocked outright (HTTP 404)
102
- },
103
- cpu: {
104
- minDifficultyBits: 8,
105
- maxDifficultyBits: 24,
106
- },
107
- // (Optional) Configure the duration (in milliseconds) for various temporary data.
108
- ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
109
- challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
110
- deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
111
- challengePagePath: './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
112
- verbose: process.env.NODE_ENV !== 'production', // Log detailed info in development, but not in production.
113
- patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
114
- velocityThreshold: 800, // ms between requests to be considered "fast"
115
- burstThreshold: 1500, // ms for identical requests to be a "burst"
116
- scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
117
- historySize: 10, // Number of requests to keep for pattern analysis
118
- minSamples: 5, // Minimum number of timings to collect before statistical analysis.
119
- regularityThreshold: 50, // Standard deviation (ms) below which behavior is "too regular".
120
- benfordThreshold: 0.15, // Benford's Law deviation threshold above which the distribution is "unnatural".
121
- patternWeight: 80, // Strong, one-time penalty when a pattern is detected.
122
- decayFactor: 0.9, // Factor by which the pattern score decreases over time.
123
- inactivityReset: 5000, // Time (ms) after which the pattern score is reset.
124
- },
125
- honeypot: {
126
- // List of field names that are traps for bots.
127
- // These should be hidden in forms for humans, or be URL parameters your app never uses.
128
- fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
129
- // List of URL paths that should never be accessed by a legitimate user.
130
- // A request to one of these paths will immediately flag the device as malicious.
131
- trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
132
- // Automatically detect common injection patterns. Can be a boolean or an array of specific types.
133
- // - `true`: Enables all available detections (default).
134
- // - `false`: Disables injection detection.
135
- // - `['sql', 'rce']`: Enables only SQL injection and Remote Command Execution detection.
136
- detectInjections: ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
137
- // (Optional) Plug in external analyzers. This allows you to extend detection with specialized libraries or custom logic.
138
- // Each function receives an object with all query and body data and should return `true` if a threat is detected.
139
- analyzers: [
140
- ...default_analyzers(), // Includes the default XSS analyzer.
141
-
142
- // Example 2: Enable a powerful WAF with ModSecurity and the OWASP Core Rule Set.
143
- // Requires `npm install modsecurity-nodejs` and downloading the OWASP CRS rules.
144
- // modsecurity_analyzer('/path/to/owasp-crs/crs-setup.conf'),
145
-
146
- // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
147
- (data) => {
148
- const spamKeywords = ['viagra', 'free money', 'crypto pump'];
149
- const dataString = JSON.stringify(data).toLowerCase();
150
- return spamKeywords.some(keyword => dataString.includes(keyword));
151
- }
152
- ]
153
- },
154
- // (Optional) Whitelisting configuration.
155
- whitelist: [
156
- // Option 1: Static IP Allowlist.
157
- // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
158
- // Useful for internal tools, trusted partners, or monitoring services.
159
- // This check is performed first for maximum efficiency.
160
- { type: 'allowlist', entries: [
161
- '192.168.1.100', // A specific internal IP
162
- '203.0.113.0/24', // A partner's network range
163
- '2001:db8::/32' // An IPv6 range
164
- ]},
165
- { type: 'hostname_allowlist', entries: [
166
- 'google.com', // A specific hostname
167
- ]},
168
- // Option 2: DNS-verified bots (e.g., search engine crawlers).
169
- // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
170
- // The result is cached per IP to avoid repeated DNS lookups.
171
- // You can use the provided default list, which contains over 50 common bots, and extend it.
172
- ...default_whitelist(), // Use the defaults
173
- { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
174
- ],
175
- // Optional: Custom function to identify static resources
176
- isStaticResource: (req) => req.path.startsWith('/static/'),
177
- // Optional: Custom function to identify API requests
178
- isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json'),
179
- // The logger is required for auto-tuning. It collects data on requests.
180
- logger: (log) => trafficData.push(log),
181
- // (Optional) Configuration for the automatic threshold and pattern tuning.
182
- autotuning: {
183
- trafficData: trafficData, // The data source for the genetic algorithm.
184
- interval: 1800000, // Optimization cycle every 30 minutes (in ms).
185
- minDataPoints: 200, // Minimum requests before starting an optimization cycle.
186
- maxDataPoints: 20000 // Minimum requests before starting an optimization cycle.
187
- },
188
- // Enables problem solving for suspicious activity (configurable in problems.config.json)
189
- enableUsefulWork: true
190
- };
191
-
192
- // Create an instance of the middleware with your security configuration.
193
- const powMiddlewareInstance = powMiddleware(securityConfig);
194
-
195
- // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
196
- // to correctly retrieve the client's IP.
197
- app.set('trust proxy', 1);
198
-
199
- // Apply the protection middleware to all routes or to specific ones.
200
- app.use(powMiddlewareInstance);
201
-
202
- app.get('/', (req, res) => {
203
- res.send('Welcome to the protected page!');
204
- });
205
-
206
- // Example of accessing the suspicion score in a subsequent middleware or route.
207
- // The `fingerprint` object is attached to the request object by the middleware.
208
- app.use((req, res, next) => {
209
- console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
210
- next();
211
- });
212
-
213
- app.listen(3000, () => console.log('Server started on port 3000'));
214
- ```
215
-
216
-
217
- ---
218
-
219
- ## Advanced Behavioral Analysis
220
-
221
- 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.
222
-
223
- This function uses several configurable parameters to identify suspicious behavior:
224
-
225
- ### Core Pattern Detection
226
-
227
- These parameters form the basis of the request pattern analysis:
228
-
229
- * `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.
230
- * `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.
231
- * `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.
232
- * `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.
233
-
234
- ### Statistical Analysis (Benford's Law)
235
-
236
- To counter more advanced bots that might try to randomize their request timings, the engine employs statistical analysis based on Benford's Law.
237
-
238
- * **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.
239
-
240
- * `benfordMinSamples`: (Default: 15) The minimum number of request timings to collect before performing a Benford's Law test.
241
- * `benfordWeight`: (Default: 50) The weight applied to the suspicion score if the distribution of timings significantly deviates from Benford's Law.
242
-
243
- ### Configuration and Auto-Tuning
244
-
245
- All these parameters are part of the `patterns` object within the main security configuration and can be fine-tuned.
246
-
247
- ## Customizing the Challenge Page
248
-
249
- You can provide your own HTML template for the Proof-of-Work challenge page to maintain a consistent user experience with your brand.
250
-
251
- 1. **Configuration**: In your `securityConfig`, specify the path to your template file using the `challengePagePath` option.
252
-
253
- ```javascript
254
- const securityConfig = {
255
- // ... other options
256
- challengePagePath: './path/to/your/custom-challenge-page.html',
257
- };
258
- ```
259
-
260
- 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.
261
-
262
- * `<!-- FINGERPRINT_SOLVER_SCRIPT -->`: This will be replaced by the script that contains the logic for solving the CPU and memory challenges.
263
- * `<!-- 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.).
264
- * `<!-- FINGERPRINT_TRAPS -->`: This will be replaced by hidden "honeypot" links designed to trap simple bots. This placeholder is crucial for an effective defense.
265
-
266
- #### Example Custom HTML Template
267
-
268
- Here is a basic example of what your `custom-challenge-page.html` could look like:
269
-
270
- ```html
271
- <!DOCTYPE html>
272
- <html lang="en">
273
- <head>
274
- <meta charset="UTF-8">
275
- <title>Security Verification</title>
276
- <style>
277
- body { font-family: sans-serif; text-align: center; padding-top: 50px; }
278
- h1 { color: #333; }
279
- </style>
280
- </head>
281
- <body>
282
- <h1>Please wait while we verify your connection...</h1>
283
- <div id="loader" style="margin:20px;">⚙️ Initializing verification...</div>
284
-
285
- <script><!-- FINGERPRINT_SOLVER_SCRIPT --></script>
286
- <script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script>
287
- <!-- FINGERPRINT_TRAPS -->
288
- </body>
289
- </html>
290
- ```
291
- ## Public API
292
-
293
- In addition to the main middleware, several functions are exported to allow for more advanced integrations.
294
-
295
- ### Main Functions
296
-
297
- #### `powMiddleware(securityConfig)`
298
- The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
299
-
300
- #### `configureStore(store)`
301
- Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
302
- 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.
303
-
304
- **Redis Example:**
305
-
306
- ```javascript
307
- import { configureStore } from './fingerprint.js';
308
- import { createRedisStore } from './redis-store.js';
309
- import Redis from 'ioredis';
310
-
311
- const redisClient = new Redis(process.env.REDIS_URL);
312
- const redisStore = createRedisStore(redisClient);
313
- configureStore(redisStore);
314
- ```
315
-
316
- **MongoDB Example:**
317
-
318
- ```javascript
319
- import { configureStore } from './fingerprint.js';
320
- import { createMongoDbStore } from './mongodb-store.js';
321
- import { MongoClient } from 'mongodb';
322
-
323
- const mongoClient = new MongoClient(process.env.MONGODB_URL);
324
-
325
- // It's recommended to connect before your application starts listening.
326
- await mongoClient.connect();
327
-
328
- const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
329
- configureStore(mongoStore);
330
-
331
- // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
332
- // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
333
- // Run this command in the mongo shell:
334
- // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
335
- ```
336
-
337
- **SQL Example (with Knex.js):**
338
-
339
- ```javascript
340
- import { configureStore } from './fingerprint.js';
341
- import { createSqlStore } from './sql-store.js';
342
- import knex from 'knex';
343
-
344
- const knexClient = knex({
345
- client: 'pg', // or 'mysql', 'sqlite3', etc.
346
- connection: process.env.DATABASE_URL,
347
- });
348
-
349
- const sqlStore = createSqlStore(knexClient, 'fingerprint_sessions'); // 'fingerprint_sessions' is the table name
350
- configureStore(sqlStore);
351
-
352
- // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
353
- // your table must have an `expiresAt` column. The store will handle cleanup of expired rows,
354
- // but you must create the table yourself.
355
- // Example schema for PostgreSQL:
356
- // CREATE TABLE fingerprint_sessions (
357
- // "key" VARCHAR(255) PRIMARY KEY,
358
- // "value" TEXT NOT NULL,
359
- // "expiresAt" TIMESTAMPTZ
360
- // );
361
- ```
362
-
363
- #### `identifyRequest(req, res)`
364
- 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.
365
-
366
- ```javascript
367
- import { RateLimiterMemory } from 'rate-limiter-flexible';
368
- import { identifyRequest } from './fingerprint.js';
369
-
370
- const rateLimiter = new RateLimiterMemory({
371
- keyPrefix: 'rate_limit',
372
- points: 10,
373
- duration: 1,
374
- });
375
-
376
- app.use(async (req, res, next) => {
377
- try {
378
- const key = await identifyRequest(req, res);
379
- await rateLimiter.consume(key);
380
- next();
381
- } catch (err) {
382
- res.status(429).send('Too Many Requests');
383
- }
384
- });
385
- ```
386
-
387
- ### Utilities
388
-
389
- #### `isTicketValid(ip, ticket)`
390
- Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
391
-
392
- #### `FingerprintBuilder`
393
- A class for building granular server-side fingerprints.
394
-
395
- ```javascript
396
- const builder = new FingerprintBuilder();
397
- builder.add("ua", req.headers["user-agent"]);
398
- builder.add("os", req.headers["sec-ch-ua-platform"]);
399
- const fp = builder.toString(); // "os:hash1|ua:hash2"
400
- ```
401
-
402
- #### `getDeviceFingerprint()`
403
- *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
404
- This is the primary function for client-side identification.
405
-
406
- #### `generateRequestSignature(payload)`
407
- *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.
408
-
409
- ```javascript
410
- // On the client
411
- const signature = generateRequestSignature({ action: 'update', id: 123 });
412
- // Send signature in headers...
413
- ```
414
-
415
- #### `generateClientSideSignature(payload, secret)`
416
- *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
417
- **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.
418
-
419
- ---
420
-
421
- ## Why Use the Client-Side Library? The Client + Server Synergy
422
-
423
- 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.
424
-
425
- Imagine your server is a fortified castle:
426
-
427
- - **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.
428
- - **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.
429
-
430
- The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
431
-
432
- 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.
433
- 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.
434
- 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.
435
-
436
- ### Strengths at a Glance
437
-
438
- | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
439
- | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
440
- | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
441
- | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
442
- | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
443
- | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
444
- | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
445
-
446
- In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
447
-
448
- ### Client-Side Integration: The `initializeClient` function
449
-
450
- To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
451
-
452
- ```javascript
453
- import { initializeClient } from './path/to/fingerprint.client.js';
454
-
455
- /**
456
- * Initializes all client-side protections.
457
- * This is the recommended way to set up the client-side library.
458
- */
459
- initializeClient({
460
- // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
461
- // Set to `false` to disable.
462
- mouse: true,
463
-
464
- // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
465
- // Set to `false` to disable.
466
- keystrokes: true,
467
-
468
- // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
469
- honeypots: ['email_confirm', 'user_nickname', 'website_url'],
470
-
471
- // (Optional) Enables automatic protection for `fetch` requests.
472
- // If the `fetch` object is present, the protection is active.
473
- fetch: {
474
- // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
475
- targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
476
-
477
- // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
478
- // solve the PoW in the background, and retry the original request with the solution.
479
- // This makes the protection seamless for API clients that use this library.
480
- handleChallenges: true
481
- }
482
- });
483
- ```
484
-
485
- ### Client-Side Behavioral Analysis
486
-
487
- 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.
488
-
489
- #### `startKeystrokeDynamicsTracker()`
490
- *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).
491
-
492
- #### `startMouseEntropyTracker()`
493
- *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.
494
-
495
- #### `initializeHoneypots(fieldNames)`
496
- *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.
497
-
498
- 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.
499
-
500
- - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
501
-
502
- ### Advanced: Manual Wrapping with `protectedFetch`
503
-
504
- 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.
505
-
506
- - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
507
- - `X-Device-Fingerprint`: The client's device fingerprint.
508
- - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
509
-
510
- **Example:**
511
-
512
- ```javascript
513
- import {
514
- initializeClient,
515
- protectedFetch
516
- } from './path/to/fingerprint.client.js';
517
-
518
- // Start tracking user behavior as soon as the app loads.
519
- // Note: You still need to initialize the trackers even if you use protectedFetch manually.
520
- initializeClient({ fetch: false }); // Disables automatic fetch patching
521
-
522
- // Now, use protectedFetch for your specific API calls.
523
- async function submitForm(data) {
524
- const response = await protectedFetch('/api/submit-data', {
525
- method: 'POST',
526
- body: JSON.stringify(data),
527
- headers: {'Content-Type': 'application/json'}
528
- });
529
- }
530
- ```
531
-
532
- ## Advanced Features
533
-
534
- ### Architecture: `FingerprintEngine`
535
-
536
- 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.
537
-
538
- The engine is responsible for:
539
- 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
540
- 2. Calculating the suspicion score using the configured weights.
541
- 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
542
-
543
- Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
544
-
545
- ### Manual Integration (outside Express.js)
546
-
547
- 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.
548
-
549
- **For concrete examples with Koa and Fastify, see our [Framework Integration Guide](https://github.com/anonympins/fingerprint/blob/main/INTEGRATION.md).**
550
-
551
- The engine is a named export from the main module.
552
-
553
- ### Useful Proof-of-Work (`ProblemManager`)
554
-
555
- 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.
556
-
557
- The `ProblemManager` reads its configuration from `problems.config.json`, which defines the problems to be solved, the type of work units, and the current state of the solutions.
558
-
559
- 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`.
560
-
561
- #### `problemManager.dispatchWork(suspicionFactor)`
562
-
563
- 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`.
564
-
565
- * **`suspicionFactor`** (`number`): A factor to adjust the difficulty of the work unit.
566
- * **Returns**: (`object|null`) An object containing the `problemId` and the `task` to be sent to the client, or `null` if no problems are available.
567
-
568
- #### `problemManager.integrateSolution(problemId, solutionData)`
569
-
570
- 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.
571
-
572
- * **`problemId`** (`string`): The ID of the problem being updated.
573
- * **`solutionData`** (`object`): The solution data returned by the client (e.g., `{ solution, energy }`).
574
-
575
- #### `problemManager.getBestSolutions([problemId])`
576
-
577
- 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.
578
-
579
- * **`problemId`** (`string`, optional): The ID of a specific problem.
580
- * **Returns**: (`object|Array<object>|null`)
581
- * If a `problemId` is provided, it returns an object with the best solution for that problem (`{ id, solution, score, lastUpdate }`).
582
- * If no `problemId` is provided, it returns an array of these objects for all problems.
583
-
584
- **Example: Creating an API endpoint to view solutions**
585
-
586
- ```javascript
587
- import { problemManager } from './fingerprint.js'; // Adjust path
588
-
589
- app.get('/api/problems/solutions', (req, res) => {
590
- const solutions = problemManager.getBestSolutions();
591
- res.json(solutions);
592
- });
593
- ```
594
-
595
- **Workflow:**
596
-
597
- 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
598
- 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.
599
- 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
600
- 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
601
-
602
- **Example with native Node.js `http` server:**
603
-
604
- ```javascript
605
- import http from 'http';
606
- import { FingerprintEngine } from './fingerprint.js'; // Adjust path
607
-
608
- const securityConfig = { /* ... your config ... */ };
609
- const engine = new FingerprintEngine(securityConfig);
610
-
611
- const server = http.createServer(async (req, res) => {
612
- // 1. Manually build the context
613
- const requestContext = {
614
- clientIp: req.socket.remoteAddress,
615
- path: req.url.split('?')[0],
616
- cookies: {}, // Parse cookies from req.headers.cookie
617
- query: Object.fromEntries(new URL(req.url, `http://${req.headers.host}`).searchParams),
618
- headers: req.headers,
619
- rawReq: req, // Pass the raw request
620
- rawRes: res, // Pass the raw response for cookie setting
621
- };
622
-
623
- // 2. Process and get a decision
624
- const decision = await engine.processRequest(requestContext);
625
-
626
- // The decision object now contains the score and the raw suspicion vector.
627
- // You can use it for logging or custom logic.
628
- console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
629
-
630
- // 3. Act on the decision
631
- if (decision.action === 'challenge') {
632
- res.writeHead(decision.status, { 'Content-Type': 'text/html' });
633
- res.end(decision.body);
634
- } else if (decision.action === 'redirect') {
635
- // The engine sets the cookie directly on `res` via `rawRes`
636
- res.writeHead(302, { 'Location': decision.path });
637
- res.end();
638
- } else { // 'next'
639
- res.writeHead(200, { 'Content-Type': 'text/plain' });
640
- res.end('Welcome to the protected page!');
641
- }
642
- });
643
-
644
- server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
645
- ```
646
-
647
- ---
648
-
649
- ## License
650
-
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 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 to the user. 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. 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, even if their suspicion score is low.
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`. The datastore must support setting a Time-To-Live (TTL) for challenge secrets.
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 not only suspicion thresholds (`low`, `medium`, `high`) but also the parameters for behavioral pattern detection, improving accuracy and reducing false positives over time.
50
+
51
+ ## Installation and Usage
52
+
53
+ This module is designed for a Node.js environment.
54
+
55
+ ### Prerequisites
56
+
57
+ 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`.
58
+
59
+ ### Configuration
60
+
61
+ Define a secret key for signing PoW tickets in your environment variables.
62
+
63
+ ```bash
64
+ export POW_SECRET="your_secret_key_of_at_least_32_characters"
65
+ ```
66
+
67
+ ### Integration Example
68
+
69
+ 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.
70
+
71
+ Available profiles:
72
+ - `balanced` (default): A general-purpose configuration suitable for most websites.
73
+ - `strict`: A more aggressive configuration for sensitive applications.
74
+ - `api`: Optimized for protecting API endpoints, with a higher sensitivity to request patterns.
75
+ - `blog`: Tuned to detect content scraping and comment spam.
76
+ - `ecommerce`: A strict profile focused on preventing inventory scalping, price scraping, and account takeover.
77
+
78
+ ```javascript
79
+ import express from 'express';
80
+ import bodyParser from 'body-parser';
81
+ import cookieParser from 'cookie-parser';
82
+ import { powMiddleware, createSecurityProfile } from './fingerprint.js'; // Adjust the path
83
+
84
+ const app = express();
85
+ app.use(cookieParser());
86
+ app.use(bodyParser.json());
87
+ app.use(bodyParser.urlencoded({ extended: true }));
88
+
89
+ // Array to store traffic analysis data for the auto-tuner.
90
+ const trafficData = [];
91
+
92
+ // 1. Choose a base profile (e.g., 'balanced', 'strict', 'api').
93
+ // 2. (Optional) Define your custom overrides. These will be deeply merged with the base profile.
94
+ const securityConfig = createSecurityProfile('api', {
95
+ // Example of overriding a specific threshold from the 'balanced' profile.
96
+ thresholds: {
97
+ low: 25, // Make the initial challenge slightly harder.
98
+ },
99
+ // Example of adding a custom whitelisting rule.
100
+ whitelist: [
101
+ { type: 'path_allowlist', entries: ['/api/v1/public-stats'] }
102
+ ],
103
+ // The logger is required if you enable auto-tuning.
104
+ logger: (log) => trafficData.push(log),
105
+ autotuning: {
106
+ trafficData: trafficData,
107
+ interval: 1800000, // 30 minutes
108
+ minDataPoints: 200,
109
+ }
110
+ });
111
+
112
+ // Create an instance of the middleware with your security configuration.
113
+ const powMiddlewareInstance = powMiddleware(securityConfig);
114
+
115
+ // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
116
+ // to correctly retrieve the client's IP.
117
+ app.set('trust proxy', 1);
118
+
119
+ // Apply the protection middleware to all routes or to specific ones.
120
+ app.use(powMiddlewareInstance);
121
+
122
+ app.get('/', (req, res) => {
123
+ res.send('Welcome to the protected page!');
124
+ });
125
+
126
+ // Example of accessing the suspicion score in a subsequent middleware or route.
127
+ // The `fingerprint` object is attached to the request object by the middleware.
128
+ app.use((req, res, next) => {
129
+ console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
130
+ next();
131
+ });
132
+
133
+ app.listen(3000, () => console.log('Server started on port 3000'));
134
+ ```
135
+
136
+ ### Full Configuration Example
137
+
138
+ 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.
139
+
140
+ ```javascript
141
+ import { default_whitelist, default_analyzers } from './fingerprint.js';
142
+
143
+ const app = express();
144
+ app.use(cookieParser());
145
+ app.use(bodyParser.json()); // For parsing application/json
146
+ app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
147
+
148
+ // Array to store traffic analysis data for the auto-tuner.
149
+ // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
150
+ const trafficData = [];
151
+
152
+ // Configuration of weights and thresholds for calculating the suspicion score.
153
+ // These values should be adjusted based on traffic and expected user behavior.
154
+ const securityConfig = {
155
+ weights: {
156
+ historyScore: 0.3, // Penalizes IP rotation (proxy)
157
+ rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
158
+ headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
159
+ requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
160
+ inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
161
+ behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
162
+ honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
163
+ crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
164
+ timeInconsistencyScore: 0.9, // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
165
+ tlsSpoofingScore: 0.8 // Penalizes mismatches between the TLS fingerprint (JA3/JA4) and the User-Agent (client spoofing)
166
+ },
167
+ thresholds: {
168
+ low: 20, // Score from which a CPU challenge is issued
169
+ medium: 45, // Score for a more difficult combined CPU/Memory challenge
170
+ high: 75, // Score for a very difficult challenge
171
+ block: 95, // Score above which the request is blocked outright (HTTP 404)
172
+ },
173
+ cpu: {
174
+ minDifficultyBits: 8,
175
+ maxDifficultyBits: 24,
176
+ },
177
+ // (Optional) Configure the duration (in milliseconds) for various temporary data.
178
+ ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
179
+ challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
180
+ deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
181
+ challengePagePath: './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
182
+ verbose: process.env.NODE_ENV !== 'production', // Log detailed info in development, but not in production.
183
+ patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
184
+ velocityThreshold: 800, // ms between requests to be considered "fast"
185
+ burstThreshold: 1500, // ms for identical requests to be a "burst"
186
+ scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
187
+ historySize: 10, // Number of requests to keep for pattern analysis
188
+ minSamples: 5, // Minimum number of timings to collect before statistical analysis.
189
+ regularityThreshold: 50, // Standard deviation (ms) below which behavior is "too regular".
190
+ benfordThreshold: 0.15, // Benford's Law deviation threshold above which the distribution is "unnatural".
191
+ patternWeight: 80, // Strong, one-time penalty when a pattern is detected.
192
+ decayFactor: 0.9, // Factor by which the pattern score decreases over time.
193
+ inactivityReset: 5000, // Time (ms) after which the pattern score is reset.
194
+ },
195
+ honeypot: {
196
+ // List of field names that are traps for bots.
197
+ // These should be hidden in forms for humans, or be URL parameters your app never uses.
198
+ fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
199
+ // List of URL paths that should never be accessed by a legitimate user.
200
+ // A request to one of these paths will immediately flag the device as malicious.
201
+ trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
202
+ // Automatically detect common injection patterns. Can be a boolean or an array of specific types.
203
+ // - `true`: Enables all available detections (default).
204
+ // - `false`: Disables injection detection.
205
+ // - `['sql', 'rce']`: Enables only SQL injection and Remote Command Execution detection.
206
+ detectInjections: ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
207
+ // (Optional) Plug in external analyzers. This allows you to extend detection with specialized libraries or custom logic.
208
+ // Each function receives an object with all query and body data and should return `true` if a threat is detected.
209
+ analyzers: [
210
+ ...default_analyzers(), // Includes the default XSS analyzer.
211
+
212
+ // Example 2: Enable a powerful WAF with ModSecurity and the OWASP Core Rule Set.
213
+ // Requires `npm install modsecurity-nodejs` and downloading the OWASP CRS rules.
214
+ // modsecurity_analyzer('/path/to/owasp-crs/crs-setup.conf'),
215
+
216
+ // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
217
+ (data) => {
218
+ const spamKeywords = ['viagra', 'free money', 'crypto pump'];
219
+ const dataString = JSON.stringify(data).toLowerCase();
220
+ return spamKeywords.some(keyword => dataString.includes(keyword));
221
+ }
222
+ ]
223
+ },
224
+ // (Optional) Whitelisting configuration.
225
+ whitelist: [
226
+ // Option 1: Static IP Allowlist.
227
+ // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
228
+ // Useful for internal tools, trusted partners, or monitoring services.
229
+ // This check is performed first for maximum efficiency.
230
+ { type: 'allowlist', entries: [
231
+ '192.168.1.100', // A specific internal IP
232
+ '203.0.113.0/24', // A partner's network range
233
+ '2001:db8::/32' // An IPv6 range
234
+ ]},
235
+ { type: 'hostname_allowlist', entries: [
236
+ 'google.com', // A specific hostname
237
+ ]},
238
+ // Option 3: Host + Path Allowlist.
239
+ // Bypasses checks for specific URL paths on specific hostnames. This is ideal for whitelisting
240
+ // an API endpoint on one domain but not another. The entry is a combination of the host
241
+ // header and the path. Supports wildcards (*) at the end of the path.
242
+ { type: 'host_path_allowlist', entries: [
243
+ 'web.primals.net/api/*', // All paths starting with /api2 on web.primal.net
244
+ ]},
245
+ // Option 3: Path Allowlist.
246
+ // Bypasses checks for specific URL paths. This is useful for trusted API endpoints, webhooks, or static content paths
247
+ // that don't need protection. Supports wildcards (*) at the end of an entry.
248
+ { type: 'path_allowlist', entries: [
249
+ '/api/v1/webhooks/trusted-source', // Exact path
250
+ '/api/v2/public/*', // All paths starting with /api/v2/public/
251
+ ]},
252
+ // Option 2: DNS-verified bots (e.g., search engine crawlers).
253
+ // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
254
+ // The result is cached per IP to avoid repeated DNS lookups.
255
+ // You can use the provided default list, which contains over 50 common bots, and extend it.
256
+ ...default_whitelist(), // Use the defaults
257
+ { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
258
+ ],
259
+ // Optional: Custom function to identify static resources
260
+ isStaticResource: (req) => req.path.startsWith('/static/'),
261
+ // Optional: Custom function to identify API requests
262
+ isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json'),
263
+ // The logger is required for auto-tuning. It collects data on requests.
264
+ logger: (log) => trafficData.push(log),
265
+ // (Optional) Configuration for the automatic threshold and pattern tuning.
266
+ autotuning: {
267
+ trafficData: trafficData, // The data source for the genetic algorithm.
268
+ interval: 1800000, // Optimization cycle every 30 minutes (in ms).
269
+ minDataPoints: 200, // Minimum requests before starting an optimization cycle.
270
+ maxDataPoints: 20000 // Minimum requests before starting an optimization cycle.
271
+ },
272
+ // Enables problem solving for suspicious activity (configurable in problems.config.json)
273
+ enableUsefulWork: true
274
+ };
275
+ ```
276
+
277
+
278
+ ---
279
+
280
+ ## Advanced Behavioral Analysis
281
+
282
+ 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.
283
+
284
+ This function uses several configurable parameters to identify suspicious behavior:
285
+
286
+ ### Core Pattern Detection
287
+
288
+ These parameters form the basis of the request pattern analysis:
289
+
290
+ * `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.
291
+ * `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.
292
+ * `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.
293
+ * `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.
294
+
295
+ ### Statistical Analysis (Benford's Law)
296
+
297
+ To counter more advanced bots that might try to randomize their request timings, the engine employs statistical analysis based on Benford's Law.
298
+
299
+ * **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.
300
+
301
+ * `benfordMinSamples`: (Default: 15) The minimum number of request timings to collect before performing a Benford's Law test.
302
+ * `benfordWeight`: (Default: 50) The weight applied to the suspicion score if the distribution of timings significantly deviates from Benford's Law.
303
+
304
+ ### Configuration and Auto-Tuning
305
+
306
+ All these parameters are part of the `patterns` object within the main security configuration and can be fine-tuned.
307
+
308
+ ## Customizing the Challenge Page
309
+
310
+ You can provide your own HTML template for the Proof-of-Work challenge page to maintain a consistent user experience with your brand.
311
+
312
+ 1. **Configuration**: In your `securityConfig`, specify the path to your template file using the `challengePagePath` option.
313
+
314
+ ```javascript
315
+ const securityConfig = {
316
+ // ... other options
317
+ challengePagePath: './path/to/your/custom-challenge-page.html',
318
+ };
319
+ ```
320
+
321
+ 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.
322
+
323
+ * `<!-- FINGERPRINT_SOLVER_SCRIPT -->`: This will be replaced by the script that contains the logic for solving the CPU and memory challenges.
324
+ * `<!-- 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.).
325
+ * `<!-- FINGERPRINT_TRAPS -->`: This will be replaced by hidden "honeypot" links designed to trap simple bots. This placeholder is crucial for an effective defense.
326
+
327
+ #### Example Custom HTML Template
328
+
329
+ Here is a basic example of what your `custom-challenge-page.html` could look like:
330
+
331
+ ```html
332
+ <!DOCTYPE html>
333
+ <html lang="en">
334
+ <head>
335
+ <meta charset="UTF-8">
336
+ <title>Security Verification</title>
337
+ <style>
338
+ body { font-family: sans-serif; text-align: center; padding-top: 50px; }
339
+ h1 { color: #333; }
340
+ </style>
341
+ </head>
342
+ <body>
343
+ <h1>Please wait while we verify your connection...</h1>
344
+ <div id="loader" style="margin:20px;">⚙️ Initializing verification...</div>
345
+
346
+ <script><!-- FINGERPRINT_SOLVER_SCRIPT --></script>
347
+ <script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script>
348
+ <!-- FINGERPRINT_TRAPS -->
349
+ </body>
350
+ </html>
351
+ ```
352
+ ## Public API
353
+
354
+ In addition to the main middleware, several functions are exported to allow for more advanced integrations.
355
+
356
+ ### Main Functions
357
+
358
+ #### `powMiddleware(securityConfig)`
359
+ The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
360
+
361
+ #### `configureStore(store)`
362
+ Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
363
+ 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.
364
+
365
+ **Redis Example:**
366
+
367
+ ```javascript
368
+ import { configureStore } from './fingerprint.js';
369
+ import { createRedisStore } from './redis-store.js';
370
+ import Redis from 'ioredis';
371
+
372
+ const redisClient = new Redis(process.env.REDIS_URL);
373
+ const redisStore = createRedisStore(redisClient);
374
+ configureStore(redisStore);
375
+ ```
376
+
377
+ **MongoDB Example:**
378
+
379
+ ```javascript
380
+ import { configureStore } from './fingerprint.js';
381
+ import { createMongoDbStore } from './mongodb-store.js';
382
+ import { MongoClient } from 'mongodb';
383
+
384
+ const mongoClient = new MongoClient(process.env.MONGODB_URL);
385
+
386
+ // It's recommended to connect before your application starts listening.
387
+ await mongoClient.connect();
388
+
389
+ const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
390
+ configureStore(mongoStore);
391
+
392
+ // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
393
+ // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
394
+ // Run this command in the mongo shell:
395
+ // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
396
+ ```
397
+
398
+ **SQL Example (with Knex.js):**
399
+
400
+ ```javascript
401
+ import { configureStore } from './fingerprint.js';
402
+ import { createSqlStore } from './sql-store.js';
403
+ import knex from 'knex';
404
+
405
+ const knexClient = knex({
406
+ client: 'pg', // or 'mysql', 'sqlite3', etc.
407
+ connection: process.env.DATABASE_URL,
408
+ });
409
+
410
+ const sqlStore = createSqlStore(knexClient, 'fingerprint_sessions'); // 'fingerprint_sessions' is the table name
411
+ configureStore(sqlStore);
412
+
413
+ // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
414
+ // your table must have an `expiresAt` column. The store will handle cleanup of expired rows,
415
+ // but you must create the table yourself.
416
+ // Example schema for PostgreSQL:
417
+ // CREATE TABLE fingerprint_sessions (
418
+ // "key" VARCHAR(255) PRIMARY KEY,
419
+ // "value" TEXT NOT NULL,
420
+ // "expiresAt" TIMESTAMPTZ
421
+ // );
422
+ ```
423
+
424
+ #### `identifyRequest(req, res)`
425
+ 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.
426
+
427
+ ```javascript
428
+ import { RateLimiterMemory } from 'rate-limiter-flexible';
429
+ import { identifyRequest } from './fingerprint.js';
430
+
431
+ const rateLimiter = new RateLimiterMemory({
432
+ keyPrefix: 'rate_limit',
433
+ points: 10,
434
+ duration: 1,
435
+ });
436
+
437
+ app.use(async (req, res, next) => {
438
+ try {
439
+ const key = await identifyRequest(req, res);
440
+ await rateLimiter.consume(key);
441
+ next();
442
+ } catch (err) {
443
+ res.status(429).send('Too Many Requests');
444
+ }
445
+ });
446
+ ```
447
+
448
+ ### Utilities
449
+
450
+ #### `isTicketValid(ip, ticket)`
451
+ Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
452
+
453
+ #### `FingerprintBuilder`
454
+ A class for building granular server-side fingerprints.
455
+
456
+ ```javascript
457
+ const builder = new FingerprintBuilder();
458
+ builder.add("ua", req.headers["user-agent"]);
459
+ builder.add("os", req.headers["sec-ch-ua-platform"]);
460
+ const fp = builder.toString(); // "os:hash1|ua:hash2"
461
+ ```
462
+
463
+ #### `getDeviceFingerprint()`
464
+ *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
465
+ This is the primary function for client-side identification.
466
+
467
+ #### `generateRequestSignature(payload)`
468
+ *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.
469
+
470
+ ```javascript
471
+ // On the client
472
+ const signature = generateRequestSignature({ action: 'update', id: 123 });
473
+ // Send signature in headers...
474
+ ```
475
+
476
+ #### `generateClientSideSignature(payload, secret)`
477
+ *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
478
+ **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.
479
+
480
+ ---
481
+
482
+ ## Why Use the Client-Side Library? The Client + Server Synergy
483
+
484
+ 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.
485
+
486
+ Imagine your server is a fortified castle:
487
+
488
+ - **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.
489
+ - **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.
490
+
491
+ The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
492
+
493
+ 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.
494
+ 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.
495
+ 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.
496
+
497
+ ### Strengths at a Glance
498
+
499
+ | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
500
+ | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
501
+ | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
502
+ | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
503
+ | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
504
+ | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
505
+ | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
506
+
507
+ In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
508
+
509
+ ### Client-Side Integration: The `initializeClient` function
510
+
511
+ To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
512
+
513
+ ```javascript
514
+ import { initializeClient } from './path/to/fingerprint.client.js';
515
+
516
+ /**
517
+ * Initializes all client-side protections.
518
+ * This is the recommended way to set up the client-side library.
519
+ */
520
+ initializeClient({
521
+ // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
522
+ // Set to `false` to disable.
523
+ mouse: true,
524
+
525
+ // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
526
+ // Set to `false` to disable.
527
+ keystrokes: true,
528
+
529
+ // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
530
+ honeypots: ['email_confirm', 'user_nickname', 'website_url'],
531
+
532
+ // (Optional) Enables automatic protection for `fetch` requests.
533
+ // If the `fetch` object is present, the protection is active.
534
+ fetch: {
535
+ // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
536
+ targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
537
+
538
+ // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
539
+ // solve the PoW in the background, and retry the original request with the solution.
540
+ // This makes the protection seamless for API clients that use this library.
541
+ handleChallenges: true
542
+ }
543
+ });
544
+ ```
545
+
546
+ ### Client-Side Behavioral Analysis
547
+
548
+ 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.
549
+
550
+ #### `startKeystrokeDynamicsTracker()`
551
+ *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).
552
+
553
+ #### `startMouseEntropyTracker()`
554
+ *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.
555
+
556
+ #### `initializeHoneypots(fieldNames)`
557
+ *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.
558
+
559
+ 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.
560
+
561
+ - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
562
+
563
+ ### Advanced: Manual Wrapping with `protectedFetch`
564
+
565
+ 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.
566
+
567
+ - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
568
+ - `X-Device-Fingerprint`: The client's device fingerprint.
569
+ - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
570
+
571
+ **Example:**
572
+
573
+ ```javascript
574
+ import {
575
+ initializeClient,
576
+ protectedFetch
577
+ } from './path/to/fingerprint.client.js';
578
+
579
+ // Start tracking user behavior as soon as the app loads.
580
+ // Note: You still need to initialize the trackers even if you use protectedFetch manually.
581
+ initializeClient({ fetch: false }); // Disables automatic fetch patching
582
+
583
+ // Now, use protectedFetch for your specific API calls.
584
+ async function submitForm(data) {
585
+ const response = await protectedFetch('/api/submit-data', {
586
+ method: 'POST',
587
+ body: JSON.stringify(data),
588
+ headers: {'Content-Type': 'application/json'}
589
+ });
590
+ }
591
+ ```
592
+
593
+ ## Advanced Features
594
+
595
+ ### Architecture: `FingerprintEngine`
596
+
597
+ 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.
598
+
599
+ The engine is responsible for:
600
+ 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
601
+ 2. Calculating the suspicion score using the configured weights.
602
+ 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
603
+
604
+ Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
605
+
606
+ ### Manual Integration (outside Express.js)
607
+
608
+ 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.
609
+
610
+ **For concrete examples with Koa and Fastify, see our [Framework Integration Guide](https://github.com/anonympins/fingerprint/blob/main/INTEGRATION.md).**
611
+
612
+ The engine is a named export from the main module.
613
+
614
+ ### Useful Proof-of-Work (`ProblemManager`)
615
+
616
+ 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.
617
+
618
+ The `ProblemManager` reads its configuration from `problems.config.json`, which defines the problems to be solved, the type of work units, and the current state of the solutions.
619
+
620
+ 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`.
621
+
622
+ #### `problemManager.dispatchWork(suspicionFactor)`
623
+
624
+ 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`.
625
+
626
+ * **`suspicionFactor`** (`number`): A factor to adjust the difficulty of the work unit.
627
+ * **Returns**: (`object|null`) An object containing the `problemId` and the `task` to be sent to the client, or `null` if no problems are available.
628
+
629
+ #### `problemManager.integrateSolution(problemId, solutionData)`
630
+
631
+ 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.
632
+
633
+ * **`problemId`** (`string`): The ID of the problem being updated.
634
+ * **`solutionData`** (`object`): The solution data returned by the client (e.g., `{ solution, energy }`).
635
+
636
+ #### `problemManager.getBestSolutions([problemId])`
637
+
638
+ 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.
639
+
640
+ * **`problemId`** (`string`, optional): The ID of a specific problem.
641
+ * **Returns**: (`object|Array<object>|null`)
642
+ * If a `problemId` is provided, it returns an object with the best solution for that problem (`{ id, solution, score, lastUpdate }`).
643
+ * If no `problemId` is provided, it returns an array of these objects for all problems.
644
+
645
+ **Example: Creating an API endpoint to view solutions**
646
+
647
+ ```javascript
648
+ import { problemManager } from './fingerprint.js'; // Adjust path
649
+
650
+ app.get('/api/problems/solutions', (req, res) => {
651
+ const solutions = problemManager.getBestSolutions();
652
+ res.json(solutions);
653
+ });
654
+
655
+ ```
656
+
657
+
658
+ #### `problemManager.updateProblemPayload(problemId, newPayload)`
659
+
660
+ 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.
661
+
662
+ * **`problemId`** (`string`): The ID of the problem to update.
663
+ * **`newPayload`** (`object`): The new payload object that will replace the existing one.
664
+ * **Returns**: (`boolean`) `true` if the update was successful, `false` otherwise.
665
+
666
+ **Example: Changing the number of facilities for `facility_location_challenge`**
667
+
668
+ ---
669
+
670
+ ## NodeJS raw integration
671
+ **Workflow:**
672
+
673
+ 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
674
+ 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.
675
+ 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
676
+ 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
677
+
678
+ **Example with native Node.js `http` server:**
679
+
680
+ ```javascript
681
+ import http from 'http';
682
+ import { FingerprintEngine } from './fingerprint.js'; // Adjust path
683
+
684
+ const securityConfig = { /* ... your config ... */ };
685
+ const engine = new FingerprintEngine(securityConfig);
686
+
687
+ const server = http.createServer(async (req, res) => {
688
+ // 1. Manually build the context
689
+ const requestContext = {
690
+ clientIp: req.socket.remoteAddress,
691
+ path: req.url.split('?')[0],
692
+ cookies: {}, // Parse cookies from req.headers.cookie
693
+ query: Object.fromEntries(new URL(req.url, `http://${req.headers.host}`).searchParams),
694
+ headers: req.headers,
695
+ rawReq: req, // Pass the raw request
696
+ rawRes: res, // Pass the raw response for cookie setting
697
+ };
698
+
699
+ // 2. Process and get a decision
700
+ const decision = await engine.processRequest(requestContext);
701
+
702
+ // The decision object now contains the score and the raw suspicion vector.
703
+ // You can use it for logging or custom logic.
704
+ console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
705
+
706
+ // 3. Act on the decision
707
+ if (decision.action === 'challenge') {
708
+ res.writeHead(decision.status, { 'Content-Type': 'text/html' });
709
+ res.end(decision.body);
710
+ } else if (decision.action === 'redirect') {
711
+ // The engine sets the cookie directly on `res` via `rawRes`
712
+ res.writeHead(302, { 'Location': decision.path });
713
+ res.end();
714
+ } else { // 'next'
715
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
716
+ res.end('Welcome to the protected page!');
717
+ }
718
+ });
719
+
720
+ server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
721
+ ```
722
+
723
+ ---
724
+
725
+ ## License
726
+
651
727
  This project is licensed under the MIT License. See the `LICENSE` file for more details.