@prosopo/provider 4.15.13 → 4.15.15

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 (236) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +106 -106
  2. package/.turbo/turbo-build$colon$tsc.log +24 -24
  3. package/.turbo/turbo-build.log +107 -104
  4. package/CHANGELOG.md +78 -0
  5. package/dist/_virtual/_rolldown/runtime.js +3 -0
  6. package/dist/api/admin/apiAdminRoutesProvider.js +27 -46
  7. package/dist/api/admin/apiClearAllCountersEndpoint.js +62 -57
  8. package/dist/api/admin/apiDnsEventEndpoint.js +79 -92
  9. package/dist/api/admin/apiGetAllDecisionMachinesEndpoint.js +43 -45
  10. package/dist/api/admin/apiGetDecisionMachineEndpoint.js +40 -40
  11. package/dist/api/admin/apiRegisterSiteKeyEndpoint.js +23 -22
  12. package/dist/api/admin/apiRegisterSiteKeysEndpoint.js +26 -30
  13. package/dist/api/admin/apiRemoveAllDecisionMachinesEndpoint.js +35 -37
  14. package/dist/api/admin/apiRemoveDecisionMachineEndpoint.js +40 -40
  15. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.d.ts.map +1 -1
  16. package/dist/api/admin/apiRemoveDetectorKeyEndpoint.js +29 -31
  17. package/dist/api/admin/apiRemoveSiteKeyEndpoint.js +33 -29
  18. package/dist/api/admin/apiRemoveSiteKeysEndpoint.js +32 -31
  19. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +43 -35
  20. package/dist/api/admin/apiUpdateDecisionMachineEndpoint.js +43 -63
  21. package/dist/api/admin/apiUpdateDetectorKeyEndpoint.js +36 -35
  22. package/dist/api/admin/createApiAdminRoutesProvider.js +6 -8
  23. package/dist/api/blacklistRequestInspector.js +285 -377
  24. package/dist/api/block.js +17 -30
  25. package/dist/api/captcha/checkSpamEmail.js +88 -98
  26. package/dist/api/captcha/contextAwareValidation.js +21 -15
  27. package/dist/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.js +126 -140
  28. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +11 -12
  29. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.js +236 -299
  30. package/dist/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.js +10 -13
  31. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +336 -462
  32. package/dist/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.js +17 -14
  33. package/dist/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.js +27 -27
  34. package/dist/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.js +42 -63
  35. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +2 -4
  36. package/dist/api/captcha/getImageCaptchaChallenge.js +136 -207
  37. package/dist/api/captcha/getPoWCaptchaChallenge.js +128 -198
  38. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +132 -201
  39. package/dist/api/captcha/maintenanceModeResponses.js +38 -47
  40. package/dist/api/captcha/submitImageCaptchaSolution.js +75 -101
  41. package/dist/api/captcha/submitPoWCaptchaSolution.js +137 -207
  42. package/dist/api/captcha/submitPuzzleCaptchaSolution.js +73 -105
  43. package/dist/api/captcha.js +76 -58
  44. package/dist/api/dnsEventUrl.js +20 -22
  45. package/dist/api/domainMiddleware.js +104 -115
  46. package/dist/api/forwardVerify.js +62 -48
  47. package/dist/api/handshakeTimingMiddleware.js +43 -62
  48. package/dist/api/hardBlockVerdictCache.js +114 -117
  49. package/dist/api/headerCheckMiddleware.js +34 -34
  50. package/dist/api/ignoreMiddleware.js +13 -15
  51. package/dist/api/ipInfoMiddleware.js +17 -19
  52. package/dist/api/ja4.js +149 -188
  53. package/dist/api/ja4Middleware.js +39 -54
  54. package/dist/api/metrics.js +245 -257
  55. package/dist/api/public.js +52 -54
  56. package/dist/api/robotsMiddleware.js +10 -10
  57. package/dist/api/startProviderApi.js +205 -232
  58. package/dist/api/testSiteKey.js +16 -16
  59. package/dist/api/validateAddress.js +23 -21
  60. package/dist/api/verify.js +334 -377
  61. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  62. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +40 -59
  63. package/dist/cjs/api/admin/apiClearAllCountersEndpoint.cjs +63 -58
  64. package/dist/cjs/api/admin/apiDnsEventEndpoint.cjs +82 -93
  65. package/dist/cjs/api/admin/apiGetAllDecisionMachinesEndpoint.cjs +46 -48
  66. package/dist/cjs/api/admin/apiGetDecisionMachineEndpoint.cjs +41 -41
  67. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +25 -24
  68. package/dist/cjs/api/admin/apiRegisterSiteKeysEndpoint.cjs +28 -32
  69. package/dist/cjs/api/admin/apiRemoveAllDecisionMachinesEndpoint.cjs +38 -40
  70. package/dist/cjs/api/admin/apiRemoveDecisionMachineEndpoint.cjs +41 -41
  71. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +31 -33
  72. package/dist/cjs/api/admin/apiRemoveSiteKeyEndpoint.cjs +35 -31
  73. package/dist/cjs/api/admin/apiRemoveSiteKeysEndpoint.cjs +34 -33
  74. package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +45 -35
  75. package/dist/cjs/api/admin/apiUpdateDecisionMachineEndpoint.cjs +45 -65
  76. package/dist/cjs/api/admin/apiUpdateDetectorKeyEndpoint.cjs +38 -37
  77. package/dist/cjs/api/admin/createApiAdminRoutesProvider.cjs +7 -8
  78. package/dist/cjs/api/blacklistRequestInspector.cjs +288 -376
  79. package/dist/cjs/api/block.cjs +18 -30
  80. package/dist/cjs/api/captcha/checkSpamEmail.cjs +90 -100
  81. package/dist/cjs/api/captcha/contextAwareValidation.cjs +21 -14
  82. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/accessPolicy.cjs +125 -140
  83. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +10 -10
  84. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decisionMachine.cjs +238 -301
  85. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/decryptSimdReadings.cjs +9 -12
  86. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +349 -473
  87. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/honeypotResponse.cjs +18 -14
  88. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/sessionDedup.cjs +26 -26
  89. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/shortCircuit.cjs +42 -63
  90. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +2 -3
  91. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +138 -208
  92. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +131 -200
  93. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +135 -203
  94. package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +38 -44
  95. package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +77 -102
  96. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +140 -209
  97. package/dist/cjs/api/captcha/submitPuzzleCaptchaSolution.cjs +76 -107
  98. package/dist/cjs/api/captcha.cjs +79 -58
  99. package/dist/cjs/api/dnsEventUrl.cjs +20 -21
  100. package/dist/cjs/api/domainMiddleware.cjs +106 -117
  101. package/dist/cjs/api/forwardVerify.cjs +63 -49
  102. package/dist/cjs/api/handshakeTimingMiddleware.cjs +43 -61
  103. package/dist/cjs/api/hardBlockVerdictCache.cjs +114 -114
  104. package/dist/cjs/api/headerCheckMiddleware.cjs +34 -34
  105. package/dist/cjs/api/ignoreMiddleware.cjs +12 -14
  106. package/dist/cjs/api/ipInfoMiddleware.cjs +16 -18
  107. package/dist/cjs/api/ja4.cjs +149 -187
  108. package/dist/cjs/api/ja4Middleware.cjs +40 -53
  109. package/dist/cjs/api/metrics.cjs +229 -228
  110. package/dist/cjs/api/public.cjs +57 -57
  111. package/dist/cjs/api/robotsMiddleware.cjs +9 -9
  112. package/dist/cjs/api/startProviderApi.cjs +219 -237
  113. package/dist/cjs/api/testSiteKey.cjs +15 -14
  114. package/dist/cjs/api/validateAddress.cjs +24 -21
  115. package/dist/cjs/api/verify.cjs +338 -379
  116. package/dist/cjs/compositeIpAddress.cjs +40 -47
  117. package/dist/cjs/index.cjs +65 -71
  118. package/dist/cjs/pairs.cjs +31 -35
  119. package/dist/cjs/rules/lang.cjs +10 -14
  120. package/dist/cjs/schedulers/captchaScheduler.cjs +26 -32
  121. package/dist/cjs/schedulers/getClientList.cjs +35 -33
  122. package/dist/cjs/schedulers/setClientEntropy.cjs +35 -33
  123. package/dist/cjs/schedulers/updateSpamEmailDomains.cjs +39 -43
  124. package/dist/cjs/services/ipComparison.cjs +122 -119
  125. package/dist/cjs/tasks/captchaManager.cjs +407 -486
  126. package/dist/cjs/tasks/client/clientTasks.cjs +347 -470
  127. package/dist/cjs/tasks/dataset/datasetTasks.cjs +25 -29
  128. package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +16 -32
  129. package/dist/cjs/tasks/decisionMachine/decisionMachineRunner.cjs +220 -307
  130. package/dist/cjs/tasks/detection/decodeBehavior.cjs +408 -317
  131. package/dist/cjs/tasks/detection/decodePayload.cjs +686 -468
  132. package/dist/cjs/tasks/detection/decodeSimd.cjs +462 -304
  133. package/dist/cjs/tasks/detection/getBotScore.cjs +37 -43
  134. package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +88 -71
  135. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +348 -486
  136. package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +13 -30
  137. package/dist/cjs/tasks/frictionless/routingMachine.cjs +71 -55
  138. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +555 -806
  139. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasksUtils.cjs +22 -23
  140. package/dist/cjs/tasks/index.cjs +2 -4
  141. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +491 -687
  142. package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +17 -29
  143. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +422 -591
  144. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasksUtils.cjs +8 -5
  145. package/dist/cjs/tasks/spam/checkSpamEmail.cjs +133 -141
  146. package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +118 -76
  147. package/dist/cjs/tasks/spam/evaluateEmailSpamRules.cjs +75 -84
  148. package/dist/cjs/tasks/spam/updateSpamEmailDomains.cjs +40 -56
  149. package/dist/cjs/tasks/tasks.cjs +107 -158
  150. package/dist/cjs/util/usageCounters.cjs +220 -211
  151. package/dist/cjs/util.cjs +329 -359
  152. package/dist/cjs/utils/devicePlatform.cjs +14 -8
  153. package/dist/cjs/utils/dns.cjs +68 -93
  154. package/dist/cjs/utils/hashUserAgent.cjs +14 -7
  155. package/dist/cjs/utils/hashUserIp.cjs +14 -6
  156. package/dist/cjs/utils/honeypot/encoders.cjs +74 -80
  157. package/dist/cjs/utils/honeypot/phraseBank.cjs +51 -44
  158. package/dist/cjs/utils/normalizeRequestIp.cjs +33 -25
  159. package/dist/compositeIpAddress.js +38 -46
  160. package/dist/index.js +12 -56
  161. package/dist/pairs.js +31 -37
  162. package/dist/rules/lang.js +11 -15
  163. package/dist/schedulers/captchaScheduler.js +25 -31
  164. package/dist/schedulers/getClientList.js +34 -32
  165. package/dist/schedulers/setClientEntropy.js +34 -32
  166. package/dist/schedulers/updateSpamEmailDomains.js +38 -42
  167. package/dist/services/ipComparison.js +122 -119
  168. package/dist/tasks/captchaManager.js +403 -483
  169. package/dist/tasks/client/clientTasks.js +343 -467
  170. package/dist/tasks/dataset/datasetTasks.js +24 -28
  171. package/dist/tasks/dataset/datasetTasksUtils.js +15 -31
  172. package/dist/tasks/decisionMachine/decisionMachineRunner.js +217 -306
  173. package/dist/tasks/detection/getBotScore.js +37 -43
  174. package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
  175. package/dist/tasks/dnsEvent/enrichDnsEvent.js +88 -74
  176. package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
  177. package/dist/tasks/frictionless/frictionlessTasks.js +342 -479
  178. package/dist/tasks/frictionless/frictionlessTasksUtils.js +14 -32
  179. package/dist/tasks/frictionless/routingMachine.js +72 -56
  180. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +550 -801
  181. package/dist/tasks/imgCaptcha/imgCaptchaTasksUtils.js +21 -22
  182. package/dist/tasks/index.js +1 -3
  183. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  184. package/dist/tasks/powCaptcha/powTasks.js +486 -682
  185. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  186. package/dist/tasks/powCaptcha/powTasksUtils.js +15 -28
  187. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  188. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +417 -586
  189. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  190. package/dist/tasks/puzzleCaptcha/puzzleTasksUtils.js +9 -6
  191. package/dist/tasks/spam/checkSpamEmail.js +133 -141
  192. package/dist/tasks/spam/checkTrafficFilter.d.ts +1 -0
  193. package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
  194. package/dist/tasks/spam/checkTrafficFilter.js +117 -77
  195. package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
  196. package/dist/tasks/spam/evaluateEmailSpamRules.js +75 -86
  197. package/dist/tasks/spam/updateSpamEmailDomains.js +36 -54
  198. package/dist/tasks/tasks.js +98 -150
  199. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js +4 -1
  200. package/dist/tests/integration/api/blacklistRequestInspector.integration.test.js.map +1 -1
  201. package/dist/tests/unit/api/block.unit.test.js +8 -4
  202. package/dist/tests/unit/api/block.unit.test.js.map +1 -1
  203. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +8 -6
  204. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -1
  205. package/dist/tests/unit/api/forwardVerify.unit.test.js +3 -1
  206. package/dist/tests/unit/api/forwardVerify.unit.test.js.map +1 -1
  207. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +15 -7
  208. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  209. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js +24 -18
  210. package/dist/tests/unit/schedulers/captchaScheduler.unit.test.js.map +1 -1
  211. package/dist/tests/unit/schedulers/getClientList.unit.test.js +19 -13
  212. package/dist/tests/unit/schedulers/getClientList.unit.test.js.map +1 -1
  213. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js +19 -13
  214. package/dist/tests/unit/schedulers/setClientEntropy.unit.test.js.map +1 -1
  215. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
  216. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js +22 -16
  217. package/dist/tests/unit/tasks/imgCaptcha/imgCaptchaTasksUtils.unit.test.js.map +1 -1
  218. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js +6 -0
  219. package/dist/tests/unit/tasks/powCaptcha/powTasks.unit.test.js.map +1 -1
  220. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js +60 -1
  221. package/dist/tests/unit/tasks/puzzleCaptcha/puzzleTasks.unit.test.js.map +1 -1
  222. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +140 -0
  223. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
  224. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +7 -7
  225. package/dist/util/usageCounters.js +220 -212
  226. package/dist/util.d.ts.map +1 -1
  227. package/dist/util.js +327 -364
  228. package/dist/util.js.map +1 -1
  229. package/dist/utils/devicePlatform.js +15 -9
  230. package/dist/utils/dns.js +64 -77
  231. package/dist/utils/hashUserAgent.js +14 -7
  232. package/dist/utils/hashUserIp.js +14 -6
  233. package/dist/utils/honeypot/encoders.js +74 -80
  234. package/dist/utils/honeypot/phraseBank.js +49 -44
  235. package/dist/utils/normalizeRequestIp.js +34 -26
  236. package/package.json +24 -24
