@anonympins/fingerprint 0.1.0 → 0.1.2

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,494 +1,495 @@
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 initial one associated with the `device_id` (cookie theft detection).
22
- * **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.
23
- * **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
24
- 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:
25
- * **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.
26
- * **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.
27
-
28
- Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges for a set period. For API clients, the challenge is delivered as a `429` JSON response, and the client is expected to solve it and retry the request.
29
-
30
- ## Features
31
-
32
- - **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
33
- - **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
34
- - **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
35
- - **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`. The datastore must support setting a Time-To-Live (TTL) for challenge secrets.
36
- - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure ticket validation.
37
- - **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.
38
- - **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.
39
-
40
- ## Installation and Usage
41
-
42
- This module is designed for a Node.js environment.
43
-
44
- ### Prerequisites
45
-
46
- 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`.
47
-
48
- ### Configuration
49
-
50
- Define a secret key for signing PoW tickets in your environment variables.
51
-
52
- ```bash
53
- export POW_SECRET="your_secret_key_of_at_least_32_characters"
54
- ```
55
-
56
- ### Integration Example
57
-
58
- The `powMiddleware` requires a configuration object defining the weights of suspicion indicators and the challenge trigger thresholds.
59
-
60
- ```javascript
61
- import express from 'express';
62
- import bodyParser from 'body-parser';
63
- import cookieParser from 'cookie-parser';
64
- import { powMiddleware, default_whitelist } from './fingerprint.js'; // Adjust the path
65
-
66
- const app = express();
67
- app.use(cookieParser());
68
- app.use(bodyParser.json()); // For parsing application/json
69
- app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
70
-
71
- // Array to store traffic analysis data for the auto-tuner.
72
- // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
73
- const trafficData = [];
74
-
75
- // Configuration of weights and thresholds for calculating the suspicion score.
76
- // These values should be adjusted based on traffic and expected user behavior.
77
- const securityConfig = {
78
- weights: {
79
- historyScore: 0.3, // Penalizes IP rotation (proxy)
80
- rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
81
- headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
82
- requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
83
- inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
84
- behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
85
- honeypotScore: 1.0 // Strongly penalizes bots filling hidden form fields
86
- },
87
- // A new, non-suspicious device will always have its score adjusted to a minimum of 1, ensuring it receives a minimal, almost imperceptible challenge on its first visit.
88
- thresholds: {
89
- low: 20, // Score from which a CPU challenge is issued
90
- medium: 45, // Score for a more difficult combined CPU/Memory challenge
91
- high: 75, // Score for a very difficult challenge
92
- block: 95, // Score above which the request is blocked outright (HTTP 403)
93
- isStaticResource: (req) => req.path.startsWith('/static/'), // Optional: Custom function to identify static resources
94
- isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json') // Optional: Custom function to identify API requests
95
- },
96
- // (Optional) Configure the duration (in milliseconds) for various temporary data.
97
- ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
98
- challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
99
- deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
100
-
101
- patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
102
- velocityThreshold: 200, // ms between requests to be considered "fast"
103
- burstThreshold: 500, // ms for identical requests to be a "burst"
104
- scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
105
- scrapeBurstWeight: 40, // Additional weight for repeated scraping patterns
106
- sequenceLength: 3, // Length of a request sequence to detect (e.g., A->B->C)
107
- sequenceWeight: 60, // Penalty for repeating a sequence
108
- historySize: 10, // Number of requests to keep for pattern analysis
109
- decayFactor: 0.9, // How quickly the pattern score decays over time
110
- inactivityReset: 30000, // ms of inactivity after which the pattern score is reset
111
- },
112
- honeypot: {
113
- // List of field names that are traps for bots.
114
- // These should be hidden in forms for humans, or be URL parameters your app never uses.
115
- fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
116
- // List of URL paths that should never be accessed by a legitimate user.
117
- // A request to one of these paths will immediately flag the device as malicious.
118
- trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
119
- // Automatically detect common SQL/NoSQL injection and RCE patterns in request values. (Optional, default: true)
120
- detectInjections: true,
121
- // (Optional) Plug in external, more robust analyzers. This allows you to extend the default detection with specialized libraries (e.g., WAFs, anti-spam) or your own custom logic.
122
- // Each function receives an object with all query and body data and should return `true` if a threat is detected.
123
- analyzers: [
124
- // Example 1: Using a general-purpose WAF library.
125
- // (npm install generic-waf)
126
- (data) => {
127
- const WAF = require('generic-waf');
128
- const waf = new WAF();
129
- // This WAF expects a string, so we stringify the data to check all values at once.
130
- return waf.isMalicious(JSON.stringify(data));
131
- },
132
- // Example 2: Using a specialized library for XSS detection.
133
- // (npm install xss)
134
- (data) => {
135
- const xss = require('xss');
136
- const originalData = JSON.stringify(data);
137
- // If the sanitized string is different from the original, it means malicious HTML/JS was found and removed.
138
- return xss(originalData) !== originalData;
139
- },
140
- // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
141
- (data) => {
142
- const spamKeywords = ['viagra', 'free money', 'crypto pump'];
143
- const dataString = JSON.stringify(data).toLowerCase();
144
- return spamKeywords.some(keyword => dataString.includes(keyword));
145
- }
146
- ]
147
- },
148
- // (Optional) Whitelisting configuration.
149
- whitelist: [
150
- // Option 1: Static IP Allowlist.
151
- // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
152
- // Useful for internal tools, trusted partners, or monitoring services.
153
- // This check is performed first for maximum efficiency.
154
- { type: 'allowlist', entries: [
155
- '192.168.1.100', // A specific internal IP
156
- '203.0.113.0/24', // A partner's network range
157
- '2001:db8::/32' // An IPv6 range
158
- ]},
159
- // Option 2: DNS-verified bots (e.g., search engine crawlers).
160
- // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
161
- // The result is cached per IP to avoid repeated DNS lookups.
162
- // You can use the provided default list, which contains over 50 common bots, and extend it.
163
- ...default_whitelist(), // Use the defaults
164
- { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
165
- ],
166
- // Or, if you only want the defaults:
167
- // whitelist: default_whitelist(),
168
- // The logger is required for auto-tuning. It collects data on requests.
169
- logger: (log) => trafficData.push(log),
170
- // (Optional) Configuration for the automatic threshold and pattern tuning.
171
- autotuning: {
172
- trafficData: trafficData, // The data source for the genetic algorithm.
173
- interval: 1800000, // Optimization cycle every 30 minutes (in ms).
174
- minDataPoints: 200 // Minimum requests before starting an optimization cycle.
175
- },
176
- };
177
-
178
- // Create an instance of the middleware with your security configuration.
179
- const powMiddlewareInstance = powMiddleware(securityConfig);
180
-
181
- // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
182
- // to correctly retrieve the client's IP.
183
- app.set('trust proxy', 1);
184
-
185
- // Apply the protection middleware to all routes or to specific ones.
186
- app.use(powMiddlewareInstance);
187
-
188
- app.get('/', (req, res) => {
189
- res.send('Welcome to the protected page!');
190
- });
191
-
192
- // Example of accessing the suspicion score in a subsequent middleware or route.
193
- // The `fingerprint` object is attached to the request object by the middleware.
194
- app.use((req, res, next) => {
195
- console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
196
- next();
197
- });
198
-
199
- app.listen(3000, () => console.log('Server started on port 3000'));
200
- ```
201
-
202
- ## Public API
203
-
204
- In addition to the main middleware, several functions are exported to allow for more advanced integrations.
205
-
206
- ### Main Functions
207
-
208
- #### `powMiddleware(securityConfig)`
209
- The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
210
-
211
- #### `configureStore(store)`
212
- Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
213
- The library provides ready-to-use adapters for popular datastores like Redis and MongoDB, which automatically handle the Time-To-Live (TTL) required for temporary data like challenge secrets.
214
-
215
- **Redis Example:**
216
-
217
- ```javascript
218
- import { configureStore } from './fingerprint.js';
219
- import { createRedisStore } from './redis-store.js';
220
- import Redis from 'ioredis';
221
-
222
- const redisClient = new Redis(process.env.REDIS_URL);
223
- const redisStore = createRedisStore(redisClient);
224
- configureStore(redisStore);
225
- ```
226
-
227
- **MongoDB Example:**
228
-
229
- ```javascript
230
- import { configureStore } from './fingerprint.js';
231
- import { createMongoDbStore } from './mongodb-store.js';
232
- import { MongoClient } from 'mongodb';
233
-
234
- const mongoClient = new MongoClient(process.env.MONGODB_URL);
235
-
236
- // It's recommended to connect before your application starts listening.
237
- await mongoClient.connect();
238
-
239
- const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
240
- configureStore(mongoStore);
241
-
242
- // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
243
- // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
244
- // Run this command in the mongo shell:
245
- // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
246
- ```
247
-
248
- #### `identifyRequest(req, res)`
249
- 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.
250
-
251
- ```javascript
252
- import { RateLimiterMemory } from 'rate-limiter-flexible';
253
- import { identifyRequest } from './fingerprint.js';
254
-
255
- const rateLimiter = new RateLimiterMemory({
256
- keyPrefix: 'rate_limit',
257
- points: 10,
258
- duration: 1,
259
- });
260
-
261
- app.use(async (req, res, next) => {
262
- try {
263
- const key = await identifyRequest(req, res);
264
- await rateLimiter.consume(key);
265
- next();
266
- } catch (err) {
267
- res.status(429).send('Too Many Requests');
268
- }
269
- });
270
- ```
271
-
272
- ### Utilities
273
-
274
- #### `isTicketValid(ip, ticket)`
275
- Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
276
-
277
- #### `FingerprintBuilder`
278
- A class for building granular server-side fingerprints.
279
-
280
- ```javascript
281
- const builder = new FingerprintBuilder();
282
- builder.add("ua", req.headers["user-agent"]);
283
- builder.add("os", req.headers["sec-ch-ua-platform"]);
284
- const fp = builder.toString(); // "os:hash1|ua:hash2"
285
- ```
286
-
287
- #### `getDeviceFingerprint()`
288
- *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
289
- This is the primary function for client-side identification.
290
-
291
- #### `generateRequestSignature(payload)`
292
- *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.
293
-
294
- ```javascript
295
- // On the client
296
- const signature = generateRequestSignature({ action: 'update', id: 123 });
297
- // Send signature in headers...
298
- ```
299
-
300
- #### `generateClientSideSignature(payload, secret)`
301
- *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
302
- **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.
303
-
304
- ---
305
-
306
- ## Why Use the Client-Side Library? The Client + Server Synergy
307
-
308
- 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.
309
-
310
- Imagine your server is a fortified castle:
311
-
312
- - **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.
313
- - **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.
314
-
315
- The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
316
-
317
- 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.
318
- 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.
319
- 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.
320
-
321
- ### Strengths at a Glance
322
-
323
- | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
324
- | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
325
- | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
326
- | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
327
- | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
328
- | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
329
- | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
330
-
331
- In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
332
-
333
- ### Client-Side Integration: The `initializeClient` function
334
-
335
- To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
336
-
337
- ```javascript
338
- import { initializeClient } from './path/to/fingerprint.client.js';
339
-
340
- /**
341
- * Initializes all client-side protections.
342
- * This is the recommended way to set up the client-side library.
343
- */
344
- initializeClient({
345
- // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
346
- // Set to `false` to disable.
347
- mouse: true,
348
-
349
- // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
350
- // Set to `false` to disable.
351
- keystrokes: true,
352
-
353
- // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
354
- honeypots: ['email_confirm', 'user_nickname', 'website_url'],
355
-
356
- // (Optional) Enables automatic protection for `fetch` requests.
357
- // If the `fetch` object is present, the protection is active.
358
- fetch: {
359
- // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
360
- targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
361
-
362
- // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
363
- // solve the PoW in the background, and retry the original request with the solution.
364
- // This makes the protection seamless for API clients that use this library.
365
- handleChallenges: true
366
- }
367
- });
368
- ```
369
-
370
- ### Client-Side Behavioral Analysis
371
-
372
- 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.
373
-
374
- #### `startKeystrokeDynamicsTracker()`
375
- *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).
376
-
377
- #### `startMouseEntropyTracker()`
378
- *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.
379
-
380
- #### `initializeHoneypots(fieldNames)`
381
- *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.
382
-
383
- 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.
384
-
385
- - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
386
-
387
- ### Advanced: Manual Wrapping with `protectedFetch`
388
-
389
- 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.
390
-
391
- - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
392
- - `X-Device-Fingerprint`: The client's device fingerprint.
393
- - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
394
-
395
- **Example:**
396
-
397
- ```javascript
398
- import {
399
- initializeClient,
400
- protectedFetch
401
- } from './path/to/fingerprint.client.js';
402
-
403
- // Start tracking user behavior as soon as the app loads.
404
- // Note: You still need to initialize the trackers even if you use protectedFetch manually.
405
- initializeClient({ fetch: false }); // Disables automatic fetch patching
406
-
407
- // Now, use protectedFetch for your specific API calls.
408
- async function submitForm(data) {
409
- const response = await protectedFetch('/api/submit-data', {
410
- method: 'POST',
411
- body: JSON.stringify(data),
412
- headers: {'Content-Type': 'application/json'}
413
- });
414
- }
415
- ```
416
-
417
- ## Advanced Features
418
-
419
- ### Architecture: `FingerprintEngine`
420
-
421
- 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.
422
-
423
- The engine is responsible for:
424
- 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
425
- 2. Calculating the suspicion score using the configured weights.
426
- 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
427
-
428
- Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
429
-
430
- ### Manual Integration (outside Express.js)
431
-
432
- 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.
433
-
434
- **For concrete examples with Koa and Fastify, see our Framework Integration Guide.**
435
-
436
- The engine is a named export from the main module.
437
-
438
- **Workflow:**
439
-
440
- 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
441
- 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.
442
- 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
443
- 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
444
-
445
- **Example with native Node.js `http` server:**
446
-
447
- ```javascript
448
- import http from 'http';
449
- import { FingerprintEngine } from './fingerprint.js'; // Adjust path
450
-
451
- const securityConfig = { /* ... your config ... */ };
452
- const engine = new FingerprintEngine(securityConfig);
453
-
454
- const server = http.createServer(async (req, res) => {
455
- // 1. Manually build the context
456
- const requestContext = {
457
- clientIp: req.socket.remoteAddress,
458
- path: req.url.split('?')[0],
459
- cookies: {}, // Parse cookies from req.headers.cookie
460
- query: new URL(req.url, `http://${req.headers.host}`).searchParams,
461
- headers: req.headers,
462
- rawReq: req, // Pass the raw request
463
- rawRes: res, // Pass the raw response for cookie setting
464
- };
465
-
466
- // 2. Process and get a decision
467
- const decision = await engine.processRequest(requestContext);
468
-
469
- // The decision object now contains the score and the raw suspicion vector.
470
- // You can use it for logging or custom logic.
471
- console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
472
-
473
- // 3. Act on the decision
474
- if (decision.action === 'challenge') {
475
- res.writeHead(decision.status, { 'Content-Type': 'text/html' });
476
- res.end(decision.body);
477
- } else if (decision.action === 'redirect') {
478
- // The engine sets the cookie directly on `res` via `rawRes`
479
- res.writeHead(302, { 'Location': decision.path });
480
- res.end();
481
- } else { // 'next'
482
- res.writeHead(200, { 'Content-Type': 'text/plain' });
483
- res.end('Welcome to the protected page!');
484
- }
485
- });
486
-
487
- server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
488
- ```
489
-
490
- ---
491
-
492
- ## License
493
-
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 initial one associated with the `device_id` (cookie theft detection).
22
+ * **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.
23
+ * **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
24
+ 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:
25
+ * **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.
26
+ * **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.
27
+
28
+ Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges for a set period. For API clients, the challenge is delivered as a `429` JSON response, and the client is expected to solve it and retry the request.
29
+
30
+ ## Features
31
+
32
+ - **Multi-Factor Fingerprinting**: Combines client-side data (`hardwareConcurrency`, `deviceMemory`, `screen`, `canvas`, `webgl`) and server-side data (`User-Agent`, `Client-Hints`).
33
+ - **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
34
+ - **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
35
+ - **Express.js Middleware**: Easy integration into an Express application with `powMiddleware`. The datastore must support setting a Time-To-Live (TTL) for challenge secrets.
36
+ - **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure ticket validation.
37
+ - **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.
38
+ - **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.
39
+
40
+ ## Installation and Usage
41
+
42
+ This module is designed for a Node.js environment.
43
+
44
+ ### Prerequisites
45
+
46
+ 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`.
47
+
48
+ ### Configuration
49
+
50
+ Define a secret key for signing PoW tickets in your environment variables.
51
+
52
+ ```bash
53
+ export POW_SECRET="your_secret_key_of_at_least_32_characters"
54
+ ```
55
+
56
+ ### Integration Example
57
+
58
+ The `powMiddleware` requires a configuration object defining the weights of suspicion indicators and the challenge trigger thresholds.
59
+
60
+ ```javascript
61
+ import express from 'express';
62
+ import bodyParser from 'body-parser';
63
+ import cookieParser from 'cookie-parser';
64
+ import { powMiddleware, default_whitelist } from './fingerprint.js'; // Adjust the path
65
+
66
+ const app = express();
67
+ app.use(cookieParser());
68
+ app.use(bodyParser.json()); // For parsing application/json
69
+ app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
70
+
71
+ // Array to store traffic analysis data for the auto-tuner.
72
+ // In a real application, this could be a more robust logging system (e.g., writing to a file or a database).
73
+ const trafficData = [];
74
+
75
+ // Configuration of weights and thresholds for calculating the suspicion score.
76
+ // These values should be adjusted based on traffic and expected user behavior.
77
+ const securityConfig = {
78
+ weights: {
79
+ historyScore: 0.3, // Penalizes IP rotation (proxy)
80
+ rotationScore: 0.5, // Penalizes rapid fingerprint changes (user-agent, etc.)
81
+ headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
82
+ requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
83
+ inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
84
+ behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
85
+ honeypotScore: 1.0 // Strongly penalizes bots filling hidden form fields
86
+ },
87
+ // A new, non-suspicious device will always have its score adjusted to a minimum of 1, ensuring it receives a minimal, almost imperceptible challenge on its first visit.
88
+ thresholds: {
89
+ low: 20, // Score from which a CPU challenge is issued
90
+ medium: 45, // Score for a more difficult combined CPU/Memory challenge
91
+ high: 75, // Score for a very difficult challenge
92
+ block: 95, // Score above which the request is blocked outright (HTTP 403)
93
+ isStaticResource: (req) => req.path.startsWith('/static/'), // Optional: Custom function to identify static resources
94
+ isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json') // Optional: Custom function to identify API requests
95
+ },
96
+ // (Optional) Configure the duration (in milliseconds) for various temporary data.
97
+ ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
98
+ challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
99
+ deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
100
+ verbose: true, // set to true to log for fingerprint detection output
101
+ patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
102
+ velocityThreshold: 800, // ms between requests to be considered "fast"
103
+ burstThreshold: 1500, // ms for identical requests to be a "burst"
104
+ scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
105
+ scrapeBurstWeight: 40, // Additional weight for repeated scraping patterns
106
+ sequenceLength: 3, // Length of a request sequence to detect (e.g., A->B->C)
107
+ sequenceWeight: 60, // Penalty for repeating a sequence
108
+ sequenceMinTimeSpan: 2000, // Sequence min time span
109
+ historySize: 10, // Number of requests to keep for pattern analysis
110
+ decayFactor: 0.9, // How quickly the pattern score decays over time
111
+ inactivityReset: 30000, // ms of inactivity after which the pattern score is reset
112
+ },
113
+ honeypot: {
114
+ // List of field names that are traps for bots.
115
+ // These should be hidden in forms for humans, or be URL parameters your app never uses.
116
+ fields: ['email_confirm', 'user_nickname', 'debug', 'test_mode', 'admin'], // (Optional)
117
+ // List of URL paths that should never be accessed by a legitimate user.
118
+ // A request to one of these paths will immediately flag the device as malicious.
119
+ trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
120
+ // Automatically detect common SQL/NoSQL injection and RCE patterns in request values. (Optional, default: true)
121
+ detectInjections: true,
122
+ // (Optional) Plug in external, more robust analyzers. This allows you to extend the default detection with specialized libraries (e.g., WAFs, anti-spam) or your own custom logic.
123
+ // Each function receives an object with all query and body data and should return `true` if a threat is detected.
124
+ analyzers: [
125
+ // Example 1: Using a general-purpose WAF library.
126
+ // (npm install generic-waf)
127
+ (data) => {
128
+ const WAF = require('generic-waf');
129
+ const waf = new WAF();
130
+ // This WAF expects a string, so we stringify the data to check all values at once.
131
+ return waf.isMalicious(JSON.stringify(data));
132
+ },
133
+ // Example 2: Using a specialized library for XSS detection.
134
+ // (npm install xss)
135
+ (data) => {
136
+ const xss = require('xss');
137
+ const originalData = JSON.stringify(data);
138
+ // If the sanitized string is different from the original, it means malicious HTML/JS was found and removed.
139
+ return xss(originalData) !== originalData;
140
+ },
141
+ // Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
142
+ (data) => {
143
+ const spamKeywords = ['viagra', 'free money', 'crypto pump'];
144
+ const dataString = JSON.stringify(data).toLowerCase();
145
+ return spamKeywords.some(keyword => dataString.includes(keyword));
146
+ }
147
+ ]
148
+ },
149
+ // (Optional) Whitelisting configuration.
150
+ whitelist: [
151
+ // Option 1: Static IP Allowlist.
152
+ // A simple array of IPs or CIDR ranges that are always allowed, bypassing all checks.
153
+ // Useful for internal tools, trusted partners, or monitoring services.
154
+ // This check is performed first for maximum efficiency.
155
+ { type: 'allowlist', entries: [
156
+ '192.168.1.100', // A specific internal IP
157
+ '203.0.113.0/24', // A partner's network range
158
+ '2001:db8::/32' // An IPv6 range
159
+ ]},
160
+ // Option 2: DNS-verified bots (e.g., search engine crawlers).
161
+ // This uses a secure DNS lookup (reverse then forward) to verify the bot's identity.
162
+ // The result is cached per IP to avoid repeated DNS lookups.
163
+ // You can use the provided default list, which contains over 50 common bots, and extend it.
164
+ ...default_whitelist(), // Use the defaults
165
+ { userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
166
+ ],
167
+ // Or, if you only want the defaults:
168
+ // whitelist: default_whitelist(),
169
+ // The logger is required for auto-tuning. It collects data on requests.
170
+ logger: (log) => trafficData.push(log),
171
+ // (Optional) Configuration for the automatic threshold and pattern tuning.
172
+ autotuning: {
173
+ trafficData: trafficData, // The data source for the genetic algorithm.
174
+ interval: 1800000, // Optimization cycle every 30 minutes (in ms).
175
+ minDataPoints: 200 // Minimum requests before starting an optimization cycle.
176
+ },
177
+ };
178
+
179
+ // Create an instance of the middleware with your security configuration.
180
+ const powMiddlewareInstance = powMiddleware(securityConfig);
181
+
182
+ // Enable trust proxy if your app is behind a reverse proxy (Nginx, etc.)
183
+ // to correctly retrieve the client's IP.
184
+ app.set('trust proxy', 1);
185
+
186
+ // Apply the protection middleware to all routes or to specific ones.
187
+ app.use(powMiddlewareInstance);
188
+
189
+ app.get('/', (req, res) => {
190
+ res.send('Welcome to the protected page!');
191
+ });
192
+
193
+ // Example of accessing the suspicion score in a subsequent middleware or route.
194
+ // The `fingerprint` object is attached to the request object by the middleware.
195
+ app.use((req, res, next) => {
196
+ console.log(`Request from ${req.ip} has a suspicion score of: ${req.fingerprint?.score}`);
197
+ next();
198
+ });
199
+
200
+ app.listen(3000, () => console.log('Server started on port 3000'));
201
+ ```
202
+
203
+ ## Public API
204
+
205
+ In addition to the main middleware, several functions are exported to allow for more advanced integrations.
206
+
207
+ ### Main Functions
208
+
209
+ #### `powMiddleware(securityConfig)`
210
+ The main Express middleware. It orchestrates identification, suspicion calculation, and challenge issuance. It is the main entry point of the library.
211
+
212
+ #### `configureStore(store)`
213
+ Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
214
+ The library provides ready-to-use adapters for popular datastores like Redis and MongoDB, which automatically handle the Time-To-Live (TTL) required for temporary data like challenge secrets.
215
+
216
+ **Redis Example:**
217
+
218
+ ```javascript
219
+ import { configureStore } from './fingerprint.js';
220
+ import { createRedisStore } from './redis-store.js';
221
+ import Redis from 'ioredis';
222
+
223
+ const redisClient = new Redis(process.env.REDIS_URL);
224
+ const redisStore = createRedisStore(redisClient);
225
+ configureStore(redisStore);
226
+ ```
227
+
228
+ **MongoDB Example:**
229
+
230
+ ```javascript
231
+ import { configureStore } from './fingerprint.js';
232
+ import { createMongoDbStore } from './mongodb-store.js';
233
+ import { MongoClient } from 'mongodb';
234
+
235
+ const mongoClient = new MongoClient(process.env.MONGODB_URL);
236
+
237
+ // It's recommended to connect before your application starts listening.
238
+ await mongoClient.connect();
239
+
240
+ const mongoStore = createMongoDbStore(mongoClient.db('your-db-name'), 'sessions'); // 'sessions' is the collection name
241
+ configureStore(mongoStore);
242
+
243
+ // IMPORTANT: For automatic expiration of challenges and other temporary data to work,
244
+ // you must create a TTL index on the `expiresAt` field in your MongoDB collection.
245
+ // Run this command in the mongo shell:
246
+ // db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
247
+ ```
248
+
249
+ #### `identifyRequest(req, res)`
250
+ 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.
251
+
252
+ ```javascript
253
+ import { RateLimiterMemory } from 'rate-limiter-flexible';
254
+ import { identifyRequest } from './fingerprint.js';
255
+
256
+ const rateLimiter = new RateLimiterMemory({
257
+ keyPrefix: 'rate_limit',
258
+ points: 10,
259
+ duration: 1,
260
+ });
261
+
262
+ app.use(async (req, res, next) => {
263
+ try {
264
+ const key = await identifyRequest(req, res);
265
+ await rateLimiter.consume(key);
266
+ next();
267
+ } catch (err) {
268
+ res.status(429).send('Too Many Requests');
269
+ }
270
+ });
271
+ ```
272
+
273
+ ### Utilities
274
+
275
+ #### `isTicketValid(ip, ticket)`
276
+ Checks the validity of a `pow_clearance` cookie. Returns `true` if the ticket is present, not expired, and correctly signed for the given IP.
277
+
278
+ #### `FingerprintBuilder`
279
+ A class for building granular server-side fingerprints.
280
+
281
+ ```javascript
282
+ const builder = new FingerprintBuilder();
283
+ builder.add("ua", req.headers["user-agent"]);
284
+ builder.add("os", req.headers["sec-ch-ua-platform"]);
285
+ const fp = builder.toString(); // "os:hash1|ua:hash2"
286
+ ```
287
+
288
+ #### `getDeviceFingerprint()`
289
+ *Client-side function only.* Generates a detailed browser fingerprint using APIs like Canvas, WebGL, etc.
290
+ This is the primary function for client-side identification.
291
+
292
+ #### `generateRequestSignature(payload)`
293
+ *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.
294
+
295
+ ```javascript
296
+ // On the client
297
+ const signature = generateRequestSignature({ action: 'update', id: 123 });
298
+ // Send signature in headers...
299
+ ```
300
+
301
+ #### `generateClientSideSignature(payload, secret)`
302
+ *Client-side function only.* Generates a secure HMAC-SHA256 signature for a given `payload` using a `secret`.
303
+ **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.
304
+
305
+ ---
306
+
307
+ ## Why Use the Client-Side Library? The Client + Server Synergy
308
+
309
+ 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.
310
+
311
+ Imagine your server is a fortified castle:
312
+
313
+ - **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.
314
+ - **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.
315
+
316
+ The client-side library (`fingerprint.client.js`) gives your server "eyes and ears" where it was previously blind, offering three major advantages:
317
+
318
+ 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.
319
+ 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.
320
+ 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.
321
+
322
+ ### Strengths at a Glance
323
+
324
+ | Feature | Server-Side Only Approach | Client + Server Approach (with the library) |
325
+ | :---------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------- |
326
+ | **Detection Point** | **Reactive** (after the request is received) | **Proactive** (before or during the request) |
327
+ | **Resource Usage** | Server is engaged for every request, good or bad. | Client-side pre-filtering saves significant server resources. |
328
+ | **Behavioral Analysis** | Limited to request velocity and sequence. | Rich data: mouse movement, typing rhythm, interaction with hidden elements. |
329
+ | **Fingerprint Evasion** | **Easy** for bots to spoof headers and rotate IPs. | **Hard** for bots to fake hardware-level fingerprints (Canvas/WebGL). |
330
+ | **Overall Strategy** | Guards at the gate. | Scouts in the field + Guards at the gate. |
331
+
332
+ In short, the client-side library is not an alternative, but a **force multiplier** for the server-side defenses.
333
+
334
+ ### Client-Side Integration: The `initializeClient` function
335
+
336
+ To simplify setup, all client-side features can be enabled and configured through a single, unified function: `initializeClient(config)`. This is the recommended approach.
337
+
338
+ ```javascript
339
+ import { initializeClient } from './path/to/fingerprint.client.js';
340
+
341
+ /**
342
+ * Initializes all client-side protections.
343
+ * This is the recommended way to set up the client-side library.
344
+ */
345
+ initializeClient({
346
+ // (Optional, default: true) Enable mouse movement tracking to detect non-human patterns.
347
+ // Set to `false` to disable.
348
+ mouse: true,
349
+
350
+ // (Optional, default: true) Enable keystroke dynamics tracking (timing between key presses).
351
+ // Set to `false` to disable.
352
+ keystrokes: true,
353
+
354
+ // (Optional) An array of `name` attributes for hidden form fields that act as bot traps.
355
+ honeypots: ['email_confirm', 'user_nickname', 'website_url'],
356
+
357
+ // (Optional) Enables automatic protection for `fetch` requests.
358
+ // If the `fetch` object is present, the protection is active.
359
+ fetch: {
360
+ // (Optional) An array of domains to protect. If empty or not provided, it protects same-origin requests by default.
361
+ targetDomains: ['api.yourdomain.com', 'auth.yourdomain.com'],
362
+
363
+ // (Optional, default: true) If enabled, the client will automatically intercept 429 challenge responses,
364
+ // solve the PoW in the background, and retry the original request with the solution.
365
+ // This makes the protection seamless for API clients that use this library.
366
+ handleChallenges: true
367
+ }
368
+ });
369
+ ```
370
+
371
+ ### Client-Side Behavioral Analysis
372
+
373
+ 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.
374
+
375
+ #### `startKeystrokeDynamicsTracker()`
376
+ *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).
377
+
378
+ #### `startMouseEntropyTracker()`
379
+ *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.
380
+
381
+ #### `initializeHoneypots(fieldNames)`
382
+ *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.
383
+
384
+ 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.
385
+
386
+ - `fieldNames`: An array of strings corresponding to the `name` attributes of the honeypot input fields in your HTML.
387
+
388
+ ### Advanced: Manual Wrapping with `protectedFetch`
389
+
390
+ 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.
391
+
392
+ - **`protectedFetch(resource, options)`**: A wrapper around the native `fetch` API that automatically enriches requests with security headers. It adds:
393
+ - `X-Device-Fingerprint`: The client's device fingerprint.
394
+ - `X-Behavior-Metrics`: A JSON string containing the metrics collected by the behavioral trackers (e.g., mouse entropy, honeypot interaction).
395
+
396
+ **Example:**
397
+
398
+ ```javascript
399
+ import {
400
+ initializeClient,
401
+ protectedFetch
402
+ } from './path/to/fingerprint.client.js';
403
+
404
+ // Start tracking user behavior as soon as the app loads.
405
+ // Note: You still need to initialize the trackers even if you use protectedFetch manually.
406
+ initializeClient({ fetch: false }); // Disables automatic fetch patching
407
+
408
+ // Now, use protectedFetch for your specific API calls.
409
+ async function submitForm(data) {
410
+ const response = await protectedFetch('/api/submit-data', {
411
+ method: 'POST',
412
+ body: JSON.stringify(data),
413
+ headers: {'Content-Type': 'application/json'}
414
+ });
415
+ }
416
+ ```
417
+
418
+ ## Advanced Features
419
+
420
+ ### Architecture: `FingerprintEngine`
421
+
422
+ 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.
423
+
424
+ The engine is responsible for:
425
+ 1. Receiving a `requestContext` (IP, headers, cookies, etc.).
426
+ 2. Calculating the suspicion score using the configured weights.
427
+ 3. Making a decision: `next`, `challenge`, or `redirect` (after solving a challenge).
428
+
429
+ Although not exported for direct public use, understanding its role can be useful for advanced integrations or debugging.
430
+
431
+ ### Manual Integration (outside Express.js)
432
+
433
+ 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.
434
+
435
+ **For concrete examples with Koa and Fastify, see our Framework Integration Guide.**
436
+
437
+ The engine is a named export from the main module.
438
+
439
+ **Workflow:**
440
+
441
+ 1. **Instantiate the Engine**: Create an instance with your `securityConfig`.
442
+ 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.
443
+ 3. **Process the Request**: Call `engine.processRequest(requestContext)`. This method is asynchronous.
444
+ 4. **Handle the Decision**: The engine returns a decision object (`{ action: 'challenge' | 'redirect' | 'next', ... }`). You are responsible for implementing the corresponding HTTP response.
445
+
446
+ **Example with native Node.js `http` server:**
447
+
448
+ ```javascript
449
+ import http from 'http';
450
+ import { FingerprintEngine } from './fingerprint.js'; // Adjust path
451
+
452
+ const securityConfig = { /* ... your config ... */ };
453
+ const engine = new FingerprintEngine(securityConfig);
454
+
455
+ const server = http.createServer(async (req, res) => {
456
+ // 1. Manually build the context
457
+ const requestContext = {
458
+ clientIp: req.socket.remoteAddress,
459
+ path: req.url.split('?')[0],
460
+ cookies: {}, // Parse cookies from req.headers.cookie
461
+ query: new URL(req.url, `http://${req.headers.host}`).searchParams,
462
+ headers: req.headers,
463
+ rawReq: req, // Pass the raw request
464
+ rawRes: res, // Pass the raw response for cookie setting
465
+ };
466
+
467
+ // 2. Process and get a decision
468
+ const decision = await engine.processRequest(requestContext);
469
+
470
+ // The decision object now contains the score and the raw suspicion vector.
471
+ // You can use it for logging or custom logic.
472
+ console.log(`Request from ${requestContext.clientIp} processed with score: ${decision.score}`);
473
+
474
+ // 3. Act on the decision
475
+ if (decision.action === 'challenge') {
476
+ res.writeHead(decision.status, { 'Content-Type': 'text/html' });
477
+ res.end(decision.body);
478
+ } else if (decision.action === 'redirect') {
479
+ // The engine sets the cookie directly on `res` via `rawRes`
480
+ res.writeHead(302, { 'Location': decision.path });
481
+ res.end();
482
+ } else { // 'next'
483
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
484
+ res.end('Welcome to the protected page!');
485
+ }
486
+ });
487
+
488
+ server.listen(3000, () => console.log('Server with manual fingerprint engine started on port 3000'));
489
+ ```
490
+
491
+ ---
492
+
493
+ ## License
494
+
494
495
  This project is licensed under the MIT License. See the `LICENSE` file for more details.