@prosopo/provider 5.2.1 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +17 -13
  2. package/.turbo/turbo-build$colon$tsc.log +27 -24
  3. package/.turbo/turbo-build.log +18 -14
  4. package/CHANGELOG.md +55 -0
  5. package/dist/api/captcha/getPuzzleCaptchaChallenge.d.ts.map +1 -1
  6. package/dist/api/captcha/getPuzzleCaptchaChallenge.js +11 -4
  7. package/dist/api/captcha/getPuzzleCaptchaChallenge.js.map +1 -1
  8. package/dist/api/captcha/maintenanceModeResponses.d.ts +1 -1
  9. package/dist/api/captcha/maintenanceModeResponses.d.ts.map +1 -1
  10. package/dist/api/captcha/maintenanceModeResponses.js +11 -6
  11. package/dist/api/captcha/maintenanceModeResponses.js.map +1 -1
  12. package/dist/api/captcha/submitPoWCaptchaSolution.d.ts.map +1 -1
  13. package/dist/api/captcha/submitPoWCaptchaSolution.js +4 -2
  14. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  15. package/dist/api/captcha/trafficFilterRequestTime.d.ts +2 -1
  16. package/dist/api/captcha/trafficFilterRequestTime.d.ts.map +1 -1
  17. package/dist/api/captcha/trafficFilterRequestTime.js +1 -0
  18. package/dist/api/captcha/trafficFilterRequestTime.js.map +1 -1
  19. package/dist/api/ja4.d.ts.map +1 -1
  20. package/dist/api/ja4.js +14 -147
  21. package/dist/api/ja4.js.map +1 -1
  22. package/dist/cjs/api/captcha/getPuzzleCaptchaChallenge.cjs +11 -4
  23. package/dist/cjs/api/captcha/maintenanceModeResponses.cjs +11 -6
  24. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +4 -2
  25. package/dist/cjs/api/captcha/trafficFilterRequestTime.cjs +1 -0
  26. package/dist/cjs/api/ja4.cjs +13 -147
  27. package/dist/cjs/tasks/dnsEvent/enrichDnsEvent.cjs +17 -11
  28. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +2 -1
  29. package/dist/cjs/tasks/puzzle/backgroundBuffer.cjs +39 -0
  30. package/dist/cjs/tasks/puzzle/puzzleRenderer.cjs +78 -0
  31. package/dist/cjs/tasks/spam/checkTrafficFilter.cjs +62 -31
  32. package/dist/tasks/dnsEvent/enrichDnsEvent.d.ts.map +1 -1
  33. package/dist/tasks/dnsEvent/enrichDnsEvent.js +17 -11
  34. package/dist/tasks/dnsEvent/enrichDnsEvent.js.map +1 -1
  35. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  36. package/dist/tasks/frictionless/frictionlessTasks.js +2 -1
  37. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  38. package/dist/tasks/puzzle/backgroundBuffer.d.ts +21 -0
  39. package/dist/tasks/puzzle/backgroundBuffer.d.ts.map +1 -0
  40. package/dist/tasks/puzzle/backgroundBuffer.js +37 -0
  41. package/dist/tasks/puzzle/backgroundBuffer.js.map +1 -0
  42. package/dist/tasks/puzzle/puzzleRenderer.d.ts +13 -0
  43. package/dist/tasks/puzzle/puzzleRenderer.d.ts.map +1 -0
  44. package/dist/tasks/puzzle/puzzleRenderer.js +75 -0
  45. package/dist/tasks/puzzle/puzzleRenderer.js.map +1 -0
  46. package/dist/tasks/spam/checkTrafficFilter.d.ts +3 -1
  47. package/dist/tasks/spam/checkTrafficFilter.d.ts.map +1 -1
  48. package/dist/tasks/spam/checkTrafficFilter.js +62 -32
  49. package/dist/tasks/spam/checkTrafficFilter.js.map +1 -1
  50. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.js +8 -7
  51. package/dist/tests/unit/api/captcha/maintenanceModeResponses.unit.test.js.map +1 -1
  52. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js +1 -1
  53. package/dist/tests/unit/api/captcha/maintenanceModeShortCircuit.unit.test.js.map +1 -1
  54. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.js +2 -1
  55. package/dist/tests/unit/api/captcha/trafficFilterRequestTime.unit.test.js.map +1 -1
  56. package/dist/tests/unit/tasks/dnsEvent/enrichDnsEvent.unit.test.js.map +1 -1
  57. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.d.ts +2 -0
  58. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.d.ts.map +1 -0
  59. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.js +84 -0
  60. package/dist/tests/unit/tasks/puzzle/backgroundBuffer.unit.test.js.map +1 -0
  61. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js +66 -4
  62. package/dist/tests/unit/tasks/spam/checkTrafficFilter.unit.test.js.map +1 -1
  63. package/package.json +16 -14
