@rester159/blacktip 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +95 -0
  3. package/dist/behavioral/parsers.d.ts +89 -0
  4. package/dist/behavioral/parsers.d.ts.map +1 -0
  5. package/dist/behavioral/parsers.js +223 -0
  6. package/dist/behavioral/parsers.js.map +1 -0
  7. package/dist/blacktip.d.ts +86 -0
  8. package/dist/blacktip.d.ts.map +1 -1
  9. package/dist/blacktip.js +193 -0
  10. package/dist/blacktip.js.map +1 -1
  11. package/dist/browser-core.d.ts.map +1 -1
  12. package/dist/browser-core.js +125 -33
  13. package/dist/browser-core.js.map +1 -1
  14. package/dist/diagnostics.d.ts +150 -0
  15. package/dist/diagnostics.d.ts.map +1 -0
  16. package/dist/diagnostics.js +389 -0
  17. package/dist/diagnostics.js.map +1 -0
  18. package/dist/identity-pool.d.ts +160 -0
  19. package/dist/identity-pool.d.ts.map +1 -0
  20. package/dist/identity-pool.js +288 -0
  21. package/dist/identity-pool.js.map +1 -0
  22. package/dist/index.d.ts +7 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/tls-side-channel.d.ts +82 -0
  27. package/dist/tls-side-channel.d.ts.map +1 -0
  28. package/dist/tls-side-channel.js +241 -0
  29. package/dist/tls-side-channel.js.map +1 -0
  30. package/dist/types.d.ts +26 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js.map +1 -1
  33. package/docs/akamai-bypass.md +257 -0
  34. package/docs/anti-bot-validation.md +84 -0
  35. package/docs/calibration-validation.md +93 -0
  36. package/docs/identity-pool.md +176 -0
  37. package/docs/tls-side-channel.md +83 -0
  38. package/native/tls-client/go.mod +21 -0
  39. package/native/tls-client/go.sum +36 -0
  40. package/native/tls-client/main.go +216 -0
  41. package/package.json +8 -2
  42. package/scripts/fit-cmu-keystroke.mjs +186 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Dataset parsers for behavioral calibration.
