@prosopo/provider 5.13.4 → 5.15.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 (205) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +30 -30
  2. package/.turbo/turbo-build$colon$tsc.log +27 -30
  3. package/.turbo/turbo-build.log +30 -33
  4. package/CHANGELOG.md +233 -0
  5. package/LICENSE +201 -0
  6. package/dist/api/adminAuthOptions.d.ts +4 -0
  7. package/dist/api/adminAuthOptions.d.ts.map +1 -0
  8. package/dist/api/adminAuthOptions.js +34 -0
  9. package/dist/api/adminAuthOptions.js.map +1 -0
  10. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts +2 -1
  11. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.d.ts.map +1 -1
  12. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js +11 -2
  13. package/dist/api/captcha/getFrictionlessCaptchaChallenge/constants.js.map +1 -1
  14. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
  15. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +16 -11
  16. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
  17. package/dist/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.d.ts +5 -0
  18. package/dist/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.d.ts.map +1 -0
  19. package/dist/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.js +20 -0
  20. package/dist/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.js.map +1 -0
  21. package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts.map +1 -1
  22. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +24 -0
  23. package/dist/api/captcha/getPuzzleCaptchaChallenge.js.map +1 -1
  24. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts +4 -11
  25. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
  26. package/dist/api/captcha/submitPoWCaptchaSolution.js +12 -17
  27. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  28. package/dist/api/detectorPoolBodyParser.d.ts +4 -0
  29. package/dist/api/detectorPoolBodyParser.d.ts.map +1 -0
  30. package/dist/api/detectorPoolBodyParser.js +31 -0
  31. package/dist/api/detectorPoolBodyParser.js.map +1 -0
  32. package/dist/api/domainMiddleware.d.ts.map +1 -1
  33. package/dist/api/domainMiddleware.js +5 -1
  34. package/dist/api/domainMiddleware.js.map +1 -1
  35. package/dist/api/metrics.d.ts +2 -1
  36. package/dist/api/metrics.d.ts.map +1 -1
  37. package/dist/api/metrics.js +46 -4
  38. package/dist/api/metrics.js.map +1 -1
  39. package/dist/api/rawTlsSignalsMiddleware.d.ts +14 -1
  40. package/dist/api/rawTlsSignalsMiddleware.d.ts.map +1 -1
  41. package/dist/api/rawTlsSignalsMiddleware.js +111 -23
  42. package/dist/api/rawTlsSignalsMiddleware.js.map +1 -1
  43. package/dist/api/startProviderApi.d.ts.map +1 -1
  44. package/dist/api/startProviderApi.js +8 -5
  45. package/dist/api/startProviderApi.js.map +1 -1
  46. package/dist/api/verify.d.ts.map +1 -1
  47. package/dist/api/verify.js +50 -8
  48. package/dist/api/verify.js.map +1 -1
  49. package/dist/cjs/api/adminAuthOptions.cjs +35 -0
  50. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/constants.cjs +10 -0
  51. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +14 -8
  52. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.cjs +20 -0
  53. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +24 -0
  54. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +10 -15
  55. package/dist/cjs/api/detectorPoolBodyParser.cjs +33 -0
  56. package/dist/cjs/api/domainMiddleware.cjs +5 -1
  57. package/dist/cjs/api/metrics.cjs +46 -3
  58. package/dist/cjs/api/rawTlsSignalsMiddleware.cjs +111 -22
  59. package/dist/cjs/api/startProviderApi.cjs +8 -5
  60. package/dist/cjs/api/verify.cjs +50 -8
  61. package/dist/cjs/compositeIpAddress.cjs +21 -7
  62. package/dist/cjs/tasks/captchaManager.cjs +15 -0
  63. package/dist/cjs/tasks/captchaTypeSelection.cjs +23 -2
  64. package/dist/cjs/tasks/client/clientTasks.cjs +25 -4
  65. package/dist/cjs/tasks/dataset/datasetTasksUtils.cjs +1 -1
  66. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +26 -23
  67. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +4 -11
  68. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +7 -11
  69. package/dist/cjs/tasks/powCaptcha/powTasksUtils.cjs +2 -0
  70. package/dist/cjs/tasks/puzzle/backgroundBuffer.cjs +3 -3
  71. package/dist/cjs/tasks/puzzle/nativeBackground.cjs +12 -5
  72. package/dist/cjs/tasks/puzzle/puzzleRenderer.cjs +4 -3
  73. package/dist/cjs/tasks/puzzleCaptcha/puzzleTasks.cjs +2 -9
  74. package/dist/cjs/utils/tarpitPadding.cjs +81 -10
  75. package/dist/compositeIpAddress.d.ts.map +1 -1
  76. package/dist/compositeIpAddress.js +21 -7
  77. package/dist/compositeIpAddress.js.map +1 -1
  78. package/dist/tasks/captchaManager.d.ts.map +1 -1
  79. package/dist/tasks/captchaManager.js +16 -1
  80. package/dist/tasks/captchaManager.js.map +1 -1
  81. package/dist/tasks/captchaTypeSelection.d.ts +3 -1
  82. package/dist/tasks/captchaTypeSelection.d.ts.map +1 -1
  83. package/dist/tasks/captchaTypeSelection.js +23 -3
  84. package/dist/tasks/captchaTypeSelection.js.map +1 -1
  85. package/dist/tasks/client/clientTasks.d.ts +1 -0
  86. package/dist/tasks/client/clientTasks.d.ts.map +1 -1
  87. package/dist/tasks/client/clientTasks.js +25 -5
  88. package/dist/tasks/client/clientTasks.js.map +1 -1
  89. package/dist/tasks/dataset/datasetTasksUtils.js +1 -1
  90. package/dist/tasks/dataset/datasetTasksUtils.js.map +1 -1
  91. package/dist/tasks/frictionless/frictionlessTasks.d.ts +3 -1
  92. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  93. package/dist/tasks/frictionless/frictionlessTasks.js +28 -25
  94. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  95. package/dist/tasks/frictionless/routingMachine.d.ts +1 -2
  96. package/dist/tasks/frictionless/routingMachine.d.ts.map +1 -1
  97. package/dist/tasks/frictionless/routingMachine.js.map +1 -1
  98. package/dist/tasks/imgCaptcha/imgCaptchaTasks.d.ts.map +1 -1
  99. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +4 -11
  100. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js.map +1 -1
  101. package/dist/tasks/powCaptcha/powTasks.d.ts.map +1 -1
  102. package/dist/tasks/powCaptcha/powTasks.js +8 -12
  103. package/dist/tasks/powCaptcha/powTasks.js.map +1 -1
  104. package/dist/tasks/powCaptcha/powTasksUtils.d.ts +1 -0
  105. package/dist/tasks/powCaptcha/powTasksUtils.d.ts.map +1 -1
  106. package/dist/tasks/powCaptcha/powTasksUtils.js +2 -1
  107. package/dist/tasks/powCaptcha/powTasksUtils.js.map +1 -1
  108. package/dist/tasks/puzzle/backgroundBuffer.d.ts +3 -2
  109. package/dist/tasks/puzzle/backgroundBuffer.d.ts.map +1 -1
  110. package/dist/tasks/puzzle/backgroundBuffer.js +3 -3
  111. package/dist/tasks/puzzle/backgroundBuffer.js.map +1 -1
  112. package/dist/tasks/puzzle/nativeBackground.d.ts +5 -1
  113. package/dist/tasks/puzzle/nativeBackground.d.ts.map +1 -1
  114. package/dist/tasks/puzzle/nativeBackground.js +12 -5
  115. package/dist/tasks/puzzle/nativeBackground.js.map +1 -1
  116. package/dist/tasks/puzzle/puzzleRenderer.d.ts +2 -0
  117. package/dist/tasks/puzzle/puzzleRenderer.d.ts.map +1 -1
  118. package/dist/tasks/puzzle/puzzleRenderer.js +4 -3
  119. package/dist/tasks/puzzle/puzzleRenderer.js.map +1 -1
  120. package/dist/tasks/puzzleCaptcha/puzzleTasks.d.ts.map +1 -1
  121. package/dist/tasks/puzzleCaptcha/puzzleTasks.js +2 -9
  122. package/dist/tasks/puzzleCaptcha/puzzleTasks.js.map +1 -1
  123. package/dist/tests/integration/clientSettingsPersistence.integration.test.js +2 -0
  124. package/dist/tests/integration/clientSettingsPersistence.integration.test.js.map +1 -1
  125. package/dist/tests/unit/api/adminAuthOptions.unit.test.d.ts +2 -0
  126. package/dist/tests/unit/api/adminAuthOptions.unit.test.d.ts.map +1 -0
  127. package/dist/tests/unit/api/adminAuthOptions.unit.test.js +38 -0
  128. package/dist/tests/unit/api/adminAuthOptions.unit.test.js.map +1 -0
  129. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.js +35 -2
  130. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/constants.unit.test.js.map +1 -1
  131. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.unit.test.d.ts +2 -0
  132. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.unit.test.d.ts.map +1 -0
  133. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.unit.test.js +45 -0
  134. package/dist/tests/unit/api/captcha/getFrictionlessCaptchaChallenge/refreshLineage.unit.test.js.map +1 -0
  135. package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js +15 -1
  136. package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js.map +1 -1
  137. package/dist/tests/unit/api/detectorPoolBodyParser.unit.test.d.ts +2 -0
  138. package/dist/tests/unit/api/detectorPoolBodyParser.unit.test.d.ts.map +1 -0
  139. package/dist/tests/unit/api/detectorPoolBodyParser.unit.test.js +64 -0
  140. package/dist/tests/unit/api/detectorPoolBodyParser.unit.test.js.map +1 -0
  141. package/dist/tests/unit/api/domainMiddlewareErrorBody.unit.test.d.ts +2 -0
  142. package/dist/tests/unit/api/domainMiddlewareErrorBody.unit.test.d.ts.map +1 -0
  143. package/dist/tests/unit/api/domainMiddlewareErrorBody.unit.test.js +67 -0
  144. package/dist/tests/unit/api/domainMiddlewareErrorBody.unit.test.js.map +1 -0
  145. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js +174 -23
  146. package/dist/tests/unit/api/getFrictionlessCaptchaChallenge.unit.test.js.map +1 -1
  147. package/dist/tests/unit/api/metricsAccess.unit.test.d.ts +2 -0
  148. package/dist/tests/unit/api/metricsAccess.unit.test.d.ts.map +1 -0
  149. package/dist/tests/unit/api/metricsAccess.unit.test.js +87 -0
  150. package/dist/tests/unit/api/metricsAccess.unit.test.js.map +1 -0
  151. package/dist/tests/unit/api/public.unit.test.js +4 -1
  152. package/dist/tests/unit/api/public.unit.test.js.map +1 -1
  153. package/dist/tests/unit/api/rawTlsSignalsMiddleware.unit.test.js +96 -1
  154. package/dist/tests/unit/api/rawTlsSignalsMiddleware.unit.test.js.map +1 -1
  155. package/dist/tests/unit/api/validateAddress.unit.test.js +1 -1
  156. package/dist/tests/unit/api/validateAddress.unit.test.js.map +1 -1
  157. package/dist/tests/unit/api/verifyBadDappSignature.unit.test.d.ts +2 -0
  158. package/dist/tests/unit/api/verifyBadDappSignature.unit.test.d.ts.map +1 -0
  159. package/dist/tests/unit/api/verifyBadDappSignature.unit.test.js +96 -0
  160. package/dist/tests/unit/api/verifyBadDappSignature.unit.test.js.map +1 -0
  161. package/dist/tests/unit/api/verifyMalformedToken.unit.test.d.ts +2 -0
  162. package/dist/tests/unit/api/verifyMalformedToken.unit.test.d.ts.map +1 -0
  163. package/dist/tests/unit/api/verifyMalformedToken.unit.test.js +89 -0
  164. package/dist/tests/unit/api/verifyMalformedToken.unit.test.js.map +1 -0
  165. package/dist/tests/unit/compositeIpAddress.unit.test.js +12 -3
  166. package/dist/tests/unit/compositeIpAddress.unit.test.js.map +1 -1
  167. package/dist/tests/unit/tasks/captchaManager.unit.test.js +51 -0
  168. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  169. package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.js +20 -1
  170. package/dist/tests/unit/tasks/captchaTypeSelection.unit.test.js.map +1 -1
  171. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js +33 -1
  172. package/dist/tests/unit/tasks/client/clientTasks.unit.test.js.map +1 -1
  173. package/dist/tests/unit/tasks/dataset/datasetTasksUtils.unit.test.js +14 -1
  174. package/dist/tests/unit/tasks/dataset/datasetTasksUtils.unit.test.js.map +1 -1
  175. package/dist/tests/unit/tasks/frictionless/allowedCaptchaTypes.unit.test.d.ts +2 -0
  176. package/dist/tests/unit/tasks/frictionless/allowedCaptchaTypes.unit.test.d.ts.map +1 -0
  177. package/dist/tests/unit/tasks/frictionless/allowedCaptchaTypes.unit.test.js +94 -0
  178. package/dist/tests/unit/tasks/frictionless/allowedCaptchaTypes.unit.test.js.map +1 -0
  179. package/dist/tests/unit/tasks/frictionless/refreshSwitch.unit.test.d.ts +2 -0
  180. package/dist/tests/unit/tasks/frictionless/refreshSwitch.unit.test.d.ts.map +1 -0
  181. package/dist/tests/unit/tasks/frictionless/refreshSwitch.unit.test.js +103 -0
  182. package/dist/tests/unit/tasks/frictionless/refreshSwitch.unit.test.js.map +1 -0
  183. package/dist/tests/unit/tasks/powCaptcha/powTasksUtils.unit.test.js +23 -1
  184. package/dist/tests/unit/tasks/powCaptcha/powTasksUtils.unit.test.js.map +1 -1
  185. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.js +1 -4
  186. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.js.map +1 -1
  187. package/dist/tests/unit/tasks/puzzle/nativeBackground.unit.test.js +10 -3
  188. package/dist/tests/unit/tasks/puzzle/nativeBackground.unit.test.js.map +1 -1
  189. package/dist/tests/unit/tasks/puzzle/puzzleRenderer.unit.test.d.ts +2 -0
  190. package/dist/tests/unit/tasks/puzzle/puzzleRenderer.unit.test.d.ts.map +1 -0
  191. package/dist/tests/unit/tasks/puzzle/puzzleRenderer.unit.test.js +35 -0
  192. package/dist/tests/unit/tasks/puzzle/puzzleRenderer.unit.test.js.map +1 -0
  193. package/dist/tests/unit/tasks/puzzleDifficultyDeviceCap.unit.test.d.ts +2 -0
  194. package/dist/tests/unit/tasks/puzzleDifficultyDeviceCap.unit.test.d.ts.map +1 -0
  195. package/dist/tests/unit/tasks/puzzleDifficultyDeviceCap.unit.test.js +49 -0
  196. package/dist/tests/unit/tasks/puzzleDifficultyDeviceCap.unit.test.js.map +1 -0
  197. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts +17 -14
  198. package/dist/tests/unit/testUtils/mockProviderEnv.d.ts.map +1 -1
  199. package/dist/tests/unit/testUtils/mockProviderEnv.js.map +1 -1
  200. package/dist/tests/unit/utils/tarpitPadding.unit.test.js +118 -36
  201. package/dist/tests/unit/utils/tarpitPadding.unit.test.js.map +1 -1
  202. package/dist/utils/tarpitPadding.d.ts.map +1 -1
  203. package/dist/utils/tarpitPadding.js +81 -10
  204. package/dist/utils/tarpitPadding.js.map +1 -1
  205. package/package.json +105 -103
