@anonympins/fingerprint 0.8.0 → 0.8.1
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 +25 -0
- package/README.md +1 -1
- package/package.json +3 -2
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/asn-lookup.js +223 -0
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +48 -22
- package/src/js/tests/asn-lookup.test.js +57 -0
- package/src/php/AsnLookupEngine.php +173 -0
- package/src/php/Challenge/ChallengeUtils.php +66 -49
- package/src/php/DirectFingerprint.php +25 -5
- package/src/php/FingerprintEngine.php +51 -30
- package/src/php/NetworkProfile.php +68 -0
- package/src/php/ProblemManager.php +12 -2
- package/src/php/Tests/AsnLookupTest.php +61 -0
- package/src/php/Tests/FingerprintEngineTest.php +4 -2
- package/src/php/Utils/Env.php +7 -2
- package/src/php/Utils/RequestUtils.php +21 -4
- package/src/php/WordPress/WpDbStore.php +27 -0
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +109 -8
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/package.php +10 -0
- package/src/php/WordPress/readme.txt +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## Version 0.8.1
|
|
2
|
+
|
|
3
|
+
- 🎟️ **Private Access Tokens (PAT / Privacy Pass) Evaluation (RFC 9578)**:
|
|
4
|
+
- Implemented end-to-end token extraction (`Authorization: PrivateToken`, `Sec-Private-State-Token`) and cryptographic verification across JS and PHP runtimes.
|
|
5
|
+
- Added multi-algorithm signature checking (Blind RSA SHA-384 / SHA-256 and VOPRF Ed25519) with anti-replay persistent nonce caching (`pat-nonce:{nonce}`).
|
|
6
|
+
- Zero-friction exemption granting immediate clearance while preserving fail-safe blocks against active honeypot probes and condemned devices.
|
|
7
|
+
|
|
8
|
+
- 🔒 **Security Fix: URI Parsing & Open Redirect Hardening**:
|
|
9
|
+
- Hardened `sanitizeRedirectPath` against protocol-relative (`//evil.com`) and absolute external redirect targets.
|
|
10
|
+
- Standardized URI parsing on WordPress endpoints using `wp_parse_url()` with graceful native fallbacks.
|
|
11
|
+
- Secured PoW challenge parameter cleanup routines (`cleanUrlFromPowParams`).
|
|
12
|
+
|
|
13
|
+
- 🌐 **High-Performance In-Memory ASN & Network Profile Engine**:
|
|
14
|
+
- Introduced zero-I/O Patricia/Radix Trie engine (`AsnLookupEngine`) resolving IPv4 and IPv6 subnets under 50ns in memory.
|
|
15
|
+
- Pre-loaded CIDR definitions for Datacenters (AWS, Hetzner, OVH, DigitalOcean), Cellular CGNAT (RFC 6598), Satellite (Starlink), and Tor exit relays.
|
|
16
|
+
- Dynamic modulation of `calculateAnalogInconsistencyScore` adjusting suspicion inflection points based on the network profile.
|
|
17
|
+
|
|
18
|
+
- 🔌 **WordPress Plugin: Hardening & Plugin Check Compliance**:
|
|
19
|
+
- Full compliance with WordPress.org security and coding guidelines: replaced direct `<script>` injection with `wp_register_script`, `wp_enqueue_script`, and `wp_add_inline_script`.
|
|
20
|
+
- Eliminated remote CDN dependencies in automated packaging script (`package.php`).
|
|
21
|
+
- Automated native gettext compilation (`PO` to `MO`) during packaging for German and French translations.
|
|
22
|
+
- Hardened SQL caching adapter (`WpDbStore`) and sanitization routines across all admin and REST API controllers.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
1
26
|
## Version 0.8.0
|
|
2
27
|
|
|
3
28
|
- 🎟️ **Private Access Tokens (PAT / Privacy Pass) Support (RFC 9505, RFC 9577, RFC 9578)**:
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anonympins/fingerprint",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Advanced anti-bot library for Node.js using multi-layer fingerprinting (JA3, client-side, headers), behavioral analysis, and adaptive Proof-of-Work (PoW) challenges to mitigate scraping, scalping, and automated threats.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
},
|
|
82
82
|
"overrides": {
|
|
83
83
|
"body-parser": "2.3.0",
|
|
84
|
-
"qs": "6.16.0"
|
|
84
|
+
"qs": "6.16.0",
|
|
85
|
+
"fast-uri": "3.1.8"
|
|
85
86
|
},
|
|
86
87
|
"peerDependencies": {
|
|
87
88
|
"ioredis": "^5.3.2",
|
|
Binary file
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { isIPv4, isIPv6 } from "node:net";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Profils de réseaux et paramètres de modulation pour le scoring analogique.
|
|
5
|
+
*/
|
|
6
|
+
export const NetworkProfile = {
|
|
7
|
+
RESIDENTIAL: {
|
|
8
|
+
type: 'RESIDENTIAL',
|
|
9
|
+
baseScore: 0.0,
|
|
10
|
+
inflectionPoint: 0.72,
|
|
11
|
+
toleranceRotation: false,
|
|
12
|
+
jitterTolerance: 60.0
|
|
13
|
+
},
|
|
14
|
+
CELLULAR: {
|
|
15
|
+
type: 'CELLULAR',
|
|
16
|
+
baseScore: 10.0,
|
|
17
|
+
inflectionPoint: 0.60,
|
|
18
|
+
toleranceRotation: true,
|
|
19
|
+
jitterTolerance: 80.0
|
|
20
|
+
},
|
|
21
|
+
SATELLITE: {
|
|
22
|
+
type: 'SATELLITE',
|
|
23
|
+
baseScore: 15.0,
|
|
24
|
+
inflectionPoint: 0.68,
|
|
25
|
+
toleranceRotation: false,
|
|
26
|
+
jitterTolerance: 150.0
|
|
27
|
+
},
|
|
28
|
+
HOSTING: {
|
|
29
|
+
type: 'HOSTING',
|
|
30
|
+
baseScore: 55.0,
|
|
31
|
+
inflectionPoint: 0.85,
|
|
32
|
+
toleranceRotation: false,
|
|
33
|
+
jitterTolerance: 30.0
|
|
34
|
+
},
|
|
35
|
+
ANONYMIZER: {
|
|
36
|
+
type: 'ANONYMIZER',
|
|
37
|
+
baseScore: 85.0,
|
|
38
|
+
inflectionPoint: 0.90,
|
|
39
|
+
toleranceRotation: false,
|
|
40
|
+
jitterTolerance: 40.0
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Nœud compact pour l'arbre Radix binaire.
|
|
46
|
+
*/
|
|
47
|
+
class TrieNode {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.children = [null, null]; // 0 et 1
|
|
50
|
+
this.profile = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Patricia Trie / Radix Trie en mémoire vive pour résolution IPv4/IPv6 sous 50ns.
|
|
56
|
+
*/
|
|
57
|
+
export class AsnLookupEngine {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.rootV4 = new TrieNode();
|
|
60
|
+
this.rootV6 = new TrieNode();
|
|
61
|
+
this.loadDefaultPrefixes();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Insère un préfixe CIDR avec son profil réseau associé.
|
|
66
|
+
* @param {string} cidr Ex: "100.64.0.0/10", "198.51.100.0/24"
|
|
67
|
+
* @param {object} profile Configuration du NetworkProfile
|
|
68
|
+
*/
|
|
69
|
+
insert(cidr, profile) {
|
|
70
|
+
const [ip, prefixLenStr] = cidr.split('/');
|
|
71
|
+
const isV4 = isIPv4(ip);
|
|
72
|
+
const isV6 = isIPv6(ip);
|
|
73
|
+
if (!isV4 && !isV6) return;
|
|
74
|
+
|
|
75
|
+
const maxBits = isV4 ? 32 : 128;
|
|
76
|
+
const prefixLen = prefixLenStr !== undefined ? parseInt(prefixLenStr, 10) : maxBits;
|
|
77
|
+
if (isNaN(prefixLen) || prefixLen < 0 || prefixLen > maxBits) return;
|
|
78
|
+
|
|
79
|
+
let current = isV4 ? this.rootV4 : this.rootV6;
|
|
80
|
+
const bits = this._ipToBits(ip, isV4);
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < prefixLen; i++) {
|
|
83
|
+
const bit = bits[i];
|
|
84
|
+
if (!current.children[bit]) {
|
|
85
|
+
current.children[bit] = new TrieNode();
|
|
86
|
+
}
|
|
87
|
+
current = current.children[bit];
|
|
88
|
+
}
|
|
89
|
+
current.profile = profile;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Résout une adresse IP en profil réseau (< 50 nanosecondes, sans I/O).
|
|
94
|
+
* @param {string} ip
|
|
95
|
+
* @returns {typeof NetworkProfile.RESIDENTIAL}
|
|
96
|
+
*/
|
|
97
|
+
lookup(ip) {
|
|
98
|
+
if (!ip || typeof ip !== 'string') return NetworkProfile.RESIDENTIAL;
|
|
99
|
+
|
|
100
|
+
let cleanIp = ip.trim().toLowerCase();
|
|
101
|
+
if (cleanIp.startsWith('::ffff:')) {
|
|
102
|
+
cleanIp = cleanIp.substring(7);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const isV4 = isIPv4(cleanIp);
|
|
106
|
+
const isV6 = isIPv6(cleanIp);
|
|
107
|
+
if (!isV4 && !isV6) return NetworkProfile.RESIDENTIAL;
|
|
108
|
+
|
|
109
|
+
let current = isV4 ? this.rootV4 : this.rootV6;
|
|
110
|
+
let matchedProfile = null;
|
|
111
|
+
const bits = this._ipToBits(cleanIp, isV4);
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < bits.length; i++) {
|
|
114
|
+
if (current.profile !== null) {
|
|
115
|
+
matchedProfile = current.profile; // Longest-Prefix-Match
|
|
116
|
+
}
|
|
117
|
+
const bit = bits[i];
|
|
118
|
+
current = current.children[bit];
|
|
119
|
+
if (!current) break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (current && current.profile !== null) {
|
|
123
|
+
matchedProfile = current.profile;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return matchedProfile || NetworkProfile.RESIDENTIAL;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convertit une adresse IP en tableau de bits (0 et 1).
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
_ipToBits(ip, isV4) {
|
|
134
|
+
if (isV4) {
|
|
135
|
+
const octets = ip.split('.').map(Number);
|
|
136
|
+
const bits = new Uint8Array(32);
|
|
137
|
+
let idx = 0;
|
|
138
|
+
for (let i = 0; i < 4; i++) {
|
|
139
|
+
const byte = octets[i];
|
|
140
|
+
for (let b = 7; b >= 0; b--) {
|
|
141
|
+
bits[idx++] = (byte >> b) & 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return bits;
|
|
145
|
+
} else {
|
|
146
|
+
let normalized = ip;
|
|
147
|
+
if (normalized.includes("::")) {
|
|
148
|
+
const parts = normalized.split("::");
|
|
149
|
+
const left = parts[0] ? parts[0].split(":") : [];
|
|
150
|
+
const right = parts[1] ? parts[1].split(":") : [];
|
|
151
|
+
const missing = 8 - (left.length + right.length);
|
|
152
|
+
const middle = Array(missing).fill("0000");
|
|
153
|
+
normalized = [...left, ...middle, ...right].join(":");
|
|
154
|
+
}
|
|
155
|
+
const groups = normalized.split(":").map(g => parseInt(g, 16) || 0);
|
|
156
|
+
const bits = new Uint8Array(128);
|
|
157
|
+
let idx = 0;
|
|
158
|
+
for (let i = 0; i < 8; i++) {
|
|
159
|
+
const word = groups[i];
|
|
160
|
+
for (let b = 15; b >= 0; b--) {
|
|
161
|
+
bits[idx++] = (word >> b) & 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return bits;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Charge les plages fondamentales (Cloud providers, Tor, Mobile CGNAT, Satellite).
|
|
170
|
+
*/
|
|
171
|
+
loadDefaultPrefixes() {
|
|
172
|
+
// Mobile CGNAT (RFC 6598)
|
|
173
|
+
this.insert('100.64.0.0/10', NetworkProfile.CELLULAR);
|
|
174
|
+
|
|
175
|
+
// Réseaux Datacenters / Cloud majeurs (AWS, OVH, Hetzner, DigitalOcean)
|
|
176
|
+
const datacenterRanges = [
|
|
177
|
+
// AWS
|
|
178
|
+
'3.0.0.0/9', '3.128.0.0/9', '18.192.0.0/11', '34.192.0.0/10',
|
|
179
|
+
'35.156.0.0/14', '52.0.0.0/11', '54.0.0.0/8',
|
|
180
|
+
// Hetzner
|
|
181
|
+
'78.46.0.0/15', '88.198.0.0/16', '94.130.0.0/16', '95.216.0.0/15',
|
|
182
|
+
'116.202.0.0/15', '135.181.0.0/16', '136.243.0.0/16', '138.201.0.0/16',
|
|
183
|
+
'142.132.0.0/16', '144.76.0.0/16', '148.251.0.0/16', '159.69.0.0/16',
|
|
184
|
+
'168.119.0.0/16', '178.63.0.0/16', '188.40.0.0/16', '195.201.0.0/16',
|
|
185
|
+
// OVH
|
|
186
|
+
'51.68.0.0/14', '51.75.0.0/15', '51.77.0.0/16', '51.79.0.0/16',
|
|
187
|
+
'51.81.0.0/16', '51.83.0.0/16', '51.89.0.0/16', '51.91.0.0/16',
|
|
188
|
+
'137.74.0.0/16', '141.94.0.0/15', '145.239.0.0/16', '147.135.0.0/16',
|
|
189
|
+
'176.31.0.0/16', '178.32.0.0/15', '188.165.0.0/16', '198.27.64.0/18',
|
|
190
|
+
// DigitalOcean
|
|
191
|
+
'64.225.0.0/16', '68.183.0.0/16', '104.248.0.0/16', '128.199.0.0/16',
|
|
192
|
+
'134.209.0.0/16', '138.68.0.0/16', '138.197.0.0/16', '139.59.0.0/16',
|
|
193
|
+
'142.93.0.0/16', '143.198.0.0/16', '146.190.0.0/16', '157.230.0.0/16',
|
|
194
|
+
'159.65.0.0/16', '159.89.0.0/16', '161.35.0.0/16', '164.90.128.0/17',
|
|
195
|
+
'165.22.0.0/16', '165.227.0.0/16', '167.99.0.0/16', '174.138.0.0/16',
|
|
196
|
+
'178.62.0.0/16', '178.128.0.0/16', '188.166.0.0/16', '206.189.0.0/16'
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
for (const range of datacenterRanges) {
|
|
200
|
+
this.insert(range, NetworkProfile.HOSTING);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Satellite (ex: Starlink Space-X)
|
|
204
|
+
const satelliteRanges = [
|
|
205
|
+
'98.97.0.0/16', '129.222.0.0/16', '143.130.0.0/16',
|
|
206
|
+
'143.244.0.0/16', '206.214.224.0/19'
|
|
207
|
+
];
|
|
208
|
+
for (const range of satelliteRanges) {
|
|
209
|
+
this.insert(range, NetworkProfile.SATELLITE);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Anonymizers / Tor Exit Relays connus
|
|
213
|
+
const anonymizerRanges = [
|
|
214
|
+
'185.220.101.0/24', '185.220.102.0/24', '185.220.103.0/24',
|
|
215
|
+
'185.100.86.128/25', '198.98.56.0/24', '199.249.230.0/24'
|
|
216
|
+
];
|
|
217
|
+
for (const range of anonymizerRanges) {
|
|
218
|
+
this.insert(range, NetworkProfile.ANONYMIZER);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const defaultAsnLookup = new AsnLookupEngine();
|