capjs-core 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/detect.js +199 -0
- package/src/hashwx-wasm.js +185 -0
- package/src/hashwx.js +175 -0
- package/src/index.d.ts +96 -3
- package/src/index.js +80 -26
- package/src/instrumentation.js +108 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capjs-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Stateless server-side challenge generator and verifier for Cap, a lightweight, modern open-source CAPTCHA alternative. No storage, no filesystem - bring your own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anti-abuse",
|
|
@@ -40,9 +40,12 @@
|
|
|
40
40
|
"src/index.js",
|
|
41
41
|
"src/index.d.ts",
|
|
42
42
|
"src/instrumentation.js",
|
|
43
|
+
"src/detect.js",
|
|
43
44
|
"src/prng.js",
|
|
44
45
|
"src/crypto.js",
|
|
45
46
|
"src/rsw.js",
|
|
47
|
+
"src/hashwx.js",
|
|
48
|
+
"src/hashwx-wasm.js",
|
|
46
49
|
"README.md",
|
|
47
50
|
"LICENSE"
|
|
48
51
|
],
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const MOBILE_UA =
|
|
2
|
+
/Android|iPhone|iPad|iPod|IEMobile|BlackBerry|Mobile Safari|; Mobile|Silk|Kindle|Tizen|Opera Mini/i;
|
|
3
|
+
|
|
4
|
+
const MAX_FONT_WIDTHS = 64;
|
|
5
|
+
const MAX_TAMPER_KEYS = 8;
|
|
6
|
+
const MAX_TAMPER_KEY_LENGTH = 32;
|
|
7
|
+
const MAX_DETAIL_FIELDS = 6;
|
|
8
|
+
const OUTER_HEIGHT_SLACK = 16;
|
|
9
|
+
|
|
10
|
+
const isNum = (x) => typeof x === "number" && Number.isFinite(x);
|
|
11
|
+
const isPlainObject = (x) => !!x && typeof x === "object" && !Array.isArray(x);
|
|
12
|
+
|
|
13
|
+
const PROBE_FIELD_TYPES = {
|
|
14
|
+
ua: "string",
|
|
15
|
+
productSub: "string",
|
|
16
|
+
oscpu: "string",
|
|
17
|
+
uaDataPresent: "boolean",
|
|
18
|
+
uaData: "nullableObject",
|
|
19
|
+
plugins: "object",
|
|
20
|
+
engine: "object",
|
|
21
|
+
screen: "object",
|
|
22
|
+
outerWH: "array",
|
|
23
|
+
isExtended: "nullableBoolean",
|
|
24
|
+
fontWidths: "array",
|
|
25
|
+
tamper: "nullableObject",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const typeOk = (value, kind) => {
|
|
29
|
+
if (kind === "string") return typeof value === "string";
|
|
30
|
+
if (kind === "boolean") return typeof value === "boolean";
|
|
31
|
+
if (kind === "object") return isPlainObject(value);
|
|
32
|
+
if (kind === "array") return Array.isArray(value);
|
|
33
|
+
if (kind === "nullableObject") return value === null || isPlainObject(value);
|
|
34
|
+
if (kind === "nullableBoolean")
|
|
35
|
+
return value === null || typeof value === "boolean";
|
|
36
|
+
return false;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function isMobileVector(S) {
|
|
40
|
+
if (S?.uaData?.mobile === true) return true;
|
|
41
|
+
return MOBILE_UA.test(String(S?.ua || ""));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function probeCompleteness(S, fontStackCount) {
|
|
45
|
+
if (!isPlainObject(S)) return ["probe"];
|
|
46
|
+
const bad = [];
|
|
47
|
+
for (const field of Object.keys(PROBE_FIELD_TYPES)) {
|
|
48
|
+
if (!typeOk(S[field], PROBE_FIELD_TYPES[field])) bad.push(field);
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
Array.isArray(S.fontWidths) &&
|
|
52
|
+
isNum(fontStackCount) &&
|
|
53
|
+
S.fontWidths.length !== fontStackCount
|
|
54
|
+
) {
|
|
55
|
+
bad.push("fontWidths.length");
|
|
56
|
+
}
|
|
57
|
+
return bad;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function geometryStats(fontWidths) {
|
|
61
|
+
const w = (Array.isArray(fontWidths) ? fontWidths : [])
|
|
62
|
+
.slice(0, MAX_FONT_WIDTHS)
|
|
63
|
+
.filter(isNum);
|
|
64
|
+
if (w.length < 6) return null;
|
|
65
|
+
let count = 0;
|
|
66
|
+
const distinct = new Set();
|
|
67
|
+
for (const x of w) {
|
|
68
|
+
if (Math.trunc(x) === x) {
|
|
69
|
+
count++;
|
|
70
|
+
distinct.add(x);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { count, distinct: distinct.size, n: w.length };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function detectAutomation(S, opts = {}) {
|
|
77
|
+
if (!S || typeof S !== "object") {
|
|
78
|
+
return {
|
|
79
|
+
pass: false,
|
|
80
|
+
checks: [],
|
|
81
|
+
blockedBy: ["probe_missing"],
|
|
82
|
+
riskFlags: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const checks = [];
|
|
87
|
+
const add = (id, passed, detail, gating = true) =>
|
|
88
|
+
checks.push({ id, passed: passed !== false, detail, gating });
|
|
89
|
+
|
|
90
|
+
const incomplete = probeCompleteness(S, opts.fontStackCount);
|
|
91
|
+
add(
|
|
92
|
+
"probe_incomplete",
|
|
93
|
+
incomplete.length === 0,
|
|
94
|
+
incomplete.length
|
|
95
|
+
? `missing or malformed: ${incomplete.slice(0, MAX_DETAIL_FIELDS).join(", ")}`
|
|
96
|
+
: "all fields present",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const gecko =
|
|
100
|
+
S.engine?.hasMozInnerScreenX === true ||
|
|
101
|
+
(S.productSub === "20100101" && S.uaDataPresent === false) ||
|
|
102
|
+
(typeof S.oscpu === "string" && S.oscpu !== "__undefined");
|
|
103
|
+
const chromium = S.uaDataPresent === true || S.engine?.hasChrome === true;
|
|
104
|
+
const mobile = isMobileVector(S);
|
|
105
|
+
|
|
106
|
+
const geo = geometryStats(S.fontWidths);
|
|
107
|
+
const quantized = !!geo && geo.count >= 5 && geo.distinct >= 3;
|
|
108
|
+
add(
|
|
109
|
+
"geometry_quantized",
|
|
110
|
+
!quantized,
|
|
111
|
+
geo
|
|
112
|
+
? `${geo.count}/${geo.n} whole-px, ${geo.distinct} distinct`
|
|
113
|
+
: "not measured",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
add(
|
|
117
|
+
"webdriver_true",
|
|
118
|
+
S.webdriver !== true,
|
|
119
|
+
`webdriver=${String(S.webdriver)}`,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const stripped = chromium && S.webdriver === undefined;
|
|
123
|
+
add(
|
|
124
|
+
"webdriver_stripped",
|
|
125
|
+
!stripped,
|
|
126
|
+
chromium ? (stripped ? "removed" : "ok") : "not a chromium engine",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const contradiction =
|
|
130
|
+
gecko && (isNum(S.deviceMemory) || S.uaDataPresent === true);
|
|
131
|
+
add("gecko_contradiction", !contradiction, gecko ? "gecko engine" : "n/a");
|
|
132
|
+
|
|
133
|
+
const sc = S.screen || {};
|
|
134
|
+
const ow = Array.isArray(S.outerWH) ? S.outerWH : [];
|
|
135
|
+
const iw = Array.isArray(S.innerWH) ? S.innerWH : [];
|
|
136
|
+
const singleMonitor = S.isExtended === false;
|
|
137
|
+
const outerTaller =
|
|
138
|
+
isNum(ow[1]) && isNum(sc.height) && ow[1] > sc.height + OUTER_HEIGHT_SLACK;
|
|
139
|
+
const exceeds = outerTaller && singleMonitor;
|
|
140
|
+
add(
|
|
141
|
+
"window_exceeds_screen",
|
|
142
|
+
!exceeds,
|
|
143
|
+
singleMonitor
|
|
144
|
+
? exceeds
|
|
145
|
+
? `outerHeight ${ow[1]} > screen.height ${sc.height}`
|
|
146
|
+
: "ok"
|
|
147
|
+
: "monitor layout unknown",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const viewportIsScreen =
|
|
151
|
+
!mobile &&
|
|
152
|
+
isNum(iw[0]) &&
|
|
153
|
+
isNum(iw[1]) &&
|
|
154
|
+
iw[0] === sc.width &&
|
|
155
|
+
iw[1] === sc.height &&
|
|
156
|
+
isNum(ow[1]) &&
|
|
157
|
+
ow[1] > iw[1];
|
|
158
|
+
add(
|
|
159
|
+
"viewport_override",
|
|
160
|
+
!viewportIsScreen,
|
|
161
|
+
viewportIsScreen
|
|
162
|
+
? `viewport ${iw[0]}x${iw[1]} == screen, window ${ow[1]} tall`
|
|
163
|
+
: "ok",
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const brands = Array.isArray(S.uaData?.brands)
|
|
167
|
+
? S.uaData.brands.join(" ")
|
|
168
|
+
: "";
|
|
169
|
+
const headless =
|
|
170
|
+
/headlesschrome/i.test(brands) ||
|
|
171
|
+
/headlesschrome/i.test(String(S.ua || ""));
|
|
172
|
+
add(
|
|
173
|
+
"headless_token",
|
|
174
|
+
!headless,
|
|
175
|
+
headless ? "HeadlessChrome present" : "absent",
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const t = isPlainObject(S.tamper) ? S.tamper : {};
|
|
179
|
+
const tampered = Object.keys(t).filter((k) => t[k] === false);
|
|
180
|
+
add(
|
|
181
|
+
"native_tamper",
|
|
182
|
+
tampered.length === 0,
|
|
183
|
+
tampered.length
|
|
184
|
+
? `JS-overridden: ${tampered
|
|
185
|
+
.slice(0, MAX_TAMPER_KEYS)
|
|
186
|
+
.map((k) => String(k).slice(0, MAX_TAMPER_KEY_LENGTH))
|
|
187
|
+
.join(", ")}`
|
|
188
|
+
: "native",
|
|
189
|
+
false,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const failed = checks.filter((c) => c.gating && !c.passed);
|
|
193
|
+
return {
|
|
194
|
+
pass: failed.length === 0,
|
|
195
|
+
checks,
|
|
196
|
+
blockedBy: failed.map((c) => c.id),
|
|
197
|
+
riskFlags: checks.filter((c) => !c.gating && !c.passed).map((c) => c.id),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Generated by scripts/vendor-hashwx.mjs. Do not edit.
|
|
2
|
+
// Source: https://github.com/tevador/hashwx @ 74b567a31276a4c5d7cb232a5b7639467f49f961
|
|
3
|
+
// Built with emcc, lowered to WebAssembly 1.0 (MVP).
|
|
4
|
+
// sha256: b1a0dbb3ef444d3c7069e0a5e0a0273ffa4cf8fef62cbbe43761c02f7cd6aff5
|
|
5
|
+
// bytes: 12541
|
|
6
|
+
|
|
7
|
+
export const HASHWX_WASM_SHA256 =
|
|
8
|
+
"b1a0dbb3ef444d3c7069e0a5e0a0273ffa4cf8fef62cbbe43761c02f7cd6aff5";
|
|
9
|
+
|
|
10
|
+
export const HASHWX_WASM_BASE64 =
|
|
11
|
+
"AGFzbQEAAAABPwxgAX8Bf2ABfwBgAABgAn9/AGACf34BfmACf34AYAF/AX5gBH9/f38AYAN/f34AYAJ/fwF/YAABf2ADf39/" +
|
|
12
|
+
"AAMaGQIBAAEDBAAAAAAABQYDBwgBAgAACQEBCgsEBQFwAQICBQYBAYICggIGCAF/AUHgpQQLB6YCEAZtZW1vcnkCAAxoYXNo" +
|
|
13
|
+
"d3hfYWxsb2MAAgtoYXNod3hfZnJlZQADC2hhc2h3eF9tYWtlAAQLaGFzaHd4X2V4ZWMABQtoYXNod3hfc2VlZAAGEGhhc2h3" +
|
|
14
|
+
"eF9yZWdpc3RlcnMABw1oYXNod3hfbWVtb3J5AAgNaGFzaHd4X21vZHVsZQAJEmhhc2h3eF9tb2R1bGVfc2l6ZQAKEWhhc2h3" +
|
|
15
|
+
"eF9leGVjX2JlZ2luAAsRaGFzaHd4X2V4ZWNfZmluYWwADAtfaW5pdGlhbGl6ZQARGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFi" +
|
|
16
|
+
"bGUBABlfZW1zY3JpcHRlbl9zdGFja19yZXN0b3JlABYcZW1zY3JpcHRlbl9zdGFja19nZXRfY3VycmVudAAXCQcBAEEBCwEA" +
|
|
17
|
+
"CrhJGTQBA38DQCAAQQR0IgEgAUHQHWoiAjYC1B0gASACNgLYHSAAQQFqIgBBwABHDQALQTAQEhoLCQAgACgCABAVC4sBAQF/" +
|
|
18
|
+
"AkBBiIEBEBMiAUUNACABQQA2AgAgAEEBcQRAIAFBtNgAEBMiADYCACAARQ0BIAFBATYCBCABDwsgAUGALBATIgA2AgAgAEUN" +
|
|
19
|
+
"ACABQQA2AgQgAQ8LIAFBAWtBfU0EQAJAIAEoAgAiAEUNACABLQAEQQFxBEAgARABDAELIAAQFQsgARAVC0EACzUBAX8gAEEB" +
|
|
20
|
+
"a0F9TQRAAkAgACgCACIBRQ0AIAAtAARBAXEEQCAAEAEMAQsgARAVCyAAEBULC/gMAQV/IwBBoCxrIgMkACADIAEpAAA3A4As" +
|
|
21
|
+
"IAMgASkACDcDiCwgAyABKQAQNwOQLCADIAEpABg3A5gsIANBkCxqIQECQCAALQAEQQFxBEAgA0GALGogAxANIAAgASkDCDcD" +
|
|
22
|
+
"ECAAIAEpAwA3AwggACgCACIAQYAIQZMBEBggAEHCgITZADYAmQEgAEGDgAE7AJcBIABBwoiE8QA2AJMBIABBnQFqIQEDQCAB" +
|
|
23
|
+
"QYOAATsAACABQQJqIQAgAyAEQbABbGohBkEAIQUDQAJAAkACQAJAAkACQAJAIAYgBUEEdGoiASgCAEEDaw4OAQICAgICAgIC" +
|
|
24
|
+
"AgMEBQYACyAAQSA6AAAgAS0ACCECIABBwgA6AAIgACACQQJqOgABIAAgASgCDDoAAyABKAIAIQIgAEEgOgAFIAAgAi0A2Qk6" +
|
|
25
|
+
"AAQgAS0ABCECIABB/sIAOwAHIAAgAkECajoABiAAIAEtAAhBAmo6AAkgAEEKaiEADAULIABBIDoAACABLQAIIQIgAEEgOgAC" +
|
|
26
|
+
"IAAgAkECajoAASABLQAEIQIgAEH+hAE7AAQgACACQQJqOgADIAEoAgwhAiAAQYrFADsAByAAIAI6AAYgAS0ACCEBIABBoRg7" +
|
|
27
|
+
"AAogACABQQJqOgAJIABBDGohAAwECyAAQSA6AAAgAS0ACCECIABBwgA6AAIgACACQQJqOgABIAAgASgCDDoAAyABKAIAIQIg" +
|
|
28
|
+
"AEEgOgAFIAAgAi0A2Qk6AAQgACABLQAEQQJqOgAGIAEoAgAhAiAAQSE6AAggACACLQDmCToAByAAIAEtAAhBAmo6AAkgAEEK" +
|
|
29
|
+
"aiEADAMLIABBjQopAAA3AA0gAEGICikDADcACCAAQYAKKQMANwAAIABBFWohAAwCCyAAQbAKLwEAOwAQIABBqAopAwA3AAgg" +
|
|
30
|
+
"AEGgCikDADcAACAAQRJqIQAMAQsgAEH4CikDADcAOCAAQfAKKQMANwAwIABB6AopAwA3ACggAEHgCikDADcAICAAQdgKKQMA" +
|
|
31
|
+
"NwAYIABB0AopAwA3ABAgAEHICikDADcACCAAQcAKKQMANwAAIABBQGshAAsgBUEBaiIFQQtHDQALIABBCzoAACAAQQFqIQEg" +
|
|
32
|
+
"BEEBaiIEQSBHDQALIAFBmAkpAAA3AAUgAUGTCSkAADcAACAAQcKAhNkANgAUIABBg4ABOwASIABBwoiE8QA2AA4gAEEYaiEB" +
|
|
33
|
+
"QQAhBANAIAFBg4ABOwAAIAFBAmohACADIARBsAFsaiEGQQAhBQNAAkACQAJAAkACQAJAIAYgBUEEdGoiASgCAEEDaw4OAQIC" +
|
|
34
|
+
"AgICAgICAgMEBQUACyABLQAEIQIgAEEgOgAMIABBADsACiAAQqCajLyKpIC1KTcAAiAAQSA6AAAgACACQQJqOgABIAEtAAgh" +
|
|
35
|
+
"AiAAQcIAOgAOIAAgAkECajoADSAAIAEoAgw6AA8gASgCACECIABB/sIAOwARIAAgAi0A2Qk6ABAgACABLQAIQQJqOgATIABB" +
|
|
36
|
+
"FGohAAwECyAAQSA6AAAgAS0ACCECIABBIDoAAiAAIAJBAmo6AAEgAS0ABCECIABB/oQBOwAEIAAgAkECajoAAyABKAIMIQIg" +
|
|
37
|
+
"AEGKxQA7AAcgACACOgAGIAEtAAghASAAQaEYOwAKIAAgAUECajoACSAAQQxqIQAMAwsgAEEgOgAAIAEtAAghAiAAQcIAOgAC" +
|
|
38
|
+
"IAAgAkECajoAASAAIAEoAgw6AAMgACABKAIALQDZCToABCABLQAEIQIgAEEAOwAPIABCoJqMvIqkgLUpNwAHIABBIDoABSAA" +
|
|
39
|
+
"IAJBAmo6AAYgASgCACECIABBIToAEiAAIAItAOYJOgARIAAgAS0ACEECajoAEyAAQRRqIQAMAgsgAEGNCikAADcADSAAQYgK" +
|
|
40
|
+
"KQMANwAIIABBgAopAwA3AAAgAEEVaiEADAELIABBsAovAQA7ABAgAEGoCikDADcACCAAQaAKKQMANwAAIABBEmohAAsgBUEB" +
|
|
41
|
+
"aiIFQQtHDQALIABBCzoAACAAQQFqIQEgBEEBaiIEQSBHDQALIAFBmAkpAAA3AAUgAUGTCSkAADcAACAAQaAJKQMANwAOIABB" +
|
|
42
|
+
"qAkpAwA3ABYgAEGwCSkDADcAHiAAQbgJKQMANwAmIABBwAkpAwA3AC4gAEHICSkDADcANiAAQdAJKQMANwA+IABB2AktAAA6" +
|
|
43
|
+
"AEYMAQsgA0GALGogACgCABANIAAgASkDCDcDECAAIAEpAwA3AwgLIANBoCxqJAAL0hUCDH8HfiMAQZABayIDJAAgA0EIaiIF" +
|
|
44
|
+
"IABBCGogARAPIANBGGohBgJAAkACQAJAAkACQAJAAkACQAJAAkACQCADKAI4IgRFBEAgBRAQIAMgAykDMCIBNwNAQQIhBQwB" +
|
|
45
|
+
"CyADIARBAWsiAjYCOEEDIQUgAyAGIAJBA3RqKQMAIgE3A0AgAg0BIANBCGoQEAsgAyAGIAVBA3RqKQMANwNIDAELIAMgBEEC" +
|
|
46
|
+
"ayIFNgI4IAMgA0EIaiAEQQN0aikDADcDSCAFRQ0BCyADIAVBAWsiAjYCOEEDIQQgAyAGIAJBA3RqKQMANwNQIAJFBEAgA0EI" +
|
|
47
|
+
"ahAQDAMLIAMgBUECayIENgI4IAMgA0EIaiAFQQN0aikDADcDWCAERQ0BDAMLIANBCGoQECADIAMpAzA3A1BBAiEEDAELIANB" +
|
|
48
|
+
"CGoQECADIAMpAzAiDzcDYEECIQUMAgsgAyAGIARBA3RqKQMANwNYCyADIARBAWsiAjYCOEEDIQUgAyAGIAJBA3RqKQMAIg83" +
|
|
49
|
+
"A2AgAg0BIANBCGoQEAsgAyAGIAVBA3RqKQMANwNoDAELIAMgBEECayIFNgI4IAMgA0EIaiAEQQN0aikDADcDaCAFRQ0BCyAD" +
|
|
50
|
+
"IAVBAWsiBTYCOCADIAYgBUEDdGopAwA3A3AgBQ0BIANBCGoQEEEEIQUMAQsgA0EIahAQIAMgAykDMDcDcEEDIQULIAMgBUEB" +
|
|
51
|
+
"ayIFNgI4IAYgBUEDdGopAwAhDiADIAEgD4VCeINCA4Q3A4ABIAMgDjcDeEIAIQEgAC0ABEEBcUUEQCAAKAIAIQwjAEGQgAFr" +
|
|
52
|
+
"IgckACAHIANBQGsiAikDQDcDgIABIAdBgIABaiEIA0ACQEEAIQpBICEAA0AgDCAKQbABbGohDUEAIQZBACEJA0AgACEFIAZB" +
|
|
53
|
+
"AWohBAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIA0gBkEEdGoiACgCAEEBaw4QAgMEBQYHCAkKCwwN" +
|
|
54
|
+
"Dg8QAAELIAUhACAKQQFqIgpBIEcNEyALQQFqIgtBBEcNFUEAIQoMEAsgAiAAKAIIQQN0aiIGIAIgACgCBEEDdGopAwAgBikD" +
|
|
55
|
+
"ACAANQIMhH43AwAMEAsgAiAAKAIIQQN0aiIGIAIgACgCBEEDdGopAwAgBikDACAANQIMhX43AwAMDwsgAiAAKAIIQQN0aiIG" +
|
|
56
|
+
"IAIgACgCBEEDdGopAwAgBikDACAANQIMfH43AwAMDgsgAiAAKAIIQQN0aiIGIAIgACgCBEEDdGopAwAgBikDAH4gADUCDIoi" +
|
|
57
|
+
"ATcDACABpyEJDA0LIAIgACgCCEEDdGoiBiACIAAoAgRBA3RqKQMAIAYpAwAgADUCDIqFNwMADAwLIAIgACgCCEEDdGoiBiAC" +
|
|
58
|
+
"IAAoAgRBA3RqKQMAIAYpAwAgADUCDIp8NwMADAsLIAIgACgCCEEDdGoiBiAGKQMAIAA1AgyKIAIgACgCBEEDdGopAwB9NwMA" +
|
|
59
|
+
"DAoLIAIgACgCCEEDdGoiBiACIAAoAgRBA3RqKQMAIAYpAwAgADUCDIeFNwMADAkLIAIgACgCCEEDdGoiBiACIAAoAgRBA3Rq" +
|
|
60
|
+
"KQMAIAYpAwAgADUCDId8NwMADAgLIAIgACgCCEEDdGoiBiAGKQMAIAA1AgyHIAIgACgCBEEDdGopAwB9NwMADAcLIAIgACgC" +
|
|
61
|
+
"CEEDdGoiBiACIAAoAgRBA3RqKQMAIAYpAwAgADUCDIiFNwMADAYLIAIgACgCCEEDdGoiBiACIAAoAgRBA3RqKQMAIAYpAwAg" +
|
|
62
|
+
"ADUCDIh8NwMADAULIAIgACgCCEEDdGoiBiAGKQMAIAA1AgyIIAIgACgCBEEDdGopAwB9NwMADAQLQQAhACAEIQYgBUUNBCAJ" +
|
|
63
|
+
"QRp0QR91IARxIQYgBSAJQSBxRWshAAwEC0EAIAQgBRshBiAFIAVBAEdrIQAMAwsgCEEIayACKQMANwAAIAhBEGsgAikDCDcA" +
|
|
64
|
+
"ACAIQRhrIAIpAxA3AAAgCEEgayACKQMYNwAAIAhBKGsgAikDIDcAACAIQTBrIAIpAyg3AAAgCEE4ayACKQMwNwAAIAhBQGoi" +
|
|
65
|
+
"CCACKQM4NwAADAELA0BBACEJQSAhAANAIAwgCUGwAWxqIQtBACEGQQAhCANAAkAgACEFIAYiBEEBaiEGAkACQAJAAkACQAJA" +
|
|
66
|
+
"AkACQAJAAkACQAJAAkACQAJAIAsgBEEEdGoiBCgCAEEBaw4QAQIDBAUGBwgJCgsMDQ4QDwALIAIgBCgCCEEDdGoiACAHIAIg" +
|
|
67
|
+
"BCgCBEEDdGooAgBB//8AcWopAAAgACkDACAENQIMhH43AwAgBSEADA8LIAIgBCgCCEEDdGoiACAHIAIgBCgCBEEDdGooAgBB" +
|
|
68
|
+
"//8AcWopAAAgACkDACAENQIMhX43AwAgBSEADA4LIAIgBCgCCEEDdGoiACAHIAIgBCgCBEEDdGooAgBB//8AcWopAAAgACkD" +
|
|
69
|
+
"ACAENQIMfH43AwAgBSEADA0LIAIgBCgCCEEDdGoiACACIAQoAgRBA3RqKQMAIAApAwB+IAQ1AgyKIgE3AwAgAachCCAFIQAM" +
|
|
70
|
+
"DAsgAiAEKAIIQQN0aiIAIAcgAiAEKAIEQQN0aigCAEH//wBxaikAACAAKQMAIAQ1AgyKhTcDACAFIQAMCwsgAiAEKAIIQQN0" +
|
|
71
|
+
"aiIAIAcgAiAEKAIEQQN0aigCAEH//wBxaikAACAAKQMAIAQ1AgyKfDcDACAFIQAMCgsgAiAEKAIIQQN0aiIAIAApAwAgBDUC" +
|
|
72
|
+
"DIogByACIAQoAgRBA3RqKAIAQf//AHFqKQAAfTcDACAFIQAMCQsgAiAEKAIIQQN0aiIAIAcgAiAEKAIEQQN0aigCAEH//wBx" +
|
|
73
|
+
"aikAACAAKQMAIAQ1AgyHhTcDACAFIQAMCAsgAiAEKAIIQQN0aiIAIAcgAiAEKAIEQQN0aigCAEH//wBxaikAACAAKQMAIAQ1" +
|
|
74
|
+
"AgyHfDcDACAFIQAMBwsgAiAEKAIIQQN0aiIAIAApAwAgBDUCDIcgByACIAQoAgRBA3RqKAIAQf//AHFqKQAAfTcDACAFIQAM" +
|
|
75
|
+
"BgsgAiAEKAIIQQN0aiIAIAcgAiAEKAIEQQN0aigCAEH//wBxaikAACAAKQMAIAQ1AgyIhTcDACAFIQAMBQsgAiAEKAIIQQN0" +
|
|
76
|
+
"aiIAIAcgAiAEKAIEQQN0aigCAEH//wBxaikAACAAKQMAIAQ1AgyIfDcDACAFIQAMBAsgAiAEKAIIQQN0aiIAIAApAwAgBDUC" +
|
|
77
|
+
"DIggByACIAQoAgRBA3RqKAIAQf//AHFqKQAAfTcDACAFIQAMAwtBACEAIAVFDQIgCEEadEEfdSAGcSEGIAUgCEEgcUVrIQAM" +
|
|
78
|
+
"AgtBACAGIAUbIQYgBSAFQQBHayEADAELCyAJQQFqIglBIEcNAAsgCkEBaiIKQQRHDQALIAdBkIABaiQADAMLIAUhACAEIQYM" +
|
|
79
|
+
"AAsACwALCyADIAMpA1giASADKQNQfCIPIAMpA0giDiADKQNAfCISIA5CDYmFIg58IhBCIIk3A1AgAyAQIA5CEYmFNwNIIAMg" +
|
|
80
|
+
"AykDeCIOIAMpA3B8IhAgAykDaCIRIAMpA2B8IhMgEUINiYUiEXwiFCARQhGJhTcDaCADIBRCIIk3A3AgAykDgAEhESADIBAg" +
|
|
81
|
+
"DkIQiYUiDiATQiCJfCIQNwNgIAMgDyABQhCJhSIBIBJCIIl8Ig83A0AgAyABQhWJIA+FIgE3A1ggESAOQhWJhSAQhSABhSEB" +
|
|
82
|
+
"CyADQZABaiQAIAELBwAgAEEYagsHACAAQThqCwgAIABBgAFqCwcAIAAoAgALBgBBtNgAC5IEAQV/IwBBQGoiAyQAIANBCGoi" +
|
|
83
|
+
"AiAAQQhqIAEQDyADQRhqIQUCQAJAAkAgACAFAn8CQAJAIAAgBQJ/AkACQCAAIAUCfyADKAI4IgRFBEAgAhAQIAAgAykDMDcD" +
|
|
84
|
+
"OEECDAELIAMgBEEBayICNgI4IAAgBSACQQN0aikDADcDOCACDQEgA0EIahAQQQMLIgJBA3RqKQMANwNADAELIAMgBEECayIC" +
|
|
85
|
+
"NgI4IAAgA0EIaiIGIARBA3RqKQMANwNAIAINACAGEBAgACADKQMwNwNIQQIMAQsgAyACQQFrIgQ2AjggACAFIARBA3RqKQMA" +
|
|
86
|
+
"NwNIIAQNASADQQhqEBBBAwsiBEEDdGopAwA3A1AMAQsgAyACQQJrIgQ2AjggACADQQhqIgYgAkEDdGopAwA3A1AgBA0AIAYQ" +
|
|
87
|
+
"ECAAIAMpAzA3A1hBAgwBCyADIARBAWsiAjYCOCAAIAUgAkEDdGopAwA3A1ggAg0BIANBCGoQEEEDCyICQQN0aikDADcDYAwB" +
|
|
88
|
+
"CyADIARBAmsiAjYCOCAAIANBCGoiBiAEQQN0aikDADcDYCACDQAgBhAQIAAgAykDMDcDaEEDIQIMAQsgAyACQQFrIgI2Ajgg" +
|
|
89
|
+
"ACAFIAJBA3RqKQMANwNoIAINACADQQhqEBBBBCECCyAAIAUgAkEDdGpBCGspAwA3A3AgACAAKQNYIAApAziFQniDQgOENwN4" +
|
|
90
|
+
"IANBQGskAAtfAQJ+IAApA3AiAUIQiSABIAApA2h8hSIBIAApA2AgACkDWHxCIIl8IAApA3ggACkDUCICQhCJIAIgACkDSHyF" +
|
|
91
|
+
"IgJCFYkgAiAAKQNAIAApAzh8QiCJfIWFhSABQhWJhQteAQJ/IwBBQGoiAiQAIAJBCGogAEJ/EA9BACEAA0AgAkEIaiIDIAEg" +
|
|
92
|
+
"AEGwAWxqQQAgAEH/AXFBBnBFEA4gAEEBaiIAQR9HDQALIAMgAUHQKmpBAUEAEA4gAkFAayQAC4gLAgt/CH4jAEFAaiIGJAAg" +
|
|
93
|
+
"AEEQaiEIAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAAoAjAiBEUEQCAAEBAgACkDKCEPQQIhBwwBCyAAIARBAWsiBTYCMEED" +
|
|
94
|
+
"IQcgCCAFQQN0aikDACEPIAUNASAAEBALIAggB0EDdGopAwAhEAwBCyAAIARBAmsiBzYCMCAAIARBA3RqKQMAIRAgB0UNAQsg" +
|
|
95
|
+
"ACAHQQFrIgU2AjBBAyEEIAggBUEDdGopAwAhESAFRQRAIAAQEAwDCyAAIAdBAmsiBDYCMCAAIAdBA3RqKQMAIRIgBEUNAQwD" +
|
|
96
|
+
"CyAAEBAgACkDKCERQQIhBAwBCyAAEBAgACkDKCETQQIhBwwCCyAIIARBA3RqKQMAIRILIAAgBEEBayIFNgIwQQMhByAIIAVB" +
|
|
97
|
+
"A3RqKQMAIRMgBQ0BIAAQEAsgCCAHQQN0aikDACEUDAELIAAgBEECayIHNgIwIAAgBEEDdGopAwAhFCAHRQ0BCyAAIAdBAWsi" +
|
|
98
|
+
"BzYCMCAIIAdBA3RqKQMAIRUgBw0BIAAQEEEEIQcMAQsgABAQIAApAyghFUEDIQcLIAAgB0EBayIHNgIwIAggB0EDdGopAwAh" +
|
|
99
|
+
"FiABQQM2AhAgAUEPNgIAIA+nIQQgASAWQiOCpy0AgAsiBUEBcQR/IARBA3AFIARBCXBBAnQoArALCzYCICAQpyEEIAEgBUEC" +
|
|
100
|
+
"cQR/IARBA3AFIARBCXBBAnQoArALCzYCMCARpyEEIAEgBUEEcQR/IARBA3AFIARBCXBBAnQoArALCzYCQCASpyEEIAEgBUEI" +
|
|
101
|
+
"cQR/IARBA3AFIARBCXBBAnQoArALCzYCUCATpyEEIA9CIIghDyAQQiCIIRAgEUIgiCERIBJCIIghEiABIAVBEHEEfyAEQQNw" +
|
|
102
|
+
"BSAEQQlwQQJ0KAKwCws2AmAgFKchBCAPpyEJIBCnIQogEachCyASpyEMIBNCIIinIQ0gASAFQSBxBH8gBEEDcAUgBEEJcEEC" +
|
|
103
|
+
"dCgCsAsLNgJwIBRCIIinIQ4gFachBCABIAVBwABxBH8gBEEDcAUgBEEJcEECdCgCsAsLNgKAASAGQoCAgIAQNwMgIAYgBkEg" +
|
|
104
|
+
"aiIFIAlBAXFBAnRyIgQoAgA2AiQgBEEBNgIAIAZBAjYCKCAGIApBA3BBAnQgBXIiBCgCADYCKCAEQQI2AgAgBkEDNgIsIAYg" +
|
|
105
|
+
"C0EDcUECdCAFciIEKAIANgIsIARBAzYCACAGQQQ2AjAgBiAMQQVwQQJ0IAVqIgQoAgA2AjAgBEEENgIAIAZBBTYCNCAGIA1B" +
|
|
106
|
+
"BnBBAnQgBWoiBCgCADYCNCAEQQU2AgAgBkEGNgI4IAYgDkEHcEECdCAFaiIEKAIANgI4IARBBjYCACAGQQc2AjwgBiAVQiCI" +
|
|
107
|
+
"p0EHcUECdCAFaiIEKAIANgI8IARBBzYCACAGQQg2AgAgBiAGQSBqIgUgAwR/IBZCGIKnQQN0QeAbagUgFqdB/wFxQQN0QeAL" +
|
|
108
|
+
"agsiBC0AAUECdGooAgA2AgQgBiAELQACQQJ0IAVqKAIANgIIIAYgBC0AA0ECdCAFaigCADYCDCAGIAQtAARBAnQgBWooAgA2" +
|
|
109
|
+
"AhAgBiAELQAFQQJ0IAVqKAIANgIUIAYgBC0ABkECdCAFaigCADYCGCAGIAQtAAdBAnQgBWooAgA2AhxBACEEA0AgASAEIgNB" +
|
|
110
|
+
"AWoiBEEEdGoiBSADQQJ0IgMgBkEgamooAgA2AgggBSADIAZqKAIANgIEIAUoAgAhAyAAIAcEfyAHBSAAEBBBBAtBAWsiBzYC" +
|
|
111
|
+
"MCAIIAdBA3RqKQMAIQ8gBQJ/IANBAk0EQCAPQgOCpy0AoB0MAQsgD0I/gqdBAWogA0EGTQ0AGiAPQgOCp0EBags2AgwgBEEI" +
|
|
112
|
+
"Rw0ACyABQRA2AqABIAFBDkENIAIbNgKQASAGQUBrJAAL5QIBBX4gASkDACEDIAEpAwghBCAAQQQ2AjAgACAENwMIIAAgAzcD" +
|
|
113
|
+
"ACAAIARC7d6R85bM3LfkAIUiBUINiSAFIANC9crNg9es27fzAIV8IgWFIgYgAiAEhULzytHLp4zZsvQAhSIEIANC4eSV89bs" +
|
|
114
|
+
"2bzsAIV8IgN8IgdCIIlCuwGFIARCEIkgA4UiA0IViSAFQiCJIAN8IgOFIgR8IgUgBEIQiYUiBEIViSAEIAZCEYkgB4UiBiAC" +
|
|
115
|
+
"IAOFfCICQiCJfCIDhSIEQhCJIAQgBSAGQg2JIAKFIgJ8IgVCIIl8IgSFIgZCFYkgBiADIAJCEYkgBYUiAnwiA0IgiXwiBYUi" +
|
|
116
|
+
"BkIQiSAGIAQgAkINiSADhSICfCIDQiCJfCIEhSIGIAUgAkIRiSADhSICfCIDQiCJfCIFNwMQIAAgAkINiSADhSICQhGJIAIg" +
|
|
117
|
+
"BHwiAoU3AxggACACQiCJNwMgIAAgBkIViSAFhTcDKAuqAgEFfiAAIAApAwgiASAAKQMohSICQhCJIAIgACkDACIEIAApAyCF" +
|
|
118
|
+
"fCIChSIFQhWJIAUgACkDGCABhSIBIAApAxAgBIV8IgRCIIl8IgWFIgNCEIkgAyACIAFCDYkgBIUiAXwiAkIgiXwiBIUiA0IV" +
|
|
119
|
+
"iSADIAUgAiABQhGJhSIBfCICQiCJfCIFhSIDQhCJIAMgBCABQg2JIAKFIgF8IgJCIIl8IgSFIgNCFYkgAyAFIAFCEYkgAoUi" +
|
|
120
|
+
"AXwiAkIgiXwiBYUiA0IQiSADIAQgAUINiSAChSIBfCICQiCJfCIEhSIDIAUgAUIRiSAChSIBfCICQiCJfCIFNwMQIAAgA0IV" +
|
|
121
|
+
"iSAFhTcDKCAAIAQgAUINiSAChSIBfCICQiCJNwMgIAAgAUIRiSAChTcDGAsEABAAC4YEAgd/AX4CfyAAQQdqQXhxIgOtIQgC" +
|
|
122
|
+
"QCAIQgd8Qvj//////////wCDQaQdKAIAIgCtfCIIQv////8PWARAIAinIgI/AEEQdE0NAQtBwB1BMDYCAEF/DAELQaQdIAI2" +
|
|
123
|
+
"AgAgAAsiAkF/RwRAIAIgA2oiAEEEa0EQNgIAIABBEGsiBEEQNgIAAkACf0HQJSgCACIBBH8gASgCCAVBAAsgAkYEQCACIAJB" +
|
|
124
|
+
"BGsoAgBBfnEiBmsiBUEEaygCACEHIAEgADYCCCAFIAdBfnEiAWsiACAAKAIAakEEaygCAEEBcQRAIAAoAgQiBCAAKAIIIgU2" +
|
|
125
|
+
"AgggBSAENgIEIAAgAyAGaiABakEQayIBNgIADAMLIAJBEGsMAQsgAkEQNgIAIAIgADYCCCACIAE2AgQgAkEQNgIMQdAlIAI2" +
|
|
126
|
+
"AgAgAkEQagsiACAEIABrIgE2AgALIAAgAUF8cWpBBGsgAUEBcjYCACAAAn8gACgCAEEIayIBQf8ATQRAIAFBA3ZBAWsMAQsg" +
|
|
127
|
+
"AUEdIAFnIgNrdkEEcyADQQJ0a0HuAGogAUH/H00NABpBPyABQR4gA2t2QQJzIANBAXRrQccAaiIBIAFBP08bCyIBQQR0IgNB" +
|
|
128
|
+
"0B1qNgIEIAAgA0HYHWoiAygCADYCCCADIAA2AgAgACgCCCAANgIEQdglQdglKQMAQgEgAa2GhDcDAAsgAkF/RwvjAwIFfwJ+" +
|
|
129
|
+
"AkACQANAIABBR0sNAUHYJSkDACIGAn9BCCAAQQNqQXxxIABBCE0bIgBB/wBNBEAgAEEDdkEBawwBCyAAQR0gAGciAWt2QQRz" +
|
|
130
|
+
"IAFBAnRrQe4AaiAAQf8fTQ0AGkE/IABBHiABa3ZBAnMgAUEBdGtBxwBqIgEgAUE/TxsLIgKtiCIHQgBSBEADQCAHIAd6IgeI" +
|
|
131
|
+
"IQYCfiACIAenaiICQQR0IgNB2B1qKAIAIgEgA0HQHWoiBEcEQCABIAAQFCIDDQYgASgCBCIDIAEoAggiBTYCCCAFIAM2AgQg" +
|
|
132
|
+
"ASAENgIIIAEgBCgCBDYCBCAEIAE2AgQgASgCBCABNgIIIAJBAWohAiAGQgGIDAELQdglQdglKQMAQn4gAq2JgzcDACAGQgGF" +
|
|
133
|
+
"CyIHQgBSDQALQdglKQMAIQYLQT8gBnmnayEEAkAgBlAEQEEAIQIMAQsgBEEEdCIDQdgdaigCACECIAZCgICAgARUDQBB4gAh" +
|
|
134
|
+
"ASACIANB0B1qIgVGDQADQCACIAAQFCIDDQQgAigCCCICIAVGDQEgASIDQQFrIQEgAw0ACwsgAEEwahASDQALIAJFDQAgAiAE" +
|
|
135
|
+
"QQR0QdAdaiIBRg0AA0AgAiAAEBQiAw0CIAIoAggiAiABRw0ACwtBACEDCyADC5EDAQV/IABBBGoiBEEHakF4cSIDIAFqIAAg" +
|
|
136
|
+
"ACgCACICakEEa00EfyAAKAIEIgUgACgCCCIGNgIIIAYgBTYCBCADIARHBEAgACAAQQRrKAIAQX5xayIFIAMgBGsiBCAFKAIA" +
|
|
137
|
+
"aiIDNgIAIAUgA0F8cWpBBGsgAzYCACAAIARqIgAgAiAEayICNgIACwJ/IAIgAUEYak8EQCAAIAFqIgQgAiABa0EIayICNgII" +
|
|
138
|
+
"IARBCGoiBSACQXxxakEEayACQQFyNgIAIAQCfyAEKAIIQQhrIgJB/wBNBEAgAkEDdkEBawwBCyACQR0gAmciA2t2QQRzIANB" +
|
|
139
|
+
"AnRrQe4AaiACQf8fTQ0AGkE/IAJBHiADa3ZBAnMgA0EBdGtBxwBqIgIgAkE/TxsLIgJBBHQiA0HQHWo2AgwgBCADQdgdaiID" +
|
|
140
|
+
"KAIANgIQIAMgBTYCACAEKAIQIAU2AgRB2CVB2CUpAwBCASACrYaENwMAIAAgAUEIaiICNgIAIAAgAkF8cWoMAQsgACACagtB" +
|
|
141
|
+
"BGsgAjYCACAAQQRqBUEACwvMAgEFfyAABEAgAEEEayIDKAIAIgQhASADIQIgAEEIaygCACIAIABBfnEiAEcEQCACIABrIgIo" +
|
|
142
|
+
"AgQiASACKAIIIgU2AgggBSABNgIEIAAgBGohAQsgAyAEaiIAKAIAIgMgACADakEEaygCAEcEQCAAKAIEIgQgACgCCCIANgII" +
|
|
143
|
+
"IAAgBDYCBCABIANqIQELIAIgATYCACACIAFBfHFqQQRrIAFBAXI2AgAgAgJ/IAIoAgBBCGsiAEH/AE0EQCAAQQN2QQFrDAEL" +
|
|
144
|
+
"IABnIQMgAEEdIANrdkEEcyADQQJ0a0HuAGogAEH/H00NABpBPyAAQR4gA2t2QQJzIANBAXRrQccAaiIAIABBP08bCyIBQQR0" +
|
|
145
|
+
"IgBB0B1qNgIEIAIgAEHYHWoiACgCADYCCCAAIAI2AgAgAigCCCACNgIEQdglQdglKQMAQgEgAa2GhDcDAAsLBgAgACQACwQA" +
|
|
146
|
+
"IwALbwEEfz8AQRB0IQQgACACaiAESyABIAJqIARLcgRAAAsgASAASQRAIAJBAWshA0F/IQRBfyEFBUEAIQMgAiEEQQEhBQsg" +
|
|
147
|
+
"AyEGAkADQCAGIARGDQEgACAGaiABIAZqLQAAOgAAIAYgBWohBgwACwALCwuPFQcAQYEIC/IBYXNtAQAAAAEGAWACf38AAg8B" +
|
|
148
|
+
"A2VudgZtZW1vcnkCAAEDAgEABwgBBGV4ZWMAAAqB2AAB/dcAAQ1+IAApAwAhAiAAKQMIIQMgACkDECEEIAApAxghBSAAKQMg" +
|
|
149
|
+
"IQYgACkDKCEHIAApAzAhCCAAKQM4IQkgACkDQCEKQgAhC0L//wAhDUGAgAEgAWohASABIAo3AwAgDkIBfSIOQgBSDQALIAAg" +
|
|
150
|
+
"AjcDACAAIAM3AwggACAENwMQIAAgBTcDGCAAIAY3AyAgACAHNwMoIAAgCDcDMCAAIAk3AzgLhIV8AIqKioeHh4iIiH5+fgCF" +
|
|
151
|
+
"fH2FfH2FfH0AQYAKCxUgCyAMhEIgg1AEQEIBIAt8IQsMAQsAQaAKCxIgC0Igg1AEQEIBIAt8IQsMAQsAQcAKC2MgAUHAAGsh" +
|
|
152
|
+
"ASABIAI3AzggASADNwMwIAEgBDcDKCABIAU3AyAgASAGNwMYIAEgBzcDECABIAg3AwggASAJNwMABwsTI0MNFSVFGSlJMVFh" +
|
|
153
|
+
"DhYmRhoqSjJSYhwsTDRUZDhYaHAAQbALCyEEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAQeELC8IRAgEEAwYH" +
|
|
154
|
+
"BQADBAEGBwUCAAQFBgcBAgMABgMHBQIBBAEABAUCAwcGAQMAAgUEBwYBAwUCAAQHBgEDBgIHAAUEAQMHAgAGBAUBAwcCAAYF" +
|
|
155
|
+
"BAEDBwIFBAAGAQMHAgYABQQBBAUAAwIHBgEEBQcCAwAGAQQGAAMHAgUBBAYHAgADBQEEBgcCAAUDAQQHAAIGBQMBBAcAAwYC" +
|
|
156
|
+
"BQEEBwUCAwAGAQQHBQMCAAYBBAcGAwAFAgEFBgcAAgMEAQUGBwACBAMBBQcABgIDBAEFBwAGAgQDAQUHBgADAgQBBQcGAAQC" +
|
|
157
|
+
"AwEGBAcCAAUDAQYFBwACBAMBBwMCAAYFBAEHAwIGAAQFAQcDAgYABQQBBwQGAgADBQEHBAYDAAUCAQcFAAYCAwQBBwUABgIE" +
|
|
158
|
+
"AwEHBQYAAgMEAQcFBgACBAMBBwYABQIEAwEHBgQCAAUDAQcGBAMABQIBBwYFAAIDBAIABAYDBwEFAgMAAQcGBAUCAwEABgcE" +
|
|
159
|
+
"BQIDAQcABgQFAgMBBwAGBQQCBAAHAQYFAwIEAQcDBgUAAgQFBwEDAAYCBAUHAwEABgIEBgcBAAMFAgQHBQMBAAYCBQAGBwME" +
|
|
160
|
+
"AQIFAQYHAAMEAgUBBgcDAAQCBQEHAAYDBAIFAQcGAAMEAgUGAAcBAwQCBQYHAAEDBAIFBwYAAQQDAgUHBgADAQQCBQcGAAME" +
|
|
161
|
+
"AQIFBwYABAEDAgYBBQcDAAQCBgEFBwQAAwIGAQUHBAMAAgYBBwUDBAACBgEHBQQDAAIHAQAFBgQDAgcBBAMGAAUCBwEEAwYF" +
|
|
162
|
+
"AAIHAQUABgQDAgcBBQYAAwQCBwEFBgMABAIHAQUGAwQAAgcBBgUDAAQCBwEGBQQAAwIHAwEABgUEAgcGBQADBAEDAAQBBgcC" +
|
|
163
|
+
"BQMABAIGBwUBAwAFAgYHBAEDAAYBBQQHAgMCAAEGBwQFAwIAAQcGBAUDAgEABQQHBgMCAQAGBwQFAwIBAAcGBQQDAgEFBwQA" +
|
|
164
|
+
"BgMCAQYABwUEAwIHAQYABAUDAgcBBgAFBAMEAAcBBgUCAwQGAQcAAgUDBAYCBwAFAQMEBgcCAAUBAwQHAgAGBQEDBQAHBgQC" +
|
|
165
|
+
"AQMFBgAHAQIEAwUGAgcABAEDBQYHAAECBAMFBgcAAgQBAwUGBwAEAgEDBQcBBgAEAgMFBwIGAAEEAwUHBgABAgQDBgUABwEC" +
|
|
166
|
+
"BAMGBwAFBAIBAwYHAQUEAAIDBwQBAgYFAAMHBQEGAAQCAwcFAQYEAAIDBwYBBQAEAgMHBgEFAgQAAwcGAQUEAAIEAAMCAQYH" +
|
|
167
|
+
"BQQAAwIGBwEFBAIABQEDBwYEAgAGAwcBBQQCAAYDBwUBBAIBAAMGBwUEAgEABwYDBQQCAQUDAAcGBAIBBwYABQMEAgYHAwAB" +
|
|
168
|
+
"BQQCBwADBgEFBAIHBQEDAAYEAwABAgYHBQQDAAEGBwUCBAMAAgUBBwYEAwACBgcBBQQDAAIHBgEFBAMAAgcGBQEEAwAGAgcB" +
|
|
169
|
+
"BQQDBQIHAQAGBAMFBwIBAAYEAwYAAgcBBQQDBgIABwUBBAMGAgcAAQUEAwYCBwAFAQQFAAYBBwMCBAUDAAIBBwYEBQMBBwIA" +
|
|
170
|
+
"BgQFAwIHAQAGBAUDBwIBAAYEBgMCAAcBBQQGAwIHAAEFBAYDBwEABQIEBgcFAQIDAAQHAwABBgIFBAcDAAIGBQEEBwUGAQIA" +
|
|
171
|
+
"AwUAAwIGBAcBBQADBAECBwYFAAMEAgEHBgUABAYBAgcDBQAEBgEHAwIFAAQGAwcCAQUABAcDBgECBQAHBAIGAwEFAgAEAQMH" +
|
|
172
|
+
"BgUCAAQDAQcGBQIABgcDBAEFAgAGBwQBAwUCAQAGBwMEBQIDAQcEAAYFAgQAAwEHBgUCBwQDAQAGBQIHBgADBAEFAwACBgcE" +
|
|
173
|
+
"AQUDBAACAQcGBQMEAgABBwYFAwcEAQIABgUDBwQCAQAGBQQABgIHAQMFBAAGAwEHAgUEAwACAQcGBQQDAQcCAAYFBAYABwEC" +
|
|
174
|
+
"AwUEBgAHAQMCBQQHBgEDAgAFBgAEAQcCAwUGAAQDBwECBQYABAMHAgEFBgQAAgcBAwUGBAADBwIBBQYHBAECAAMGAAMBBQQH" +
|
|
175
|
+
"AgYAAwEFBwIEBgADAgUEBwEGAAMHBQQBAgYABAUBAgcDBgAEBQEDBwIGAAQFAQcCAwYABAUBBwMCBgAEBQIDBwEGAAQFAgcD" +
|
|
176
|
+
"AQYABAcFAwECBgAEBwUDAgEGAAUEAQIHAwYABQQCAQcDBgAFBAIHAwEGAAUEAwEHAgYABQQDAgcBBgAHBAUDAQIGAgAEAwcF" +
|
|
177
|
+
"AQYCAQUHAwQABgIEAAMHAQUGAwABBQQHAgYDBQIHBAABBgQDAQcAAgUGBAUAAgMHAQYEBQcBAgMABgQFBwEDAgAGBAUHAgED" +
|
|
178
|
+
"AAYFAAQCAQcDBgUABAcDAgEGBwMBBQIABAYHAwEFBAACBgcDAQUEAgAGBwUEAQIDAAcABAUCBgEDBwAFBAMGAQIHAgEEAwYF" +
|
|
179
|
+
"AAcCAQYFAwQABwIBBgUEAwAHAgUGAAEDBAcDBAEFAgAGBwMFAQYEAgAHAwUCBgQBAAcDBgEFBAACBwMGBQAEAQIHBAYFAQAC" +
|
|
180
|
+
"AwcEBgUBAgMABwUABAEGAgMHBQMBBgIEAAcFAwEGBAIABwUDAgYBBAAHBgAEBQIBAwcGAAQFAwECBwYDAAUEAQIHBgMBBQQC" +
|
|
181
|
+
"AAcGAwIFBAEABwYEBQEAAwIHBgQFAQIDAAcGBAUBAwIABwYEBQIBAwAHBgQFAgMBAAcGBAUDAQIABwYEBQMCAQAHBgUEAAMB" +
|
|
182
|
+
"AgcGBQQBAgMABwYFBAEDAgAHBgUEAgEDAAACBwEDBAUGAAMBBwIEBQYABAECBwMFBgAFAQIDBwQGAAYBAgMEBwUBAAcCAwQF" +
|
|
183
|
+
"BgEHAAIDBAUGAgAHAQMEBQYCBwABAwQFBgMAAQcCBAUGAwcBAAIEBQYEAAECBwMFBgQHAQIAAwUGBQABAgMHBAYFBwECAwAE" +
|
|
184
|
+
"BgYAAQIDBAcFBgcBAgMEAAUGBwECAwQFAAcCAAEDBAUGBwMBAAIEBQYHBAECAAMFBgcFAQIDAAQGBwYBAgMEAAUHBgECAwQF" +
|
|
185
|
+
"AAEJIQBBpB0LA+ASAQ==";
|
package/src/hashwx.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { HASHWX_WASM_BASE64 } from "./hashwx-wasm.js";
|
|
3
|
+
|
|
4
|
+
export const HASHWX_SEED_SIZE = 32;
|
|
5
|
+
export const HASHWX_CHALLENGE_SIZE = 32;
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_HASHWX_NONCES_PER_HASH = 65536;
|
|
8
|
+
export const DEFAULT_HASHWX_DIFFICULTY = 1_000_000;
|
|
9
|
+
export const DEFAULT_HASHWX_CHALLENGE_COUNT = 4;
|
|
10
|
+
|
|
11
|
+
export const MAX_HASHWX_DIFFICULTY = 1_000_000_000;
|
|
12
|
+
export const MAX_HASHWX_NONCES_PER_HASH = 1_048_576;
|
|
13
|
+
export const MAX_HASHWX_CHALLENGE_COUNT = 64;
|
|
14
|
+
|
|
15
|
+
const U64_MAX = (1n << 64n) - 1n;
|
|
16
|
+
const HASHWX_INTERPRETED = 0;
|
|
17
|
+
|
|
18
|
+
function decodeBase64(b64) {
|
|
19
|
+
if (typeof Buffer !== "undefined")
|
|
20
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
21
|
+
const bin = atob(b64);
|
|
22
|
+
const out = new Uint8Array(bin.length);
|
|
23
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let readyPromise = null;
|
|
28
|
+
|
|
29
|
+
async function init() {
|
|
30
|
+
const mod = await WebAssembly.compile(decodeBase64(HASHWX_WASM_BASE64));
|
|
31
|
+
const instance = new WebAssembly.Instance(mod, {});
|
|
32
|
+
const exports = instance.exports;
|
|
33
|
+
if (typeof exports._initialize === "function") exports._initialize();
|
|
34
|
+
|
|
35
|
+
const ctx = exports.hashwx_alloc(HASHWX_INTERPRETED);
|
|
36
|
+
if (ctx <= 0) throw new Error("[capjs-core] hashwx_alloc failed");
|
|
37
|
+
|
|
38
|
+
return { exports, ctx, seedPtr: exports.hashwx_seed(ctx) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hashwxReady() {
|
|
42
|
+
if (!readyPromise) {
|
|
43
|
+
readyPromise = init().catch((err) => {
|
|
44
|
+
readyPromise = null;
|
|
45
|
+
throw err;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return readyPromise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function hashwxTarget(difficulty) {
|
|
52
|
+
const d = BigInt(difficulty);
|
|
53
|
+
if (d <= 0n)
|
|
54
|
+
throw new Error("[capjs-core] hashwx difficulty must be positive");
|
|
55
|
+
return U64_MAX / d;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function hashwxSeed(challenge, block) {
|
|
59
|
+
const buf = new Uint8Array(HASHWX_CHALLENGE_SIZE + 8);
|
|
60
|
+
buf.set(challenge, 0);
|
|
61
|
+
let rest = BigInt(block);
|
|
62
|
+
for (let i = 0; i < 8; i++) {
|
|
63
|
+
buf[HASHWX_CHALLENGE_SIZE + i] = Number(rest & 0xffn);
|
|
64
|
+
rest >>= 8n;
|
|
65
|
+
}
|
|
66
|
+
return new Uint8Array(crypto.createHash("sha256").update(buf).digest());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function hashwxHash(state, seed, nonce) {
|
|
70
|
+
const { exports, ctx, seedPtr } = state;
|
|
71
|
+
new Uint8Array(exports.memory.buffer, seedPtr, HASHWX_SEED_SIZE).set(seed);
|
|
72
|
+
exports.hashwx_make(ctx, seedPtr);
|
|
73
|
+
return BigInt.asUintN(64, exports.hashwx_exec(ctx, nonce));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseNonce(value) {
|
|
77
|
+
if (typeof value === "bigint")
|
|
78
|
+
return value >= 0n && value <= U64_MAX ? value : null;
|
|
79
|
+
if (typeof value === "number") {
|
|
80
|
+
if (!Number.isSafeInteger(value) || value < 0) return null;
|
|
81
|
+
return BigInt(value);
|
|
82
|
+
}
|
|
83
|
+
if (typeof value !== "string" || !/^[0-9]{1,20}$/.test(value)) return null;
|
|
84
|
+
const parsed = BigInt(value);
|
|
85
|
+
return parsed <= U64_MAX ? parsed : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hexToBytes(hex, expectedLength) {
|
|
89
|
+
if (typeof hex !== "string" || hex.length !== expectedLength * 2) return null;
|
|
90
|
+
if (!/^[0-9a-f]*$/i.test(hex)) return null;
|
|
91
|
+
const out = new Uint8Array(expectedLength);
|
|
92
|
+
for (let i = 0; i < expectedLength; i++) {
|
|
93
|
+
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function mintHashwxChallenges(opts = {}) {
|
|
99
|
+
const difficulty = opts.hashwxDifficulty ?? DEFAULT_HASHWX_DIFFICULTY;
|
|
100
|
+
const noncesPerHash =
|
|
101
|
+
opts.hashwxNoncesPerHash ?? DEFAULT_HASHWX_NONCES_PER_HASH;
|
|
102
|
+
const count = opts.hashwxChallengeCount ?? DEFAULT_HASHWX_CHALLENGE_COUNT;
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
!Number.isInteger(difficulty) ||
|
|
106
|
+
difficulty < 1 ||
|
|
107
|
+
difficulty > MAX_HASHWX_DIFFICULTY
|
|
108
|
+
) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`[capjs-core] hashwxDifficulty must be an integer in [1, ${MAX_HASHWX_DIFFICULTY}]`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (
|
|
114
|
+
!Number.isInteger(noncesPerHash) ||
|
|
115
|
+
noncesPerHash < 1 ||
|
|
116
|
+
noncesPerHash > MAX_HASHWX_NONCES_PER_HASH
|
|
117
|
+
) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[capjs-core] hashwxNoncesPerHash must be an integer in [1, ${MAX_HASHWX_NONCES_PER_HASH}]`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
!Number.isInteger(count) ||
|
|
124
|
+
count < 1 ||
|
|
125
|
+
count > MAX_HASHWX_CHALLENGE_COUNT
|
|
126
|
+
) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`[capjs-core] hashwxChallengeCount must be an integer in [1, ${MAX_HASHWX_CHALLENGE_COUNT}]`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const each = Math.max(1, Math.round(difficulty / count));
|
|
133
|
+
const out = [];
|
|
134
|
+
for (let i = 0; i < count; i++) {
|
|
135
|
+
const spec = {
|
|
136
|
+
c: crypto.randomBytes(HASHWX_CHALLENGE_SIZE).toString("hex"),
|
|
137
|
+
d: each,
|
|
138
|
+
n: noncesPerHash,
|
|
139
|
+
};
|
|
140
|
+
out.push({ payload: { ...spec }, expected: { ...spec } });
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function verifyHashwxSolution(expected, solution) {
|
|
146
|
+
if (!expected || typeof expected !== "object") return false;
|
|
147
|
+
if (!solution || typeof solution !== "object") return false;
|
|
148
|
+
|
|
149
|
+
const challenge = hexToBytes(expected.c, HASHWX_CHALLENGE_SIZE);
|
|
150
|
+
if (!challenge) return false;
|
|
151
|
+
|
|
152
|
+
const noncesPerHash = expected.n;
|
|
153
|
+
if (
|
|
154
|
+
!Number.isInteger(noncesPerHash) ||
|
|
155
|
+
noncesPerHash < 1 ||
|
|
156
|
+
noncesPerHash > MAX_HASHWX_NONCES_PER_HASH
|
|
157
|
+
) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const nonce = parseNonce(solution.nonce);
|
|
162
|
+
if (nonce === null) return false;
|
|
163
|
+
|
|
164
|
+
let target;
|
|
165
|
+
try {
|
|
166
|
+
target = hashwxTarget(expected.d);
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const state = await hashwxReady();
|
|
172
|
+
const block = nonce / BigInt(noncesPerHash);
|
|
173
|
+
const seed = hashwxSeed(challenge, block);
|
|
174
|
+
return hashwxHash(state, seed, nonce) <= target;
|
|
175
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface GeneratedInstrumentation {
|
|
|
19
19
|
instrumentation: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export type ProtocolName = "sha256-pow" | "rsw" | "instrumentation";
|
|
22
|
+
export type ProtocolName = "sha256-pow" | "rsw" | "hashwx" | "instrumentation";
|
|
23
23
|
|
|
24
24
|
export interface RswKeypair {
|
|
25
25
|
N: bigint;
|
|
@@ -71,6 +71,13 @@ export interface GenerateChallengeOptions {
|
|
|
71
71
|
t?: number;
|
|
72
72
|
/** Alternative to `keypair`: pass a pre-built minter (advanced). */
|
|
73
73
|
rsw?: RswMinter;
|
|
74
|
+
|
|
75
|
+
/** Expected number of HashWX hashes per solve, across all sub-challenges. Default 1_000_000. */
|
|
76
|
+
hashwxDifficulty?: number;
|
|
77
|
+
/** Nonces covered by each generated hash function. Default 65_536. */
|
|
78
|
+
hashwxNoncesPerHash?: number;
|
|
79
|
+
/** Independent sub-challenges the difficulty is split across. Default 4. */
|
|
80
|
+
hashwxChallengeCount?: number;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
export interface Format2ChallengeEntry {
|
|
@@ -94,14 +101,56 @@ export interface Format2ChallengeResult {
|
|
|
94
101
|
|
|
95
102
|
export type ChallengeResult = Format1ChallengeResult | Format2ChallengeResult;
|
|
96
103
|
|
|
104
|
+
export interface ProbeVector {
|
|
105
|
+
ua?: string | null;
|
|
106
|
+
productSub?: string | null;
|
|
107
|
+
webdriver?: boolean | null;
|
|
108
|
+
oscpu?: string | null;
|
|
109
|
+
deviceMemory?: number | null;
|
|
110
|
+
uaDataPresent?: boolean | null;
|
|
111
|
+
uaData?: { mobile?: boolean; brands?: string[] } | null;
|
|
112
|
+
plugins?: { length: number } | null;
|
|
113
|
+
pdfViewerEnabled?: boolean | null;
|
|
114
|
+
engine?: { hasMozInnerScreenX: boolean; hasChrome: boolean } | null;
|
|
115
|
+
screen?: { width: number; height: number } | null;
|
|
116
|
+
innerWH?: [number, number] | null;
|
|
117
|
+
outerWH?: [number, number] | null;
|
|
118
|
+
isExtended?: boolean | null;
|
|
119
|
+
fontWidths?: number[] | null;
|
|
120
|
+
tamper?: Record<string, boolean> | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DetectionCheck {
|
|
124
|
+
id: string;
|
|
125
|
+
passed: boolean;
|
|
126
|
+
detail: string;
|
|
127
|
+
gating: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface DetectionResult {
|
|
131
|
+
pass: boolean;
|
|
132
|
+
checks: DetectionCheck[];
|
|
133
|
+
blockedBy: string[];
|
|
134
|
+
riskFlags: string[];
|
|
135
|
+
}
|
|
136
|
+
|
|
97
137
|
export interface InstrumentationPayload {
|
|
98
138
|
i: string;
|
|
99
139
|
state: Record<string, number>;
|
|
140
|
+
p?: ProbeVector;
|
|
141
|
+
vp?: [number, number];
|
|
100
142
|
ts?: number;
|
|
101
143
|
}
|
|
102
144
|
|
|
103
|
-
export interface Format2SolutionShaPow {
|
|
104
|
-
|
|
145
|
+
export interface Format2SolutionShaPow {
|
|
146
|
+
nonce: number | string;
|
|
147
|
+
}
|
|
148
|
+
export interface Format2SolutionRsw {
|
|
149
|
+
y: string;
|
|
150
|
+
}
|
|
151
|
+
export interface Format2SolutionHashwx {
|
|
152
|
+
nonce: number | string;
|
|
153
|
+
}
|
|
105
154
|
export interface Format2SolutionInstrumentation {
|
|
106
155
|
instr?: InstrumentationPayload;
|
|
107
156
|
blocked?: boolean;
|
|
@@ -110,6 +159,7 @@ export interface Format2SolutionInstrumentation {
|
|
|
110
159
|
export type Format2Solution =
|
|
111
160
|
| Format2SolutionShaPow
|
|
112
161
|
| Format2SolutionRsw
|
|
162
|
+
| Format2SolutionHashwx
|
|
113
163
|
| Format2SolutionInstrumentation;
|
|
114
164
|
|
|
115
165
|
export interface ValidateChallengeBody {
|
|
@@ -143,12 +193,16 @@ export interface ValidateChallengeSuccess {
|
|
|
143
193
|
expires: number;
|
|
144
194
|
scope: string | null;
|
|
145
195
|
iat?: number;
|
|
196
|
+
/** Non-gating automation signals (e.g. `native_tamper`). Raise difficulty, do not block. */
|
|
197
|
+
riskFlags: string[];
|
|
146
198
|
}
|
|
147
199
|
|
|
148
200
|
export interface ValidateChallengeFailure {
|
|
149
201
|
success: false;
|
|
150
202
|
reason: string;
|
|
151
203
|
instr_error?: boolean;
|
|
204
|
+
/** Ids of the gating detection checks that failed, when reason is `instr_automated_browser`. */
|
|
205
|
+
blockedBy?: string[];
|
|
152
206
|
error?: string;
|
|
153
207
|
}
|
|
154
208
|
|
|
@@ -184,3 +238,42 @@ export function buildRswMinter(
|
|
|
184
238
|
args: { N: bigint; p: bigint; q: bigint; t: number; g?: bigint },
|
|
185
239
|
opts?: { bits?: number },
|
|
186
240
|
): RswMinter;
|
|
241
|
+
|
|
242
|
+
// ─── HashWX (format-2) helpers ─────────────────────────────────────────────
|
|
243
|
+
export const DEFAULT_HASHWX_DIFFICULTY: number;
|
|
244
|
+
export const DEFAULT_HASHWX_NONCES_PER_HASH: number;
|
|
245
|
+
export const DEFAULT_HASHWX_CHALLENGE_COUNT: number;
|
|
246
|
+
|
|
247
|
+
export interface HashwxState {
|
|
248
|
+
exports: WebAssembly.Exports;
|
|
249
|
+
ctx: number;
|
|
250
|
+
seedPtr: number;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Compiles and initializes the embedded HashWX wasm. Called automatically on
|
|
254
|
+
* first verification; await it at boot to move the cost off the first
|
|
255
|
+
* request. Resolves to a state handle reusable with `hashwxHash`. */
|
|
256
|
+
export function hashwxReady(): Promise<HashwxState>;
|
|
257
|
+
|
|
258
|
+
/** Largest 64-bit hash accepted at the given difficulty. */
|
|
259
|
+
export function hashwxTarget(difficulty: number | bigint): bigint;
|
|
260
|
+
|
|
261
|
+
/** sha256(challenge || u64le(block)), the seed for one generated hash
|
|
262
|
+
* function. Each seed covers `hashwxNoncesPerHash` consecutive nonces. */
|
|
263
|
+
export function hashwxSeed(
|
|
264
|
+
challenge: Uint8Array,
|
|
265
|
+
block: number | bigint,
|
|
266
|
+
): Uint8Array;
|
|
267
|
+
|
|
268
|
+
/** Generates the hash function for `seed` and runs it over `nonce`. Returns an
|
|
269
|
+
* unsigned 64-bit value; a solution needs it at or below `hashwxTarget(d)`. */
|
|
270
|
+
export function hashwxHash(
|
|
271
|
+
state: HashwxState,
|
|
272
|
+
seed: Uint8Array,
|
|
273
|
+
nonce: bigint,
|
|
274
|
+
): bigint;
|
|
275
|
+
|
|
276
|
+
/** Pure server-side automation detector over the probe vector the
|
|
277
|
+
* instrumentation script ships back. Exposed for testing and for callers
|
|
278
|
+
* that run their own instrumentation pipeline. */
|
|
279
|
+
export function detectAutomation(vector: ProbeVector): DetectionResult;
|
package/src/index.js
CHANGED
|
@@ -10,7 +10,25 @@ import {
|
|
|
10
10
|
sha256Bytes,
|
|
11
11
|
sha256Hex,
|
|
12
12
|
} from "./crypto.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
generateInstrumentation,
|
|
15
|
+
verifyInstrumentationResult,
|
|
16
|
+
} from "./instrumentation.js";
|
|
17
|
+
|
|
18
|
+
export { detectAutomation } from "./detect.js";
|
|
19
|
+
|
|
20
|
+
import { mintHashwxChallenges, verifyHashwxSolution } from "./hashwx.js";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_HASHWX_CHALLENGE_COUNT,
|
|
24
|
+
DEFAULT_HASHWX_DIFFICULTY,
|
|
25
|
+
DEFAULT_HASHWX_NONCES_PER_HASH,
|
|
26
|
+
hashwxHash,
|
|
27
|
+
hashwxReady,
|
|
28
|
+
hashwxSeed,
|
|
29
|
+
hashwxTarget,
|
|
30
|
+
} from "./hashwx.js";
|
|
31
|
+
|
|
14
32
|
import { fnv1a, fnv1aResume, prngFromHash } from "./prng.js";
|
|
15
33
|
import {
|
|
16
34
|
buildRswMinter as _buildRswMinter,
|
|
@@ -20,20 +38,16 @@ import {
|
|
|
20
38
|
|
|
21
39
|
export {
|
|
22
40
|
buildRswMinter,
|
|
41
|
+
deserializeRswKeypair,
|
|
23
42
|
generateRswKeypair,
|
|
24
43
|
serializeRswKeypair,
|
|
25
|
-
deserializeRswKeypair,
|
|
26
44
|
} from "./rsw.js";
|
|
27
45
|
|
|
28
46
|
const DEFAULT_RSW_T = 75_000;
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
// pass the same `keypair` object back across requests get zero per-call
|
|
32
|
-
// overhead beyond the mint itself.
|
|
33
|
-
const _minterCache = new WeakMap(); // keypair → Map<t, minter>
|
|
48
|
+
const _minterCache = new WeakMap();
|
|
34
49
|
|
|
35
50
|
function resolveRswMinter(opts) {
|
|
36
|
-
// explicit minter takes precedence (existing API)
|
|
37
51
|
if (opts.rsw && typeof opts.rsw.mint === "function") return opts.rsw;
|
|
38
52
|
|
|
39
53
|
if (!opts.keypair) {
|
|
@@ -42,13 +56,11 @@ function resolveRswMinter(opts) {
|
|
|
42
56
|
);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
const kp =
|
|
60
|
+
typeof opts.keypair.N === "string"
|
|
61
|
+
? deserializeRswKeypair(opts.keypair)
|
|
62
|
+
: opts.keypair;
|
|
49
63
|
|
|
50
|
-
// Memoize {keypair, t} → minter. Reuses the precomputed h across calls so
|
|
51
|
-
// the first request after boot is the only one that pays the ~5 ms setup.
|
|
52
64
|
const t = opts.t ?? DEFAULT_RSW_T;
|
|
53
65
|
let byT = _minterCache.get(opts.keypair);
|
|
54
66
|
if (!byT) {
|
|
@@ -82,9 +94,14 @@ function assertSecret(secret) {
|
|
|
82
94
|
if (typeof secret !== "string" && !Buffer.isBuffer(secret)) {
|
|
83
95
|
throw new Error("[capjs-core] secret must be a string or Buffer");
|
|
84
96
|
}
|
|
85
|
-
const len =
|
|
97
|
+
const len =
|
|
98
|
+
typeof secret === "string"
|
|
99
|
+
? Buffer.byteLength(secret, "utf8")
|
|
100
|
+
: secret.length;
|
|
86
101
|
if (len < 16) {
|
|
87
|
-
throw new Error(
|
|
102
|
+
throw new Error(
|
|
103
|
+
"[capjs-core] secret must be at least 16 bytes for security",
|
|
104
|
+
);
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
|
|
@@ -130,7 +147,8 @@ export async function generateChallenge(secret, opts = {}) {
|
|
|
130
147
|
|
|
131
148
|
let instrumentation;
|
|
132
149
|
if (opts.instrumentation) {
|
|
133
|
-
const instrOpts =
|
|
150
|
+
const instrOpts =
|
|
151
|
+
typeof opts.instrumentation === "object" ? opts.instrumentation : {};
|
|
134
152
|
const generator = opts.instrumentationGenerator || generateInstrumentation;
|
|
135
153
|
const instr = await generator({ ...instrOpts, ttlMs });
|
|
136
154
|
payload.ei = encryptGcm(
|
|
@@ -160,7 +178,8 @@ export async function validateChallenge(secret, body, opts = {}) {
|
|
|
160
178
|
assertSecret(secret);
|
|
161
179
|
|
|
162
180
|
if (!body || typeof body !== "object") return fail("invalid_body");
|
|
163
|
-
if (!body.token || typeof body.token !== "string")
|
|
181
|
+
if (!body.token || typeof body.token !== "string")
|
|
182
|
+
return fail("missing_token");
|
|
164
183
|
const solutions = body.solutions;
|
|
165
184
|
if (!Array.isArray(solutions)) return fail("missing_solutions");
|
|
166
185
|
|
|
@@ -212,6 +231,7 @@ export async function validateChallenge(secret, body, opts = {}) {
|
|
|
212
231
|
}
|
|
213
232
|
}
|
|
214
233
|
|
|
234
|
+
let riskFlags = [];
|
|
215
235
|
if (payload.ei) {
|
|
216
236
|
const instrMeta = decryptGcm(payload.ei, secret);
|
|
217
237
|
if (!instrMeta) return fail("instr_corrupted", { instr_error: true });
|
|
@@ -227,7 +247,13 @@ export async function validateChallenge(secret, body, opts = {}) {
|
|
|
227
247
|
return fail("instr_timeout", { instr_error: true });
|
|
228
248
|
} else if (body.instr) {
|
|
229
249
|
const r = verifyInstrumentationResult(instrMeta, body.instr);
|
|
230
|
-
if (!r.valid)
|
|
250
|
+
if (!r.valid) {
|
|
251
|
+
return fail(r.reason || "instr_failed", {
|
|
252
|
+
instr_error: true,
|
|
253
|
+
...(r.blockedBy && { blockedBy: r.blockedBy }),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (r.riskFlags?.length) riskFlags = [...riskFlags, ...r.riskFlags];
|
|
231
257
|
} else {
|
|
232
258
|
return fail("instr_missing", { instr_error: true });
|
|
233
259
|
}
|
|
@@ -261,6 +287,7 @@ export async function validateChallenge(secret, body, opts = {}) {
|
|
|
261
287
|
expires: tokenExpires,
|
|
262
288
|
scope: payload.sk ?? null,
|
|
263
289
|
iat: payload.iat,
|
|
290
|
+
riskFlags,
|
|
264
291
|
};
|
|
265
292
|
}
|
|
266
293
|
|
|
@@ -274,12 +301,15 @@ export async function validateChallenge(secret, body, opts = {}) {
|
|
|
274
301
|
expires: tokenExpires,
|
|
275
302
|
scope: payload.sk ?? null,
|
|
276
303
|
iat: payload.iat,
|
|
304
|
+
riskFlags,
|
|
277
305
|
};
|
|
278
306
|
}
|
|
279
307
|
|
|
280
308
|
async function generateChallengeV2(secret, opts) {
|
|
281
309
|
const protocols =
|
|
282
|
-
Array.isArray(opts.protocols) && opts.protocols.length
|
|
310
|
+
Array.isArray(opts.protocols) && opts.protocols.length
|
|
311
|
+
? opts.protocols
|
|
312
|
+
: ["rsw"];
|
|
283
313
|
const ttlMs = opts.expiresMs ?? DEFAULT_CHALLENGE_TTL_MS;
|
|
284
314
|
const now = Date.now();
|
|
285
315
|
const exp = now + ttlMs;
|
|
@@ -321,14 +351,24 @@ async function generateChallengeV2(secret, opts) {
|
|
|
321
351
|
payload: { N: minter.N_hex, x: x_hex, t: minter.t },
|
|
322
352
|
});
|
|
323
353
|
expected.push({ protocol: "rsw", y: y_hex });
|
|
354
|
+
} else if (proto === "hashwx") {
|
|
355
|
+
for (const minted of mintHashwxChallenges(opts)) {
|
|
356
|
+
challenges.push({ protocol: "hashwx", payload: minted.payload });
|
|
357
|
+
expected.push({ protocol: "hashwx", ...minted.expected });
|
|
358
|
+
}
|
|
324
359
|
} else if (proto === "instrumentation") {
|
|
325
360
|
const instrOpts =
|
|
326
|
-
typeof opts.instrumentation === "object" &&
|
|
361
|
+
typeof opts.instrumentation === "object" &&
|
|
362
|
+
opts.instrumentation !== null
|
|
327
363
|
? opts.instrumentation
|
|
328
364
|
: {};
|
|
329
|
-
const generator =
|
|
365
|
+
const generator =
|
|
366
|
+
opts.instrumentationGenerator || generateInstrumentation;
|
|
330
367
|
const instr = await generator({ ...instrOpts, ttlMs });
|
|
331
|
-
challenges.push({
|
|
368
|
+
challenges.push({
|
|
369
|
+
protocol: "instrumentation",
|
|
370
|
+
payload: { blob: instr.instrumentation },
|
|
371
|
+
});
|
|
332
372
|
expected.push({
|
|
333
373
|
protocol: "instrumentation",
|
|
334
374
|
instrMeta: {
|
|
@@ -363,9 +403,11 @@ async function validateChallengeV2(secret, body, payload, opts = {}) {
|
|
|
363
403
|
if (!payload.exp || payload.exp < Date.now()) return fail("expired");
|
|
364
404
|
|
|
365
405
|
const decrypted = decryptGcm(payload.ev, secret, "cap:fmt2-v1");
|
|
366
|
-
if (!decrypted || !Array.isArray(decrypted.expected))
|
|
406
|
+
if (!decrypted || !Array.isArray(decrypted.expected))
|
|
407
|
+
return fail("invalid_token");
|
|
367
408
|
const expected = decrypted.expected;
|
|
368
409
|
const solutions = body.solutions;
|
|
410
|
+
let riskFlags = [];
|
|
369
411
|
|
|
370
412
|
if (solutions.length !== expected.length) return fail("invalid_solutions");
|
|
371
413
|
|
|
@@ -376,11 +418,15 @@ async function validateChallengeV2(secret, body, payload, opts = {}) {
|
|
|
376
418
|
|
|
377
419
|
if (e.protocol === "sha256-pow") {
|
|
378
420
|
const nonce = s.nonce;
|
|
379
|
-
if (typeof nonce !== "number" && typeof nonce !== "string")
|
|
421
|
+
if (typeof nonce !== "number" && typeof nonce !== "string")
|
|
422
|
+
return fail("invalid_solution");
|
|
380
423
|
const hash = sha256Bytes(e.salt + nonce);
|
|
381
|
-
if (!powMatchesPrefix(hash, parseHexPrefix(e.target)))
|
|
424
|
+
if (!powMatchesPrefix(hash, parseHexPrefix(e.target)))
|
|
425
|
+
return fail("invalid_solution");
|
|
382
426
|
} else if (e.protocol === "rsw") {
|
|
383
427
|
if (!verifyRswSolution(e.y, s.y)) return fail("invalid_solution");
|
|
428
|
+
} else if (e.protocol === "hashwx") {
|
|
429
|
+
if (!(await verifyHashwxSolution(e, s))) return fail("invalid_solution");
|
|
384
430
|
} else if (e.protocol === "instrumentation") {
|
|
385
431
|
const meta = e.instrMeta;
|
|
386
432
|
if (!meta) return fail("instr_corrupted", { instr_error: true });
|
|
@@ -395,7 +441,13 @@ async function validateChallengeV2(secret, body, payload, opts = {}) {
|
|
|
395
441
|
return fail("instr_timeout", { instr_error: true });
|
|
396
442
|
} else if (s.instr) {
|
|
397
443
|
const r = verifyInstrumentationResult(meta, s.instr);
|
|
398
|
-
if (!r.valid)
|
|
444
|
+
if (!r.valid) {
|
|
445
|
+
return fail(r.reason || "instr_failed", {
|
|
446
|
+
instr_error: true,
|
|
447
|
+
...(r.blockedBy && { blockedBy: r.blockedBy }),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (r.riskFlags?.length) riskFlags = [...riskFlags, ...r.riskFlags];
|
|
399
451
|
} else {
|
|
400
452
|
return fail("instr_missing", { instr_error: true });
|
|
401
453
|
}
|
|
@@ -432,6 +484,7 @@ async function validateChallengeV2(secret, body, payload, opts = {}) {
|
|
|
432
484
|
expires: tokenExpires,
|
|
433
485
|
scope: payload.sk ?? null,
|
|
434
486
|
iat: payload.iat,
|
|
487
|
+
riskFlags,
|
|
435
488
|
};
|
|
436
489
|
}
|
|
437
490
|
|
|
@@ -445,5 +498,6 @@ async function validateChallengeV2(secret, body, payload, opts = {}) {
|
|
|
445
498
|
expires: tokenExpires,
|
|
446
499
|
scope: payload.sk ?? null,
|
|
447
500
|
iat: payload.iat,
|
|
501
|
+
riskFlags,
|
|
448
502
|
};
|
|
449
503
|
}
|
package/src/instrumentation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes, randomInt } from "node:crypto";
|
|
2
2
|
import { deflateRawSync } from "node:zlib";
|
|
3
|
+
import { detectAutomation } from "./detect.js";
|
|
3
4
|
|
|
4
5
|
let _obfuscator = null;
|
|
5
6
|
async function getObfuscator() {
|
|
@@ -151,6 +152,25 @@ const DOC_PROP_MARKERS = [
|
|
|
151
152
|
"__webdriver_script_func",
|
|
152
153
|
"__webdriver_script_function",
|
|
153
154
|
];
|
|
155
|
+
const NAV_OWN_PROP_MARKERS = [
|
|
156
|
+
"webdriver",
|
|
157
|
+
"userAgent",
|
|
158
|
+
"appVersion",
|
|
159
|
+
"userAgentData",
|
|
160
|
+
"platform",
|
|
161
|
+
"vendor",
|
|
162
|
+
"product",
|
|
163
|
+
"productSub",
|
|
164
|
+
"languages",
|
|
165
|
+
"language",
|
|
166
|
+
"plugins",
|
|
167
|
+
"mimeTypes",
|
|
168
|
+
"hardwareConcurrency",
|
|
169
|
+
"deviceMemory",
|
|
170
|
+
"maxTouchPoints",
|
|
171
|
+
"permissions",
|
|
172
|
+
"connection",
|
|
173
|
+
];
|
|
154
174
|
const ATTR_SUBSTRING_MARKERS = ["selenium", "webdriver", "driver"];
|
|
155
175
|
const STACK_SUBSTRING_MARKERS = ["pptr:", "UtilityScript.", "PhantomJS"];
|
|
156
176
|
const WINDOW_PREFIX_MARKERS = ["puppeteer_", "cdc_", "$cdc_"];
|
|
@@ -165,6 +185,54 @@ const WEBGL_RENDERER_MARKER = "Mesa OffScreen";
|
|
|
165
185
|
const PRODUCTSUB_GECKO = "20030107";
|
|
166
186
|
const SEQUENTUM_MARKER = "Sequentum";
|
|
167
187
|
|
|
188
|
+
const PROBE_FONT_STACKS = [
|
|
189
|
+
"sans-serif",
|
|
190
|
+
"serif",
|
|
191
|
+
"monospace",
|
|
192
|
+
"Arial",
|
|
193
|
+
"'Times New Roman'",
|
|
194
|
+
"'Courier New'",
|
|
195
|
+
"'Segoe UI'",
|
|
196
|
+
"'SF Pro Text'",
|
|
197
|
+
"'Helvetica Neue'",
|
|
198
|
+
"'Ubuntu'",
|
|
199
|
+
"'DejaVu Sans'",
|
|
200
|
+
"'Roboto'",
|
|
201
|
+
"Tahoma",
|
|
202
|
+
"Calibri",
|
|
203
|
+
"'Cantarell'",
|
|
204
|
+
"'Liberation Sans'",
|
|
205
|
+
"'Noto Sans'",
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
function buildProbe(P) {
|
|
209
|
+
const sf = rVar();
|
|
210
|
+
const nat = rVar();
|
|
211
|
+
const fields = [
|
|
212
|
+
`${sf}("ua",function(){return navigator.userAgent});`,
|
|
213
|
+
`${sf}("productSub",function(){return navigator.productSub});`,
|
|
214
|
+
`${sf}("webdriver",function(){return navigator.webdriver});`,
|
|
215
|
+
`${sf}("oscpu",function(){return navigator.oscpu===undefined?"__undefined":navigator.oscpu});`,
|
|
216
|
+
`${sf}("deviceMemory",function(){return navigator.deviceMemory});`,
|
|
217
|
+
`${sf}("uaDataPresent",function(){return !!navigator.userAgentData});`,
|
|
218
|
+
`${sf}("uaData",function(){var u=navigator.userAgentData;return u?{mobile:u.mobile,brands:(u.brands||[]).map(function(b){return b.brand+"/"+b.version})}:null});`,
|
|
219
|
+
`${sf}("plugins",function(){return {length:navigator.plugins.length}});`,
|
|
220
|
+
`${sf}("pdfViewerEnabled",function(){return navigator.pdfViewerEnabled});`,
|
|
221
|
+
`${sf}("engine",function(){return {hasMozInnerScreenX:typeof window.mozInnerScreenX!=="undefined",hasChrome:typeof window.chrome!=="undefined"}});`,
|
|
222
|
+
`${sf}("screen",function(){return {width:screen.width,height:screen.height}});`,
|
|
223
|
+
`${sf}("outerWH",function(){return [outerWidth,outerHeight]});`,
|
|
224
|
+
`${sf}("isExtended",function(){return screen.isExtended===undefined?null:screen.isExtended});`,
|
|
225
|
+
`${sf}("fontWidths",function(){var sp=document.createElement("span");sp.style.cssText="position:absolute;left:-9999px;top:-9999px;font-size:72px;white-space:nowrap";sp.textContent="mmmmmmmmmmlli WwWwGg @#Xj";document.body.appendChild(sp);var st=${JSON.stringify(PROBE_FONT_STACKS)};var out=[];for(var i=0;i<st.length;i++){sp.style.fontFamily=st[i];out.push(Math.round(sp.getBoundingClientRect().width*100)/100)}document.body.removeChild(sp);return out});`,
|
|
226
|
+
`${sf}("tamper",function(){return {getParameterWebGL:${nat}(WebGLRenderingContext.prototype.getParameter),toDataURL:${nat}(HTMLCanvasElement.prototype.toDataURL),getImageData:${nat}(CanvasRenderingContext2D.prototype.getImageData),permissionsQuery:navigator.permissions?${nat}(navigator.permissions.query):true,fnToString:${nat}(Function.prototype.toString)}});`,
|
|
227
|
+
];
|
|
228
|
+
shuffle(fields);
|
|
229
|
+
return (
|
|
230
|
+
`var ${P}={};function ${sf}(k,f){try{${P}[k]=f()}catch(e){${P}[k]=null}}` +
|
|
231
|
+
`function ${nat}(fn){try{return /\\{\\s*\\[native code\\]\\s*\\}/.test(Function.prototype.toString.call(fn))}catch(e){return true}}` +
|
|
232
|
+
fields.join("")
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
168
236
|
function hashWith(seed) {
|
|
169
237
|
return (s) => {
|
|
170
238
|
let h = seed >>> 0;
|
|
@@ -179,6 +247,7 @@ function hashWith(seed) {
|
|
|
179
247
|
function buildBlockChecks(b, id, hF, hSet, h) {
|
|
180
248
|
const winHashes = WINDOW_PROP_MARKERS.map(h);
|
|
181
249
|
const docHashes = DOC_PROP_MARKERS.map(h);
|
|
250
|
+
const navOwnHashes = NAV_OWN_PROP_MARKERS.map(h);
|
|
182
251
|
const attrSubHashes = ATTR_SUBSTRING_MARKERS.map(h);
|
|
183
252
|
const stackSubHashes = STACK_SUBSTRING_MARKERS.map(h);
|
|
184
253
|
const winPrefixHashes = WINDOW_PREFIX_MARKERS.map(h);
|
|
@@ -187,16 +256,19 @@ function buildBlockChecks(b, id, hF, hSet, h) {
|
|
|
187
256
|
const webglRendererHash = h(WEBGL_RENDERER_MARKER);
|
|
188
257
|
const productSubGeckoHash = h(PRODUCTSUB_GECKO);
|
|
189
258
|
const sequentumHash = h(SEQUENTUM_MARKER);
|
|
190
|
-
|
|
259
|
+
|
|
191
260
|
const checks = [];
|
|
192
261
|
|
|
193
262
|
checks.push(
|
|
194
263
|
`if (!${b}) { try { var d = Object.getOwnPropertyDescriptors(navigator); var __wh = ${h("webdriver")}; for (const k in d) { if (${hF}(k) === __wh) { ${b} = true; break; } } if (!${b}) { var p = Object.getPrototypeOf(navigator); while (p && !${b}) { for (const k of Object.getOwnPropertyNames(p)) { if (${hF}(k) === __wh) { try { if (navigator[k]) ${b} = true; } catch {} break; } } p = Object.getPrototypeOf(p); } } } catch { ${b} = true; } }`,
|
|
195
264
|
);
|
|
196
265
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
266
|
+
{
|
|
267
|
+
const a = rVar();
|
|
268
|
+
checks.push(
|
|
269
|
+
`if (!${b}) { try { var ${a} = ${JSON.stringify(navOwnHashes)}; for (const k of Object.getOwnPropertyNames(navigator)) { if (${hSet}(${a}, ${hF}(k))) { ${b} = true; break; } } } catch { ${b} = true; } }`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
200
272
|
|
|
201
273
|
{
|
|
202
274
|
const a = rVar();
|
|
@@ -325,6 +397,7 @@ function buildClientScript({
|
|
|
325
397
|
const dvKey = rVar();
|
|
326
398
|
const nKey = rVar();
|
|
327
399
|
const outKey = rVar();
|
|
400
|
+
const pKey = rVar();
|
|
328
401
|
|
|
329
402
|
const envChecks = [
|
|
330
403
|
`try { const ${nKey}st = (new Error()).stack || ''; if (${nKey}st.indexOf('node:internal') !== -1 || ${nKey}st.indexOf('moduleEvaluation') !== -1 || ${nKey}st.indexOf('loadAndEvaluateModule') !== -1 || ${nKey}st.indexOf('file:///') !== -1 || ${nKey}st.indexOf('[eval]') !== -1 || /\\(native:/.test(${nKey}st)) return null; } catch { return null }`,
|
|
@@ -344,7 +417,7 @@ function buildClientScript({
|
|
|
344
417
|
|
|
345
418
|
return `(function(){window.onload=async function(){try {${helpers}const ${dvKey}=await (async function(){${shuffle(envChecks).join("")}${blockChecks}
|
|
346
419
|
var ${vars[0]}=${initVals[0]};var ${vars[1]}=${initVals[1]};var ${vars[2]}=${initVals[2]};var ${vars[3]}=${initVals[3]};${clientEqs}
|
|
347
|
-
var ${outKey}={};${outKey}["${vars[0]}"]=${vars[0]};${outKey}["${vars[1]}"]=${vars[1]};${outKey}["${vars[2]}"]=${vars[2]};${outKey}["${vars[3]}"]=${vars[3]};return ${outKey};})();if (!${dvKey} || typeof ${dvKey} !== 'object') return
|
|
420
|
+
var ${outKey}={};${outKey}["${vars[0]}"]=${vars[0]};${outKey}["${vars[1]}"]=${vars[1]};${outKey}["${vars[2]}"]=${vars[2]};${outKey}["${vars[3]}"]=${vars[3]};return ${outKey};})();if (!${dvKey} || typeof ${dvKey} !== 'object') return;${buildProbe(pKey)}parent.postMessage({type: 'cap:instr',nonce:${JSON.stringify(id)},result:{i:${JSON.stringify(id)},state:${dvKey},p:${pKey},ts:Date.now()}},'*');} catch {}};})();`;
|
|
348
421
|
}
|
|
349
422
|
|
|
350
423
|
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
@@ -534,5 +607,34 @@ export function verifyInstrumentationResult(challengeMeta, payload) {
|
|
|
534
607
|
return { valid: false, reason: "failed_challenge" };
|
|
535
608
|
}
|
|
536
609
|
|
|
537
|
-
|
|
610
|
+
const probe = payload.p;
|
|
611
|
+
if (probe && typeof probe === "object") {
|
|
612
|
+
const vector =
|
|
613
|
+
Array.isArray(payload.vp) && payload.vp.length === 2
|
|
614
|
+
? { ...probe, innerWH: payload.vp }
|
|
615
|
+
: probe;
|
|
616
|
+
const d = detectAutomation(vector, {
|
|
617
|
+
fontStackCount: PROBE_FONT_STACKS.length,
|
|
618
|
+
});
|
|
619
|
+
if (challengeMeta.blockAutomatedBrowsers && !d.pass) {
|
|
620
|
+
return {
|
|
621
|
+
valid: false,
|
|
622
|
+
reason: "instr_automated_browser",
|
|
623
|
+
blockedBy: d.blockedBy,
|
|
624
|
+
checks: d.checks,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
return {
|
|
628
|
+
valid: true,
|
|
629
|
+
riskFlags: d.riskFlags,
|
|
630
|
+
blockedBy: d.blockedBy,
|
|
631
|
+
checks: d.checks,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (challengeMeta.blockAutomatedBrowsers) {
|
|
636
|
+
return { valid: false, reason: "instr_probe_missing" };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return { valid: true, riskFlags: [] };
|
|
538
640
|
}
|