package/dist/api/ja4.js CHANGED
@@ -1,203 +1,164 @@
1
1
  import { createHash } from "node:crypto";
2
- const RECORD_HEADER_LENGTH = 5;
3
- const HANDSHAKE_HEADER_LENGTH = 4;
4
- const HANDSHAKE_RECORD_TYPE = 22;
5
- const HANDSHAKE_MESSAGE_TYPE = 1;
6
- const EXT_SERVER_NAME = 0;
7
- const EXT_SIGNATURE_ALGORITHMS = 13;
8
- const EXT_ALPN = 16;
9
- const EXT_SUPPORTED_VERSIONS = 43;
2
+ //#region src/api/ja4.ts
3
+ var RECORD_HEADER_LENGTH = 5;
4
+ var HANDSHAKE_HEADER_LENGTH = 4;
5
+ var HANDSHAKE_RECORD_TYPE = 22;
6
+ var HANDSHAKE_MESSAGE_TYPE = 1;
7
+ var EXT_SERVER_NAME = 0;
8
+ var EXT_SIGNATURE_ALGORITHMS = 13;
9
+ var EXT_ALPN = 16;
10
+ var EXT_SUPPORTED_VERSIONS = 43;
10
11
  function isGrease(id) {
11
- return (id & 3855) === 2570 && id >> 8 === (id & 255);
12
+ return (id & 3855) === 2570 && id >> 8 === (id & 255);
12
13
  }