3
+ *
4
+ * The calibration scaffold (`./calibration.ts`) defines normalized
5
+ * `MouseMovement` and `TypingSession` shapes plus distribution fitters.
6
+ * What was missing through v0.2.0 was the bridge from real public datasets
7
+ * into those shapes — without parsers, the scaffold required every user
8
+ * to write their own ETL, which kept "Tier 2 behavioral calibration" on
9
+ * the deferred list.
10
+ *
11
+ * This module ships parsers for the two datasets we point users at
12
+ * most often:
13
+ *
14
+ * 1. **CMU Keystroke Dynamics** (Killourhy & Maxion, 2009).
15
+ * Free for research. CSV format with columns `subject`, `sessionIndex`,
16
+ * `rep`, then for the fixed phrase `.tie5Roanl` a tuple of
17
+ * `H.<key>` (hold), `DD.<k1>.<k2>` (down-down latency), and
18
+ * `UD.<k1>.<k2>` (up-down = flight time). 51 subjects × 8 sessions
19
+ * × 50 repetitions = 20,400 phrases.
20
+ *
21
+ * 2. **Balabit Mouse Dynamics Challenge** (Antal & Egyed-Zsigmond, 2014).
22
+ * Free for research. Per-session CSV with columns
23
+ * `record_timestamp`, `client_timestamp`, `button`, `state`, `x`, `y`.
24
+ * Each row is a single mouse event; consecutive `Mouse` rows
25
+ * followed by a `Pressed` row form one movement.
26
+ *
27
+ * Plus a generic JSON loader for users with their own telemetry exported
28
+ * in the `MouseMovement` / `TypingSession` shapes directly.
29
+ *
30
+ * The parsers do NOT download the datasets — both have ToS that say "do
31
+ * not redistribute". Users acquire the data themselves and feed the file
32
+ * contents in as a string. We just turn raw text into normalized samples.
33
+ */
34
+ // ── CMU Keystroke Dynamics ──
35
+ /** The fixed phrase typed by every CMU subject. Used to map column index → key. */
36
+ export const CMU_PHRASE = '.tie5Roanl';
37
+ /**
38
+ * Parse the CMU Keystroke Dynamics CSV (DSL-StrongPasswordData.csv).
39
+ *
40
+ * Each row is one repetition of the phrase `.tie5Roanl` plus Return,
41
+ * yielding 11 keys, 11 hold-times, 10 down-down and 10 up-down latencies.
42
+ * We normalize each row into one `TypingSession` of 11 keystrokes.
43
+ *
44
+ * The CSV looks like:
45
+ * subject,sessionIndex,rep,H.period,DD.period.t,UD.period.t,H.t,DD.t.i,UD.t.i,H.i,...
46
+ * s002,1,1,0.1491,0.3979,0.2488,0.1069,0.1674,0.0605,...
47
+ *
48
+ * All time values are in **seconds** in the source file; we convert to
49
+ * milliseconds.
50
+ *
51
+ * Returns one `TypingSession` per CSV row. Pass these straight into
52
+ * `fitTypingDynamics()`.
53
+ */
54
+ export function parseCmuKeystrokeCsv(csvText) {
55
+ const lines = csvText.trim().split(/\r?\n/);
56
+ if (lines.length < 2)
57
+ return [];
58
+ const header = lines[0].split(',').map((h) => h.trim());
59
+ // Build column index maps for the H.<key> (hold) and UD.<k1>.<k2> (flight) columns.
60
+ // We use UD (up-down) as flight time: time from previous key release to next key down.
61
+ const holdIdx = new Map(); // keyIndex → column
62
+ const flightIdx = new Map(); // keyIndex (1..n-1) → column
63
+ // The phrase keys, in order. The CMU dataset includes Return at the
64
+ // end, so the full key sequence is `.tie5Roanl` plus Enter (column
65
+ // labeled `Return` in the source).
66
+ const keys = [...CMU_PHRASE.split(''), 'Return'];
67
+ for (let i = 0; i < keys.length; i++) {
68
+ const key = cmuKeyLabel(keys[i]);
69
+ const colName = `H.${key}`;
70
+ const idx = header.indexOf(colName);
71
+ if (idx >= 0)
72
+ holdIdx.set(i, idx);
73
+ }
74
+ for (let i = 1; i < keys.length; i++) {
75
+ const prev = cmuKeyLabel(keys[i - 1]);
76
+ const cur = cmuKeyLabel(keys[i]);
77
+ const colName = `UD.${prev}.${cur}`;
78
+ const idx = header.indexOf(colName);
79
+ if (idx >= 0)
80
+ flightIdx.set(i, idx);
81
+ }
82
+ const sessions = [];
83
+ for (let rowIdx = 1; rowIdx < lines.length; rowIdx++) {
84
+ const cells = lines[rowIdx].split(',');
85
+ if (cells.length < 4)
86
+ continue;
87
+ const keystrokes = [];
88
+ for (let i = 0; i < keys.length; i++) {
89
+ const holdCol = holdIdx.get(i);
90
+ const flightCol = flightIdx.get(i);
91
+ const holdSec = holdCol != null ? parseFloat(cells[holdCol] ?? '') : NaN;
92
+ const flightSec = i === 0 ? 0 : flightCol != null ? parseFloat(cells[flightCol] ?? '') : NaN;
93
+ if (!Number.isFinite(holdSec))
94
+ continue;
95
+ keystrokes.push({
96
+ key: keys[i],
97
+ flightTimeMs: Number.isFinite(flightSec) ? Math.max(0, flightSec * 1000) : 0,
98
+ holdTimeMs: Math.max(0, holdSec * 1000),
99
+ });
100
+ }
101
+ if (keystrokes.length > 0) {
102
+ sessions.push({ keystrokes, phrase: CMU_PHRASE });
103
+ }
104
+ }
105
+ return sessions;
106
+ }
107
+ /**
108
+ * CMU's CSV uses specific labels for non-letter keys:
109
+ * - `.` → `period`
110
+ * - `5` → `five` (the dataset spells digits out)
111
+ * - `R` (capital, requires Shift) → `Shift.r`
112
+ * - Return → `Return`
113
+ * - lowercase letters are bare
114
+ */
115
+ function cmuKeyLabel(ch) {
116
+ if (ch === '.')
117
+ return 'period';
118
+ if (ch === '5')
119
+ return 'five';
120
+ if (ch === 'Return')
121
+ return 'Return';
122
+ if (ch === 'R')
123
+ return 'Shift.r';
124
+ return ch;
125
+ }
126
+ // ── Balabit Mouse Dynamics ──
127
+ /**
128
+ * Parse a single Balabit Mouse Dynamics session CSV.
129
+ *
130
+ * Each row is one mouse event:
131
+ * record_timestamp,client_timestamp,button,state,x,y
132
+ * 1424866316.93,0.000,NoButton,Move,1192,529
133
+ * 1424866316.99,0.064,NoButton,Move,1183,532
134
+ * ...
135
+ * 1424866317.84,0.911,Left,Pressed,820,440
136
+ *
137
+ * We segment into movements: each contiguous run of `Move`/`Drag` rows
138
+ * followed by a `Pressed` row becomes one `MouseMovement`. The press row
139
+ * is the click target. Movements that don't end in a press are also
140
+ * recorded but with `endedWithClick: false` — these are navigation moves.
141
+ *
142
+ * Time is normalized to milliseconds since the first sample of the
143
+ * movement.
144
+ */
145
+ export function parseBalabitMouseCsv(csvText) {
146
+ const lines = csvText.trim().split(/\r?\n/);
147
+ if (lines.length < 2)
148
+ return [];
149
+ // Header detection — Balabit ships either with or without a header row.
150
+ let startIdx = 0;
151
+ if (/[A-Za-z]/.test(lines[0].split(',')[0] ?? ''))
152
+ startIdx = 1;
153
+ const movements = [];
154
+ let current = [];
155
+ let currentStart = null;
156
+ const flush = (endedWithClick) => {
157
+ if (current.length >= 2) {
158
+ movements.push({ samples: current, endedWithClick });
159
+ }
160
+ current = [];
161
+ currentStart = null;
162
+ };
163
+ for (let i = startIdx; i < lines.length; i++) {
164
+ const cells = lines[i].split(',');
165
+ if (cells.length < 6)
166
+ continue;
167
+ const clientTs = parseFloat(cells[1] ?? '');
168
+ const state = (cells[3] ?? '').trim();
169
+ const x = parseFloat(cells[4] ?? '');
170
+ const y = parseFloat(cells[5] ?? '');
171
+ if (!Number.isFinite(clientTs) || !Number.isFinite(x) || !Number.isFinite(y))
172
+ continue;
173
+ const tMs = clientTs * 1000;
174
+ if (currentStart == null)
175
+ currentStart = tMs;
176
+ if (state === 'Move' || state === 'Drag') {
177
+ current.push({ timestampMs: tMs - currentStart, x, y });
178
+ }
179
+ else if (state === 'Pressed') {
180
+ // Treat the press location as the target.
181
+ current.push({
182
+ timestampMs: tMs - currentStart,
183
+ x,
184
+ y,
185
+ targetX: x,
186
+ targetY: y,
187
+ // Balabit doesn't ship target widths — leave undefined so the
188
+ // Fitts' Law fitter skips these and falls back to canonical values.
189
+ });
190
+ flush(true);
191
+ }
192
+ else if (state === 'Released') {
193
+ // End-of-click; ignore.
194
+ }
195
+ else {
196
+ // Scroll, etc. — terminate any in-flight movement.
197
+ flush(false);
198
+ }
199
+ }
200
+ // Flush trailing movement if any
201
+ flush(false);
202
+ return movements;
203
+ }
204
+ // ── Generic JSON loader ──
205
+ /**
206
+ * Generic loader for users who already export their telemetry in the
207
+ * normalized shapes. Accepts JSON of the form:
208
+ *
209
+ * { "movements": MouseMovement[], "sessions": TypingSession[] }
210
+ *
211
+ * Either field may be absent. Returns the parsed object with empty
212
+ * defaults filled in. This is the "bring your own data" path — most
213
+ * production users will write a tiny exporter on their own telemetry
214
+ * pipeline that emits this shape, then feed it through `fitFromSamples()`.
215
+ */
216
+ export function parseGenericTelemetryJson(jsonText) {
217
+ const parsed = JSON.parse(jsonText);
218
+ return {
219
+ movements: Array.isArray(parsed.movements) ? parsed.movements : [],
220
+ sessions: Array.isArray(parsed.sessions) ? parsed.sessions : [],
221
+ };
222
+ }
223
+ //# sourceMappingURL=parsers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsers.js","sourceRoot":"","sources":["../../src/behavioral/parsers.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,8 @@ import type { Frame, ElementHandle as PlaywrightElementHandle } from 'patchright
4
4
  import { BehavioralEngine } from './behavioral-engine.js';
