@anonympins/fingerprint 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +193 -0
- package/README.md +1080 -834
- package/composer.json +39 -0
- package/index.js +5 -0
- package/package.json +31 -23
- package/phpunit.xml +21 -0
- package/public/fp.js +2 -0
- package/src/js/build-client.js +69 -0
- package/{fingerprint.builder.js → src/js/fingerprint.builder.js} +168 -175
- package/{fingerprint.client.js → src/js/fingerprint.client.js} +634 -538
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/{fingerprint.js → src/js/fingerprint.js} +3733 -3294
- package/{library.js → src/js/library.js} +1729 -1729
- package/{problem-manager.js → src/js/problem-manager.js} +539 -522
- package/src/php/AutoTuner.php +155 -0
- package/src/php/Challenge/ChallengeUtils.php +306 -0
- package/src/php/Config/SecurityProfiles.php +267 -0
- package/src/php/DirectFingerprint.php +81 -0
- package/src/php/FingerprintBuilder.php +186 -0
- package/src/php/FingerprintClient.php +132 -0
- package/src/php/FingerprintEngine.php +863 -0
- package/src/php/Optimization/FunctionRegistry.php +63 -0
- package/src/php/Optimization/Optimization.php +256 -0
- package/src/php/Optimization/OptimizationOperators.php +305 -0
- package/src/php/Optimization/ProblemInitializers.php +53 -0
- package/src/php/ProblemManager.php +255 -0
- package/src/php/RequestContext.php +91 -0
- package/src/php/Store/IStore.php +42 -0
- package/src/php/Store/InMemoryStore.php +67 -0
- package/src/php/Store/StoreManager.php +36 -0
- package/src/php/Tests/ChallengeUtilsTest.php +82 -0
- package/src/php/Tests/FingerprintBuilderTest.php +58 -0
- package/src/php/Tests/FingerprintEngineTest.php +300 -0
- package/src/php/Tests/IpReputationTest.php +157 -0
- package/src/php/Tests/PowTest.php +40 -0
- package/src/php/Tests/ProblemManagerTest.php +295 -0
- package/src/php/Tests/RequestUtilsTest.php +81 -0
- package/src/php/Tests/problems.config.json +9 -0
- package/src/php/Utils/BigInt.php +145 -0
- package/src/php/Utils/BlockList.php +100 -0
- package/src/php/Utils/Logger.php +30 -0
- package/src/php/Utils/MaliciousPatterns.php +59 -0
- package/src/php/Utils/RequestUtils.php +962 -0
- package/fingerprint.client.obfuscated.js +0 -1
- /package/{mongodb-store.js → src/js/mongodb-store.js} +0 -0
- /package/{optimization.worker.js → src/js/optimization.worker.js} +0 -0
- /package/{pow.solver.inline.js → src/js/pow.solver.inline.js} +0 -0
- /package/{pow.solver.js → src/js/pow.solver.js} +0 -0
- /package/{pow.worker.js → src/js/pow.worker.js} +0 -0
- /package/{redis-store.js → src/js/redis-store.js} +0 -0
- /package/{sql-store.js → src/js/sql-store.js} +0 -0
package/README.md
CHANGED
|
@@ -1,835 +1,1081 @@
|
|
|
1
|
-
# fingerprint
|
|
2
|
-
[](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
|
|
3
|
-
[](https://github.com/anonympins/fingerprint/releases)
|
|
4
|
-
[](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
|
|
5
|
-

|
|
6
|
-
[](https://github.com/anonympins/fingerprint/watchers)
|
|
7
|
-
|
|
8
|
-
An HTTP(S) client mitigation and anti-bot protection library for both PHP and Node.js/Express, based on digital fingerprinting and dynamic Proof-of-Work (PoW) challenges.
|
|
9
|
-
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
This
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* **
|
|
25
|
-
* **
|
|
26
|
-
* **
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
* **
|
|
30
|
-
|
|
31
|
-
* **
|
|
32
|
-
* **
|
|
33
|
-
* **
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
For
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
### Prerequisites
|
|
61
|
-
|
|
62
|
-
* **PHP 7.4+**
|
|
63
|
-
* The **
|
|
64
|
-
* **Composer** for package management.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
* **
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
$
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Example
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
]
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
import {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1
|
+
# fingerprint
|
|
2
|
+
[](https://github.com/anonympins/fingerprint/actions/workflows/ci.yml)
|
|
3
|
+
[](https://github.com/anonympins/fingerprint/releases)
|
|
4
|
+
[](https://github.com/anonympins/fingerprint/blob/main/LICENSE)
|
|
5
|
+

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