@anonympins/fingerprint 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -41
- package/fingerprint.client.js +470 -458
- package/fingerprint.js +608 -267
- package/library.js +1619 -1577
- package/package.json +1 -1
- package/pow.solver.js +177 -2
package/README.md
CHANGED
|
@@ -18,14 +18,20 @@ The process unfolds in three steps:
|
|
|
18
18
|
* **Header Anomalies**: Missing `User-Agent`, `Accept-Language`, etc.
|
|
19
19
|
* **Device Behavior**: Rapid fingerprint changes (User-Agent rotation).
|
|
20
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
|
|
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`).
|
|
22
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.
|
|
23
24
|
* **Honeypot Trap**: Detection of bots that automatically fill hidden form fields or probe for common but unused URL parameters (e.g., `?debug=true`).
|
|
24
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:
|
|
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
|
+
* **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
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.
|
|
27
29
|
|
|
28
|
-
Once the challenge is solved, a clearance "ticket" is issued via a secure cookie, exempting the user from new challenges
|
|
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.
|
|
29
35
|
|
|
30
36
|
## Features
|
|
31
37
|
|
|
@@ -33,7 +39,7 @@ Once the challenge is solved, a clearance "ticket" is issued via a secure cookie
|
|
|
33
39
|
- **Secure Ticket System**: Uses HMAC-SHA256 signatures to validate clearances and prevent tampering.
|
|
34
40
|
- **Pluggable Datastore**: Supports external datastores like Redis for state persistence and scalability across multiple server instances.
|
|
35
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.
|
|
36
|
-
- **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure
|
|
42
|
+
- **Timing Attack Protection**: Uses `crypto.timingSafeEqual` for secure validation of tickets and other signatures.
|
|
37
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.
|
|
38
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.
|
|
39
45
|
|
|
@@ -61,7 +67,7 @@ The `powMiddleware` requires a configuration object defining the weights of susp
|
|
|
61
67
|
import express from 'express';
|
|
62
68
|
import bodyParser from 'body-parser';
|
|
63
69
|
import cookieParser from 'cookie-parser';
|
|
64
|
-
import { powMiddleware, default_whitelist } from './fingerprint.js'; // Adjust the path
|
|
70
|
+
import { powMiddleware, default_whitelist, default_analyzers } from './fingerprint.js'; // Adjust the path
|
|
65
71
|
|
|
66
72
|
const app = express();
|
|
67
73
|
app.use(cookieParser());
|
|
@@ -81,32 +87,27 @@ const securityConfig = {
|
|
|
81
87
|
headerAnomalyScore: 0.1, // Penalizes abnormal headers (missing UA, etc.)
|
|
82
88
|
requestPatternScore: 0.6,// Penalizes bot-like request sequences (scraping, etc.)
|
|
83
89
|
inconsistencyScore: 0.8, // Strongly penalizes inconsistency between the current and initial fingerprint (stolen cookie)
|
|
84
|
-
behaviorScore: 0.7,
|
|
85
|
-
honeypotScore: 1.0
|
|
90
|
+
behaviorScore: 0.7, // Penalizes non-human interactions (no mouse/keyboard activity)
|
|
91
|
+
honeypotScore: 1.0, // Strongly penalizes bots filling hidden form fields
|
|
92
|
+
crossLayerInconsistencyScore: 0.4, // Penalizes mismatches between client-side data (e.g., OS) and server-side headers (e.g., User-Agent)
|
|
93
|
+
timeInconsistencyScore: 0.9 // Strongly penalizes large time gaps between client metric collection and server reception (replay attack)
|
|
86
94
|
},
|
|
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
95
|
thresholds: {
|
|
89
96
|
low: 20, // Score from which a CPU challenge is issued
|
|
90
97
|
medium: 45, // Score for a more difficult combined CPU/Memory challenge
|
|
91
98
|
high: 75, // Score for a very difficult challenge
|
|
92
|
-
block: 95, // Score above which the request is blocked outright (HTTP
|
|
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
|
|
99
|
+
block: 95, // Score above which the request is blocked outright (HTTP 404)
|
|
95
100
|
},
|
|
96
101
|
// (Optional) Configure the duration (in milliseconds) for various temporary data.
|
|
97
102
|
ticketMaxAge: 3600000, // 1 hour. Duration for which a solved challenge ticket is valid.
|
|
98
103
|
challengeTtl: 300000, // 5 minutes. Time during which a challenge nonce is valid.
|
|
99
104
|
deviceIdCookieMaxAge: undefined, // By default, it's a session cookie. Set a value in ms for a persistent cookie.
|
|
100
105
|
challengePagePath: './path/to/your/custom-challenge-page.html', // (Optional) Path to a custom HTML template for the challenge page.
|
|
101
|
-
verbose:
|
|
106
|
+
verbose: process.env.NODE_ENV !== 'production', // Log detailed info in development, but not in production.
|
|
102
107
|
patterns: { // (Optional) Initial values for request pattern detection, optimized by auto-tuner if enabled.
|
|
103
108
|
velocityThreshold: 800, // ms between requests to be considered "fast"
|
|
104
109
|
burstThreshold: 1500, // ms for identical requests to be a "burst"
|
|
105
110
|
scrapeThreshold: 1000, // ms for sequential requests to be "scraping"
|
|
106
|
-
scrapeBurstWeight: 40, // Additional weight for repeated scraping patterns
|
|
107
|
-
sequenceLength: 3, // Length of a request sequence to detect (e.g., A->B->C)
|
|
108
|
-
sequenceWeight: 60, // Penalty for repeating a sequence
|
|
109
|
-
sequenceMinTimeSpan: 2000, // Sequence min time span
|
|
110
111
|
historySize: 10, // Number of requests to keep for pattern analysis
|
|
111
112
|
decayFactor: 0.9, // How quickly the pattern score decays over time
|
|
112
113
|
inactivityReset: 30000, // ms of inactivity after which the pattern score is reset
|
|
@@ -118,27 +119,20 @@ const securityConfig = {
|
|
|
118
119
|
// List of URL paths that should never be accessed by a legitimate user.
|
|
119
120
|
// A request to one of these paths will immediately flag the device as malicious.
|
|
120
121
|
trapUrls: ['/wp-admin', '/.env', '/admin.php', '/phpmyadmin'], // (Optional)
|
|
121
|
-
// Automatically detect common
|
|
122
|
-
|
|
123
|
-
//
|
|
122
|
+
// Automatically detect common injection patterns. Can be a boolean or an array of specific types.
|
|
123
|
+
// - `true`: Enables all available detections (default).
|
|
124
|
+
// - `false`: Disables injection detection.
|
|
125
|
+
// - `['sql', 'rce']`: Enables only SQL injection and Remote Command Execution detection.
|
|
126
|
+
detectInjections: ['sql', 'rce', 'traversal', 'xxe', 'ssti', 'log4shell'], // (Optional, default: true)
|
|
127
|
+
// (Optional) Plug in external analyzers. This allows you to extend detection with specialized libraries or custom logic.
|
|
124
128
|
// Each function receives an object with all query and body data and should return `true` if a threat is detected.
|
|
125
129
|
analyzers: [
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return waf.isMalicious(JSON.stringify(data));
|
|
133
|
-
},
|
|
134
|
-
// Example 2: Using a specialized library for XSS detection.
|
|
135
|
-
// (npm install xss)
|
|
136
|
-
(data) => {
|
|
137
|
-
const xss = require('xss');
|
|
138
|
-
const originalData = JSON.stringify(data);
|
|
139
|
-
// If the sanitized string is different from the original, it means malicious HTML/JS was found and removed.
|
|
140
|
-
return xss(originalData) !== originalData;
|
|
141
|
-
},
|
|
130
|
+
...default_analyzers(), // Includes the default XSS analyzer.
|
|
131
|
+
|
|
132
|
+
// Example 2: Enable a powerful WAF with ModSecurity and the OWASP Core Rule Set.
|
|
133
|
+
// Requires `npm install modsecurity-nodejs` and downloading the OWASP CRS rules.
|
|
134
|
+
// modsecurity_analyzer('/path/to/owasp-crs/crs-setup.conf'),
|
|
135
|
+
|
|
142
136
|
// Example 3: A custom function to detect specific keywords (e.g., for anti-spam).
|
|
143
137
|
(data) => {
|
|
144
138
|
const spamKeywords = ['viagra', 'free money', 'crypto pump'];
|
|
@@ -165,8 +159,10 @@ const securityConfig = {
|
|
|
165
159
|
...default_whitelist(), // Use the defaults
|
|
166
160
|
{ userAgent: 'MyIndustrySpecificBot', hostnameSuffix: '.my-bot-verifier.com' }, // Add a custom bot
|
|
167
161
|
],
|
|
168
|
-
//
|
|
169
|
-
|
|
162
|
+
// Optional: Custom function to identify static resources
|
|
163
|
+
isStaticResource: (req) => req.path.startsWith('/static/'),
|
|
164
|
+
// Optional: Custom function to identify API requests
|
|
165
|
+
isApiRequest: (req) => req.path.startsWith('/api/') || req.headers.accept?.includes('application/json'),
|
|
170
166
|
// The logger is required for auto-tuning. It collects data on requests.
|
|
171
167
|
logger: (log) => trafficData.push(log),
|
|
172
168
|
// (Optional) Configuration for the automatic threshold and pattern tuning.
|
|
@@ -175,6 +171,8 @@ const securityConfig = {
|
|
|
175
171
|
interval: 1800000, // Optimization cycle every 30 minutes (in ms).
|
|
176
172
|
minDataPoints: 200 // Minimum requests before starting an optimization cycle.
|
|
177
173
|
},
|
|
174
|
+
// Enables problem solving for suspicious activity (configurable in problems.config.json)
|
|
175
|
+
enableUsefulWork: true
|
|
178
176
|
};
|
|
179
177
|
|
|
180
178
|
// Create an instance of the middleware with your security configuration.
|
|
@@ -200,7 +198,39 @@ app.use((req, res, next) => {
|
|
|
200
198
|
|
|
201
199
|
app.listen(3000, () => console.log('Server started on port 3000'));
|
|
202
200
|
```
|
|
203
|
-
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Advanced Behavioral Analysis
|
|
206
|
+
|
|
207
|
+
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.
|
|
208
|
+
|
|
209
|
+
This function uses several configurable parameters to identify suspicious behavior:
|
|
210
|
+
|
|
211
|
+
### Core Pattern Detection
|
|
212
|
+
|
|
213
|
+
These parameters form the basis of the request pattern analysis:
|
|
214
|
+
|
|
215
|
+
* `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.
|
|
216
|
+
* `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.
|
|
217
|
+
* `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.
|
|
218
|
+
* `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.
|
|
219
|
+
|
|
220
|
+
### Statistical Analysis (Benford's Law)
|
|
221
|
+
|
|
222
|
+
To counter more advanced bots that might try to randomize their request timings, the engine employs statistical analysis based on Benford's Law.
|
|
223
|
+
|
|
224
|
+
* **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.
|
|
225
|
+
|
|
226
|
+
* `benfordMinSamples`: (Default: 15) The minimum number of request timings to collect before performing a Benford's Law test.
|
|
227
|
+
* `benfordWeight`: (Default: 50) The weight applied to the suspicion score if the distribution of timings significantly deviates from Benford's Law.
|
|
228
|
+
|
|
229
|
+
### Configuration and Auto-Tuning
|
|
230
|
+
|
|
231
|
+
All these parameters are part of the `patterns` object within the main security configuration and can be fine-tuned.
|
|
232
|
+
|
|
233
|
+
## Customizing the Challenge Page
|
|
204
234
|
|
|
205
235
|
You can provide your own HTML template for the Proof-of-Work challenge page to maintain a consistent user experience with your brand.
|
|
206
236
|
|
|
@@ -255,7 +285,7 @@ The main Express middleware. It orchestrates identification, suspicion calculati
|
|
|
255
285
|
|
|
256
286
|
#### `configureStore(store)`
|
|
257
287
|
Allows replacing the in-memory store with an external datastore (like Redis) for persistence and scaling.
|
|
258
|
-
The library provides ready-to-use adapters for popular datastores like Redis and
|
|
288
|
+
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.
|
|
259
289
|
|
|
260
290
|
**Redis Example:**
|
|
261
291
|
|
|
@@ -290,6 +320,32 @@ configureStore(mongoStore);
|
|
|
290
320
|
// db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 })
|
|
291
321
|
```
|
|
292
322
|
|
|
323
|
+
**SQL Example (with Knex.js):**
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
import { configureStore } from './fingerprint.js';
|
|
327
|
+
import { createSqlStore } from './sql-store.js';
|
|
328
|
+
import knex from 'knex';
|
|
329
|
+
|
|
330
|
+
const knexClient = knex({
|
|
331
|
+
client: 'pg', // or 'mysql', 'sqlite3', etc.
|
|
332
|
+
connection: process.env.DATABASE_URL,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const sqlStore = createSqlStore(knexClient, 'fingerprint_sessions'); // 'fingerprint_sessions' is the table name
|
|
336
|
+
configureStore(sqlStore);
|
|
337
|
+
|
|
338
|
+
// IMPORTANT: For automatic expiration of challenges and other temporary data to work,
|
|
339
|
+
// your table must have an `expiresAt` column. The store will handle cleanup of expired rows,
|
|
340
|
+
// but you must create the table yourself.
|
|
341
|
+
// Example schema for PostgreSQL:
|
|
342
|
+
// CREATE TABLE fingerprint_sessions (
|
|
343
|
+
// "key" VARCHAR(255) PRIMARY KEY,
|
|
344
|
+
// "value" TEXT NOT NULL,
|
|
345
|
+
// "expiresAt" TIMESTAMPTZ
|
|
346
|
+
// );
|
|
347
|
+
```
|
|
348
|
+
|
|
293
349
|
#### `identifyRequest(req, res)`
|
|
294
350
|
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.
|
|
295
351
|
|
|
@@ -476,7 +532,7 @@ Although not exported for direct public use, understanding its role can be usefu
|
|
|
476
532
|
|
|
477
533
|
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.
|
|
478
534
|
|
|
479
|
-
**For concrete examples with Koa and Fastify, see our Framework Integration Guide.**
|
|
535
|
+
**For concrete examples with Koa and Fastify, see our [Framework Integration Guide](https://github.com/anonympins/fingerprint/blob/main/INTEGRATION.md).**
|
|
480
536
|
|
|
481
537
|
The engine is a named export from the main module.
|
|
482
538
|
|
|
@@ -502,7 +558,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
502
558
|
clientIp: req.socket.remoteAddress,
|
|
503
559
|
path: req.url.split('?')[0],
|
|
504
560
|
cookies: {}, // Parse cookies from req.headers.cookie
|
|
505
|
-
query: new URL(req.url, `http://${req.headers.host}`).searchParams,
|
|
561
|
+
query: Object.fromEntries(new URL(req.url, `http://${req.headers.host}`).searchParams),
|
|
506
562
|
headers: req.headers,
|
|
507
563
|
rawReq: req, // Pass the raw request
|
|
508
564
|
rawRes: res, // Pass the raw response for cookie setting
|