@anonympins/fingerprint 0.7.5 → 0.8.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/public/anonympins-bot-mitigation-pow.zip +0 -0
  5. package/src/js/fingerprint.client.js +1784 -1772
  6. package/src/js/fingerprint.client.obfuscated.js +1 -1
  7. package/src/js/fingerprint.js +198 -28
  8. package/src/js/fingerprint.utils.js +252 -2
  9. package/src/js/pow.worker.js +2 -2
  10. package/src/js/tests/cross_parity.test.js +3 -3
  11. package/src/js/tests/dns-circuit-breaker.test.js +6 -6
  12. package/src/js/tests/fingerprint.builder.test.js +76 -78
  13. package/src/js/tests/fingerprint.client.test.js +16 -17
  14. package/src/js/tests/fingerprint.test.js +19 -12
  15. package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
  16. package/src/js/tests/library.test.js +94 -95
  17. package/src/js/tests/obfuscation.test.js +8 -8
  18. package/src/js/tests/quicFingerprint.test.js +47 -0
  19. package/src/js/tests/tcpFingerprint.test.js +4 -4
  20. package/src/js/upow-model-task.js +155 -112
  21. package/src/php/Challenge/ChallengeUtils.php +382 -86
  22. package/src/php/Config/SecurityProfiles.php +75 -62
  23. package/src/php/DirectFingerprint.php +55 -8
  24. package/src/php/FingerprintBuilder.php +13 -13
  25. package/src/php/FingerprintClient.php +6 -6
  26. package/src/php/FingerprintEngine.php +2120 -1837
  27. package/src/php/Ja3AnomalyDetector.php +22 -22
  28. package/src/php/Optimization/FunctionRegistry.php +4 -4
  29. package/src/php/Optimization/Optimization.php +6 -6
  30. package/src/php/RequestContext.php +11 -11
  31. package/src/php/Store/InMemoryStore.php +96 -66
  32. package/src/php/Store/MongoDbStore.php +2 -2
  33. package/src/php/Store/RedisStore.php +1 -1
  34. package/src/php/Store/StoreManager.php +48 -35
  35. package/src/php/Tests/ChallengeUtilsTest.php +31 -0
  36. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  37. package/src/php/Tests/FingerprintClientTest.php +70 -70
  38. package/src/php/Tests/PowTest.php +37 -39
  39. package/src/php/Tests/QuicFingerprintTest.php +115 -53
  40. package/src/php/Tests/ThreatIntelTest.php +2 -2
  41. package/src/php/Utils/BigInt.php +202 -202
  42. package/src/php/Utils/BlockList.php +13 -13
  43. package/src/php/Utils/Logger.php +19 -1
  44. package/src/php/Utils/MaliciousPatterns.php +6 -6
  45. package/src/php/Utils/MetricsManager.php +209 -209
  46. package/src/php/Utils/RequestUtils.php +327 -14
  47. package/src/php/Utils/TLSClientHelloParser.php +385 -369
  48. package/src/php/WordPress/WpDbStore.php +7 -7
  49. package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1121 -0
  50. package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
  51. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
  52. package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
  53. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  54. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
  55. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  56. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
  57. package/src/php/WordPress/package.php +80 -101
  58. package/src/php/WordPress/readme.txt +21 -5
  59. package/src/php/bin/auto-tune.php +1 -1
  60. package/public/fingerprint-anti-bot.zip +0 -0
@@ -1,13 +1,13 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { __internal } from '../fingerprint.js';
3
3
 