@@ -7,9 +7,44 @@ var HEADER_ACK_NS = "x-tls-ack-ns";
7
7
  var HEADER_OBSERVED_TTL = "x-tls-observed-ttl";
8
8
  var HEADER_TCP_MSS = "x-tls-tcp-mss";
9
9
  var HEADER_TCP_WSCALE = "x-tls-tcp-wscale";
10
+ var HEADER_TCP_WINDOW = "x-tls-tcp-window";
11
+ var HEADER_TCP_OPTS_KINDS = "x-tls-tcp-opts-kinds";
12
+ var HEADER_TCP_OPTS_PRESENT = "x-tls-tcp-opts-present";
13
+ var HEADER_TCP_OPTS_COUNT = "x-tls-tcp-opts-count";
14
+ var HEADER_TCP_TSVAL = "x-tls-tcp-tsval";
15
+ var HEADER_TCP_TSECR = "x-tls-tcp-tsecr";
16
+ var HEADER_TCP_FLAGS = "x-tls-tcp-flags";
17
+ var HEADER_TCP_DATA_OFFSET_RESV = "x-tls-tcp-data-offset-resv";
18
+ var HEADER_TCP_URG_PTR = "x-tls-tcp-urg-ptr";
19
+ var HEADER_IP_IDENT = "x-tls-ip-ident";
20
+ var HEADER_IP_TOTAL_LEN = "x-tls-ip-total-len";
21
+ var HEADER_IP_FRAG_FLAGS = "x-tls-ip-frag-flags";
22
+ var HEADER_IP_TOS = "x-tls-ip-tos";
10
23
  var HEADER_TCP_OPTS_FLAGS = "x-tls-tcp-opts-flags";
