@anonympins/fingerprint 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +1 -1
- package/package.json +4 -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 +214 -29
- package/src/js/tests/asn-lookup.test.js +57 -0
- package/src/php/AsnLookupEngine.php +173 -0
- package/src/php/Challenge/ChallengeUtils.php +162 -64
- package/src/php/DirectFingerprint.php +25 -5
- package/src/php/FingerprintEngine.php +126 -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 +24 -6
- package/src/php/WordPress/WpDbStore.php +27 -0
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +303 -64
- 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 +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,57 @@
|
|
|
1
|
+
## Version 0.8.2
|
|
2
|
+
|
|
3
|
+
- 🌐 **Federated whitelist synchronization (`share_whitelist`)**:
|
|
4
|
+
- Implementation of the decentralized whitelist sharing protocol (`share_whitelist`) across all runtimes (JavaScript, Python, Java, PHP).
|
|
5
|
+
- Asymmetric cryptographic Ed25519 signature and verification (`signature_ed25519`) with strict timestamps and anti-replay tolerance window (5 minutes).
|
|
6
|
+
- Cross-node synchronization of trusted entries (IP addresses, CIDR subnets, and User-Agents) associated with a validity duration (TTL) capped at 7 days.
|
|
7
|
+
- Security guardrails strictly prohibiting the broadcast of permissive wildcards or dangerous open masks (`*`, `0.0.0.0/0`, `::/0`).
|
|
8
|
+
|
|
9
|
+
- 🐍 **Python: race condition fixes and LRU cache**:
|
|
10
|
+
- **Resolution of race condition (PAT & token nonces)**: secured concurrent access in `PatValidator` via conditional atomic acquisition (`set_nx`) of nonces in the store, neutralizing bypasses via simultaneous requests.
|
|
11
|
+
- **LRU cache on network lookup**: added `@lru_cache(maxsize=4096)` decorator to `AsnLookupEngine.lookup` to cache recurring IPv4/IPv6 address resolutions and alleviate Patricia Trie traversal overhead under heavy load.
|
|
12
|
+
- **Memory and fast-path optimization**: reliable periodic pruning of the local fast-path cache (`_fast_path_cache`) and non-blocking cleanup of IP tracking structures.
|
|
13
|
+
|
|
14
|
+
- ☕ **Java: classpath HTML template support**:
|
|
15
|
+
- Added support for the `classpath:` prefix in the `challengePagePath` option in `ChallengeUtils.generateCombinedPoWChallengePage`.
|
|
16
|
+
- Direct loading of custom HTML templates from internal JAR/WAR resources via `getResourceAsStream`, with transparent fallback to the standard filesystem.
|
|
17
|
+
|
|
18
|
+
- 🔌 **WordPress: HTML template editor for challenges**:
|
|
19
|
+
- Integrated a dedicated editor in the WordPress administration interface to customize the PoW interception challenge page HTML.
|
|
20
|
+
- Support and replacement of engine placeholders (`<!-- FINGERPRINT_SOLVER_SCRIPT -->`, `<!-- FINGERPRINT_CHALLENGE_SCRIPT -->`, `<!-- FINGERPRINT_TRAPS -->`).
|
|
21
|
+
- Secure persistence of template customizations in the options table with capability and permission checks.
|
|
22
|
+
|
|
23
|
+
- 🛡️ **WordPress Plugin Check compliance**:
|
|
24
|
+
- Full audit and code cleanup adhering to the official *WordPress Plugin Check* requirements.
|
|
25
|
+
- Hardened output escaping (`esc_html`, `esc_attr`, `esc_url`) and input sanitization.
|
|
26
|
+
- Resolved static analysis warnings regarding user capabilities and security nonces.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Version 0.8.1
|
|
31
|
+
|
|
32
|
+
- 🎟️ **Private Access Tokens (PAT / Privacy Pass) Evaluation (RFC 9578)**:
|
|
33
|
+
- Implemented end-to-end token extraction (`Authorization: PrivateToken`, `Sec-Private-State-Token`) and cryptographic verification across JS and PHP runtimes.
|
|
34
|
+
- Added multi-algorithm signature checking (Blind RSA SHA-384 / SHA-256 and VOPRF Ed25519) with anti-replay persistent nonce caching (`pat-nonce:{nonce}`).
|
|
35
|
+
- Zero-friction exemption granting immediate clearance while preserving fail-safe blocks against active honeypot probes and condemned devices.
|
|
36
|
+
|
|
37
|
+
- 🔒 **Security Fix: URI Parsing & Open Redirect Hardening**:
|
|
38
|
+
- Hardened `sanitizeRedirectPath` against protocol-relative (`//evil.com`) and absolute external redirect targets.
|
|
39
|
+
- Standardized URI parsing on WordPress endpoints using `wp_parse_url()` with graceful native fallbacks.
|
|
40
|
+
- Secured PoW challenge parameter cleanup routines (`cleanUrlFromPowParams`).
|
|
41
|
+
|
|
42
|
+
- 🌐 **High-Performance In-Memory ASN & Network Profile Engine**:
|
|
43
|
+
- Introduced zero-I/O Patricia/Radix Trie engine (`AsnLookupEngine`) resolving IPv4 and IPv6 subnets under 50ns in memory.
|
|
44
|
+
- Pre-loaded CIDR definitions for Datacenters (AWS, Hetzner, OVH, DigitalOcean), Cellular CGNAT (RFC 6598), Satellite (Starlink), and Tor exit relays.
|
|
45
|
+
- Dynamic modulation of `calculateAnalogInconsistencyScore` adjusting suspicion inflection points based on the network profile.
|
|
46
|
+
|
|
47
|
+
- 🔌 **WordPress Plugin: Hardening & Plugin Check Compliance**:
|
|
48
|
+
- 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`.
|
|
49
|
+
- Eliminated remote CDN dependencies in automated packaging script (`package.php`).
|
|
50
|
+
- Automated native gettext compilation (`PO` to `MO`) during packaging for German and French translations.
|
|
51
|
+
- Hardened SQL caching adapter (`WpDbStore`) and sanitization routines across all admin and REST API controllers.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
1
55
|
## Version 0.8.0
|
|
2
56
|
|
|
3
57
|
- 🎟️ **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.2",
|
|
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,9 @@
|
|
|
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",
|
|
86
|
+
"brace-expansion": "1.1.21"
|
|
85
87
|
},
|
|
86
88
|
"peerDependencies": {
|
|
87
89
|
"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();
|