4
- describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
4
+ describe('Polymorphic System and Obfuscation Compiler', () => {
5
5
 
6
6
  it('should generate a valid session mapping with randomized headers and globals', () => {
7
7
  const mapping1 = __internal.generateSessionMapping();
8
8
  const mapping2 = __internal.generateSessionMapping();
9
9
 
10
- // Vérification de la présence des structures attendues
10
+ // Verify expected mapping structure
11
11
  expect(mapping1).toBeDefined();
12
12
  expect(mapping1.headers).toHaveProperty('x-device-fingerprint');
13
13
  expect(mapping1.headers).toHaveProperty('x-behavior-metrics');
@@ -17,7 +17,7 @@ describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
17
17
  expect(mapping1.keys).toHaveProperty('ua');
18
18
  expect(mapping1.wasmConstants).toHaveProperty('seed');
19
19
 
20
- // Vérification de l'aléa (deux sessions consécutives doivent avoir des mappings uniques)
20
+ // Verify randomness: two consecutive sessions must yield unique mappings
21
21
  expect(mapping1.headers['x-device-fingerprint']).not.toBe(mapping2.headers['x-device-fingerprint']);
22
22
  expect(mapping1.globals['ClientLibrary']).not.toBe(mapping2.globals['ClientLibrary']);
23
23
  expect(mapping1.keys['ua']).not.toBe(mapping2.keys['ua']);
@@ -27,22 +27,22 @@ describe('Système de Polymorphisme et Compilateur d\'Obfuscation', () => {
27
27
  it('should compile and obfuscate client script asynchronously using the obfuscation worker', async () => {
28
28
  const mapping = __internal.generateSessionMapping();
29
29
 
30
- // On lance la compilation lourde qui instancie le Worker d'arrière-plan
30
+ // Run background worker obfuscation task
31
31
  const obfuscatedCode = await __internal.compilePolymorphicJs(mapping);
32
32
 
33
33
  expect(obfuscatedCode).toBeDefined();
34
34
  expect(typeof obfuscatedCode).toBe('string');
35
35
  expect(obfuscatedCode.length).toBeGreaterThan(0);
36
36
 
37
- // Vérification que les placeholders originaux ont bien été purgés du code client final
37
+ // Verify original identifiers were rewritten in compiled client script
38
38
  expect(obfuscatedCode).not.toContain('X-Device-Fingerprint');
39
39
  expect(obfuscatedCode).not.toContain('X-Behavior-Metrics');
40
40
  expect(obfuscatedCode).not.toContain('ClientLibrary');
41
41
 
42
- // Vérification que le code est minifié/obfusqué (ne contient pas de commentaires de développement par exemple)
42
+ // Verify code is obfuscated without plain dev comments
43
43
  expect(obfuscatedCode).not.toContain('// Nom trompeur aléatoire pour attirer les analyseurs');
44
44
 
45
- // La structure globale de javascript-obfuscator génère typiquement un tableau de chaînes au début
45
+ // Obfuscator emits string array wrapper prefix
46
46
  expect(obfuscatedCode).toMatch(/_0x/);
47
- }, 15000); // Augmentation du timeout à 15s car l'obfuscation via Worker thread consomme du CPU
47
+ }, 15000); // 15s timeout for CPU-intensive worker execution
48
48
  });
@@ -43,4 +43,51 @@ describe('Protocol Anomaly Flow Profiling', () => {
43
43
  const score = getProtocolAnomalyScore(context);
44
44
  expect(score.protocolAnomalyScore).toBe(100.0);
45
45
  });
46
+
47
+ it('should detect RFC 9218 invalid urgency value out of range (u=9)', () => {
48
+ const context = {
49
+ headers: {
50
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
51
+ 'x-quic-fp': '1;1=1572864,4=100;u=9,i'
52
+ }
53
+ };
54
+ const score = getProtocolAnomalyScore(context);
55
+ expect(score.quicAnomalyScore).toBeGreaterThanOrEqual(50.0);
56
+ });
57
+
58
+ it('should detect HTTP/2 flat dependency tree from Go net/http2 scraper', () => {
59
+ const context = {
60
+ headers: {
61
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
62
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:16|m,a,s,p|p:3,w:4,c:1'
63
+ },
64
+ httpVersion: '2.0'
65
+ };
66
+ const score = getProtocolAnomalyScore(context);
67
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(55.0);
68
+ });
69
+
70
+ it('should detect HTTP/2 self-dependency violation', () => {
71
+ const context = {
72
+ headers: {
73
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
74
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|3:0:3:256|m,a,s,p|p:3,w:4,c:1'
75
+ },
76
+ httpVersion: '2.0'
77
+ };
78
+ const score = getProtocolAnomalyScore(context);
79
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(60.0);
80
+ });
81
+
82
+ it('should detect scrapers with 0 dynamic table entries on repeated requests', () => {
83
+ const context = {
84
+ headers: {
85
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
86
+ 'x-compression-info': 'req_count:5,dynamic_table_entries:0'
87
+ },
88
+ httpVersion: '2.0'
89
+ };
90
+ const score = getProtocolAnomalyScore(context);
91
+ expect(score.http2AnomalyScore).toBeGreaterThanOrEqual(45.0);
92
+ });
46
93
  });
