capjs-core 0.1.6 → 0.1.7
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/README.md +5 -2
- package/package.json +5 -2
- package/src/agents.js +700 -0
- package/src/behavior.js +437 -0
- package/src/detect.js +189 -8
- package/src/hashwx-wasm.js +1 -5
- package/src/index.d.ts +198 -43
- package/src/index.js +298 -67
- package/src/instrumentation.js +742 -116
- package/src/rsw.js +13 -5
package/src/index.d.ts
CHANGED
|
@@ -6,16 +6,31 @@ export interface ChallengeSpec {
|
|
|
6
6
|
|
|
7
7
|
export interface InstrumentationOptions {
|
|
8
8
|
blockAutomatedBrowsers?: boolean;
|
|
9
|
+
detectAutomatedInteractions?: boolean;
|
|
10
|
+
blockAutomatedInteractions?: boolean;
|
|
9
11
|
obfuscationLevel?: number;
|
|
10
12
|
ttlMs?: number;
|
|
13
|
+
behavior?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface InstrumentationBinding {
|
|
17
|
+
pv: number[];
|
|
18
|
+
sl: number[];
|
|
19
|
+
hs: [number, number];
|
|
20
|
+
j: [number, number];
|
|
11
21
|
}
|
|
12
22
|
|
|
13
23
|
export interface GeneratedInstrumentation {
|
|
14
24
|
id: string;
|
|
25
|
+
iat?: number;
|
|
15
26
|
expires: number;
|
|
16
|
-
expectedVals: number[];
|
|
17
27
|
vars: string[];
|
|
28
|
+
binding?: InstrumentationBinding;
|
|
29
|
+
expectedVals?: number[];
|
|
18
30
|
blockAutomatedBrowsers: boolean;
|
|
31
|
+
detectAutomatedInteractions?: boolean;
|
|
32
|
+
blockAutomatedInteractions?: boolean;
|
|
33
|
+
behavior?: boolean;
|
|
19
34
|
instrumentation: string;
|
|
20
35
|
}
|
|
21
36
|
|
|
@@ -47,8 +62,108 @@ export interface RswMinter {
|
|
|
47
62
|
mint(): { x_hex: string; y_hex: string };
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
export interface BehaviorOptions {
|
|
66
|
+
block?: boolean;
|
|
67
|
+
threshold?: number;
|
|
68
|
+
strict?: boolean;
|
|
69
|
+
requireInteraction?: boolean;
|
|
70
|
+
blockAgents?: false | "identified" | "all";
|
|
71
|
+
allowVerifiedAgents?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AgentResult {
|
|
75
|
+
detected: boolean;
|
|
76
|
+
name: string | null;
|
|
77
|
+
confidence: "low" | "medium" | "high";
|
|
78
|
+
verified: boolean;
|
|
79
|
+
evidence: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface BehaviorTrace {
|
|
83
|
+
v: 1;
|
|
84
|
+
res: number;
|
|
85
|
+
now: number;
|
|
86
|
+
vis: number;
|
|
87
|
+
last: number;
|
|
88
|
+
env: number[];
|
|
89
|
+
n: number[];
|
|
90
|
+
m: [number, number, number, number, number][];
|
|
91
|
+
k: [number, number][];
|
|
92
|
+
a: Record<string, number> | null;
|
|
93
|
+
us: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BehaviorResult {
|
|
97
|
+
score: number | null;
|
|
98
|
+
verdict:
|
|
99
|
+
| "ok"
|
|
100
|
+
| "no_interaction"
|
|
101
|
+
| "suspicious"
|
|
102
|
+
| "agent"
|
|
103
|
+
| "automated"
|
|
104
|
+
| "missing";
|
|
105
|
+
flags: string[];
|
|
106
|
+
signals: Record<string, unknown>;
|
|
107
|
+
agent?: AgentResult;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function verifyWebBotAuth(
|
|
111
|
+
req: {
|
|
112
|
+
method: string;
|
|
113
|
+
url: string;
|
|
114
|
+
headers: Headers | Record<string, string>;
|
|
115
|
+
},
|
|
116
|
+
opts?: {
|
|
117
|
+
fetch?: typeof fetch;
|
|
118
|
+
now?: number;
|
|
119
|
+
maxSkewSec?: number;
|
|
120
|
+
trustedOrigins?: string[];
|
|
121
|
+
nonceCheck?: (nonce: string, ttlMs: number) => boolean | Promise<boolean>;
|
|
122
|
+
},
|
|
123
|
+
): Promise<{
|
|
124
|
+
verified: boolean;
|
|
125
|
+
agent?: string;
|
|
126
|
+
origin?: string;
|
|
127
|
+
keyid?: string;
|
|
128
|
+
reason?: string;
|
|
129
|
+
}>;
|
|
130
|
+
|
|
131
|
+
export interface AgentEvidence {
|
|
132
|
+
agent: string | null;
|
|
133
|
+
confidence: "low" | "medium" | "high";
|
|
134
|
+
evidence: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function agentFromHeaders(
|
|
138
|
+
headers: Headers | Record<string, string>,
|
|
139
|
+
): AgentEvidence[];
|
|
140
|
+
|
|
141
|
+
export function agentFromTrace(trace: BehaviorTrace): AgentEvidence[];
|
|
142
|
+
|
|
143
|
+
export function summarizeAgent(
|
|
144
|
+
found: AgentEvidence[],
|
|
145
|
+
verification?: {
|
|
146
|
+
verified: boolean;
|
|
147
|
+
agent?: string;
|
|
148
|
+
origin?: string;
|
|
149
|
+
} | null,
|
|
150
|
+
): AgentResult | null;
|
|
151
|
+
|
|
152
|
+
export function parseSignatureAgent(
|
|
153
|
+
value: string | null | undefined,
|
|
154
|
+
): { label: string | null; url: string }[];
|
|
155
|
+
|
|
156
|
+
export const BEHAVIOR_WEIGHTS: Record<string, number>;
|
|
157
|
+
export const DEFAULT_BEHAVIOR_THRESHOLD: number;
|
|
158
|
+
export const WEAK_CAP: number;
|
|
159
|
+
|
|
160
|
+
export function analyzeBehavior(
|
|
161
|
+
trace: unknown,
|
|
162
|
+
ctx?: { replayed?: boolean; strict?: boolean; threshold?: number },
|
|
163
|
+
): BehaviorResult & { digest: string | null };
|
|
164
|
+
|
|
50
165
|
export interface GenerateChallengeOptions {
|
|
51
|
-
|
|
166
|
+
behavior?: boolean | BehaviorOptions;
|
|
52
167
|
challengeCount?: number;
|
|
53
168
|
challengeSize?: number;
|
|
54
169
|
challengeDifficulty?: number;
|
|
@@ -60,23 +175,14 @@ export interface GenerateChallengeOptions {
|
|
|
60
175
|
opts: InstrumentationOptions,
|
|
61
176
|
) => Promise<GeneratedInstrumentation>;
|
|
62
177
|
|
|
63
|
-
// ── format-2 opt-in ──
|
|
64
178
|
format?: 2;
|
|
65
179
|
protocols?: ProtocolName[];
|
|
66
|
-
/** RSW keypair, either as raw BigInts (from `generateRswKeypair`) or as a
|
|
67
|
-
* serialized JSON-friendly form (from `serializeRswKeypair`). cap-core
|
|
68
|
-
* builds the minter on first call and memoizes it on this object. */
|
|
69
180
|
keypair?: RswKeypair | SerializedRswKeypair;
|
|
70
|
-
/** Number of sequential squarings the client must perform. Default 75_000. */
|
|
71
181
|
t?: number;
|
|
72
|
-
/** Alternative to `keypair`: pass a pre-built minter (advanced). */
|
|
73
182
|
rsw?: RswMinter;
|
|
74
183
|
|
|
75
|
-
/** Expected number of HashWX hashes per solve, across all sub-challenges. Default 1_000_000. */
|
|
76
184
|
hashwxDifficulty?: number;
|
|
77
|
-
/** Nonces covered by each generated hash function. Default 65_536. */
|
|
78
185
|
hashwxNoncesPerHash?: number;
|
|
79
|
-
/** Independent sub-challenges the difficulty is split across. Default 4. */
|
|
80
186
|
hashwxChallengeCount?: number;
|
|
81
187
|
}
|
|
82
188
|
|
|
@@ -90,6 +196,7 @@ export interface Format1ChallengeResult {
|
|
|
90
196
|
token: string;
|
|
91
197
|
expires: number;
|
|
92
198
|
instrumentation?: string;
|
|
199
|
+
behavior?: true;
|
|
93
200
|
}
|
|
94
201
|
|
|
95
202
|
export interface Format2ChallengeResult {
|
|
@@ -97,6 +204,7 @@ export interface Format2ChallengeResult {
|
|
|
97
204
|
format: 2;
|
|
98
205
|
challenges: Format2ChallengeEntry[];
|
|
99
206
|
expires: number;
|
|
207
|
+
behavior?: true;
|
|
100
208
|
}
|
|
101
209
|
|
|
102
210
|
export type ChallengeResult = Format1ChallengeResult | Format2ChallengeResult;
|
|
@@ -134,14 +242,60 @@ export interface DetectionResult {
|
|
|
134
242
|
riskFlags: string[];
|
|
135
243
|
}
|
|
136
244
|
|
|
245
|
+
export interface BoundData {
|
|
246
|
+
v: 1;
|
|
247
|
+
el: number;
|
|
248
|
+
p: ProbeVector | null;
|
|
249
|
+
vp: [number, number] | null;
|
|
250
|
+
hp: Record<string, unknown> | null;
|
|
251
|
+
ag: string[];
|
|
252
|
+
ia: [number, number] | null;
|
|
253
|
+
bh: BehaviorTrace | null;
|
|
254
|
+
}
|
|
255
|
+
|
|
137
256
|
export interface InstrumentationPayload {
|
|
138
257
|
i: string;
|
|
139
258
|
state: Record<string, number>;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
259
|
+
d: string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface InstrumentationVerdict {
|
|
263
|
+
valid: boolean;
|
|
264
|
+
reason?: string;
|
|
265
|
+
riskFlags?: string[];
|
|
266
|
+
blockedBy?: string[];
|
|
267
|
+
checks?: DetectionCheck[];
|
|
268
|
+
agent?: {
|
|
269
|
+
all: string[];
|
|
270
|
+
host: boolean;
|
|
271
|
+
legacy: boolean;
|
|
272
|
+
isolated: boolean;
|
|
273
|
+
browser: string | null;
|
|
274
|
+
};
|
|
275
|
+
trace?: BehaviorTrace | null;
|
|
143
276
|
}
|
|
144
277
|
|
|
278
|
+
export const MAX_BOUND_DATA: number;
|
|
279
|
+
|
|
280
|
+
export function generateInstrumentation(
|
|
281
|
+
opts?: InstrumentationOptions,
|
|
282
|
+
): Promise<GeneratedInstrumentation & { binding: InstrumentationBinding }>;
|
|
283
|
+
|
|
284
|
+
export function canonicalize<T>(value: T): T;
|
|
285
|
+
|
|
286
|
+
export function serializeBound(data: BoundData): string;
|
|
287
|
+
|
|
288
|
+
export function boundValues(
|
|
289
|
+
binding: InstrumentationBinding,
|
|
290
|
+
serialized: string,
|
|
291
|
+
): number[];
|
|
292
|
+
|
|
293
|
+
export function verifyInstrumentationResult(
|
|
294
|
+
meta: GeneratedInstrumentation,
|
|
295
|
+
payload: InstrumentationPayload,
|
|
296
|
+
now?: number,
|
|
297
|
+
): InstrumentationVerdict;
|
|
298
|
+
|
|
145
299
|
export interface Format2SolutionShaPow {
|
|
146
300
|
nonce: number | string;
|
|
147
301
|
}
|
|
@@ -155,6 +309,7 @@ export interface Format2SolutionInstrumentation {
|
|
|
155
309
|
instr?: InstrumentationPayload;
|
|
156
310
|
blocked?: boolean;
|
|
157
311
|
timeout?: boolean;
|
|
312
|
+
error?: string;
|
|
158
313
|
}
|
|
159
314
|
export type Format2Solution =
|
|
160
315
|
| Format2SolutionShaPow
|
|
@@ -164,12 +319,11 @@ export type Format2Solution =
|
|
|
164
319
|
|
|
165
320
|
export interface ValidateChallengeBody {
|
|
166
321
|
token: string;
|
|
167
|
-
/** Format-1: an array of nonces. Format-2: an array of protocol-specific
|
|
168
|
-
* solution objects, in the same order as `challenges`. */
|
|
169
322
|
solutions: number[] | Format2Solution[];
|
|
170
323
|
instr?: InstrumentationPayload;
|
|
171
324
|
instr_blocked?: boolean;
|
|
172
325
|
instr_timeout?: boolean;
|
|
326
|
+
instr_error?: string;
|
|
173
327
|
}
|
|
174
328
|
|
|
175
329
|
export interface ValidateChallengeOptions {
|
|
@@ -184,6 +338,23 @@ export interface ValidateChallengeOptions {
|
|
|
184
338
|
expires: number;
|
|
185
339
|
iat?: number;
|
|
186
340
|
}) => Promise<string> | string;
|
|
341
|
+
request?: {
|
|
342
|
+
method: string;
|
|
343
|
+
url: string;
|
|
344
|
+
headers: Headers | Record<string, string>;
|
|
345
|
+
};
|
|
346
|
+
verifyWebBotAuth?: boolean;
|
|
347
|
+
webBotAuthAuthority?: string;
|
|
348
|
+
fetch?: typeof fetch;
|
|
349
|
+
trustedSignatureAgents?: string[];
|
|
350
|
+
webBotAuthNonceCheck?: (
|
|
351
|
+
nonce: string,
|
|
352
|
+
ttlMs: number,
|
|
353
|
+
) => Promise<boolean> | boolean;
|
|
354
|
+
behaviorReplayCheck?: (
|
|
355
|
+
digest: string,
|
|
356
|
+
ttlMs: number,
|
|
357
|
+
) => Promise<boolean> | boolean;
|
|
187
358
|
}
|
|
188
359
|
|
|
189
360
|
export interface ValidateChallengeSuccess {
|
|
@@ -193,17 +364,18 @@ export interface ValidateChallengeSuccess {
|
|
|
193
364
|
expires: number;
|
|
194
365
|
scope: string | null;
|
|
195
366
|
iat?: number;
|
|
196
|
-
/** Non-gating automation signals (e.g. `native_tamper`). Raise difficulty, do not block. */
|
|
197
367
|
riskFlags: string[];
|
|
368
|
+
behavior?: BehaviorResult;
|
|
369
|
+
agent?: AgentResult;
|
|
198
370
|
}
|
|
199
371
|
|
|
200
372
|
export interface ValidateChallengeFailure {
|
|
201
373
|
success: false;
|
|
202
374
|
reason: string;
|
|
203
375
|
instr_error?: boolean;
|
|
204
|
-
/** Ids of the gating detection checks that failed, when reason is `instr_automated_browser`. */
|
|
205
376
|
blockedBy?: string[];
|
|
206
377
|
error?: string;
|
|
378
|
+
behavior?: BehaviorResult;
|
|
207
379
|
}
|
|
208
380
|
|
|
209
381
|
export type ValidateChallengeResult =
|
|
@@ -221,25 +393,16 @@ export function validateChallenge(
|
|
|
221
393
|
opts?: ValidateChallengeOptions,
|
|
222
394
|
): Promise<ValidateChallengeResult>;
|
|
223
395
|
|
|
224
|
-
// ─── RSW (format-2) helpers ────────────────────────────────────────────────
|
|
225
|
-
/** Generates a fresh 2048-bit-by-default RSA-style modulus + factors. Expensive
|
|
226
|
-
* (~700 ms to several seconds depending on prime-roll luck). Generate once
|
|
227
|
-
* at boot and persist the result. */
|
|
228
396
|
export function generateRswKeypair(bits?: number): RswKeypair;
|
|
229
397
|
|
|
230
|
-
/** JSON-friendly form, safe to write to disk or env. */
|
|
231
398
|
export function serializeRswKeypair(kp: RswKeypair): SerializedRswKeypair;
|
|
232
399
|
export function deserializeRswKeypair(s: SerializedRswKeypair): RswKeypair;
|
|
233
400
|
|
|
234
|
-
/** Build a minter for a specific squaring count `t`. Memoized when used via
|
|
235
|
-
* `generateChallenge({ keypair, t })`; this direct API is for callers who
|
|
236
|
-
* want full control. */
|
|
237
401
|
export function buildRswMinter(
|
|
238
402
|
args: { N: bigint; p: bigint; q: bigint; t: number; g?: bigint },
|
|
239
403
|
opts?: { bits?: number },
|
|
240
404
|
): RswMinter;
|
|
241
405
|
|
|
242
|
-
// ─── HashWX (format-2) helpers ─────────────────────────────────────────────
|
|
243
406
|
export const DEFAULT_HASHWX_DIFFICULTY: number;
|
|
244
407
|
export const DEFAULT_HASHWX_NONCES_PER_HASH: number;
|
|
245
408
|
export const DEFAULT_HASHWX_CHALLENGE_COUNT: number;
|
|
@@ -250,36 +413,28 @@ export interface HashwxState {
|
|
|
250
413
|
seedPtr: number;
|
|
251
414
|
}
|
|
252
415
|
|
|
253
|
-
/** Supplies a precompiled HashWX module for runtimes that forbid compiling
|
|
254
|
-
* wasm at runtime. Cloudflare Workers pick up
|
|
255
|
-
* the bundled `hashwx.wasm` automatically. Must be called before the first
|
|
256
|
-
* verification or `hashwxReady()`. */
|
|
257
416
|
export function setHashwxModule(module: WebAssembly.Module): void;
|
|
258
417
|
|
|
259
|
-
/** Compiles and initializes the embedded HashWX wasm. Called automatically on
|
|
260
|
-
* first verification; await it at boot to move the cost off the first
|
|
261
|
-
* request. Resolves to a state handle reusable with `hashwxHash`. */
|
|
262
418
|
export function hashwxReady(): Promise<HashwxState>;
|
|
263
419
|
|
|
264
|
-
/** Largest 64-bit hash accepted at the given difficulty. */
|
|
265
420
|
export function hashwxTarget(difficulty: number | bigint): bigint;
|
|
266
421
|
|
|
267
|
-
/** sha256(challenge || u64le(block)), the seed for one generated hash
|
|
268
|
-
* function. Each seed covers `hashwxNoncesPerHash` consecutive nonces. */
|
|
269
422
|
export function hashwxSeed(
|
|
270
423
|
challenge: Uint8Array,
|
|
271
424
|
block: number | bigint,
|
|
272
425
|
): Uint8Array;
|
|
273
426
|
|
|
274
|
-
/** Generates the hash function for `seed` and runs it over `nonce`. Returns an
|
|
275
|
-
* unsigned 64-bit value; a solution needs it at or below `hashwxTarget(d)`. */
|
|
276
427
|
export function hashwxHash(
|
|
277
428
|
state: HashwxState,
|
|
278
429
|
seed: Uint8Array,
|
|
279
430
|
nonce: bigint,
|
|
280
431
|
): bigint;
|
|
281
432
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
433
|
+
export interface DetectAutomationOptions {
|
|
434
|
+
fontStackCount?: number;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function detectAutomation(
|
|
438
|
+
vector: ProbeVector,
|
|
439
|
+
opts?: DetectAutomationOptions,
|
|
440
|
+
): DetectionResult;
|