5
5
  import { ElementFinder } from './element-finder.js';
6
6
  import { Logger } from './logging.js';
7
+ import { type FingerprintSnapshot, type IpReputationResult, type AkamaiTestResult, type AntiBotTestResult } from './diagnostics.js';
8
+ import { type TlsRequest, type TlsResponse } from './tls-side-channel.js';
7
9
  import type { BlackTipConfig, ProfileConfig, ActionResult, NavigateResult, ScreenshotResult, WaitResult, TabInfo, FrameInfo, ClickOptions, TypeOptions, ScrollOptions, HoverOptions, SelectOptions, PressKeyOptions, UploadFileOptions, NavigateOptions, ScreenshotOptions, WaitForOptions, WaitForNavigationOptions, ExtractTextOptions, PageContentOptions } from './types.js';
8
10
  /**
9
11
  * BlackTip — Stealth browser instrument for AI agents.
@@ -53,6 +55,7 @@ export declare class BlackTip extends EventEmitter {
53
55
  private core;
54
56
  private engine;
55
57
  private finder;
58
+ private tlsChannel;
56
59
  private logger;
57
60
  private config;
58
61
  private customProfiles;
@@ -200,6 +203,89 @@ export declare class BlackTip extends EventEmitter {
200
203
  hidden: number;
201
204
  selectors: string[];
202
205
  }>;
206
+ /**
207
+ * Capture the active session's TLS, HTTP/2, and HTTP header fingerprint
208
+ * by navigating to tls.peet.ws/api/all and httpbin.org/headers.
209
+ *
210
+ * The most important field in the result is `headers.uaConsistent`. If
211
+ * that's `false`, you're emitting a User-Agent / Sec-Ch-Ua mismatch
212
+ * which Akamai / DataDome / PerimeterX will flag as a textbook spoofing
213
+ * tell. v0.2.0 fixed this for the default config; if you see it, your
214
+ * code is overriding the User-Agent in a way that doesn't update the
215
+ * client hint headers in lockstep.
216
+ */
217
+ captureFingerprint(): Promise<FingerprintSnapshot>;
218
+ /**
219
+ * Query the active session's egress IP and ASN, score it against
220
+ * known datacenter / residential ASN patterns, and return a structured
221
+ * result. Uses the free ipinfo.io endpoint.
222
+ *
223
+ * If `isDatacenter: true`, your IP is on a known cloud provider's
224
+ * range and Akamai will almost certainly flag it. Use a residential
225
+ * proxy or a different network.
226
+ */
227
+ checkIpReputation(): Promise<IpReputationResult>;
228
+ /**
229
+ * Visit an Akamai-protected URL and report the result with diagnosis.
230
+ * Recognizes the Akamai Access Denied error page format and extracts
231
+ * the reference number for triage.
232
+ *
233
+ * Use this in CI as a regression check, or interactively when you're
234
+ * trying to figure out why a specific target is blocking you.
235
+ */
236
+ testAgainstAkamai(url: string): Promise<AkamaiTestResult>;
237
+ /**
238
+ * Multi-vendor anti-bot probe. Recognises Akamai, DataDome, Cloudflare,
239
+ * PerimeterX/HUMAN, Imperva, Kasada, and Arkose. Reports both the
240
+ * vendors that served a block/challenge AND the vendor signals (cookies,
241
+ * scripts) present on a passing page — so you can verify the target is
242
+ * actually protected and BlackTip is sliding past it, not a false negative
243
+ * on an unprotected URL.
244
+ */
245
+ testAgainstAntiBot(url: string): Promise<AntiBotTestResult>;
246
+ /**
247
+ * Perform an HTTP request through the bogdanfinn/tls-client Go daemon
248
+ * with a real Chrome TLS ClientHello, real H2 frame settings, and real
249
+ * H2 frame order. Lazily spawns the daemon on first call and reuses it
250
+ * across subsequent calls in the same BlackTip session.
251
+ *
252
+ * Use this when an edge gates the very first request before BlackTip's
253
+ * browser has a usable session — make the gating request via the side
254
+ * channel, then call `bt.injectTlsCookies(resp)` to push the resulting
255
+ * cookies into the browser session before navigating.
256
+ *
257
+ * Requires the Go daemon binary at `native/tls-client/blacktip-tls[.exe]`.
258
+ * Build it once with: `cd native/tls-client && go build .`
259
+ */
260
+ fetchWithTls(req: TlsRequest): Promise<TlsResponse>;
261
+ /**
262
+ * Inject cookies returned by `fetchWithTls()` into the browser session
263
+ * so a subsequent `bt.navigate()` carries the same edge-issued tokens
264
+ * the gating request earned. The cookies are filtered to those whose
265
+ * domain matches the target URL's eTLD+1.
266
+ */
267
+ injectTlsCookies(resp: TlsResponse, targetUrl?: string): Promise<number>;
268
+ /**
269
+ * Visit a sequence of "normal" sites with realistic dwell times before
270
+ * the target navigation. Accumulates cookies, populates History API,
271
+ * and triggers the natural behavioral signals Akamai's profiler
272
+ * expects to see from a real user.
273
+ *
274
+ * Default sites are a small list of safe, fast-loading targets that
275
+ * don't run heavy detection themselves. Override `sites` to use your
276
+ * own list, e.g. industry-specific sites for the target you're warming
277
+ * up against.
278
+ *
279
+ * Pass an empty `sites: []` array to skip warming and just dwell on
280
+ * the current page (useful as a "let the page settle" pause).
281
+ */
282
+ warmSession(options?: {
283
+ sites?: string[];
284
+ dwellMsRange?: [number, number];
285
+ }): Promise<{
286
+ visited: string[];
287
+ durationMs: number;
288
+ }>;
203
289
  frame(selector: string): Promise<BlackTipFrame>;