@@ -5,7 +5,7 @@ const { parseTcpSyn, classifyTcpOs, getTcpAnomalyScore } = __internal;
5
5
 
6
6
  describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
7
7
 
8
- // Mock d'un paquet IP + TCP SYN d'un système Linux (TTL=64, WS=7)
8
+ // Mock Linux IP + TCP SYN packet (TTL=64, WS=7)
9
9
  const mockLinuxSynPacket = Buffer.from([
10
10
  0x45, 0x00, 0x00, 0x3c, 0x1a, 0x2b, 0x40, 0x00,
11
11
  0x40, 0x06, 0x3c, 0x1a, 0x7f, 0x00, 0x00, 0x01, // TTL=0x40 (64)
@@ -19,7 +19,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
19
19
  0x03, 0x03, 0x07 // Option WS: 7
20
20
  ]);
21
21
 
22
- // Mock d'un paquet IP + TCP SYN d'un système Windows (TTL=128, WS=8)
22
+ // Mock Windows IP + TCP SYN packet (TTL=128, WS=8)
23
23
  const mockWindowsSynPacket = Buffer.from([
24
24
  0x45, 0x00, 0x00, 0x3c, 0x1a, 0x2b, 0x40, 0x00,
25
25
  0x80, 0x06, 0x3c, 0x1a, 0x7f, 0x00, 0x00, 0x01, // TTL=0x80 (128)
@@ -57,7 +57,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
57
57
  });
58
58
 