13
14
  function hash12(input) {
14
- return createHash("sha256").update(input).digest("hex").slice(0, 12);
15
+ return createHash("sha256").update(input).digest("hex").slice(0, 12);
15
16
  }
16
17
  function formatIdList(ids) {
17
- return ids.map((id) => id.toString(16).padStart(4, "0")).join(",");
18
+ return ids.map((id) => id.toString(16).padStart(4, "0")).join(",");
18
19
  }
19
20
  function alpnChar(b) {
20
- if (b >= 48 && b <= 57 || // 0-9
21
- b >= 65 && b <= 90 || // A-Z
22
- b >= 97 && b <= 122) {
23
- return String.fromCharCode(b);
24
- }
25
- return b.toString(16).padStart(2, "0");
21
+ if (b >= 48 && b <= 57 || b >= 65 && b <= 90 || b >= 97 && b <= 122) return String.fromCharCode(b);
22
+ return b.toString(16).padStart(2, "0");
26
23
  }
27
24
  function alpnFirstLast(protocol) {
28
- const first = protocol.length > 0 ? alpnChar(protocol.readUInt8(0)) : "0";
29
- const last = protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0";
30
- return [first, last];
25
+ return [protocol.length > 0 ? alpnChar(protocol.readUInt8(0)) : "0", protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0"];
31
26
  }
32
27
  function tlsVersionStr(version) {
33
- switch (version) {
34
- case 769:
35
- return "10";
36
- case 770:
37
- return "11";
38
- case 771:
39
- return "12";
40
- case 772:
41
- return "13";
42
- default:
43
- return "00";
44
- }
45
- }
46
- class Ja4ParseError extends Error {
47
- constructor(message) {
48
- super(message);
49
- this.name = "Ja4ParseError";
50
- }
28
+ switch (version) {
29
+ case 769: return "10";
30
+ case 770: return "11";
31
+ case 771: return "12";
32
+ case 772: return "13";
33
+ default: return "00";
34
+ }
51
35
  }
36
+ var Ja4ParseError = class extends Error {
37
+ constructor(message) {
38
+ super(message);
39
+ this.name = "Ja4ParseError";
40
+ }
41
+ };
42
+ /**
43
+ * Compute a JA4 TLS fingerprint from raw ClientHello bytes.
44
+ *
45
+ * Expects the full TLS record:
46
+ * 5-byte record header (content_type + version + record_len)
47
+ * 4-byte handshake header (msg_type + msg_len)
48
+ * ClientHello body
49
+ *
50
+ * Throws Ja4ParseError for structural failures in the TLS record or handshake
51
+ * header (wrong record type, wrong message type, declared lengths exceeding the
52
+ * buffer, odd cipher-suite length).
53
+ *
54
+ * Extension-level malformation (truncated extension body, malformed ALPN
55
+ * entries) is handled defensively: parsing stops at the first bad entry and
56
+ * a fingerprint is produced from whatever was successfully parsed. This matches
57
+ * the Rust reference implementation and avoids turning extension parse errors
58
+ * into connection-fingerprinting failures.
59
+ */
52
60
  function calculateJa4(data) {
53
- let offset = 0;
54
- if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) {
55
- throw new Ja4ParseError("ClientHello too short");
56
- }
57
- const recordType = data.readUInt8(offset);
58
- offset += 1;
59
- if (recordType !== HANDSHAKE_RECORD_TYPE) {
60
- throw new Ja4ParseError(
61
- `Invalid record type: 0x${recordType.toString(16)}`
62
- );
63
- }
64
- offset += 2;
65
- const recordLen = data.readUInt16BE(offset);
66
- offset += 2;
67
- if (data.length < RECORD_HEADER_LENGTH + recordLen) {
68
- throw new Ja4ParseError("Record length exceeds buffer");
69
- }
70
- data = data.subarray(0, RECORD_HEADER_LENGTH + recordLen);
71
- if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) {
72
- throw new Ja4ParseError("Record too short for handshake header");
73
- }
74
- const msgType = data.readUInt8(offset);
75
- offset += 1;
76
- if (msgType !== HANDSHAKE_MESSAGE_TYPE) {
77
- throw new Ja4ParseError(
78
- `Not a ClientHello: handshake type 0x${msgType.toString(16)}`
79
- );
80
- }
81
- const msgLen = data.readUIntBE(offset, 3);
82
- offset += 3;
83
- if (data.length - offset < msgLen) {
84
- throw new Ja4ParseError("Handshake message length exceeds record");
85
- }
86
- data = data.subarray(0, offset + msgLen);
87
- if (data.length - offset < 35) {
88
- throw new Ja4ParseError("ClientHello body too short");
89
- }
90
- let clientVersion = data.readUInt16BE(offset);
91
- offset += 2;
92
- offset += 32;
93
- const sessionIdLen = data.readUInt8(offset);
94
- offset += 1;
95
- if (data.length - offset < sessionIdLen) {
96
- throw new Ja4ParseError("Truncated at session ID");
97
- }
98
- offset += sessionIdLen;
99
- if (data.length - offset < 2) {
100
- throw new Ja4ParseError("Truncated at cipher suites length");
101
- }
102
- const cipherSuitesLen = data.readUInt16BE(offset);
103
- offset += 2;
104
- if (data.length - offset < cipherSuitesLen) {
105
- throw new Ja4ParseError("Truncated at cipher suites");
106
- }
107
- if (cipherSuitesLen % 2 !== 0) {
108
- throw new Ja4ParseError("Cipher suites length is not a multiple of 2");
109
- }
110
- const cipherSuites = [];
111
- for (let i = 0; i < cipherSuitesLen; i += 2) {
112
- cipherSuites.push(data.readUInt16BE(offset + i));
113
- }
114
- offset += cipherSuitesLen;
115
- if (data.length - offset < 1) {
116
- throw new Ja4ParseError("Truncated at compression methods");
117
- }
118
- const compressionLen = data.readUInt8(offset);
119
- offset += 1;
120
- if (data.length - offset < compressionLen) {
121
- throw new Ja4ParseError("Truncated at compression methods data");
122
- }
123
- offset += compressionLen;
124
- if (data.length - offset < 2) {
125
- throw new Ja4ParseError("Truncated at extensions length");
126
- }
127
- const extensionsLen = data.readUInt16BE(offset);
128
- offset += 2;
129
- if (data.length - offset < extensionsLen) {
130
- throw new Ja4ParseError("Truncated at extensions");
131
- }
132
- const extIds = [];
133
- const sigAlgorithms = [];
134
- const alpnProtocols = [];
135
- let extOffset = offset;
136
- const extEnd = offset + extensionsLen;
137
- while (extOffset + 4 <= extEnd) {
138
- const extId = data.readUInt16BE(extOffset);
139
- extOffset += 2;
140
- const extLen = data.readUInt16BE(extOffset);
141
- extOffset += 2;
142
- if (extOffset + extLen > extEnd) break;
143
- const extData = data.subarray(extOffset, extOffset + extLen);
144
- extOffset += extLen;
145
- extIds.push(extId);
146
- if (extId === EXT_SUPPORTED_VERSIONS) {
147
- if (extData.length >= 1) {
148
- const listLen = extData.readUInt8(0);
149
- let best = 0;
150
- for (let i = 1; i + 1 < extData.length && i + 1 <= listLen; i += 2) {
151
- const v = extData.readUInt16BE(i);
152
- if (!isGrease(v) && v > best) best = v;
153
- }
154
- if (best > 0) clientVersion = best;
155
- }
156
- } else if (extId === EXT_SIGNATURE_ALGORITHMS) {
157
- if (extData.length >= 2) {
158
- const algLen = extData.readUInt16BE(0);
159
- for (let i = 2; i + 1 < extData.length && i + 1 < algLen + 2; i += 2) {
160
- const alg = extData.readUInt16BE(i);
161
- if (!isGrease(alg)) sigAlgorithms.push(alg);
162
- }
163
- }
164
- } else if (extId === EXT_ALPN) {
165
- if (extData.length >= 2) {
166
- const listLen = extData.readUInt16BE(0);
167
- const listEnd = Math.min(extData.length, 2 + listLen);
168
- let alpnOff = 2;
169
- while (alpnOff < listEnd) {
170
- const protoLen = extData.readUInt8(alpnOff);
171
- alpnOff += 1;
172
- if (alpnOff + protoLen > listEnd) break;
173
- alpnProtocols.push(extData.subarray(alpnOff, alpnOff + protoLen));
174
- alpnOff += protoLen;
175
- }
176
- }
177
- }
178
- }
179
- const versionStr = tlsVersionStr(clientVersion);
180
- const sni = extIds.includes(EXT_SERVER_NAME) ? "d" : "i";
181
- const firstAlpn = alpnProtocols[0];
182
- const [alpnFirst, alpnLast] = firstAlpn ? alpnFirstLast(firstAlpn) : ["0", "0"];
183
- const nonGreaseCiphers = cipherSuites.filter((c) => !isGrease(c));
184
- const nonGreaseExts = extIds.filter((e) => !isGrease(e));
185
- const cipherCount = Math.min(99, nonGreaseCiphers.length).toString().padStart(2, "0");
186
- const extCount = Math.min(99, nonGreaseExts.length).toString().padStart(2, "0");
187
- const firstChunk = `t${versionStr}${sni}${cipherCount}${extCount}${alpnFirst}${alpnLast}`;
188
- const sortedCiphers = [...nonGreaseCiphers].sort((a, b) => a - b);
189
- const cipherHash = hash12(formatIdList(sortedCiphers));
190
- const filteredExts = nonGreaseExts.filter(
191
- (e) => e !== EXT_SERVER_NAME && e !== EXT_ALPN
192
- );
193
- const sortedExts = [...filteredExts].sort((a, b) => a - b);
194
- const extStr = formatIdList(sortedExts);
195
- const sigAlgStr = formatIdList(sigAlgorithms);
196
- const extHashInput = sigAlgStr.length > 0 ? `${extStr}_${sigAlgStr}` : extStr;
197
- const extHash = hash12(extHashInput);
198
- return `${firstChunk}_${cipherHash}_${extHash}`;
61
+ let offset = 0;
62
+ if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) throw new Ja4ParseError("ClientHello too short");
63
+ const recordType = data.readUInt8(offset);
64
+ offset += 1;
65
+ if (recordType !== HANDSHAKE_RECORD_TYPE) throw new Ja4ParseError(`Invalid record type: 0x${recordType.toString(16)}`);
66
+ offset += 2;
67
+ const recordLen = data.readUInt16BE(offset);
68
+ offset += 2;
69
+ if (data.length < RECORD_HEADER_LENGTH + recordLen) throw new Ja4ParseError("Record length exceeds buffer");
70
+ data = data.subarray(0, RECORD_HEADER_LENGTH + recordLen);
71
+ if (data.length < RECORD_HEADER_LENGTH + HANDSHAKE_HEADER_LENGTH) throw new Ja4ParseError("Record too short for handshake header");
72
+ const msgType = data.readUInt8(offset);
73
+ offset += 1;
74
+ if (msgType !== HANDSHAKE_MESSAGE_TYPE) throw new Ja4ParseError(`Not a ClientHello: handshake type 0x${msgType.toString(16)}`);
75
+ const msgLen = data.readUIntBE(offset, 3);
76
+ offset += 3;
77
+ if (data.length - offset < msgLen) throw new Ja4ParseError("Handshake message length exceeds record");
78
+ data = data.subarray(0, offset + msgLen);
79
+ if (data.length - offset < 35) throw new Ja4ParseError("ClientHello body too short");
80
+ let clientVersion = data.readUInt16BE(offset);
81
+ offset += 2;
82
+ offset += 32;
83
+ const sessionIdLen = data.readUInt8(offset);
84
+ offset += 1;
85
+ if (data.length - offset < sessionIdLen) throw new Ja4ParseError("Truncated at session ID");
86
+ offset += sessionIdLen;
87
+ if (data.length - offset < 2) throw new Ja4ParseError("Truncated at cipher suites length");
88
+ const cipherSuitesLen = data.readUInt16BE(offset);
89
+ offset += 2;
90
+ if (data.length - offset < cipherSuitesLen) throw new Ja4ParseError("Truncated at cipher suites");
91
+ if (cipherSuitesLen % 2 !== 0) throw new Ja4ParseError("Cipher suites length is not a multiple of 2");
92
+ const cipherSuites = [];
93
+ for (let i = 0; i < cipherSuitesLen; i += 2) cipherSuites.push(data.readUInt16BE(offset + i));
94
+ offset += cipherSuitesLen;
95
+ if (data.length - offset < 1) throw new Ja4ParseError("Truncated at compression methods");
96
+ const compressionLen = data.readUInt8(offset);
97
+ offset += 1;
98
+ if (data.length - offset < compressionLen) throw new Ja4ParseError("Truncated at compression methods data");
99
+ offset += compressionLen;
100
+ if (data.length - offset < 2) throw new Ja4ParseError("Truncated at extensions length");
101
+ const extensionsLen = data.readUInt16BE(offset);
102
+ offset += 2;
103
+ if (data.length - offset < extensionsLen) throw new Ja4ParseError("Truncated at extensions");
104
+ const extIds = [];
105
+ const sigAlgorithms = [];
106
+ const alpnProtocols = [];
107
+ let extOffset = offset;
108
+ const extEnd = offset + extensionsLen;
109
+ while (extOffset + 4 <= extEnd) {
110
+ const extId = data.readUInt16BE(extOffset);
111
+ extOffset += 2;
112
+ const extLen = data.readUInt16BE(extOffset);
113
+ extOffset += 2;
114
+ if (extOffset + extLen > extEnd) break;
115
+ const extData = data.subarray(extOffset, extOffset + extLen);
116
+ extOffset += extLen;
117
+ extIds.push(extId);
118
+ if (extId === EXT_SUPPORTED_VERSIONS) {
119
+ if (extData.length >= 1) {
120
+ const listLen = extData.readUInt8(0);
121
+ let best = 0;
122
+ for (let i = 1; i + 1 < extData.length && i + 1 <= listLen; i += 2) {
123
+ const v = extData.readUInt16BE(i);
124
+ if (!isGrease(v) && v > best) best = v;
125
+ }
126
+ if (best > 0) clientVersion = best;
127
+ }
128
+ } else if (extId === EXT_SIGNATURE_ALGORITHMS) {
129
+ if (extData.length >= 2) {
130
+ const algLen = extData.readUInt16BE(0);
131
+ for (let i = 2; i + 1 < extData.length && i + 1 < algLen + 2; i += 2) {
132
+ const alg = extData.readUInt16BE(i);
133
+ if (!isGrease(alg)) sigAlgorithms.push(alg);
134
+ }
135
+ }
136
+ } else if (extId === EXT_ALPN) {
137
+ if (extData.length >= 2) {
138
+ const listLen = extData.readUInt16BE(0);
139
+ const listEnd = Math.min(extData.length, 2 + listLen);
140
+ let alpnOff = 2;
141
+ while (alpnOff < listEnd) {
142
+ const protoLen = extData.readUInt8(alpnOff);
143
+ alpnOff += 1;
144
+ if (alpnOff + protoLen > listEnd) break;
145
+ alpnProtocols.push(extData.subarray(alpnOff, alpnOff + protoLen));
146
+ alpnOff += protoLen;
147
+ }
148
+ }
149
+ }
150
+ }
151
+ const versionStr = tlsVersionStr(clientVersion);
152
+ const sni = extIds.includes(EXT_SERVER_NAME) ? "d" : "i";
153
+ const firstAlpn = alpnProtocols[0];
154
+ const [alpnFirst, alpnLast] = firstAlpn ? alpnFirstLast(firstAlpn) : ["0", "0"];
155
+ const nonGreaseCiphers = cipherSuites.filter((c) => !isGrease(c));
156
+ const nonGreaseExts = extIds.filter((e) => !isGrease(e));
157
+ const firstChunk = `t${versionStr}${sni}${Math.min(99, nonGreaseCiphers.length).toString().padStart(2, "0")}${Math.min(99, nonGreaseExts.length).toString().padStart(2, "0")}${alpnFirst}${alpnLast}`;
158
+ const cipherHash = hash12(formatIdList([...nonGreaseCiphers].sort((a, b) => a - b)));
159
+ const extStr = formatIdList([...nonGreaseExts.filter((e) => e !== EXT_SERVER_NAME && e !== EXT_ALPN)].sort((a, b) => a - b));
160
+ const sigAlgStr = formatIdList(sigAlgorithms);
161
+ return `${firstChunk}_${cipherHash}_${hash12(sigAlgStr.length > 0 ? `${extStr}_${sigAlgStr}` : extStr)}`;
199
162
  }