11
24
  var HEADER_TCP_OPTS_ORDER = "x-tls-tcp-opts-order";
12
- var HEADER_TCP_WINDOW = "x-tls-tcp-window";
25
+ var RAW_TLS_SIGNAL_KEYS = [
26
+ "synNs",
27
+ "synackNs",
28
+ "ackNs",
29
+ "observedTtl",
30
+ "tcpMss",
31
+ "tcpWscale",
32
+ "tcpWindow",
33
+ "tcpOptsKinds",
34
+ "tcpOptsPresent",
35
+ "tcpOptsCount",
36
+ "tcpTsval",
37
+ "tcpTsecr",
38
+ "tcpFlags",
39
+ "tcpDataOffsetResv",
40
+ "tcpUrgPtr",
41
+ "ipIdent",
42
+ "ipTotalLen",
43
+ "ipFragFlags",
44
+ "ipTos",
45
+ "tcpOptsFlags",
46
+ "tcpOptsOrder"
47
+ ];
13
48
  var parseIntHeader = (raw, logger, headerName, max) => {
14
49
  if (raw === void 0) return;
15
50
  const value = Array.isArray(raw) ? raw[0] : raw;
@@ -31,6 +66,46 @@ var MAX_NS = 2 ** 63;
31
66
  var MAX_U8 = 255;
32
67
  var MAX_U16 = 65535;
33
68
  var MAX_U32 = 4294967295;
69
+ var OPTS_KINDS_SLOTS = 8;
70
+ var MAX_U64 = (1n << 64n) - 1n;
71
+ var parseOptsKindsHeader = (raw, logger) => {
72
+ if (raw === void 0) return;
73
+ const value = Array.isArray(raw) ? raw[0] : raw;
74
+ if (value === void 0 || !/^\d+$/.test(value)) {
75
+ if (value !== void 0) logger.debug(() => ({
76
+ msg: "Ignoring malformed raw TLS signal header",
77
+ data: {
78
+ header: HEADER_TCP_OPTS_KINDS,
79
+ raw: value
80
+ }
81
+ }));
82
+ return;
83
+ }
84
+ let packed;
85
+ try {
86
+ packed = BigInt(value);
87
+ } catch {
88
+ return;
89
+ }
90
+ if (packed > MAX_U64) {
91
+ logger.debug(() => ({
92
+ msg: "Ignoring out-of-range raw TLS signal header",
93
+ data: {
94
+ header: HEADER_TCP_OPTS_KINDS,
95
+ raw: value
96
+ }
97
+ }));
98
+ return;
99
+ }
100
+ if (packed === 0n) return;
101
+ const kinds = [];
102
+ for (let slot = 0; slot < OPTS_KINDS_SLOTS; slot++) {
103
+ const kind = Number(packed >> BigInt(slot * 8) & 255n);
104
+ if (kind === 0) break;
105
+ kinds.push(kind);
106
+ }
107
+ return kinds.length > 0 ? kinds : void 0;
108
+ };
34
109
  var getRawTlsSignals = (headers, logger) => {
35
110
  const log = logger ?? (0, _prosopo_logger.getLogger)("info", "provider:raw-tls-signals");
36
111
  return {
@@ -40,37 +115,50 @@ var getRawTlsSignals = (headers, logger) => {
40
115
  observedTtl: parseIntHeader(headers[HEADER_OBSERVED_TTL], log, HEADER_OBSERVED_TTL, MAX_U8),
41
116
  tcpMss: parseIntHeader(headers[HEADER_TCP_MSS], log, HEADER_TCP_MSS, MAX_U16),
42
117
  tcpWscale: parseIntHeader(headers[HEADER_TCP_WSCALE], log, HEADER_TCP_WSCALE, MAX_U8),
118
+ tcpWindow: parseIntHeader(headers[HEADER_TCP_WINDOW], log, HEADER_TCP_WINDOW, MAX_U16),
119
+ tcpOptsKinds: parseOptsKindsHeader(headers[HEADER_TCP_OPTS_KINDS], log),
120
+ tcpOptsPresent: parseIntHeader(headers[HEADER_TCP_OPTS_PRESENT], log, HEADER_TCP_OPTS_PRESENT, MAX_U16),
121
+ tcpOptsCount: parseIntHeader(headers[HEADER_TCP_OPTS_COUNT], log, HEADER_TCP_OPTS_COUNT, MAX_U8),
122
+ tcpTsval: parseIntHeader(headers[HEADER_TCP_TSVAL], log, HEADER_TCP_TSVAL, MAX_U32),
123
+ tcpTsecr: parseIntHeader(headers[HEADER_TCP_TSECR], log, HEADER_TCP_TSECR, MAX_U32),
124
+ tcpFlags: parseIntHeader(headers[HEADER_TCP_FLAGS], log, HEADER_TCP_FLAGS, MAX_U8),
125
+ tcpDataOffsetResv: parseIntHeader(headers[HEADER_TCP_DATA_OFFSET_RESV], log, HEADER_TCP_DATA_OFFSET_RESV, MAX_U8),
126
+ tcpUrgPtr: parseIntHeader(headers[HEADER_TCP_URG_PTR], log, HEADER_TCP_URG_PTR, MAX_U16),
127
+ ipIdent: parseIntHeader(headers[HEADER_IP_IDENT], log, HEADER_IP_IDENT, MAX_U16),
128
+ ipTotalLen: parseIntHeader(headers[HEADER_IP_TOTAL_LEN], log, HEADER_IP_TOTAL_LEN, MAX_U16),
129
+ ipFragFlags: parseIntHeader(headers[HEADER_IP_FRAG_FLAGS], log, HEADER_IP_FRAG_FLAGS, MAX_U16),
130
+ ipTos: parseIntHeader(headers[HEADER_IP_TOS], log, HEADER_IP_TOS, MAX_U8),
43
131
  tcpOptsFlags: parseIntHeader(headers[HEADER_TCP_OPTS_FLAGS], log, HEADER_TCP_OPTS_FLAGS, MAX_U8),
44
- tcpOptsOrder: parseIntHeader(headers[HEADER_TCP_OPTS_ORDER], log, HEADER_TCP_OPTS_ORDER, MAX_U32),
45
- tcpWindow: parseIntHeader(headers[HEADER_TCP_WINDOW], log, HEADER_TCP_WINDOW, MAX_U16)
132
+ tcpOptsOrder: parseIntHeader(headers[HEADER_TCP_OPTS_ORDER], log, HEADER_TCP_OPTS_ORDER, MAX_U32)
46
133
  };
47
134
  };
48
- var rawTlsSignalsForSession = (req) => {
135
+ var rawTlsSignalsForSession = (req) => rawTlsSignalsFromRecord(req);
136
+ /**
137
+ * Pull the raw TLS signals out of a persisted Session — or anything shaped
138
+ * like one — for a decision machine's `raw` bag, or to carry forward onto a
139
+ * new Session record.
140
+ *
141
+ * Six call sites used to name every field by hand, so adding one to the
142
+ * probe meant remembering six places; the fields that were forgotten read as
143
+ * `undefined` and the rules gating on them silently never fired.
144
+ */
145
+ var rawTlsSignalsFromRecord = (record) => {
49
146
  const out = {};
50
- if (req.synNs !== void 0) out.synNs = req.synNs;
51
- if (req.synackNs !== void 0) out.synackNs = req.synackNs;
52
- if (req.ackNs !== void 0) out.ackNs = req.ackNs;
53
- if (req.observedTtl !== void 0) out.observedTtl = req.observedTtl;
54
- if (req.tcpMss !== void 0) out.tcpMss = req.tcpMss;
55
- if (req.tcpWscale !== void 0) out.tcpWscale = req.tcpWscale;
56
- if (req.tcpOptsFlags !== void 0) out.tcpOptsFlags = req.tcpOptsFlags;
57
- if (req.tcpOptsOrder !== void 0) out.tcpOptsOrder = req.tcpOptsOrder;
58
- if (req.tcpWindow !== void 0) out.tcpWindow = req.tcpWindow;
147
+ if (!record) return out;
148
+ for (const key of RAW_TLS_SIGNAL_KEYS) {
149
+ const value = record[key];
150
+ if (value !== void 0) Object.assign(out, { [key]: value });
151
+ }
59
152
  return out;
60
153
  };
61
154
  var rawTlsSignalsMiddleware = (env) => {
62
155
  return async (req, res, next) => {
63
156
  try {
64
157
  const signals = getRawTlsSignals(req.headers, req.logger);
65
- if (signals.synNs !== void 0) req.synNs = signals.synNs;
66
- if (signals.synackNs !== void 0) req.synackNs = signals.synackNs;
67
- if (signals.ackNs !== void 0) req.ackNs = signals.ackNs;
68
- if (signals.observedTtl !== void 0) req.observedTtl = signals.observedTtl;
69
- if (signals.tcpMss !== void 0) req.tcpMss = signals.tcpMss;
70
- if (signals.tcpWscale !== void 0) req.tcpWscale = signals.tcpWscale;
71
- if (signals.tcpOptsFlags !== void 0) req.tcpOptsFlags = signals.tcpOptsFlags;
72
- if (signals.tcpOptsOrder !== void 0) req.tcpOptsOrder = signals.tcpOptsOrder;
73
- if (signals.tcpWindow !== void 0) req.tcpWindow = signals.tcpWindow;
158
+ for (const key of RAW_TLS_SIGNAL_KEYS) {
159
+ const value = signals[key];
160
+ if (value !== void 0) Object.assign(req, { [key]: value });
161
+ }
74
162
  if (Object.values(signals).some((v) => v !== void 0)) req.logger = req.logger.with(signals, "rawTlsSignals");
75
163
  next();
76
164
  } catch (err) {
@@ -81,4 +169,5 @@ var rawTlsSignalsMiddleware = (env) => {
81
169
  //#endregion
82
170
  exports.getRawTlsSignals = getRawTlsSignals;
83
171
  exports.rawTlsSignalsForSession = rawTlsSignalsForSession;
172
+ exports.rawTlsSignalsFromRecord = rawTlsSignalsFromRecord;
84
173
  exports.rawTlsSignalsMiddleware = rawTlsSignalsMiddleware;
@@ -1,9 +1,9 @@
1
1
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
2
  const require_metrics = require("./metrics.cjs");
3
+ const require_rawTlsSignalsMiddleware = require("./rawTlsSignalsMiddleware.cjs");
3
4
  const require_blacklistRequestInspector = require("./blacklistRequestInspector.cjs");
4
5
  const require_bundlePool = require("../tasks/detection/bundlePool.cjs");
5
6
  const require_block = require("./block.cjs");
6
- const require_rawTlsSignalsMiddleware = require("./rawTlsSignalsMiddleware.cjs");
7
7
  const require_captcha = require("./captcha.cjs");
8
8
  const require_verify = require("./verify.cjs");
9
9
  const require_ja4Middleware = require("./ja4Middleware.cjs");
@@ -11,20 +11,22 @@ const require_handshakeTimingMiddleware = require("./handshakeTimingMiddleware.c
11
11
  const require_public = require("./public.cjs");
12
12
  const require_domainMiddleware = require("./domainMiddleware.cjs");
13
13
  const require_createApiAdminRoutesProvider = require("./admin/createApiAdminRoutesProvider.cjs");
14
+ const require_adminAuthOptions = require("./adminAuthOptions.cjs");
14
15
  const require_cpuProfiler = require("./cpuProfiler.cjs");
16
+ const require_detectorPoolBodyParser = require("./detectorPoolBodyParser.cjs");
15
17
  const require_headerCheckMiddleware = require("./headerCheckMiddleware.cjs");
16
18
  const require_ignoreMiddleware = require("./ignoreMiddleware.cjs");
17
19
  const require_ipInfoMiddleware = require("./ipInfoMiddleware.cjs");
18
20
  const require_robotsMiddleware = require("./robotsMiddleware.cjs");
19
21
  let _prosopo_logger = require("@prosopo/logger");
20
22
  let _prosopo_types = require("@prosopo/types");
23
+ let _prosopo_api_express_router = require("@prosopo/api-express-router");
21
24
  let node_fs = require("node:fs");
22
25
  node_fs = require_runtime.__toESM(node_fs, 1);
23
26
  let node_path = require("node:path");
24
27
  node_path = require_runtime.__toESM(node_path, 1);
25
28
  let node_https = require("node:https");
26
29
  node_https = require_runtime.__toESM(node_https, 1);
27
- let _prosopo_api_express_router = require("@prosopo/api-express-router");
28
30
  let express = require("express");
29
31
  express = require_runtime.__toESM(express, 1);
30
32
  let node_url = require("node:url");
@@ -162,7 +164,7 @@ async function startProviderApi(env, admin = false, port) {
162
164
  ],
163
165
  maxAge: 86400
164
166
  }));
165
- apiApp.use(_prosopo_types.AdminApiPaths.ReplaceDetectorPool, express.default.json({ limit: DETECTOR_POOL_BODY_LIMIT }));
167
+ apiApp.use(_prosopo_types.AdminApiPaths.ReplaceDetectorPool, require_detectorPoolBodyParser.detectorPoolBodyParser(DETECTOR_POOL_BODY_LIMIT, env.pair, env.authAccount));
166
168
  apiApp.use(express.default.json({ limit: "1mb" }));
167
169
  apiApp.use(require_public.publicRouter(env));
168
170
  apiApp.use(require_metrics.metricsMiddleware());
@@ -218,10 +220,11 @@ async function startProviderApi(env, admin = false, port) {
218
220
  apiApp.use(clientPathsExcludingVerify, require_headerCheckMiddleware.headerCheckMiddleware(env));
219
221
  apiApp.use(require_verify.prosopoVerifyRouter(env));
220
222
  env.logger.info(() => ({ msg: "Enabling admin auth middleware" }));
221
- apiApp.use("/v1/prosopo/provider/admin", (0, _prosopo_api_express_router.authMiddleware)(env.pair, env.authAccount));
223
+ const adminAuth = (0, _prosopo_api_express_router.authMiddleware)(env.pair, env.authAccount, require_adminAuthOptions.adminAuthOptions(process.env, env.config.host));
224
+ apiApp.use("/v1/prosopo/provider/admin", adminAuth);
222
225
  if (apiRuleRoutesProvider) {
223
226
  const userAccessRuleRoutes = apiRuleRoutesProvider.getRoutes();
224
- for (const userAccessRuleRoute in userAccessRuleRoutes) apiApp.use(userAccessRuleRoute, (0, _prosopo_api_express_router.authMiddleware)(env.pair, env.authAccount));
227
+ for (const userAccessRuleRoute in userAccessRuleRoutes) apiApp.use(userAccessRuleRoute, adminAuth);
225
228
  for (const userAccessRuleRoute in userAccessRuleRoutes) apiApp.use(userAccessRuleRoute, (req, res, next) => {
226
229
  if (req.method === "POST") res.on("finish", () => {
227
230
  if (res.statusCode >= 200 && res.statusCode < 300) require_blacklistRequestInspector.getVerdictCache().clear();
@@ -30,6 +30,36 @@ var VERIFY_PATH_TYPE = {
30
30
  [_prosopo_types.ClientApiPaths.VerifyPowCaptchaSolution]: _prosopo_types.CaptchaType.pow,
31
31
  [_prosopo_types.ClientApiPaths.VerifyPuzzleCaptchaSolution]: _prosopo_types.CaptchaType.puzzle
32
32
  };
33
+ var decodeTokenOr400 = (token, i18n, logger, next) => {
34
+ try {
35
+ return (0, _prosopo_types.decodeProcaptchaOutput)(token);
36
+ } catch (error) {
37
+ next(new _prosopo_common.ProsopoApiError("CAPTCHA.PARSE_ERROR", {
38
+ context: {
39
+ code: 400,
40
+ error
41
+ },
42
+ i18n,
43
+ logger
44
+ }));
45
+ return null;
46
+ }
47
+ };
48
+ var dappSignatureError = (signature, message, pair, req) => {
49
+ try {
50
+ (0, _prosopo_api_express_router.verifySignature)(signature, message, pair);
51
+ return;
52
+ } catch {
53
+ return new _prosopo_common.ProsopoApiError("GENERAL.INVALID_SIGNATURE", {
54
+ context: {
55
+ code: 400,
56
+ siteKey: pair.address
57
+ },
58
+ i18n: req.i18n,
59
+ logger: req.logger
60
+ });
61
+ }
62
+ };
33
63
  /**
34
64
  * Returns a router connected to the database which can interact with the Proposo protocol
35
65
  *
@@ -98,7 +128,9 @@ function prosopoVerifyRouter(env) {
98
128
  }
99
129
  const { dappSignature, token, ip, maxVerifiedTime, email, clientSessionId } = parsed;
100
130
  try {
101
- const { user, dapp, timestamp, commitmentId, providerUrl } = (0, _prosopo_types.decodeProcaptchaOutput)(token);
131
+ const decoded = decodeTokenOr400(token, req.i18n, req.logger, next);
132
+ if (decoded === null) return;
133
+ const { user, dapp, timestamp, commitmentId, providerUrl } = decoded;
102
134
  const testVerdict = require_testSiteKey.resolveTestSiteKeyVerdict(dapp, req.logger);
103
135
  if (testVerdict !== null) {
104
136
  const verificationResponse = {
@@ -132,7 +164,8 @@ function prosopoVerifyRouter(env) {
132
164
  logger: req.logger
133
165
  }));
134
166
  const keyPair = env.keyring.addFromAddress(dapp);
135
- (0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), keyPair);
167
+ const signatureError = dappSignatureError(dappSignature, timestamp.toString(), keyPair, req);
168
+ if (signatureError) return next(signatureError);
136
169
  const response = await tasks.imgCaptchaManager.verifyImageCaptchaSolution(user, dapp, commitmentId, env, maxVerifiedTime, ip, clientRecord.settings.disallowWebView, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.spamFilter, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata, clientSessionId);
137
170
  req.logger.debug(() => ({ data: { response } }));
138
171
  const verificationResponse = tasks.imgCaptchaManager.getVerificationResponse(response[_prosopo_types.ApiParams.verified], clientRecord, req.i18n.t, response[_prosopo_types.ApiParams.score], response[_prosopo_types.ApiParams.commitmentId], response[_prosopo_types.ApiParams.status], response[_prosopo_types.ApiParams.sessionId]);
@@ -183,7 +216,9 @@ function prosopoVerifyRouter(env) {
183
216
  }
184
217
  try {
185
218
  const { token, dappSignature, ip, email, clientSessionId } = parsed;
186
- const { dapp, user, timestamp, challenge, providerUrl } = (0, _prosopo_types.decodeProcaptchaOutput)(token);
219
+ const decoded = decodeTokenOr400(token, req.i18n, req.logger, next);
220
+ if (decoded === null) return;
221
+ const { dapp, user, timestamp, challenge, providerUrl } = decoded;
187
222
  const testVerdict = require_testSiteKey.resolveTestSiteKeyVerdict(dapp, req.logger);
188
223
  if (testVerdict !== null) {
189
224
  const verificationResponse = {
@@ -223,7 +258,8 @@ function prosopoVerifyRouter(env) {
223
258
  return res.json(unverifiedResponse);
224
259
  }
225
260
  const dappPair = env.keyring.addFromAddress(dapp);
226
- (0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), dappPair);
261
+ const signatureError = dappSignatureError(dappSignature, timestamp.toString(), dappPair, req);
262
+ if (signatureError) return next(signatureError);
227
263
  const { verified, score, reason, sessionId } = await tasks.powCaptchaManager.serverVerifyPowCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.spamFilter, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata, clientSessionId);
228
264
  const verificationResponse = tasks.powCaptchaManager.getVerificationResponse(verified, clientRecord, req.i18n.t, score, reason, sessionId);
229
265
  return res.json(verificationResponse);
@@ -272,7 +308,9 @@ function prosopoVerifyRouter(env) {
272
308
  }
273
309
  try {
274
310
  const { token, dappSignature, ip, email, clientSessionId } = parsed;
275
- const { dapp, user, timestamp, challenge, providerUrl } = (0, _prosopo_types.decodeProcaptchaOutput)(token);
311
+ const decoded = decodeTokenOr400(token, req.i18n, req.logger, next);
312
+ if (decoded === null) return;
313
+ const { dapp, user, timestamp, challenge, providerUrl } = decoded;
276
314
  const testVerdict = require_testSiteKey.resolveTestSiteKeyVerdict(dapp, req.logger);
277
315
  if (testVerdict !== null) {
278
316
  const verificationResponse = {
@@ -312,7 +350,8 @@ function prosopoVerifyRouter(env) {
312
350
  return res.json(unverifiedResponse);
313
351
  }
314
352
  const dappPair = env.keyring.addFromAddress(dapp);
315
- (0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), dappPair);
353
+ const signatureError = dappSignatureError(dappSignature, timestamp.toString(), dappPair, req);
354
+ if (signatureError) return next(signatureError);
316
355
  const { verified, score, sessionId } = await tasks.puzzleCaptchaManager.serverVerifyPuzzleCaptchaSolution(dapp, challenge, clientRecord.settings.verifiedTimeout, env, ip, userAccessRulesStorage, email, clientRecord.settings.spamEmailDomainCheckEnabled, clientRecord.settings.spamFilter, clientRecord.settings.trafficFilter, clientRecord.settings.storeMetadata, clientSessionId);
317
356
  const verificationResponse = tasks.puzzleCaptchaManager.getVerificationResponse(verified, clientRecord, req.i18n.t, score, void 0, sessionId);
318
357
  return res.json(verificationResponse);
@@ -361,7 +400,9 @@ function prosopoVerifyRouter(env) {
361
400
  }
362
401
  const { dappSignature, token, ip, clientSessionId } = parsed;
363
402
  try {
364
- const { user, dapp, timestamp, commitmentId: sessionId, providerUrl } = (0, _prosopo_types.decodeProcaptchaOutput)(token);
403
+ const decoded = decodeTokenOr400(token, req.i18n, req.logger, next);
404
+ if (decoded === null) return;
405
+ const { user, dapp, timestamp, commitmentId: sessionId, providerUrl } = decoded;
365
406
  const testVerdict = require_testSiteKey.resolveTestSiteKeyVerdict(dapp, req.logger);
366
407
  if (testVerdict !== null) return res.json({
367
408
  status: "ok",
@@ -391,7 +432,8 @@ function prosopoVerifyRouter(env) {
391
432
  logger: req.logger
392
433
  }));
393
434
  const keyPair = env.keyring.addFromAddress(dapp);
394
- (0, _prosopo_api_express_router.verifySignature)(dappSignature, timestamp.toString(), keyPair);
435
+ const signatureError = dappSignatureError(dappSignature, timestamp.toString(), keyPair, req);
436
+ if (signatureError) return next(signatureError);
395
437
  if (!sessionId) return res.json({
396
438
  status: "API.USER_NOT_VERIFIED_NO_SOLUTION",
397
439
  verified: false
@@ -4,6 +4,11 @@ let ip_address = require("ip-address");
4
4
  //#region src/compositeIpAddress.ts
5
5
  var V6_SHIFT = 64n;
6
6
  var v6_LOWER_MASK = (1n << V6_SHIFT) - 1n;
7
+ /**
8
+ * The part of a v4 address treated as the host rather than the network, for
9
+ * the purposes of `isSameIpOrigin`. 8 bits — a /24.
10
+ */
11
+ var V4_HOST_BITS = 8n;
7
12
  var getCompositeIpAddress = (ip) => {
8
13
  let ipAddress;
9
14
  try {
@@ -60,10 +65,19 @@ var isUnknownIp = (ip) => ip.type === _prosopo_types.IpAddressType.v4 && readPar
60
65
  /**
61
66
  * Do two observations of a client come from the same place on the network?
62
67
  *
63
- * v4 is compared exactly. v6 is compared on the /64 prefix alone — the
64
- * interface identifier in the low 64 bits is designed to rotate (RFC 8981
65
- * privacy extensions), often several times a day on one unchanged connection,
66
- * so comparing it would call an ordinary phone a different host.
68
+ * Both families are compared on the network part and neither on the host part.
69
+ *
70
+ * v6 is the /64 prefix: the interface identifier in the low 64 bits is designed
71
+ * to rotate (RFC 8981 privacy extensions), often several times a day on one
72
+ * unchanged connection, so comparing it would call an ordinary phone a
73
+ * different host.
74
+ *
75
+ * v4 is the /24. A carrier NAT or mobile pool reassigns the low octet mid
76
+ * session — an EE subscriber was observed moving one address in four seconds —
77
+ * so comparing the whole address called every rotating mobile and CGNAT client
78
+ * a different host, which is the opposite of the leniency this check is for.
79
+ * The /24 still separates an unrelated network, which is the case worth
80
+ * catching: a challenge handed to a second host to farm the work out.
67
81
  *
68
82
  * A value we do not actually have counts as the same — one that will not
69
83
  * parse, or the all-zero v4 `getCompositeIpAddress` returns when handed an
@@ -73,13 +87,13 @@ var isUnknownIp = (ip) => ip.type === _prosopo_types.IpAddressType.v4 && readPar
73
87
  var isSameIpOrigin = (a, b) => {
74
88
  if (isUnknownIp(a) || isUnknownIp(b)) return true;
75
89
  if (a.type !== b.type) return false;
76
- const compare = (left, right) => {
90
+ const sameNetwork = (left, right, hostBits) => {
77
91
  const leftPart = readPart(left);
78
92
  const rightPart = readPart(right);
79
93
  if (leftPart === void 0 || rightPart === void 0) return true;
80
- return leftPart === rightPart;
94
+ return leftPart >> hostBits === rightPart >> hostBits;
81
95
  };
82
- return a.type === _prosopo_types.IpAddressType.v6 ? compare(a.upper, b.upper) : compare(a.lower, b.lower);
96
+ return a.type === _prosopo_types.IpAddressType.v6 ? sameNetwork(a.upper, b.upper, 0n) : sameNetwork(a.lower, b.lower, V4_HOST_BITS);
83
97
  };
84
98
  var never = () => {
85
99
  throw new Error("Unhandled type");
@@ -225,6 +225,21 @@ var CaptchaManager = class CaptchaManager {
225
225
  sessionId
226
226
  }
227
227
  }));
228
+ if (!(0, _prosopo_types.isCaptchaTypeFeatureEnabled)(requestedCaptchaType, clientSettings.settings?.captchaTypeFeatureFlags)) {
229
+ this.logger.warn(() => ({
230
+ msg: "Captcha type is switched off for this site",
231
+ data: {
232
+ account: clientSettings.account,
233
+ requestedCaptchaType,
234
+ sessionId
235
+ }
236
+ }));
237
+ return {
238
+ valid: false,
239
+ reason: _prosopo_types.ResultReason.INCORRECT_CAPTCHA_TYPE,
240
+ type: requestedCaptchaType
241
+ };
242
+ }
228
243
  if (sessionId) {
229
244
  const cachedBeforeRemove = this.writeQueue ? await this.writeQueue.getCachedSession(sessionId) : null;
230
245
  const cachedHash = typeof cachedBeforeRemove?.userSitekeyIpHash === "string" ? cachedBeforeRemove.userSitekeyIpHash : void 0;
@@ -18,12 +18,16 @@ let _prosopo_types = require("@prosopo/types");
18
18
  * Restrict rules, traffic-filter category policies, routing-machine actions
19
19
  * and detector-generated rules.
20
20
  *
21
+ * Pass `allowedTypes` from `resolveAllowedCaptchaTypes`, so Prosopo's
22
+ * per-site captcha type feature flags are applied alongside the site owner's
23
+ * own `frictionlessTypes`. Absent means every type is allowed.
24
+ *
21
25
  * Coercion only ever narrows. A disabled type falls back to the other
22
26
  * interactive type if that is permitted, and to PoW otherwise — never the
23
27
  * reverse, so this can't hand a user a harder challenge than was asked for.
24
28
  */
25
- var coerceToEnabledCaptchaType = (requested, frictionlessTypes, logger) => {
26
- const types = (0, _prosopo_types.resolveFrictionlessTypes)(frictionlessTypes);
29
+ var coerceToEnabledCaptchaType = (requested, allowedTypes, logger) => {
30
+ const types = (0, _prosopo_types.resolveFrictionlessTypes)(allowedTypes);
27
31
  const puzzleAllowed = types.puzzle && require_puzzleRenderer.isPuzzleRenderAvailable();
28
32
  const imageAllowed = types.image;
29
33
  const resolved = (() => {
@@ -49,5 +53,22 @@ var coerceToEnabledCaptchaType = (requested, frictionlessTypes, logger) => {
49
53
  }));
50
54
  return resolved;
51
55
  };
56
+ /**
57
+ * Swap a puzzle for an image challenge once the user has refreshed it
58
+ * `PUZZLE_REFRESHES_BEFORE_IMAGE` times in a row.
59
+ *
60
+ * A user who keeps refreshing either cannot solve this kind of puzzle or is a
61
+ * solver shopping for an easy instance. The image challenge answers both.
62
+ *
63
+ * The refresh count is client-reported, so this only moves one way: from
64
+ * puzzle to image, never back. Lying about it can only earn a harder
65
+ * challenge. Leaving it out looks like a fresh visit, which is what a page
66
+ * reload already gets.
67
+ *
68
+ * A site that has not enabled image keeps its puzzle. Serving a type the site
69
+ * turned off is exactly what `coerceToEnabledCaptchaType` exists to stop.
70
+ */
71
+ var switchTypeAfterRefreshes = (resolved, refreshCount, allowedTypes) => _prosopo_types.CaptchaType.puzzle === resolved && (refreshCount ?? 0) >= 3 && (0, _prosopo_types.resolveFrictionlessTypes)(allowedTypes).image ? _prosopo_types.CaptchaType.image : resolved;
52
72
  //#endregion
53
73
  exports.coerceToEnabledCaptchaType = coerceToEnabledCaptchaType;
74
+ exports.switchTypeAfterRefreshes = switchTypeAfterRefreshes;
@@ -5,6 +5,29 @@ let _prosopo_database = require("@prosopo/database");
5
5
  let _prosopo_types = require("@prosopo/types");
6
6
  require("node:crypto");
7
7
  let _prosopo_util = require("@prosopo/util");
8
+ /**
9
+ * Matches `text` against a pattern where `*` matches any run of characters.
10
+ * Greedy two-pointer match, retrying from the last `*` on mismatch, so time
11
+ * is bounded by pattern length times text length.
12
+ */
13
+ var matchesGlob = (pattern, text) => {
14
+ let p = 0;
15
+ let t = 0;
16
+ let starP = -1;
17
+ let starT = 0;
18
+ while (t < text.length) if (p < pattern.length && pattern[p] === "*") {
19
+ starP = p++;
20
+ starT = t;
21
+ } else if (p < pattern.length && pattern[p] === text[t]) {
22
+ p++;
23
+ t++;
24
+ } else if (starP !== -1) {
25
+ p = starP + 1;
26
+ t = ++starT;
27
+ } else return false;
28
+ while (p < pattern.length && pattern[p] === "*") p++;
29
+ return p === pattern.length;
30
+ };
8
31
  var ClientTaskManager = class {
9
32
  constructor(config, logger, db) {
10
33
  this.config = config;
@@ -251,10 +274,7 @@ var ClientTaskManager = class {
251
274
  const allowed = (0, _prosopo_util.parseUrl)(suffix).hostname.replace(/\.$/, "");
252
275
  return referrerHost.endsWith(`.${allowed}`) || referrerHost === allowed;
253
276
  }
254
- if (pattern.includes("*")) {
255
- const escaped = pattern.replace(/[.+?^${}()|\[\]\\]/g, "\\$&").replace(/\*/g, ".*");
256
- return new RegExp(`^${escaped}$`, "i").test(referrerHost);
257
- }
277
+ if (pattern.includes("*")) return matchesGlob(pattern, referrerHost.toLowerCase());
258
278
  const allowedHost = (0, _prosopo_util.parseUrl)(pattern).hostname.replace(/\.$/, "");
259
279
  return referrerHost === allowedHost || referrerHost.endsWith(`.${allowedHost}`);
260
280
  } catch (e) {
@@ -297,3 +317,4 @@ var ClientTaskManager = class {
297
317
  };
298
318
  //#endregion
299
319
  exports.ClientTaskManager = ClientTaskManager;
320
+ exports.matchesGlob = matchesGlob;
@@ -5,7 +5,7 @@ var providerValidateDataset = async (datasetRaw, minSolvedCaptchas, minUnsolvedC
5
5
  if (datasetRaw.captchas.length < minSolvedCaptchas + minUnsolvedCaptchas) throw new _prosopo_common.ProsopoEnvError("DATASET.CAPTCHAS_COUNT_LESS_THAN_CONFIGURED", { context: { failedFuncName: providerValidateDataset.name } });
6
6
  const solutions = datasetRaw.captchas.map((captcha) => captcha.solution ? 1 : 0).reduce((partialSum, b) => partialSum + b, 0);
7
7
  if (solutions < minSolvedCaptchas) throw new _prosopo_common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", { context: { failedFuncName: providerValidateDataset.name } });
8
- if (solutions < minUnsolvedCaptchas) throw new _prosopo_common.ProsopoEnvError("DATASET.SOLUTIONS_COUNT_LESS_THAN_CONFIGURED", { context: { failedFuncName: providerValidateDataset.name } });
8
+ if (datasetRaw.captchas.length - solutions < minUnsolvedCaptchas) throw new _prosopo_common.ProsopoEnvError("DATASET.CAPTCHAS_COUNT_LESS_THAN_CONFIGURED", { context: { failedFuncName: providerValidateDataset.name } });
9
9
  const dataset = await (0, _prosopo_datasets.buildDataset)(datasetRaw);
10
10
  if (!dataset.datasetId || !dataset.datasetContentId) throw new _prosopo_common.ProsopoEnvError("DATASET.DATASET_ID_UNDEFINED", { context: {
11
11
  failedFuncName: providerValidateDataset.name,
@@ -1,6 +1,7 @@
1
1
  const require_usageCounters = require("../../util/usageCounters.cjs");
2
2
  const require_decisionMachineRunner = require("../decisionMachine/decisionMachineRunner.cjs");
3
3
  const require_dnsEventUrl = require("../../api/dnsEventUrl.cjs");
4
+ const require_rawTlsSignalsMiddleware = require("../../api/rawTlsSignalsMiddleware.cjs");
4
5
  const require_lang = require("../../rules/lang.cjs");
5
6
  const require_clientMetaData = require("../../utils/clientMetaData.cjs");
6
7
  const require_captchaManager = require("../captchaManager.cjs");
@@ -33,6 +34,15 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
33
34
  this.routingContext = ctx;
34
35
  }
35
36
  /**
37
+ * The types this site may be served, from `resolveAllowedCaptchaTypes`.
38
+ * Set before any path can mint a session, including the short-circuits
39
+ * that skip routing, so the site's feature flags hold on every path.
40
+ * Unset means every type is allowed.
41
+ */
42
+ setAllowedCaptchaTypes(allowed) {
43
+ this.allowedCaptchaTypes = allowed;
44
+ }
45
+ /**
36
46
  * Evaluate the configured routing machine without going through
37
47
  * `sendCaptcha`. Returns the supplied baseline on any failure (no machine,
38
48
  * machine throws, counter fetch failure), matching applyRouter's contract.
@@ -63,15 +73,10 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
63
73
  d: params.d,
64
74
  tcpToChelloUs: params.tcpToChelloUs,
65
75
  chelloToHandshakeUs: params.chelloToHandshakeUs,
66
- synNs: params.synNs,
67
- synackNs: params.synackNs,
68
- ackNs: params.ackNs,
69
- observedTtl: params.observedTtl,
70
- tcpMss: params.tcpMss,
71
- tcpWscale: params.tcpWscale,
72
- tcpOptsFlags: params.tcpOptsFlags,
73
- tcpOptsOrder: params.tcpOptsOrder,
74
- tcpWindow: params.tcpWindow
76
+ ...require_rawTlsSignalsMiddleware.rawTlsSignalsFromRecord(params),
77
+ refreshOf: params.refreshOf,
78
+ refreshCount: params.refreshCount,
79
+ refreshedAfterMs: params.refreshedAfterMs
75
80
  };
76
81
  }
77
82
  /**
@@ -91,7 +96,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
91
96
  return require_lang.checkLangRules(this.config, acceptLanguage);
92
97
  }
93
98
  async createSession(input) {
94
- const { token, score, threshold, scoreComponents, ipAddress, captchaType, siteKey, mode, solvedImagesCount, powDifficulty, userSitekeyIpHash, reason, blocked, deleted, ipInfo, headers, bundleId, currentUrl, iframeUrl, d, tcpToChelloUs, chelloToHandshakeUs, synNs, synackNs, ackNs, observedTtl, tcpMss, tcpWscale, tcpOptsFlags, tcpOptsOrder, tcpWindow, simdReadings, puzzleTolerance, puzzle, isEscalation, originSessionId, isProtect, matchedRule, clientMetaData, webView = false, iFrame = false, decryptedHeadHash = "" } = input;
99
+ const { token, score, threshold, scoreComponents, ipAddress, captchaType, siteKey, mode, solvedImagesCount, powDifficulty, userSitekeyIpHash, reason, blocked, deleted, ipInfo, headers, bundleId, currentUrl, iframeUrl, d, tcpToChelloUs, chelloToHandshakeUs, simdReadings, puzzleTolerance, puzzle, isEscalation, originSessionId, refreshOf, refreshCount, refreshedAfterMs, isProtect, matchedRule, clientMetaData, webView = false, iFrame = false, decryptedHeadHash = "" } = input;
95
100
  const sessionRecord = {
96
101
  sessionId: `${getSessionIDPrefix(this.config.host)}-${(0, uuid.v4)()}`,
97
102
  createdAt: /* @__PURE__ */ new Date(),
@@ -111,6 +116,9 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
111
116
  iFrame,
112
117
  ...isEscalation && { isEscalation: true },
113
118
  ...originSessionId && { originSessionId },
119
+ ...refreshOf && { refreshOf },
120
+ ...refreshCount !== void 0 && { refreshCount },
121
+ ...refreshedAfterMs !== void 0 && { refreshedAfterMs },
114
122
  decryptedHeadHash,
115
123
  bundleId,
116
124
  reason,
@@ -127,15 +135,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
127
135
  d,
128
136
  tcpToChelloUs,
129
137
  chelloToHandshakeUs,
130
- synNs,
131
- synackNs,
132
- ackNs,
133
- observedTtl,
134
- tcpMss,
135
- tcpWscale,
136
- tcpOptsFlags,
137
- tcpOptsOrder,
138
- tcpWindow,
138
+ ...require_rawTlsSignalsMiddleware.rawTlsSignalsFromRecord(input),
139
139
  ...matchedRule && { matchedRule },
140
140
  ...clientMetaData && { clientMetaData }
141
141
  };
@@ -247,14 +247,16 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
247
247
  powDifficulty: captchaType === _prosopo_types.CaptchaType.pow ? effectiveParams.powDifficulty : void 0
248
248
  };
249
249
  const routed = this.routingContext ? await require_routingMachine.applyRouter(this.decisionMachineRunner, this.usageCounters, baseline, this.routingContext, this.logger) : baseline;
250
- const finalCaptchaType = require_captchaTypeSelection.coerceToEnabledCaptchaType(routed.captchaType, this.routingContext?.frictionlessTypes, this.logger);
250
+ const enabledCaptchaType = require_captchaTypeSelection.coerceToEnabledCaptchaType(routed.captchaType, this.allowedCaptchaTypes, this.logger);
251
+ const finalCaptchaType = require_captchaTypeSelection.switchTypeAfterRefreshes(enabledCaptchaType, effectiveParams.refreshCount, this.allowedCaptchaTypes);
252
+ const switchedByRefreshes = finalCaptchaType !== enabledCaptchaType;
251
253
  const requestedSolvedImagesCount = routed.solvedImagesCount ?? effectiveParams.solvedImagesCount;
252
254
  const imageRoundsBounds = this.routingContext ?? {};
253
255
  const finalSolvedImagesCount = finalCaptchaType === _prosopo_types.CaptchaType.image ? requestedSolvedImagesCount !== void 0 ? (0, _prosopo_types.clampImageRounds)(requestedSolvedImagesCount, imageRoundsBounds) : requestedSolvedImagesCount : void 0;
254
256
  const finalPowDifficulty = finalCaptchaType === _prosopo_types.CaptchaType.pow ? routed.powDifficulty ?? effectiveParams.powDifficulty : void 0;
255
- const finalReason = routed.reason ?? effectiveParams.reason;
257
+ const finalReason = switchedByRefreshes ? _prosopo_types.FrictionlessReason.PUZZLE_REFRESH_LIMIT : routed.reason ?? effectiveParams.reason;
256
258
  const finalPuzzleOverrides = finalCaptchaType === _prosopo_types.CaptchaType.puzzle ? (() => {
257
- const maxLevel = this.routingContext?.puzzleMaxDifficulty ?? _prosopo_types.puzzleMaxDifficultyDefault;
259
+ const maxLevel = (0, _prosopo_captcha_severity.resolveMaxEscalationLevel)(this.routingContext?.puzzleMaxDifficulty ?? _prosopo_types.puzzleMaxDifficultyDefault, this.routingContext?.platform.isMobile ?? false);
258
260
  const level = finalReason !== void 0 && _prosopo_types.NO_MEASUREMENT_REASONS.has(finalReason) ? 0 : (0, _prosopo_captcha_severity.severityToPuzzleDifficulty)(requestedSolvedImagesCount, this.routingContext?.baseImageRounds ?? this.config.captchas.solved.count, maxLevel);
259
261
  const difficulty = level > 0 ? require_puzzleDifficulty.samplePuzzleDifficulty(level, _prosopo_puzzle_assets.DEFAULT_RENDER_SETTINGS.holeDarken) : void 0;
260
262
  const tolerance = routed.puzzleTolerance ?? difficulty?.tolerance;
@@ -264,7 +266,8 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
264
266
  } : void 0;
265
267
  return {
266
268
  ...tolerance !== void 0 && { puzzleTolerance: tolerance },
267
- ...puzzle !== void 0 && { puzzle }
269
+ ...puzzle !== void 0 && { puzzle },
270
+ ...difficulty !== void 0 && { puzzleLevel: difficulty.level }
268
271
  };
269
272
  })() : {};
270
273
  const blocked = finalCaptchaType === _prosopo_types.CaptchaType.image ? effectiveParams.blocked : void 0;