@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
@@ -13,6 +13,9 @@ import {GpuPowSolver} from "./gpu_pow.solver.js";
13
13
  import {
14
14
  verifyZkpProof,
15
15
  sanitizeRedirectPath,
16
+ extractPrivateAccessTokens,
17
+ parsePrivateAccessToken,
18
+ verifyPatSignature,
16
19
  decodePolymorphicFingerprint,
17
20
  deepMerge,
18
21
  getHeaderSignature,
@@ -23,7 +26,8 @@ import {
23
26
  isLoopbackIp,
24
27
  isPrivateIp,
25
28
  parseUserAgent,
26
- safeJsonStringify
29
+ safeJsonStringify,
30
+ generateIssuerPemKeys
27
31
  } from "./fingerprint.utils.js";
28
32
 
29
33
 
@@ -400,6 +404,12 @@ export function getProtocolAnomalyScore(context) {
400
404
  const ua = context.headers?.['user-agent'] || '';
401
405
  const uaParts = parseUserAgent(ua);
402
406
  const browser = uaParts.browser;
407
+ const isChromium = browser?.startsWith('Chrome') || browser?.startsWith('Edge');
408
+ const isFirefox = browser?.startsWith('Firefox');
409
+ const isSafari = browser?.startsWith('Safari');
410
+ const isHumanBrowser = ['Chrome', 'Firefox', 'Safari', 'Edge'].includes(
411
+ browser?.split('/')[0] || null
412
+ );
403
413
 
404
414
  if (browser) {
405
415
  // HTTP/2 Anomaly Logic
@@ -410,9 +420,6 @@ export function getProtocolAnomalyScore(context) {
410
420
  const connWindow = parseInt(parts[1], 10);
411
421
  const streamPriority = parts[2] || '';
412
422
  const headerOrder = parts.length > 3 ? parts[3] : '';
413
- const isChromium = browser.startsWith('Chrome') || browser.startsWith('Edge');
414
- const isFirefox = browser.startsWith('Firefox');
415
- const isSafari = browser.startsWith('Safari');
416
423
 
417
424
  if (isChromium) {
418
425
  if (headerOrder && headerOrder !== 'm,a,s,p') http2Anomaly += 60.0;
@@ -424,6 +431,33 @@ export function getProtocolAnomalyScore(context) {
424
431
  if (headerOrder && headerOrder !== 'm,s,p,a') http2Anomaly += 60.0;
425
432
  }
426
433
 
434
+ // Analyse de la structure des dépendances de flux HTTP/2 (RFC 7540)
435
+ if (streamPriority) {
436
+ const priorityEntries = streamPriority.split(',').map(s => s.trim()).filter(Boolean);
437
+ for (const entry of priorityEntries) {
438
+ const sub = entry.split(':');
439
+ if (sub.length >= 4) {
440
+ const streamId = parseInt(sub[0], 10);
441
+ const exclusive = parseInt(sub[1], 10);
442
+ const depStreamId = parseInt(sub[2], 10);
443
+ const weight = parseInt(sub[3], 10);
444
+
445
+ // Auto-dépendance interdite (RFC 7540 Section 5.3.1)
446
+ if (streamId === depStreamId && streamId > 0) {
447
+ http2Anomaly += 60.0;
448
+ }
449
+ // Poids en dehors de [1, 256] (RFC 7540 Section 5.3.2)
450
+ if (weight < 1 || weight > 256) {
451
+ http2Anomaly += 45.0;
452
+ }
453
+ // Signature scraper : Go net/http2 ou Python h2 par défaut (poids 16 sur flux 0 non-exclusif)
454
+ if (isChromium && depStreamId === 0 && weight === 16 && exclusive === 0) {
455
+ http2Anomaly += 55.0;
456
+ }
457
+ }
458
+ }
459
+ }
460
+
427
461
  // Analyse fine des trames (PRIORITY, WINDOW_UPDATE, CONTINUATION)
428
462
  if (parts.length >= 5) {
429
463
  const frameCountsStr = parts[4];
@@ -444,6 +478,13 @@ export function getProtocolAnomalyScore(context) {
444
478
  if (priorityCount === 0) http2Anomaly += 25.0;
445
479
  // Chrome est agressif avec les WINDOW_UPDATE
446
480
  if (windowUpdateCount < 2) http2Anomaly += 20.0;
481
+
482
+ // NOUVEAU: Analyse de l'arbre de dépendances HTTP/2.
483
+ // Un arbre plat (peu de dépendances) est suspect pour Chromium.
484
+ const priorityDependencies = streamPriority.split(',').length - 1;
485
+ if (streamPriority.includes(',') && priorityDependencies < 2 && priorityCount > 0) {
486
+ http2Anomaly += 35.0;
487
+ }
447
488
  } else if (isFirefox) {
448
489
  // Firefox utilise un schéma de priorité différent, souvent avec moins de trames PRIORITY
449
490
  if (priorityCount > 1) http2Anomaly += 20.0;
@@ -471,10 +512,6 @@ export function getProtocolAnomalyScore(context) {
471
512
  const frameOrderRaw = parts[3] || context.headers?.['x-quic-frame-order'] || context.quicFrameOrder || '';
472
513
  const frameOrder = frameOrderRaw.toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
473
514
 
474
- const isChromium = browser.startsWith('Chrome') || browser.startsWith('Edge');
475
- const isFirefox = browser.startsWith('Firefox');
476
- const isSafari = browser.startsWith('Safari');
477
-
478
515
  const maxData = parseInt(params['1'] || params['0x01'] || '0', 10);
479
516
  const maxStreams = parseInt(params['4'] || params['8'] || params['0x08'] || '0', 10);
480
517
  const bidiLocal = parseInt(params['5'] || params['0x05'] || '0', 10);
@@ -484,7 +521,33 @@ export function getProtocolAnomalyScore(context) {
484
521
  // Contrôle de flux global et nombre de flux bidirectionnels
485
522
  if (maxData > 0 && maxData < 1048576) quicAnomaly += 40.0;
486
523
  if (maxStreams > 0 && maxStreams !== 100) quicAnomaly += 30.0;
487
- if (priorityOrder && !priorityOrder.includes('u=')) quicAnomaly += 30.0;
524
+
525
+ // NOUVEAU: Analyse des trames PRIORITY_UPDATE (RFC 9218)
526
+ if (priorityOrder) {
527
+ // Contrôle de l'urgence (u: 0 à 7 strict)
528
+ const uMatch = priorityOrder.match(/\bu=(-?\d+)\b/);
529
+ if (uMatch) {
530
+ const urgency = parseInt(uMatch[1], 10);
531
+ if (urgency < 0 || urgency > 7) {
532
+ quicAnomaly += 50.0;
533
+ }
534
+ } else if (!priorityOrder.includes('u=')) {
535
+ quicAnomaly += 30.0;
536
+ }
537
+
538
+ // Paramètre incrémental invalide ou malformé
539
+ if (/\bi=\d+\b/.test(priorityOrder)) {
540
+ quicAnomaly += 40.0;
541
+ }
542
+ if (/\bw=\d+\b/.test(priorityOrder)) {
543
+ quicAnomaly += 45.0;
544
+ }
545
+ // Un navigateur légitime enverra probablement une mise à jour incrémentale 'i'
546
+ if (priorityOrder.includes('u=') && !priorityOrder.includes('i') && !/\bu=[0-7],i\b/.test(priorityOrder)) {
547
+ quicAnomaly += 40.0; // Urgence envoyée sans drapeau incrémental, suspect pour Chrome
548
+ }
549
+ if (priorityOrder === 'p' || priorityOrder === 'i') quicAnomaly += 50.0; // Champ malformé
550
+ }
488
551
 
489
552
  // Contrôle de flux bidi (Chromium alloue 6MB = 6291456 ou au minimum 512 Ko)
490
553
  // curl-impersonate / quiche alloue 256 Ko (262144) ou 128 Ko (131072)
@@ -506,6 +569,12 @@ export function getProtocolAnomalyScore(context) {
506
569
  if (pIdx !== -1 && sIdx !== -1 && pIdx < sIdx) {
507
570
  quicAnomaly += 60.0;
508
571
  }
572
+
573
+ // RFC 9114: Le flux de contrôle ne doit pas contenir de doublon SETTINGS
574
+ const sCount = frameOrder.filter(f => f === 's' || f === 'settings' || f === '4').length;
575
+ if (sCount > 1) {
576
+ quicAnomaly += 50.0;
577
+ }
509
578
  }
510
579
  } else if (isFirefox) {
511
580
  const maxData = parseInt(params['1'] || '0', 10);
@@ -535,6 +604,55 @@ export function getProtocolAnomalyScore(context) {
535
604
  }
536
605
  }
537
606
  }
607
+
608
+ // NOUVEAU: Analyse du ratio de compression HPACK/QPACK
609
+ const compressionInfo = context.headers?.['x-compression-info'] || null;
610
+ if (compressionInfo) {
611
+ const info = {};
612
+ compressionInfo.split(',').forEach(p => {
613
+ const [key, value] = p.split(':');
614
+ if (key && value) info[key.trim()] = parseFloat(value);
615
+ });
616
+
617
+ const reqCount = info.req_count || 0;
618
+ const hpackRatio = info.hpack_ratio;
619
+ const qpackRatio = info.qpack_ratio;
620
+ const dynamicEntries = info.dynamic_table_entries !== undefined ? info.dynamic_table_entries : info.table_entries;
621
+ const dynamicHits = info.dynamic_hits;
622
+ const dynamicTableSize = info.dynamic_table_size;
623
+
624
+ // Après quelques requêtes, un vrai navigateur doit avoir un bon ratio de compression
625
+ if (reqCount > 3 && isHumanBrowser) {
626
+ if (hpackRatio !== undefined) {
627
+ if (hpackRatio < 0.4) {
628
+ http2Anomaly += (1 - hpackRatio) * 50;
629
+ } else if (hpackRatio > 0.75) {
630
+ http2Anomaly += Math.min(50.0, (hpackRatio - 0.5) * 100);
631
+ }
632
+ }
633
+ if (qpackRatio !== undefined) {
634
+ if (qpackRatio < 0.4) {
635
+ quicAnomaly += (1 - qpackRatio) * 50;
636
+ } else if (qpackRatio > 0.75) {
637
+ quicAnomaly += Math.min(50.0, (qpackRatio - 0.5) * 100);
638
+ }
639
+ }
640
+
641
+ // Détection de réinitialisation de table dynamique
642
+ if (dynamicEntries !== undefined && dynamicEntries === 0) {
643
+ http2Anomaly += 45.0;
644
+ quicAnomaly += 45.0;
645
+ }
646
+ if (dynamicHits !== undefined && dynamicHits === 0) {
647
+ http2Anomaly += 35.0;
648
+ quicAnomaly += 35.0;
649
+ }
650
+ if (dynamicTableSize !== undefined && dynamicTableSize === 0) {
651
+ http2Anomaly += 40.0;
652
+ quicAnomaly += 40.0;
653
+ }
654
+ }
655
+ }
538
656
  }
539
657
 
540
658
  return {
@@ -2369,29 +2487,17 @@ function getBehaviorScore(context) {
2369
2487
  return { behaviorScore: 0 }; // Pas de données, pas de pénalité.
2370
2488
  }
2371
2489
 
2372
- // Traitement direct si un flag binaire client-side (1 = humain, 0 = bot) est envoyé
2373
- if (behaviorHeader === 1 || behaviorHeader === '1') {
2374
- return { behaviorScore: 0 };
2375
- }
2376
- if (behaviorHeader === 0 || behaviorHeader === '0') {
2377
- return { behaviorScore: 100 };
2378
- }
2379
-
2380
2490
  // Traitement direct si un score numérique est envoyé côté client
2381
2491
  if (typeof behaviorHeader === 'number') {
2382
2492
  return { behaviorScore: Math.max(0, Math.min(100, behaviorHeader)) };
2383
2493
  }
2384
2494
  if (typeof behaviorHeader === 'string' && !isNaN(Number(behaviorHeader)) && behaviorHeader.trim() !== '') {
2385
2495
  const trimmed = behaviorHeader.trim();
2386
- if (trimmed === '1') return { behaviorScore: 0 };
2387
- if (trimmed === '0') return { behaviorScore: 100 };
2388
2496
  return { behaviorScore: Math.max(0, Math.min(100, parseFloat(trimmed))) };
2389
2497
  }
2390
2498
 
2391
2499
  try {
2392
2500
  const metrics = JSON.parse(behaviorHeader);
2393
- if (metrics === 1) return { behaviorScore: 0 };
2394
- if (metrics === 0) return { behaviorScore: 100 };
2395
2501
  if (typeof metrics === 'number') {
2396
2502
  return { behaviorScore: Math.max(0, Math.min(100, metrics)) };
2397
2503
  }
@@ -2713,7 +2819,7 @@ export function getTlsSpoofingScore(context, getTlsFingerprintFn = getTlsFingerp
2713
2819
  }
2714
2820
 
2715
2821
  const { ja3, ja4 } = actualGetTlsFingerprintFn(context) || { ja3: null, ja4: null }; // Defensive check
2716
- const ua = context.headers["user-agent"] || '';
2822
+ const ua = context.headers?.["user-agent"] || '';
2717
2823
  const ja3Raw = context.headers['x-ja3-raw'] || null;
2718
2824
  const httpVersion = context.httpVersion || '';
2719
2825
 
@@ -2735,7 +2841,8 @@ export function getTlsSpoofingScore(context, getTlsFingerprintFn = getTlsFingerp
2735
2841
  score = Math.max(score, 100);
2736
2842
  }
2737
2843
 
2738
- const claimedBrowser = parseUserAgent(ua).browser?.split('/')[0] || null;
2844
+ const uaParts = parseUserAgent(ua);
2845
+ const claimedBrowser = uaParts.browser?.split('/')[0] || null;
2739
2846
  const isHumanBrowser = ['Chrome', 'Firefox', 'Safari', 'Edge'].includes(claimedBrowser);
2740
2847
 
2741
2848
  // --- ANALYSE 2 : CONTRÔLE PROFOND SUR L'EMPREINTE BRUTE (RAW JA3) ---
@@ -2777,8 +2884,6 @@ export function getTlsSpoofingScore(context, getTlsFingerprintFn = getTlsFingerp
2777
2884
  if (ja4) {
2778
2885
  const parsedJa4 = parseJa4(ja4);
2779
2886
  if (parsedJa4) {
2780
- const uaParts = parseUserAgent(ua);
2781
-
2782
2887
  // Check 1: Incohérence ALPN / HTTP Version
2783
2888
  if (parsedJa4.alpn === 'h2' && (context.httpVersion === '1.1' || context.httpVersion === '1.0')) {
2784
2889
  const hasProxy = context.headers['via'] || context.headers['forwarded'] || context.headers['x-forwarded-proto'] || context.headers['x-forwarded-for'];
@@ -2802,6 +2907,25 @@ export function getTlsSpoofingScore(context, getTlsFingerprintFn = getTlsFingerp
2802
2907
  }
2803
2908
  }
2804
2909
 
2910
+ // Validation TLS 1.3 Encrypted Client Hello (ECH) & Discrépance SNI
2911
+ const outerSni = context.headers?.['x-ech-outer-sni'];
2912
+ const host = (context.headers?.['host'] || '').split(':')[0].trim();
2913
+ const hasEch = context.headers?.['x-ech-present'] === 'true' || context.headers?.['x-has-ech'] === 'true';
2914
+ const echExpected = context.headers?.['x-ech-expected'] === 'true' || Boolean(outerSni);
2915
+
2916
+ if (outerSni && host && outerSni.toLowerCase() !== host.toLowerCase() && !hasEch) {
2917
+ score = Math.max(score, 60.0);
2918
+ }
2919
+
2920
+ const browserVersion = parseInt(uaParts.browser?.split('/')[1] || '0', 10);
2921
+ const isModernEchBrowser = (
2922
+ (claimedBrowser === 'Chrome' || claimedBrowser === 'Edge') && browserVersion >= 119
2923
+ ) || (claimedBrowser === 'Firefox' && browserVersion >= 118);
2924
+
2925
+ if (context.isHttps && isModernEchBrowser && echExpected && !hasEch) {
2926
+ score = Math.max(score, 55.0);
2927
+ }
2928
+
2805
2929
  // 2. If no JA3 hash is available, we cannot perform the consistency check.
2806
2930
  if (ja3 && ua) {
2807
2931
  // Look up the expected browser family (or families) from our database.
@@ -2840,7 +2964,6 @@ export function getTlsSpoofingScore(context, getTlsFingerprintFn = getTlsFingerp
2840
2964
  // Create the promise for the async part (Check 4)
2841
2965
  const promise = (async () => {
2842
2966
  let asyncScore = score;
2843
- const uaParts = parseUserAgent(ua);
2844
2967
 
2845
2968
  if (uaParts.browser) {
2846
2969
  const browserFamily = uaParts.browser.split('/')[0];
@@ -4640,6 +4763,8 @@ export class FingerprintEngine {
4640
4763
  // Auto-generate Ed25519 key pair on load if indicated and keys are not set
4641
4764
  if (securityConfig && (securityConfig.useAsymmetricTickets || securityConfig.ed25519 === 'auto') && !process.env.ED25519_PRIVATE_KEY) {
4642
4765
  const persistentKeyPath = join(configDir, 'ed25519_key.json');
4766
+ const privatePemPath = join(configDir, 'issuer-private.pem');
4767
+ const publicPemPath = join(configDir, 'issuer-public.pem');
4643
4768
  if (existsSync(persistentKeyPath)) {
4644
4769
  try {
4645
4770
  const keys = JSON.parse(readFileSync(persistentKeyPath, 'utf-8'));
@@ -4649,6 +4774,14 @@ export class FingerprintEngine {
4649
4774
  } catch (e) {
4650
4775
  console.error('[Fingerprint] Failed to load persistent Ed25519 keys:', e.message);
4651
4776
  }
4777
+ } else if (existsSync(privatePemPath) && existsSync(publicPemPath)) {
4778
+ try {
4779
+ process.env.ED25519_PRIVATE_KEY = readFileSync(privatePemPath, 'utf-8');
4780
+ process.env.ED25519_PUBLIC_KEY = readFileSync(publicPemPath, 'utf-8');
4781
+ this._log('Persistent Ed25519 PEM keys loaded from disk');
4782
+ } catch (e) {
4783
+ console.error('[Fingerprint] Failed to load persistent Ed25519 PEM keys:', e.message);
4784
+ }
4652
4785
  } else {
4653
4786
  try {
4654
4787
  const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519', {
@@ -4751,6 +4884,7 @@ export class FingerprintEngine {
4751
4884
  'federatedPeers', 'federationSecret', 'filterWhitelist',
4752
4885
  'challengeRateLimit',
4753
4886
  'differentialPrivacy', 'dpEpsilon'
4887
+ ,'pat'
4754
4888
  ]);
4755
4889
 
4756
4890
  // 1. Check for essential keys
@@ -5218,6 +5352,39 @@ export class FingerprintEngine {
5218
5352
  return { action: 'next', score: 0, vector: {} };
5219
5353
  }
5220
5354
 
5355
+ // --- PRIVATE ACCESS TOKENS (PAT / RFC 9505 & Privacy Pass) ZERO-FRICTION BYPASS ---
5356
+ const rawPatTokens = extractPrivateAccessTokens(requestContext.headers);
5357
+ if (rawPatTokens.length > 0) {
5358
+ const patConfig = this.securityConfig.pat || {};
5359
+ for (const rawToken of rawPatTokens) {
5360
+ const parsedToken = parsePrivateAccessToken(rawToken);
5361
+ if (parsedToken) {
5362
+ const nonceKey = `pat-nonce:${parsedToken.nonce}`;
5363
+ const isReplayed = await store.has(nonceKey);
5364
+ if (!isReplayed) {
5365
+ const isValid = verifyPatSignature(parsedToken, patConfig);
5366
+ if (isValid) {
5367
+ // Anti-replay: cache nonce with 24-hour TTL
5368
+ const nonceTtl = patConfig.nonceTtl || 86400;
5369
+ await store.set(nonceKey, true, nonceTtl);
5370
+ this._log('Private Access Token (PAT) cryptographically verified - granting zero-friction bypass', {
5371
+ tokenType: parsedToken.tokenType,
5372
+ tokenKeyId: parsedToken.tokenKeyId
5373
+ });
5374
+ const decision = {
5375
+ action: 'next',
5376
+ score: 0.0,
5377
+ vector: { pat_verified: 100.0, privacy_pass: 100.0 },
5378
+ intendedAction: 'next'
5379
+ };
5380
+ if (newCookie) decision.newCookieForResponse = newCookie;
5381
+ return decision;
5382
+ }
5383
+ }
5384
+ }
5385
+ }
5386
+ }
5387
+
5221
5388
  // Resolve identity and check for persisted "condemned" status early.
5222
5389
  const currentDeviceHash = getCompositeDeviceHash(requestContext);
5223
5390
  const isNewDevice = !!newCookie;
@@ -6426,7 +6593,8 @@ function sanitizeProxyHeaders(context, securityConfig) {
6426
6593
  'x-ja4-hash',
6427
6594
  'x-http2-fingerprint',
6428
6595
  'x-tcp-fingerprint',
6429
- 'x-ja3-raw'
6596
+ 'x-ja3-raw',
6597
+ 'x-compression-info'
6430
6598
  ];
6431
6599
 
6432
6600
  if (securityConfig && securityConfig.trustedProxies) {
@@ -7270,6 +7438,7 @@ export const __internal = {
7270
7438
  findPeerInSubnet,
7271
7439
  handleCooperativeRequest,
7272
7440
  broadcastBannedZkp,
7441
+ generateIssuerPemKeys,
7273
7442
  getMetric,
7274
7443
  incrementCounter,
7275
7444
  observeValue
@@ -7969,4 +8138,5 @@ export async function handleMetricsRequest(req, res, securityConfig) {
7969
8138
  // 3. Si autorisé, servir les métriques.
7970
8139
  res.set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
7971
8140
  res.send(MetricsManager.getPrometheusMetrics(securityConfig));
7972
- }
8141
+ }
8142
+ export { generateIssuerPemKeys };