204
290
  frames(): Promise<FrameInfo[]>;
205
291
  getTabs(): Promise<TabInfo[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"blacktip.d.ts","sourceRoot":"","sources":["../src/blacktip.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blacktip.d.ts","sourceRoot":"","sources":["../src/blacktip.ts"],"names":[],"mappings":"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"}
package/dist/blacktip.js CHANGED
@@ -4,6 +4,8 @@ import { BrowserCore } from './browser-core.js';
4
4
  import { BehavioralEngine, HUMAN_PROFILE, SCRAPER_PROFILE } from './behavioral-engine.js';
5
5
  import { ElementFinder } from './element-finder.js';
6
6
  import { Logger } from './logging.js';
7
+ import { captureFingerprint as diagnosticsCaptureFingerprint, checkIpReputation as diagnosticsCheckIpReputation, testAgainstAkamai as diagnosticsTestAgainstAkamai, testAgainstAntiBot as diagnosticsTestAgainstAntiBot, } from './diagnostics.js';
8
+ import { TlsSideChannel } from './tls-side-channel.js';
7
9
  const RETRY_STRATEGIES = ['standard', 'wait', 'reload', 'altSelector', 'scroll', 'clearOverlays'];
8
10
  /**
9
11
  * Text patterns that suggest an action is high-importance — submit,
@@ -90,6 +92,7 @@ export class BlackTip extends EventEmitter {
90
92
  core;
91
93
  engine;
92
94
  finder;
95
+ tlsChannel = null;
93
96
  logger;
94
97
  config;
95
98
  customProfiles = new Map();
@@ -127,9 +130,44 @@ export class BlackTip extends EventEmitter {
127
130
  async launch() {
128
131
  await this.core.launch();
129
132
  this.launched = true;
133
+ // IP reputation gate (v0.4.0). When `requireResidentialIp` is set,
134
+ // we run the same check as `bt.checkIpReputation()` and either warn
135
+ // or throw based on the verdict. The check itself navigates to
136
+ // ipinfo.io, so we do it after launch is complete.
137
+ const gate = this.config.requireResidentialIp;
138
+ if (gate) {
139
+ try {
140
+ const verdict = await diagnosticsCheckIpReputation(this);
141
+ if (verdict.isDatacenter) {
142
+ const msg = `IP reputation gate: egress IP ${verdict.ip} (${verdict.org}) is on a known datacenter ASN. ${verdict.notes.join(' ')}`;
143
+ if (gate === 'warn') {
144
+ this.logger.warn(msg);
145
+ }
146
+ else {
147
+ // 'throw' or boolean true
148
+ await this.core.close();
149
+ this.launched = false;
150
+ throw new Error(msg);
151
+ }
152
+ }
153
+ }
154
+ catch (err) {
155
+ // If the check itself fails (offline, etc.) and the gate is
156
+ // 'throw', we re-raise; if 'warn', we log and continue.
157
+ if (err instanceof Error && err.message.startsWith('IP reputation gate:'))
158
+ throw err;
159
+ if (gate !== 'warn') {
160
+ this.logger.warn(`IP reputation gate check failed (allowing launch): ${err instanceof Error ? err.message : String(err)}`);
161
+ }
162
+ }
163
+ }
130
164
  return BlackTip.agentGuide();
131
165
  }
132
166
  async close() {
167
+ if (this.tlsChannel) {
168
+ await this.tlsChannel.close().catch(() => undefined);
169
+ this.tlsChannel = null;
170
+ }
133
171
  await this.core.close();
134
172
  this.launched = false;
135
173
  }
@@ -900,6 +938,161 @@ export class BlackTip extends EventEmitter {
900
938
  return { hidden: count, selectors: hiddenSelectors };
901
939
  })()`);
902
940
  }
941
+ // ── Stealth diagnostics (v0.2.0) ──
942
+ /**
943
+ * Capture the active session's TLS, HTTP/2, and HTTP header fingerprint
944
+ * by navigating to tls.peet.ws/api/all and httpbin.org/headers.
945
+ *
946
+ * The most important field in the result is `headers.uaConsistent`. If
947
+ * that's `false`, you're emitting a User-Agent / Sec-Ch-Ua mismatch
948
+ * which Akamai / DataDome / PerimeterX will flag as a textbook spoofing
949
+ * tell. v0.2.0 fixed this for the default config; if you see it, your
950
+ * code is overriding the User-Agent in a way that doesn't update the
951
+ * client hint headers in lockstep.
952
+ */
953
+ async captureFingerprint() {
954
+ this.ensureLaunched();
955
+ return diagnosticsCaptureFingerprint(this);
956
+ }
957
+ /**
958
+ * Query the active session's egress IP and ASN, score it against
959
+ * known datacenter / residential ASN patterns, and return a structured
960
+ * result. Uses the free ipinfo.io endpoint.
961
+ *
962
+ * If `isDatacenter: true`, your IP is on a known cloud provider's
963
+ * range and Akamai will almost certainly flag it. Use a residential
964
+ * proxy or a different network.
965
+ */
966
+ async checkIpReputation() {
967
+ this.ensureLaunched();
968
+ return diagnosticsCheckIpReputation(this);
969
+ }
970
+ /**
971
+ * Visit an Akamai-protected URL and report the result with diagnosis.
972
+ * Recognizes the Akamai Access Denied error page format and extracts
973
+ * the reference number for triage.
974
+ *
975
+ * Use this in CI as a regression check, or interactively when you're
976
+ * trying to figure out why a specific target is blocking you.
977
+ */
978
+ async testAgainstAkamai(url) {
979
+ this.ensureLaunched();
980
+ return diagnosticsTestAgainstAkamai(this, url);
981
+ }
982
+ /**
983
+ * Multi-vendor anti-bot probe. Recognises Akamai, DataDome, Cloudflare,
984
+ * PerimeterX/HUMAN, Imperva, Kasada, and Arkose. Reports both the
985
+ * vendors that served a block/challenge AND the vendor signals (cookies,
986
+ * scripts) present on a passing page — so you can verify the target is
987
+ * actually protected and BlackTip is sliding past it, not a false negative
988
+ * on an unprotected URL.
989
+ */
990
+ async testAgainstAntiBot(url) {
991
+ this.ensureLaunched();
992
+ return diagnosticsTestAgainstAntiBot(this, url);
993
+ }
994
+ // ── TLS side-channel (v0.3.0) ──
995
+ /**
996
+ * Perform an HTTP request through the bogdanfinn/tls-client Go daemon
997
+ * with a real Chrome TLS ClientHello, real H2 frame settings, and real
998
+ * H2 frame order. Lazily spawns the daemon on first call and reuses it
999
+ * across subsequent calls in the same BlackTip session.
1000
+ *
1001
+ * Use this when an edge gates the very first request before BlackTip's
1002
+ * browser has a usable session — make the gating request via the side
1003
+ * channel, then call `bt.injectTlsCookies(resp)` to push the resulting
1004
+ * cookies into the browser session before navigating.
1005
+ *
1006
+ * Requires the Go daemon binary at `native/tls-client/blacktip-tls[.exe]`.
1007
+ * Build it once with: `cd native/tls-client && go build .`
1008
+ */
1009
+ async fetchWithTls(req) {
1010
+ if (!this.tlsChannel) {
1011
+ this.tlsChannel = await TlsSideChannel.spawn();
1012
+ }
1013
+ return this.tlsChannel.fetch(req);
1014
+ }
1015
+ /**
1016
+ * Inject cookies returned by `fetchWithTls()` into the browser session
1017
+ * so a subsequent `bt.navigate()` carries the same edge-issued tokens
1018
+ * the gating request earned. The cookies are filtered to those whose
1019
+ * domain matches the target URL's eTLD+1.
1020
+ */
1021
+ async injectTlsCookies(resp, targetUrl) {
1022
+ this.ensureLaunched();
1023
+ const targetHost = (() => {
1024
+ if (!targetUrl)
1025
+ return null;
1026
+ try {
1027
+ return new URL(targetUrl).hostname;
1028
+ }
1029
+ catch {
1030
+ return null;
1031
+ }
1032
+ })();
1033
+ const filtered = resp.cookies.filter((c) => {
1034
+ if (!targetHost)
1035
+ return true;
1036
+ const cookieDomain = c.domain.replace(/^\./, '');
1037
+ return targetHost === cookieDomain || targetHost.endsWith('.' + cookieDomain);
1038
+ });
1039
+ if (filtered.length === 0)
1040
+ return 0;
1041
+ await this.setCookies(filtered.map((c) => ({
1042
+ name: c.name,
1043
+ value: c.value,
1044
+ domain: c.domain.startsWith('.') ? c.domain : '.' + c.domain,
1045
+ path: c.path || '/',
1046
+ })));
1047
+ return filtered.length;
1048
+ }
1049
+ // ── Session warming (v0.2.0) ──
1050
+ /**
1051
+ * Visit a sequence of "normal" sites with realistic dwell times before
1052
+ * the target navigation. Accumulates cookies, populates History API,
1053
+ * and triggers the natural behavioral signals Akamai's profiler
1054
+ * expects to see from a real user.
1055
+ *
1056
+ * Default sites are a small list of safe, fast-loading targets that
1057
+ * don't run heavy detection themselves. Override `sites` to use your
1058
+ * own list, e.g. industry-specific sites for the target you're warming
1059
+ * up against.
1060
+ *
1061
+ * Pass an empty `sites: []` array to skip warming and just dwell on
1062
+ * the current page (useful as a "let the page settle" pause).
1063
+ */
1064
+ async warmSession(options = {}) {
1065
+ this.ensureLaunched();
1066
+ const sites = options.sites ?? [
1067
+ 'https://www.google.com/',
1068
+ 'https://en.wikipedia.org/wiki/Special:Random',
1069
+ 'https://news.ycombinator.com/',
1070
+ ];
1071
+ const [dwellMin, dwellMax] = options.dwellMsRange ?? [3000, 7000];
1072
+ const startedAt = Date.now();
1073
+ const visited = [];
1074
+ for (const url of sites) {
1075
+ try {
1076
+ await this.navigate(url);
1077
+ // Random dwell to mimic human reading time
1078
+ const dwell = Math.floor(dwellMin + Math.random() * (dwellMax - dwellMin));
1079
+ // Simulate a small scroll during dwell — humans don't sit still
1080
+ try {
1081
+ await this.scroll({ direction: 'down', amount: 200 + Math.floor(Math.random() * 400) });
1082
+ }
1083
+ catch { /* scroll may fail on some pages, harmless */ }
1084
+ await this.sleep(dwell);
1085
+ visited.push(url);
1086
+ }
1087
+ catch (err) {
1088
+ this.logger.warn('warmSession site failed, continuing', {
1089
+ url,
1090
+ error: err instanceof Error ? err.message : String(err),
1091
+ });
1092
+ }
1093
+ }
1094
+ return { visited, durationMs: Date.now() - startedAt };
1095
+ }
903
1096
  // ── Iframe Support ──
904
1097
  async frame(selector) {
905
1098
  this.ensureLaunched();