59
59
  it('should calculate correct TCP/IP anomaly scores', () => {
60
- // Cas 1 : Le User-Agent prétend être Windows, mais la pile TCP/IP est Linux
60
+ // Case 1: User-Agent claims Windows, but TCP/IP stack signature is Linux
61
61
  const contextAnomaly = {
62
62
  headers: {
63
63
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0',
@@ -66,7 +66,7 @@ describe('Passive TCP/IP Fingerprinting (Type p0f)', () => {
66
66
  };
67
67
  expect(getTcpAnomalyScore(contextAnomaly).tcpAnomalyScore).toBe(80.1);
68
68
 
69
- // Cas 2 : Cohérence complète (UA Windows et pile TCP/IP Windows)
69
+ // Case 2: Consistent stack (Windows UA and Windows TCP/IP stack)
70
70
  const contextCoherent = {
71
71
  headers: {
72
72
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0',
@@ -1,113 +1,156 @@
1
- import crypto from "node:crypto";
2
-
3
- /**
4
- * Abstract base class defining useful Proof-of-Work (uPoW) tasks based on ML training
5
- * or inference validation (e.g. PyTorch models exported to ONNX format).
6
- */
7
- export class UpowModelTask {
8
- constructor(problemId, modelPath, config = {}) {
9
- if (new.target === UpowModelTask) {
10
- throw new TypeError("Cannot construct UpowModelTask instances directly");
11
- }
12
- this.problemId = problemId;
13
- this.modelPath = modelPath;
14
- this.config = {
15
- batchSize: 32,
16
- learningRate: 0.01,
17
- validationTolerance: 0.15,
18
- ...config
19
- };
20
- }
21
-
22
- /**
23
- * Returns the unique problem identifier.
24
- * @returns {string}
25
- */
26
- getProblemId() {
27
- return this.problemId;
28
- }
29
-
30
- /**
31
- * Returns the network asset path of the model.
32
- * @returns {string}
33
- */
34
- getModelAssetPath() {
35
- return this.modelPath;
36
- }
37
-
38
- /**
39
- * Dispatches task workload to the client.
40
- * Transmits current model weights and mini-batch sample data.
41
- * @param {number} suspicionFactor
42
- * @returns {object} Task payload dispatched to client
43
- */
44
- dispatchTask(suspicionFactor) {
45
- throw new Error("Method 'dispatchTask(suspicionFactor)' must be implemented.");
46
- }
47
-
48
- /**
49
- * Validates solution returned by the client.
50
- * Implements anti-poisoning guards and gradient bounds checks.
51
- * @param {object} taskContext
52
- * @param {object} solution
53
- * @returns {boolean}
54
- */
55
- verifySolution(taskContext, solution) {
56
- throw new Error("Method 'verifySolution(taskContext, solution)' must be implemented.");
57
- }
58
-
59
- /**
60
- * Integrates validated gradients into global model weights (e.g. FedAvg).
61
- * @param {object} solution
62
- */
63
- integrateSolution(solution) {
64
- throw new Error("Method 'integrateSolution(solution)' must be implemented.");
65
- }
66
- }
67
-
68
- /**
69
- * Example training task implementation (suspicious request classifier).
70
- */
71
- export class RequestClassifierTask extends UpowModelTask {
72
- constructor(modelPath, config) {
73
- super("request_classifier_nn", modelPath, config);
74
- // Initial model weights (linear neural network)
75
- this.weights = [0.1, -0.2, 0.8, 0.5];
76
- }
77
-
78
- dispatchTask(suspicionFactor) {
79
- // Anonymized sample training batch dispatched to client
80
- const inputs = [
81
- [1.0, 0.5, 0.0, 1.2],
82
- [0.0, 1.0, -0.5, 0.8]
83
- ];
84
- return {
85
- weights: [...this.weights],
86
- inputs: inputs,
87
- learningRate: this.config.learningRate,
88
- batchSize: this.config.batchSize
89
- };
90
- }
91
-
92
- verifySolution(taskContext, solution) {
93
- const gradients = solution?.gradients;
94
- if (!Array.isArray(gradients) || gradients.length !== this.weights.length) {
95
- return false;
96
- }
97
- // Anti-poisoning guard: ensure gradients do not exceed threshold norms (gradient clipping)
98
- const maxGradientNorm = 10.0;
99
- for (const grad of gradients) {
100
- if (typeof grad !== 'number' || isNaN(grad) || Math.abs(grad) > maxGradientNorm) {
101
- return false;
102
- }
103
- }
104
- return true;
105
- }
106
-
107
- integrateSolution(solution) {
108
- const gradients = solution.gradients;
109
- for (let i = 0; i < this.weights.length; i++) {
110
- this.weights[i] -= this.config.learningRate * gradients[i];
111
- }
112
- }
1
+ import crypto from "node:crypto";
2
+
3
+ /**
4
+ * Abstract base class defining useful Proof-of-Work (uPoW) tasks based on ML training
5
+ * or inference validation (e.g. PyTorch models exported to ONNX format).
6
+ */
7
+ export class UpowModelTask {
8
+ /**
9
+ * Creates a new UpowModelTask. This class is abstract and cannot be
10
+ * instantiated directly.
11
+ *
12
+ * @param {string} problemId - The unique identifier of the problem.
13
+ * @param {string} modelPath - The network asset path of the model.
14
+ * @param {object} [config={}] - Optional configuration overrides.
15
+ * @param {number} [config.batchSize=32] - Mini-batch size.
16
+ * @param {number} [config.learningRate=0.01] - Learning rate.
17
+ * @param {number} [config.validationTolerance=0.15] - Validation tolerance.
18
+ * @throws {TypeError} If instantiated directly.
19
+ */
20
+ constructor(problemId, modelPath, config = {}) {
21
+ if (new.target === UpowModelTask) {
22
+ throw new TypeError("Cannot construct UpowModelTask instances directly");
23
+ }
24
+ this.problemId = problemId;
25
+ this.modelPath = modelPath;
26
+ this.config = {
27
+ batchSize: 32,
28
+ learningRate: 0.01,
29
+ validationTolerance: 0.15,
30
+ ...config
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Returns the unique problem identifier.
36
+ * @returns {string}
37
+ */
38
+ getProblemId() {
39
+ return this.problemId;
40
+ }
41
+
42
+ /**
43
+ * Returns the network asset path of the model.
44
+ * @returns {string}
45
+ */
46
+ getModelAssetPath() {
47
+ return this.modelPath;
48
+ }
49
+
50
+ /**
51
+ * Dispatches task workload to the client.
52
+ * Transmits current model weights and mini-batch sample data.
53
+ * @param {number} suspicionFactor
54
+ * @returns {object} Task payload dispatched to client
55
+ */
56
+ dispatchTask(suspicionFactor) {
57
+ throw new Error("Method 'dispatchTask(suspicionFactor)' must be implemented.");
58
+ }
59
+
60
+ /**
61
+ * Validates solution returned by the client.
62
+ * Implements anti-poisoning guards and gradient bounds checks.
63
+ * @param {object} taskContext
64
+ * @param {object} solution
65
+ * @returns {boolean}
66
+ */
67
+ verifySolution(taskContext, solution) {
68
+ throw new Error("Method 'verifySolution(taskContext, solution)' must be implemented.");
69
+ }
70
+
71
+ /**
72
+ * Integrates validated gradients into global model weights (e.g. FedAvg).
73
+ * @param {object} solution
74
+ */
75
+ integrateSolution(solution) {
76
+ throw new Error("Method 'integrateSolution(solution)' must be implemented.");
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Example training task implementation (suspicious request classifier).
82
+ */
83
+ export class RequestClassifierTask extends UpowModelTask {
84
+ /**
85
+ * Creates a new RequestClassifierTask with an initial set of linear
86
+ * neural network weights.
87
+ *
88
+ * @param {string} modelPath - The network asset path of the model.
89
+ * @param {object} config - Configuration object (batch size, learning rate, etc.).
90
+ */
91
+ constructor(modelPath, config) {
92
+ super("request_classifier_nn", modelPath, config);
93
+ // Initial model weights (linear neural network)
94
+ this.weights = [0.1, -0.2, 0.8, 0.5];
95
+ }
96
+
97
+ /**
98
+ * Builds and returns the task payload sent to the client, including the
99
+ * current model weights, an anonymized sample training batch, the learning
100
+ * rate, and the batch size.
101
+ *
102
+ * @param {number} suspicionFactor - The suspicion factor for this request.
103
+ * @returns {object} The task payload dispatched to the client.
104
+ */
105
+ dispatchTask(suspicionFactor) {
106
+ // Anonymized sample training batch dispatched to client
107
+ const inputs = [
108
+ [1.0, 0.5, 0.0, 1.2],
109
+ [0.0, 1.0, -0.5, 0.8]
110
+ ];
111
+ return {
112
+ weights: [...this.weights],
113
+ inputs: inputs,
114
+ learningRate: this.config.learningRate,
115
+ batchSize: this.config.batchSize
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Validates the solution returned by the client. Ensures the gradients
121
+ * array has the expected length and applies an anti-poisoning guard by
122
+ * rejecting any gradient whose absolute value exceeds a fixed threshold
123
+ * (gradient clipping).
124
+ *
125
+ * @param {object} taskContext - The context of the dispatched task.
126
+ * @param {object} solution - The solution returned by the client.
127
+ * @returns {boolean} True if the solution is valid, false otherwise.
128
+ */
129
+ verifySolution(taskContext, solution) {
130
+ const gradients = solution?.gradients;
131
+ if (!Array.isArray(gradients) || gradients.length !== this.weights.length) {
132
+ return false;
133
+ }
134
+ // Anti-poisoning guard: ensure gradients do not exceed threshold norms (gradient clipping)
135
+ const maxGradientNorm = 10.0;
136
+ for (const grad of gradients) {
137
+ if (typeof grad !== 'number' || isNaN(grad) || Math.abs(grad) > maxGradientNorm) {
138
+ return false;
139
+ }
140
+ }
141
+ return true;
142
+ }
143
+
144
+ /**
145
+ * Integrates the validated gradients into the global model weights using
146
+ * a simple gradient descent update: w_i -= learningRate * grad_i.
147
+ *
148
+ * @param {object} solution - The validated solution containing gradients.
149
+ */
150
+ integrateSolution(solution) {
151
+ const gradients = solution.gradients;
152
+ for (let i = 0; i < this.weights.length; i++) {
153
+ this.weights[i] -= this.config.learningRate * gradients[i];
154
+ }
155
+ }
113
156
  }