@@ -1,5 +1,5 @@
1
1
  import type { Logger } from "@prosopo/logger";
2
- import { CaptchaType, type IPInfoResponse, type ITrafficFilter } from "@prosopo/types";
2
+ import { CaptchaType, type IPInfoResponse, type IPuzzleSettings, type ITrafficFilter } from "@prosopo/types";
3
3
  import type { ClientRecord } from "@prosopo/types-database";
4
4
  import type { Response } from "express";
5
5
  import type { FrictionlessManager } from "../../tasks/frictionless/frictionlessTasks.js";
@@ -12,6 +12,7 @@ export type RequestTimeTrafficVerdict = {
12
12
  powDifficulty?: number;
13
13
  solvedImagesCount?: number;
14
14
  puzzleTolerance?: number;
15
+ puzzleSettings?: IPuzzleSettings;
15
16
  sourceCategories: ResolvedChallengePolicy["sourceCategories"];
16
17
  };
17
18
  export declare const applyTrafficFilterAtRequestTime: (ipInfo: IPInfoResponse | undefined, trafficFilter: Partial<ITrafficFilter> | undefined, logger?: Logger) => RequestTimeTrafficVerdict;
@@ -1 +1 @@
1
- {"version":3,"file":"trafficFilterRequestTime.d.ts","sourceRoot":"","sources":["../../../src/api/captcha/trafficFilterRequestTime.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACN,WAAW,EAEX,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACzF,OAAO,EACN,KAAK,uBAAuB,EAG5B,MAAM,wCAAwC,CAAC;AAEhD,MAAM,MAAM,yBAAyB,GAClC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IACA,IAAI,EAAE,WAAW,CAAC;IAGlB,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;CAC7D,CAAC;AAeL,eAAO,MAAM,+BAA+B,WACnC,cAAc,GAAG,SAAS,iBACnB,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,WACzC,MAAM,KACb,yBAgCF,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACzC;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAClB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzC,MAAM,MAAM,8BAA8B,GAAG;IAC5C,OAAO,EAAE,yBAAyB,CAAC;IACnC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,YAAY,EAAE,YAAY,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAWF,eAAO,MAAM,+BAA+B,UACpC,8BAA8B,OAChC,QAAQ,KACX,OAAO,CAAC,gCAAgC,CAyD1C,CAAC"}
1
+ {"version":3,"file":"trafficFilterRequestTime.d.ts","sourceRoot":"","sources":["../../../src/api/captcha/trafficFilterRequestTime.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EACN,WAAW,EAEX,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACzF,OAAO,EACN,KAAK,uBAAuB,EAG5B,MAAM,wCAAwC,CAAC;AAEhD,MAAM,MAAM,yBAAyB,GAClC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IACA,IAAI,EAAE,WAAW,CAAC;IAGlB,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IAKzB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,gBAAgB,EAAE,uBAAuB,CAAC,kBAAkB,CAAC,CAAC;CAC7D,CAAC;AAeL,eAAO,MAAM,+BAA+B,WACnC,cAAc,GAAG,SAAS,iBACnB,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,WACzC,MAAM,KACb,yBAiCF,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACzC;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAClB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzC,MAAM,MAAM,8BAA8B,GAAG;IAC5C,OAAO,EAAE,yBAAyB,CAAC;IACnC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,YAAY,EAAE,YAAY,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAWF,eAAO,MAAM,+BAA+B,UACpC,8BAA8B,OAChC,QAAQ,KACX,OAAO,CAAC,gCAAgC,CAyD1C,CAAC"}
@@ -34,6 +34,7 @@ var applyTrafficFilterAtRequestTime = (ipInfo, trafficFilter, logger) => {
34
34
  powDifficulty: resolved.powDifficulty,
35
35
  solvedImagesCount: resolved.solvedImagesCount,
36
36
  puzzleTolerance: resolved.puzzleTolerance,
37
+ puzzleSettings: resolved.puzzleSettings,
37
38
  sourceCategories: resolved.sourceCategories
38
39
  };
39
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"trafficFilterRequestTime.js","sourceRoot":"","sources":["../../../src/api/captcha/trafficFilterRequestTime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"trafficFilterRequestTime.js","sourceRoot":"","sources":["../../../src/api/captcha/trafficFilterRequestTime.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ja4.d.ts","sourceRoot":"","sources":["../../src/api/ja4.ts"],"names":[],"mappings":"AA8EA,qBAAa,aAAc,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI3B;AAoBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAyMjD"}
1
+ {"version":3,"file":"ja4.d.ts","sourceRoot":"","sources":["../../src/api/ja4.ts"],"names":[],"mappings":"AAkCA,qBAAa,aAAc,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI3B;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjD"}
package/dist/api/ja4.js CHANGED
@@ -1,38 +1,13 @@
1
- import { createHash } from "node:crypto";
1
+ import { createRequire } from "node:module";
2
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;
11
- function isGrease(id) {
12
- return (id & 3855) === 2570 && id >> 8 === (id & 255);
13
- }
14
- function hash12(input) {
15
- return createHash("sha256").update(input).digest("hex").slice(0, 12);
16
- }
17
- function formatIdList(ids) {
18
- return ids.map((id) => id.toString(16).padStart(4, "0")).join(",");
19
- }
20
- function alpnChar(b) {
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");
23
- }
24
- function alpnFirstLast(protocol) {
25
- return [protocol.length > 0 ? alpnChar(protocol.readUInt8(0)) : "0", protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0"];
26
- }
27
- function tlsVersionStr(version) {
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";
3
+ var req = createRequire(import.meta.url);
4
+ var nativeCalculateJa4 = (() => {
5
+ try {
6
+ return req("@prosopo/native-ja4");
7
+ } catch {
8
+ return req("./index.linux-x64-gnu.node");
34
9
  }
35
- }
10
+ })().calculateJa4;
36
11
  var Ja4ParseError = class extends Error {
37
12
  constructor(message) {
38
13
  super(message);
@@ -42,123 +17,15 @@ var Ja4ParseError = class extends Error {
42
17
  /**
43
18
  * Compute a JA4 TLS fingerprint from raw ClientHello bytes.
44
19
  *
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.
20
+ * Delegates to the Rust implementation in @prosopo/native-ja4. Structural
21
+ * TLS parse failures are rethrown as Ja4ParseError.
59
22
  */
60
23
  function calculateJa4(data) {
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
- }
24
+ try {
25
+ return nativeCalculateJa4(data);
26
+ } catch (err) {
27
+ throw new Ja4ParseError(err instanceof Error ? err.message : String(err));
150
28
  }
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)}`;
162
29
  }
163
30
  //#endregion
164
31
  export { Ja4ParseError, calculateJa4 };
@@ -1 +1 @@
1
- {"version":3,"file":"ja4.js","sourceRoot":"","sources":["../../src/api/ja4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ja4.js","sourceRoot":"","sources":["../../src/api/ja4.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAU5C,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3C,MAAM,eAAe,GAAoB,CAAC,GAAG,EAAE;IAC9C,IAAI,CAAC;QACJ,OAAO,GAAG,CAAC,qBAAqB,CAAoB,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,GAAG,CAAC,4BAA4B,CAAoB,CAAC;IAC7D,CAAC;AACF,CAAC,CAAC,EAAE,CAAC;AACL,MAAM,kBAAkB,GAAG,eAAe,CAAC,YAAY,CAAC;AAExD,MAAM,OAAO,aAAc,SAAQ,KAAK;IACvC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC7B,CAAC;CACD;AAQD,MAAM,UAAU,YAAY,CAAC,IAAY;IACxC,IAAI,CAAC;QACJ,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,IAAI,aAAa,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3E,CAAC;AACF,CAAC"}
@@ -2,6 +2,7 @@ const require_validateAddress = require("../validateAddress.cjs");
2
2
  const require_compositeIpAddress = require("../../compositeIpAddress.cjs");
3
3
  const require_metrics = require("../metrics.cjs");
4
4
  const require_blacklistRequestInspector = require("../blacklistRequestInspector.cjs");
5
+ const require_puzzleRenderer = require("../../tasks/puzzle/puzzleRenderer.cjs");
5
6
  const require_tasks = require("../../tasks/tasks.cjs");
6
7
  const require_apiToggleMaintenanceModeEndpoint = require("../admin/apiToggleMaintenanceModeEndpoint.cjs");
7
8
  const require_normalizeRequestIp = require("../../utils/normalizeRequestIp.cjs");
@@ -37,7 +38,7 @@ var getPuzzleCaptchaChallenge_default = (env, userAccessRulesStorage) => async (
37
38
  sessionId
38
39
  }
39
40
  }));
40
- return res.json(require_maintenanceModeResponses.buildPuzzleMaintenanceResponse(user, dapp));
41
+ return res.json(await require_maintenanceModeResponses.buildPuzzleMaintenanceResponse(user, dapp));
41
42
  }
42
43
  const tasks = new require_tasks.Tasks(env, req.logger);
43
44
  try {
@@ -80,6 +81,8 @@ var getPuzzleCaptchaChallenge_default = (env, userAccessRulesStorage) => async (
80
81
  }));
81
82
  const trafficVerdict = require_trafficFilterRequestTime.applyTrafficFilterAtRequestTime(req.ipInfo, clientSettings.settings?.trafficFilter, req.logger);
82
83
  const tolerance = (trafficVerdict.kind === "challenge" ? trafficVerdict.puzzleTolerance : void 0) ?? clientSettings?.settings?.puzzleTolerance;
84
+ const trafficPuzzleSettings = trafficVerdict.kind === "challenge" ? trafficVerdict.puzzleSettings : void 0;
85
+ const effectivePuzzleSettings = require_puzzleRenderer.resolvePuzzleRenderSettings(clientSettings?.settings?.puzzle, trafficPuzzleSettings);
83
86
  const challenge = await tasks.puzzleCaptchaManager.getPuzzleCaptchaChallenge(user, dapp, origin, tolerance);
84
87
  if (validSessionId && simdReadings) await tasks.frictionlessManager.decryptAndAttachSimdReadingsIfAbsent(validSessionId, simdReadings, _prosopo_types.SimdReadingsStage.challenge).catch((updateErr) => {
85
88
  req.logger.warn(() => ({
@@ -92,14 +95,18 @@ var getPuzzleCaptchaChallenge_default = (env, userAccessRulesStorage) => async (
92
95
  userAccount: user,
93
96
  dappAccount: dapp
94
97
  }, challenge.targetX, challenge.targetY, challenge.originX, challenge.originY, challenge.tolerance, challenge.providerSignature, require_compositeIpAddress.getCompositeIpAddress(normalizedIp), (0, _prosopo_util.flatten)(req.headers), req.ja4, validSessionId, req.ipInfo);
98
+ const images = await require_puzzleRenderer.renderPuzzleImages({
99
+ targetX: challenge.targetX,
100
+ targetY: challenge.targetY
101
+ }, effectivePuzzleSettings);
95
102
  const getPuzzleCaptchaResponse = {
96
103
  [_prosopo_types.ApiParams.status]: "ok",
97
104
  [_prosopo_types.ApiParams.challenge]: challenge.challenge,
98
- [_prosopo_types.ApiParams.targetX]: challenge.targetX,
99
- [_prosopo_types.ApiParams.targetY]: challenge.targetY,
105
+ [_prosopo_types.ApiParams.background]: images.background,
106
+ [_prosopo_types.ApiParams.piece]: images.piece,
107
+ [_prosopo_types.ApiParams.pieceSize]: images.pieceSize,
100
108
  [_prosopo_types.ApiParams.originX]: challenge.originX,
101
109
  [_prosopo_types.ApiParams.originY]: challenge.originY,
102
- [_prosopo_types.ApiParams.tolerance]: challenge.tolerance,
103
110
  [_prosopo_types.ApiParams.timestamp]: challenge.requestedAtTimestamp.toString(),
104
111
  [_prosopo_types.ApiParams.signature]: { [_prosopo_types.ApiParams.provider]: { [_prosopo_types.ApiParams.challenge]: challenge.providerSignature } }
105
112
  };
@@ -1,3 +1,4 @@
1
+ const require_puzzleRenderer = require("../../tasks/puzzle/puzzleRenderer.cjs");
1
2
  let _prosopo_types = require("@prosopo/types");
2
3
  let node_crypto = require("node:crypto");
3
4
  //#region src/api/captcha/maintenanceModeResponses.ts
@@ -30,16 +31,20 @@ var buildImageMaintenanceResponse = () => ({
30
31
  [_prosopo_types.ApiParams.timestamp]: Date.now().toString(),
31
32
  [_prosopo_types.ApiParams.signature]: { [_prosopo_types.ApiParams.provider]: { [_prosopo_types.ApiParams.requestHash]: "" } }
32
33
  });
33
- var buildPuzzleMaintenanceResponse = (user, dapp) => {
34
+ var buildPuzzleMaintenanceResponse = async (user, dapp) => {
34
35
  const timestamp = Date.now();
36
+ const images = await require_puzzleRenderer.renderPuzzleImages({
37
+ targetX: 200,
38
+ targetY: 100
39
+ });
35
40
  return {
36
41
  [_prosopo_types.ApiParams.status]: "ok",
37
42
  [_prosopo_types.ApiParams.challenge]: buildChallenge(user, dapp),
38
- [_prosopo_types.ApiParams.targetX]: 100,
39
- [_prosopo_types.ApiParams.targetY]: 100,
40
- [_prosopo_types.ApiParams.originX]: 0,
41
- [_prosopo_types.ApiParams.originY]: 0,
42
- [_prosopo_types.ApiParams.tolerance]: 1e3,
43
+ [_prosopo_types.ApiParams.background]: images.background,
44
+ [_prosopo_types.ApiParams.piece]: images.piece,
45
+ [_prosopo_types.ApiParams.pieceSize]: images.pieceSize,
46
+ [_prosopo_types.ApiParams.originX]: 60,
47
+ [_prosopo_types.ApiParams.originY]: 100,
43
48
  [_prosopo_types.ApiParams.timestamp]: timestamp.toString(),
44
49
  [_prosopo_types.ApiParams.signature]: { [_prosopo_types.ApiParams.provider]: { [_prosopo_types.ApiParams.challenge]: "" } }
45
50
  };
@@ -1,4 +1,5 @@
1
1
  const require_validateAddress = require("../validateAddress.cjs");
2
+ const require_puzzleRenderer = require("../../tasks/puzzle/puzzleRenderer.cjs");
2
3
  const require_tasks = require("../../tasks/tasks.cjs");
3
4
  const require_apiToggleMaintenanceModeEndpoint = require("../admin/apiToggleMaintenanceModeEndpoint.cjs");
4
5
  const require_devicePlatform = require("../../utils/devicePlatform.cjs");
@@ -130,11 +131,12 @@ var buildEscalation = async (tasks, result, challenge, handshakeTiming) => {
130
131
  const originSession = await tasks.db.getSessionRecordBySessionId(powRecord.sessionId);
131
132
  if (!originSession) return void 0;
132
133
  const routed = result.routingOutput;
134
+ const escalatedType = require_puzzleRenderer.downgradePuzzleIfUnavailable(routed.captchaType);
133
135
  const selectionReason = routed.reason ?? originSession.reason;
134
- const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, routed.captchaType, originSession.siteKey ?? powRecord.dappAccount, routed.captchaType === _prosopo_types.CaptchaType.image ? routed.solvedImagesCount ?? originSession.solvedImagesCount : void 0, void 0, originSession.userSitekeyIpHash, originSession.webView, originSession.iFrame, originSession.decryptedHeadHash, selectionReason, void 0, void 0, originSession.ipInfo, originSession.headers, originSession.mode, originSession.simdReadings, originSession.entropyMathRandomFingerprint, originSession.entropyCryptoFingerprint, originSession.entropyWallClockOffsetMs, originSession.entropyMathRandomFirst, originSession.bundleId, originSession.currentUrl, handshakeTiming?.tcpToChelloUs, handshakeTiming?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect, originSession.sessionId, originSession.g, void 0, originSession.i, originSession.sw, originSession.md, originSession.bn, originSession.fs);
136
+ const newSession = await tasks.frictionlessManager.createSession(originSession.token, originSession.score, originSession.threshold, originSession.scoreComponents, originSession.ipAddress, escalatedType, originSession.siteKey ?? powRecord.dappAccount, escalatedType === _prosopo_types.CaptchaType.image ? routed.solvedImagesCount ?? originSession.solvedImagesCount : void 0, void 0, originSession.userSitekeyIpHash, originSession.webView, originSession.iFrame, originSession.decryptedHeadHash, selectionReason, void 0, void 0, originSession.ipInfo, originSession.headers, originSession.mode, originSession.simdReadings, originSession.entropyMathRandomFingerprint, originSession.entropyCryptoFingerprint, originSession.entropyWallClockOffsetMs, originSession.entropyMathRandomFirst, originSession.bundleId, originSession.currentUrl, handshakeTiming?.tcpToChelloUs, handshakeTiming?.chelloToHandshakeUs, true, originSession.iframeUrl, originSession.isProtect, originSession.sessionId, originSession.g, void 0, originSession.i, originSession.sw, originSession.md, originSession.bn, originSession.fs);
135
137
  if (tasks.writeQueue) await tasks.writeQueue.cacheSessionEscalation(powRecord.sessionId, newSession.sessionId);
136
138
  return {
137
- captchaType: routed.captchaType,
139
+ captchaType: escalatedType,
138
140
  sessionId: newSession.sessionId
139
141
  };
140
142
  };
@@ -35,6 +35,7 @@ var applyTrafficFilterAtRequestTime = (ipInfo, trafficFilter, logger) => {
35
35
  powDifficulty: resolved.powDifficulty,
36
36
  solvedImagesCount: resolved.solvedImagesCount,
37
37
  puzzleTolerance: resolved.puzzleTolerance,
38
+ puzzleSettings: resolved.puzzleSettings,
38
39
  sourceCategories: resolved.sourceCategories
39
40
  };
40
41
  };
@@ -1,38 +1,12 @@
1
- let node_crypto = require("node:crypto");
2
1
  //#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;
11
- function isGrease(id) {
12
- return (id & 3855) === 2570 && id >> 8 === (id & 255);
13
- }
14
- function hash12(input) {
15
- return (0, node_crypto.createHash)("sha256").update(input).digest("hex").slice(0, 12);
16
- }
17
- function formatIdList(ids) {
18
- return ids.map((id) => id.toString(16).padStart(4, "0")).join(",");
19
- }
20
- function alpnChar(b) {
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");
23
- }
24
- function alpnFirstLast(protocol) {
25
- return [protocol.length > 0 ? alpnChar(protocol.readUInt8(0)) : "0", protocol.length > 1 ? alpnChar(protocol.readUInt8(protocol.length - 1)) : "0"];
26
- }
27
- function tlsVersionStr(version) {
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";
2
+ var req = (0, require("node:module").createRequire)(require("url").pathToFileURL(__filename).href);
3
+ var nativeCalculateJa4 = (() => {
4
+ try {
5
+ return req("@prosopo/native-ja4");
6
+ } catch {
7
+ return req("./index.linux-x64-gnu.node");
34
8
  }
35
- }
9
+ })().calculateJa4;
36
10
  var Ja4ParseError = class extends Error {
37
11
  constructor(message) {
38
12
  super(message);
@@ -42,123 +16,15 @@ var Ja4ParseError = class extends Error {
42
16
  /**
43
17
  * Compute a JA4 TLS fingerprint from raw ClientHello bytes.
44
18
  *
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.
19
+ * Delegates to the Rust implementation in @prosopo/native-ja4. Structural
20
+ * TLS parse failures are rethrown as Ja4ParseError.
59
21
  */
60
22
  function calculateJa4(data) {
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
- }
23
+ try {
24
+ return nativeCalculateJa4(data);
25
+ } catch (err) {
26
+ throw new Ja4ParseError(err instanceof Error ? err.message : String(err));
150
27
  }
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)}`;
162
28
  }
163
29
  //#endregion
164
30
  exports.Ja4ParseError = Ja4ParseError;
@@ -45,18 +45,24 @@ var extraIpInfosFromEnrichedDnsEvent = (enriched) => {
45
45
  * - resolver / peer classified `isAbuser`
46
46
  * - client IP is a consumer ISP but resolver is datacenter (compound)
47
47
  *
48
- * When `trafficFilter` is supplied, the datacenter and abuser contributions
49
- * are gated on the same rules `checkTrafficFilter` uses for the client IP.
50
- * A category counts here whenever the operator has configured any policy
51
- * for it (block or challenge) — either intent signals suspicion.
48
+ * When `trafficFilter` is supplied, the datacenter contribution is gated
49
+ * on operator-policy-aware shielding, *not* on the strict per-IP precedence
50
+ * chain the client-IP path uses. DNS resolvers are legitimately often on
51
+ * datacenter ranges (Google/Cloudflare/consumer-VPN DNS), so signal
52
+ * counting has to consider the operator's stance:
52
53
  *
53
- * - datacenter category must be configured; IPs whose
54
- * `providerType === "isp"` short-circuit; named allowlist skips
55
- * - abuser category defaults to configured, with the score threshold
56
- * - cross-category suppression: VPN / proxy / Tor / crawler IPs that also
57
- * carry `isDatacenter=true` are NOT counted as datacenter when the
58
- * operator hasn't configured that more specific category — mirrors
59
- * the `datacenterSuppressedByCategory` rule in evaluateIpInfo.
54
+ * - datacenter category must be configured; `providerType === "isp"`
55
+ * short-circuits; `datacenterNameAllowlist` skips; `datacenterNameDenylist`
56
+ * forces the signal through (denylist wins over all other exemptions)
57
+ * - a higher-precedence flag the operator has left **unconfigured** (VPN,
58
+ * proxy, Tor, crawler) on the same resolver IP shields the datacenter
59
+ * signal — the flag "explains" the DC status and the operator has said
60
+ * they don't care about that category. Flags that are configured (block
61
+ * or challenge) do *not* shield, because the operator is treating that
62
+ * category as suspicious in its own right.
63
+ * - abuser is not shielded by any other category: its signal counts
64
+ * whenever the abuser category is configured and the score meets the
65
+ * threshold.
60
66
  *
61
67
  * `pathValid` is a protocol signal, not a category — it always contributes
62
68
  * regardless of trafficFilter. When `trafficFilter` is omitted, all
@@ -4,6 +4,7 @@ const require_dnsEventUrl = require("../../api/dnsEventUrl.cjs");
4
4
  const require_lang = require("../../rules/lang.cjs");
5
5
  const require_captchaManager = require("../captchaManager.cjs");
6
6
  const require_getBotScore = require("../detection/getBotScore.cjs");
7
+ const require_puzzleRenderer = require("../puzzle/puzzleRenderer.cjs");
7
8
  const require_routingMachine = require("./routingMachine.cjs");
8
9
  let _prosopo_types = require("@prosopo/types");
9
10
  let uuid = require("uuid");
@@ -177,7 +178,7 @@ var FrictionlessManager = class extends require_captchaManager.CaptchaManager {
177
178
  powDifficulty: captchaType === _prosopo_types.CaptchaType.pow ? effectiveParams.powDifficulty : void 0
178
179
  };
179
180
  const routed = this.routingContext ? await require_routingMachine.applyRouter(this.decisionMachineRunner, this.usageCounters, baseline, this.routingContext, this.logger) : baseline;
180
- const finalCaptchaType = routed.captchaType;
181
+ const finalCaptchaType = require_puzzleRenderer.downgradePuzzleIfUnavailable(routed.captchaType, this.logger);
181
182
  const finalSolvedImagesCount = finalCaptchaType === _prosopo_types.CaptchaType.image ? routed.solvedImagesCount ?? effectiveParams.solvedImagesCount : void 0;
182
183
  const finalPowDifficulty = finalCaptchaType === _prosopo_types.CaptchaType.pow ? routed.powDifficulty ?? effectiveParams.powDifficulty : void 0;
183
184
  const blocked = finalCaptchaType === _prosopo_types.CaptchaType.image ? effectiveParams.blocked : void 0;