200
- export {
201
- Ja4ParseError,
202
- calculateJa4
203
- };
163
+ //#endregion
164
+ export { Ja4ParseError, calculateJa4 };
@@ -1,58 +1,43 @@
1
- import { handleErrors } from "@prosopo/api-express-router";
1
+ import { calculateJa4 } from "./ja4.js";
2
2
  import { getLogger } from "@prosopo/logger";
3
3
  import { randomAsHex } from "@prosopo/util-crypto";
4
- import { calculateJa4 } from "./ja4.js";
5
- const DEFAULT_JA4 = "ja4";
6
- const getJA4 = async (headers, logger) => {
7
- logger = logger || getLogger("info", "provider:ja4");
8
- if (process.env.NODE_ENV === "development") {
9
- return {
10
- ja4PlusFingerprint: `${DEFAULT_JA4}${randomAsHex().slice(28, 32)}`
11
- };
12
- }
13
- try {
14
- const xTlsClientHello = (headers["x-tls-clienthello"] || "").toString();
15
- const clientHelloBuffer = Buffer.from(xTlsClientHello, "base64");
16
- logger.debug(() => ({
17
- msg: "ClientHello First Bytes:",
18
- data: { hex: clientHelloBuffer.subarray(0, 5).toString("hex") }
19
- }));
20
- if (clientHelloBuffer[5] !== 1) {
21
- logger.debug(() => ({
22
- msg: "Invalid ClientHello message: First byte is not 0x01"
23
- }));
24
- return { ja4PlusFingerprint: DEFAULT_JA4 };
25
- }
26
- const ja4PlusFingerprint = calculateJa4(clientHelloBuffer);
27
- return { ja4PlusFingerprint };
28
- } catch (e) {
29
- logger.error(() => ({
30
- msg: "Error generating JA4+ fingerprint:",
31
- err: e instanceof Error ? e : new Error(String(e))
32
- }));
33
- return { ja4PlusFingerprint: DEFAULT_JA4 };
34
- }
35
- };
36
- const ja4Middleware = (env) => {
37
- return async (req, res, next) => {
38
- try {
39
- req.logger.debug(() => ({ data: { url: req.url } }));
40
- const ja4 = await getJA4(req.headers, req.logger);
41
- req.ja4 = ja4.ja4PlusFingerprint || "";
42
- req.logger = req.logger.with(
43
- {
44
- ja4: req.ja4
45
- },
46
- "ja4"
47
- );
48
- next();
49
- } catch (err) {
50
- return handleErrors(err, req, res, next);
51
- }
52
- };
4
+ import { handleErrors } from "@prosopo/api-express-router";
5
+ //#region src/api/ja4Middleware.ts
6
+ var DEFAULT_JA4 = "ja4";
7
+ var getJA4 = async (headers, logger) => {
8
+ logger = logger || getLogger("info", "provider:ja4");
9
+ if (process.env.NODE_ENV === "development") return { ja4PlusFingerprint: `ja4${randomAsHex().slice(28, 32)}` };
10
+ try {
11
+ const xTlsClientHello = (headers["x-tls-clienthello"] || "").toString();
12
+ const clientHelloBuffer = Buffer.from(xTlsClientHello, "base64");
13
+ logger.debug(() => ({
14
+ msg: "ClientHello First Bytes:",
15
+ data: { hex: clientHelloBuffer.subarray(0, 5).toString("hex") }
16
+ }));
17
+ if (clientHelloBuffer[5] !== 1) {
18
+ logger.debug(() => ({ msg: "Invalid ClientHello message: First byte is not 0x01" }));
19
+ return { ja4PlusFingerprint: "ja4" };
20
+ }
21
+ return { ja4PlusFingerprint: calculateJa4(clientHelloBuffer) };
22
+ } catch (e) {
23
+ logger.error(() => ({
24
+ msg: "Error generating JA4+ fingerprint:",
25
+ err: e instanceof Error ? e : new Error(String(e))
26
+ }));
27
+ return { ja4PlusFingerprint: "ja4" };
28
+ }
53
29
  };
54
- export {
55
- DEFAULT_JA4,
56
- getJA4,
57
- ja4Middleware
30
+ var ja4Middleware = (env) => {
31
+ return async (req, res, next) => {
32
+ try {
33
+ req.logger.debug(() => ({ data: { url: req.url } }));
34
+ req.ja4 = (await getJA4(req.headers, req.logger)).ja4PlusFingerprint || "";
35
+ req.logger = req.logger.with({ ja4: req.ja4 }, "ja4");
36
+ next();
37
+ } catch (err) {
38
+ return handleErrors(err, req, res, next);
39
+ }
40
+ };
58
41
  };
42
+ //#endregion
43
+ export { DEFAULT_JA4, getJA4, ja4Middleware };