@rester159/blacktip 0.2.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 (39) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +21 -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 +34 -1
  8. package/dist/blacktip.d.ts.map +1 -1
  9. package/dist/blacktip.js +105 -1
  10. package/dist/blacktip.js.map +1 -1
  11. package/dist/diagnostics.d.ts +31 -0
  12. package/dist/diagnostics.d.ts.map +1 -1
  13. package/dist/diagnostics.js +146 -0
  14. package/dist/diagnostics.js.map +1 -1
  15. package/dist/identity-pool.d.ts +160 -0
  16. package/dist/identity-pool.d.ts.map +1 -0
  17. package/dist/identity-pool.js +288 -0
  18. package/dist/identity-pool.js.map +1 -0
  19. package/dist/index.d.ts +7 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +7 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/tls-side-channel.d.ts +82 -0
  24. package/dist/tls-side-channel.d.ts.map +1 -0
  25. package/dist/tls-side-channel.js +241 -0
  26. package/dist/tls-side-channel.js.map +1 -0
  27. package/dist/types.d.ts +15 -0
  28. package/dist/types.d.ts.map +1 -1
  29. package/dist/types.js.map +1 -1
  30. package/docs/akamai-bypass.md +257 -0
  31. package/docs/anti-bot-validation.md +84 -0
  32. package/docs/calibration-validation.md +93 -0
  33. package/docs/identity-pool.md +176 -0
  34. package/docs/tls-side-channel.md +83 -0
  35. package/native/tls-client/go.mod +21 -0
  36. package/native/tls-client/go.sum +36 -0
  37. package/native/tls-client/main.go +216 -0
  38. package/package.json +8 -2
  39. package/scripts/fit-cmu-keystroke.mjs +186 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,190 @@
1
+ # Changelog
2
+
3
+ All notable changes to **BlackTip** will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.4.0] — 2026-04-10
10
+
11
+ The "close the remaining gaps" release. v0.4.0 ships everything that had been accumulated since v0.2.0 plus three new pieces that close out the gaps named in the v0.3.0 wrap-up: a Kasada-validated pass on a real armed endpoint (Twitch), an `IdentityPool` for long-running session and identity rotation, and a launch-time IP reputation gate. There is no separate v0.3.0 release on npm — the v0.3.0 work was developed in the same release cycle and rolls into 0.4.0 as one shipment.
12
+
13
+ ### Added — IdentityPool for long-running session rotation
14
+
15
+ - **`IdentityPool`** in `src/identity-pool.ts` — long-running identity management. An identity is the union of cookies, localStorage, proxy, device profile, behavior profile, locale, and timezone. The pool persists to a JSON file so identities survive restarts. Each identity has a per-domain burn list so an identity blocked on opentable.com is still eligible for amazon.com. Composes `SnapshotManager` (for cookies + storage) and `ProxyPool` (for proxy selection + ban feedback).
16
+ - **Rotation policy** — `maxUses` and `maxAgeMs` auto-burn thresholds, plus `preferLeastRecentlyUsed` for fair distribution across identities.
17
+ - **Proxy feedback loop** — `pool.markBurned(id, reason, 'opentable.com')` reports the ban to the bound `ProxyPool` so the next identity drawn from the pool won't reuse the same proxy on the same target until the ban window decays. This is the loop that was missing in v0.2.0 — proxies could go stale silently because nothing was telling the pool which ones had been blocked.
18
+ - **`pool.applyToConfig(identity, baseConfig?)`** produces a `BlackTipConfig` ready to pass to `new BlackTip(config)`. **`pool.restoreSnapshot(bt, identity)`** rehydrates a saved session into the running browser. **`pool.captureSnapshot(bt, identity)`** saves the post-flow state back into the identity for next time.
19
+ - **16 new unit tests** in `tests/identity-pool.test.ts` covering CRUD, persistence round-trip, acquisition with per-domain burning, rotation policy auto-burn, proxy feedback to ProxyPool, and `applyToConfig`/`clearBurn` semantics.
20
+ - **`docs/identity-pool.md`** — usage guide, API reference, persistence format, and a worked example of an end-to-end flow with identity rotation and proxy feedback.
21
+
22
+ ### Added — IP reputation gate
23
+
24
+ - **`BlackTipConfig.requireResidentialIp`** — `'throw'`, `'warn'`, or `false` (default). When set, BlackTip runs `bt.checkIpReputation()` immediately after `launch()` and either warns or throws if the egress IP is on a known datacenter ASN. Use `'throw'` in production / CI where a flagged IP would burn a real account; use `'warn'` for local dev.
25
+ - This is the launch-time defensive companion to `IdentityPool`'s outbound proxy selection. Together they make it impossible for a misconfigured BlackTip to silently launch from a datacenter IP and burn a target.
26
+
27
+ ### Added — Kasada validated pass
28
+
29
+ - **Twitch (Kasada)** validated as a real-world pass. `bt.testAgainstAntiBot('https://www.twitch.tv/')` returns `passed: true` with `vendorSignals: [{ vendor: 'kasada', signal: 'script' }]` — the Kasada client script is detected on the page (proving the target is actually armed) and BlackTip slides past without triggering the challenge interstitial.
30
+ - This closes the only remaining "validated against" gap from the v0.3.0 wrap-up. BlackTip is now validated against eight commercial detector vendors on real targets: Akamai (OpenTable, BestBuy, Walmart), DataDome (Vinted), Cloudflare (Crunchbase, ChatGPT — with `cf_clearance` proof of managed-challenge auto-pass), PerimeterX/HUMAN (Walmart), and Kasada (Twitch).
31
+
32
+ ### Added — multi-vendor diagnostics
33
+
34
+ - **`bt.testAgainstAntiBot(url)`** — generic multi-vendor anti-bot probe. Recognises Akamai, DataDome, Cloudflare, PerimeterX/HUMAN, Imperva, Kasada, and Arkose. Reports both detected challenges/blocks AND vendor signals (cookies, scripts) on passing pages — so a `passed: true` result on a target with `vendorSignals: ['datadome']` is proof that BlackTip is actually sliding past DataDome, not a false negative on an unprotected URL.
35
+ - **`docs/anti-bot-validation.md`** — multi-vendor scoreboard for the v0.2.0 line with reproduction recipe, the cookie/script signal table, and caveats. Walmart (Akamai + PerimeterX simultaneously), BestBuy (Akamai, corrected from earlier PerimeterX classification), Vinted (DataDome with real catalog rendering), Crunchbase (Cloudflare), Ticketmaster, and OpenTable (Akamai Bot Manager regression) all pass — eight commercial-detector targets, eight passes from a residential connection.
36
+ - **2 new diagnostics integration tests** for `testAgainstAntiBot` — the shape check on a non-protected URL plus a real-DataDome regression that asserts the `datadome` cookie signal is captured against vinted.com.
37
+
38
+ ### Added — Tier 2 calibration parsers (v0.3.0 prep)
39
+
40
+ - **`src/behavioral/parsers.ts`** — dataset parsers that turn the calibration scaffold from a skeleton into an end-to-end pipeline. Ships:
41
+ - **`parseCmuKeystrokeCsv()`** — parses the CMU Keystroke Dynamics CSV (Killourhy & Maxion 2009) into `TypingSession[]`. Maps the fixed phrase `.tie5Roanl` plus Return through the CSV's `H.<key>`, `DD.<k1>.<k2>`, `UD.<k1>.<k2>` columns; converts seconds to milliseconds; uses up-down latency as flight time.
42
+ - **`parseBalabitMouseCsv()`** — parses Balabit Mouse Dynamics Challenge per-session CSVs into `MouseMovement[]`. Segments contiguous Move/Drag rows ending in a Pressed event into one movement; normalises timestamps to ms-since-movement-start; records click coordinates as the target.
43
+ - **`parseGenericTelemetryJson()`** — bring-your-own-data path for users who export telemetry in the normalized `MouseMovement` / `TypingSession` shapes directly.
44
+ - **15 new unit tests** in `tests/behavioral-parsers.test.ts` covering both parsers' shape, time-unit conversion, header detection, CRLF tolerance, and end-to-end fitting through `fitFromSamples()`. Synthetic fixtures only — neither dataset is bundled (both have no-redistribute terms).
45
+ - The parsers close the gap that had kept "Tier 2 behavioral calibration" on the deferred list since v0.2.0: users can now drop a CMU or Balabit file in and get a `CalibratedProfile` with no ETL of their own.
46
+
47
+ ### Added — TLS side-channel via bogdanfinn/tls-client
48
+
49
+ - **`bt.fetchWithTls(req)` and `bt.injectTlsCookies(resp, targetUrl)`** — perform an HTTP request through a Go-based daemon built on `bogdanfinn/tls-client` that presents a real Chrome TLS ClientHello, real H2 frame settings, and real H2 frame order. Then inject the resulting cookies into the BlackTip browser session before navigating. Solves the "edge gates the very first request before BlackTip's browser has a session" problem and is the v0.3.0 path for cross-platform UA spoofing now that L016 closed the broken context-level override.
50
+ - **`native/tls-client/main.go`** — newline-delimited JSON daemon. Stays alive across many requests so we don't pay subprocess startup cost per call. Per-request `id` field lets multiple `fetch()` calls run concurrently without interleaving.
51
+ - **`src/tls-side-channel.ts`** — Node-side wrapper. `TlsSideChannel.spawn()` lazily starts the daemon, manages the JSON-line protocol, parses Set-Cookie headers into structured cookies, cleans up on `close()`. The wrapper is also exported standalone for callers that want to use it without a `BlackTip` instance.
52
+ - **`docs/tls-side-channel.md`** — usage guide, build instructions (Go is the only build dep), validated TLS fingerprint (`JA4: t13d1516h2_8daaf6152771_d8a2da3f94cd`, GREASE first cipher, Chrome H2 frame order), and the four scenarios where the side-channel adds value over the browser alone.
53
+ - **4 new integration tests** in `tests/tls-side-channel.integration.test.ts` covering JA4/GREASE/H2 fingerprint, cookie parsing, concurrent requests via per-request IDs, and post-close rejection. Tests skip cleanly if the Go daemon binary isn't built.
54
+ - **End-to-end validated against OpenTable**: side-channel fetch returns 403 with three Akamai bot manager session cookies (`bm_ss`, `bm_s`, `bm_so`) — exactly the place a real browser would be after one request — and the browser session carries those cookies into the subsequent navigation.
55
+
56
+ ### Added — calibration validated against real CMU dataset
57
+
58
+ - **Real CMU Keystroke Dynamics validation.** The Tier 2 calibration parsers shipped earlier in the v0.3.0 cycle have now been validated end-to-end against the real CMU dataset (Killourhy & Maxion 2009 — 51 subjects × 8 sessions × 50 reps = 20,400 phrases of `.tie5Roanl`). Deterministic 80/20 subject split: train on 40 subjects → fit `TypingFit` → KS-distance compare against the 11 held-out subjects.
59
+ - **Result: calibrated profile beats canonical `HUMAN_PROFILE` by 53% on hold time** (KS distance 0.4297 → 0.2018) and **13.7% on flight time** (KS 0.4811 → 0.4152) on the held-out set. This is the first time BlackTip's behavioral pipeline has been validated end-to-end against a real public dataset; up through v0.2.0 the parameters were sane defaults, v0.3.0 makes them empirically grounded.
60
+ - **`scripts/fit-cmu-keystroke.mjs`** — reproducible validation script. Run with `node scripts/fit-cmu-keystroke.mjs` after downloading the CMU CSV to `data/cmu-keystroke/`. Writes the fitted `CalibratedProfile` to `data/cmu-keystroke/calibrated-profile.json` for users to load via `new BlackTip({ behaviorProfile: calibrated.profileConfig })`.
61
+ - **`docs/calibration-validation.md`** — methodology, fitted parameters, KS-distance table, what the result proves and what it does NOT prove, future calibration sources (Balabit, GREYC-NISLAB, Buffalo Free-Text).
62
+ - **CMU parser fixes**: the original parser used bare characters as CSV column labels but the CMU CSV uses `period` for `.`, `five` for `5`, and `Shift.r` for the capital `R` (which requires a Shift modifier in `.tie5Roanl`). Fixed and the synthetic-fixture tests updated to match.
63
+
64
+ ### Added — diagnostics fixes
65
+
66
+ - **Cookie detection now reads via the BlackTip cookies API instead of `document.cookie`.** The earlier implementation missed httpOnly cookies — and `cf_clearance`, `__cf_bm`, `_abck`, `datadome`, `bm_sz` are all httpOnly. This caused false negatives where a target was actually protected and BlackTip was passing it but the diagnostic reported `vendorSignals: []`. Fixed: `testAgainstAntiBot` now lists Cloudflare cookie signals on chatgpt.com, vinted.com, crunchbase.com (cf_clearance present, proving each had served and BlackTip had passed a managed challenge silently).
67
+ - **More commercial detector targets validated**: ChatGPT (Cloudflare with cf_clearance present), Canada Goose, Hyatt, Footlocker (no live Kasada cookies on homepages — they may arm only on cart/checkout). Documented in the updated `docs/anti-bot-validation.md`.
68
+
69
+ ### Test suite
70
+
71
+ - **78 unit tests passing (was 63), zero regressions.** Plus 4 new TLS integration tests (skip if daemon not built), 2 new diagnostics integration tests, 15 new behavioral parser tests.
72
+
73
+ ## [0.2.0] — 2026-04-10
74
+
75
+ ### Defeating Akamai Bot Manager — the L016 fix
76
+
77
+ The headline change in 0.2.0 is fixing the User-Agent / Sec-Ch-Ua consistency bug that had been silently undermining BlackTip against top-tier commercial detectors since 0.1.0. **OpenTable's Akamai Bot Manager went from blocking us at the edge to letting us into the booking flow on the very next request after the fix landed.** Same machine, same network, same IP.
78
+
79
+ See `docs/akamai-bypass.md` for the full plan, methodology, and status against each Akamai detection layer.
80
+
81
+ ### Fixed
82
+
83
+ - **L016 — User-Agent / Sec-Ch-Ua consistency.** `browser-core.ts` was setting `userAgent` at the Playwright context level via `newContext({userAgent: ...})`. Playwright's `userAgent` option overrides the `User-Agent` HTTP header but does NOT update the `Sec-Ch-Ua` / `Sec-Ch-Ua-Mobile` / `Sec-Ch-Ua-Platform` client hint headers — those come from the actual Chromium binary version. The result was BlackTip broadcasting `User-Agent: Chrome/125` and `Sec-Ch-Ua: "Google Chrome";v="146"` simultaneously, an inconsistency real Chrome NEVER produces and that Akamai Bot Manager catches as a textbook spoofing tell. Fix: removed the `userAgent` context override entirely; real Chrome's natural User-Agent comes through and matches Sec-Ch-Ua.
84
+ - This bug had been silently invalidating BlackTip's stealth against high-tier detectors. CreepJS / bot.sannysoft / browserleaks / fingerprint.com don't cross-check UA against client hints, so they didn't catch it. Akamai / DataDome / PerimeterX do, and they did.
85
+ - **Side effect:** cross-platform UA spoofing (declaring a `desktop-macos` profile while running on Linux) is no longer supported by default. v0.3.0 will reintroduce it via `setExtraHTTPHeaders` for callers who need it.
86
+
87
+ ### Added
88
+
89
+ - **`bt.captureFingerprint()`** — captures TLS, HTTP/2, and HTTP header fingerprint via `tls.peet.ws/api/all` and `httpbin.org/headers`. Returns a structured `FingerprintSnapshot` with the critical `headers.uaConsistent` flag for verifying L016 is fixed in any given install.
90
+ - **`bt.checkIpReputation()`** — queries the active session's egress IP via `ipinfo.io`, scores it against known datacenter / residential ASN patterns (Amazon, Google Cloud, Azure, OVH, DigitalOcean, Hetzner, Linode, Vultr — and major residential ISPs), returns an `IpReputationResult` with `isDatacenter` / `isResidential` flags and free-form notes.
91
+ - **`bt.testAgainstAkamai(url)`** — visits an Akamai-protected URL and reports the result with diagnosis. Recognizes the Akamai Access Denied page format, extracts the reference number, and returns a suggested next step. Use as a regression check in CI or interactively when troubleshooting.
92
+ - **`bt.warmSession({sites?, dwellMsRange?})`** — visits a sequence of "normal" sites with realistic dwell times and small scrolls before the target navigation. Accumulates cookies, populates History, and triggers natural behavioral signals so the target sees a session that already looks human.
93
+ - **`BlackTipConfig.userDataDir`** — persistent Chrome user data directory. When set, BlackTip uses `chromium.launchPersistentContext()` instead of the default fresh context, so cookies / localStorage / history / visited sites accumulate across runs. Critical for sites with "first request from unknown session" challenges.
94
+ - **`docs/akamai-bypass.md`** — comprehensive 7-phase plan for defeating Akamai Bot Manager, with current passing/failing matrix per detection layer, validated targets, recipes, and a troubleshooting checklist for users who hit blocks.
95
+ - **10 new diagnostics integration tests** in `tests/v04-diagnostics.integration.test.ts`. The most important is the L016 consistency check, which serves as a regression guard so we never reintroduce the UA / Sec-Ch-Ua mismatch.
96
+
97
+ ### Validated against
98
+
99
+ - **OpenTable** (Akamai Bot Manager): blocked at every URL in 0.1.0 with Access Denied references; passing as of 0.2.0. Confirmed against the deep-link booking page for Gjelina (Venice).
100
+ - All 0.1.0 detector targets continue to pass (bot.sannysoft, CreepJS, tls.peet.ws, browserleaks×4, fingerprint.com, pixelscan, browserscan, nowsecure.nl).
101
+
102
+ ### Test suite
103
+
104
+ - **162 → 172 tests passing**, zero regressions.
105
+
106
+ ### What's deferred to 0.3.0+
107
+
108
+ - TLS-rewriting proxy (`bogdanfinn/tls-client` integration) for cross-platform UA spoofing and Chrome version impersonation
109
+ - Akamai sensor data analysis and targeted JS-level patches for sites that probe deeper than headers
110
+ - Tier 2 behavioral calibration against real public datasets (Balabit, CMU Keystroke, GREYC)
111
+
112
+ ## [0.1.0] — 2026-04-10
113
+
114
+ ### First public release
115
+
116
+ BlackTip is a stealth browser instrument for AI agents. Real Chrome via `patchright` with CDP-level stealth patches, human-calibrated behavioral simulation, and an agent-friendly TCP serve protocol with bundled JSON responses. Passes every free fingerprint detector tested and known public Cloudflare bot-fight test targets.
117
+
118
+ ### Added — core architecture
119
+
120
+ - **Real Chrome via `channel: 'chrome'`** — uses the installed Chrome Stable binary for an authentic TLS ClientHello with rotating GREASE values and a native HTTP/2 frame order. No TLS proxy required for most use cases.
121
+ - **`patchright`-based CDP stealth** — drop-in Playwright replacement with patches that neutralize `Runtime.Enable` detection, automation string artifacts, and Error-stack hooks that vanilla Playwright leaks.
122
+ - **Device profiles** — `desktop-windows`, `desktop-macos`, `desktop-linux` with matching user agents, hardware concurrency, plugins, fonts, and WebGL vendor/renderer strings.
123
+ - **Behavioral engine** — Bézier mouse paths with Fitts' Law movement time, digraph-aware typing with realistic typo-and-correction patterns, scroll deceleration curves, normal-distribution sampling via Box-Muller, and reading pause estimation tied to the profile's WPM range.
124
+ - **Retry engine** — six-strategy cascade (`standard`, `wait`, `reload`, `altSelector`, `scroll`, `clearOverlays`) with event emission on each retry.
125
+ - **Seeded noise layers** — canvas and audio fingerprints receive profile-seeded noise via Mulberry32 PRNG so they're stable cross-session but unique per profile, matching how real hardware variance looks.
126
+
127
+ ### Added — agent primitives
128
+
129
+ - **`bt.waitForStable({networkIdleMs, domIdleMs, maxMs})`** — waits until no network requests and no DOM mutations for a window. Replaces fixed `setTimeout` sleeps with a real page-settled signal.
130
+ - **`bt.waitForText(text, {timeout})`** — polls body `innerText` for a target string. For server-rendered confirmations, OCR completion, and async content.
131
+ - **`bt.inspect(selector)`** — returns `{exists, visible, tagName, text, attributes, boundingBox}` in one call. Replaces multiple hand-written `executeJS` queries for diagnostics.
132
+ - **`bt.listOptions(selectorOrBaseId)`** — enumerates Angular/React-style custom dropdown options via the `{baseId}_option-{n}` pattern.
133
+ - **`bt.networkSince(ms, pattern?)`** and **`bt.didRequestFireSince(pattern, ms)`** — filtered Performance API resource entries and a boolean convenience for "did my submit actually reach the server?" diagnostics. Critical for avoiding burned retries on lockout-protected forms.
134
+ - **`bt.dismissOverlays()`** — proactively hides fixed/sticky overlays matching known patterns (Intercom, Drift, Zendesk, OneTrust, Medallia, cookie banners).
135
+ - **`bt.pauseForInput({prompt, validate?, timeoutMs?})`** — first-class MFA / user-in-the-loop support. Emits a `paused` frame to the client via the serve protocol; resumes when the client sends `RESUME <id>\n<value>`.
136
+ - **`bt.findInShadowDom(cssSelector, {timeout?})`** — recursive walker over open shadow roots. Supports modern component libraries (Lit, Stencil, Material Web Components).
137
+ - **`bt.download(selector, {saveTo})`** — click-to-download with returned `{path, size, suggestedFilename, url}` metadata.
138
+
139
+ ### Added — click robustness
140
+
141
+ - **Live bounding-box re-read before `mouse.click()`** — re-captures the target's current box immediately before the click. If the element moved more than ~5 pixels during the mouse-movement phase (DOM reflow, async scripts, layout shifts), performs a short correction move so the click lands on the right place. Fixes the L011 coordinate-reflow issue.
142
+ - **Pre-click hit verification** — uses `document.elementFromPoint` to verify the click coordinates actually land on the intended interactive element. If an overlay is covering the target, dismisses it and falls back to `locator.click({force: true})`.
143
+ - **Auto-importance detection on click/clickText/clickRole** — buttons whose visible text matches `Submit`, `Pay`, `Confirm`, `Place Order`, `Delete`, `Remove`, `Checkout`, `Purchase`, etc. automatically receive `importance: 'high'` which triggers longer pre-action hesitation (2–3× base pause plus a hesitation spike). Matches the long-tail distribution behavioral biometrics systems expect on consequential actions. Callers can override with explicit `importance`.
144
+ - **`BlackTipFrame.type()` iframe hardening** — ported the `Control+A + Backspace + keyboard.type + inputValue` verification pattern from the main `type()` method to the iframe variant. Ready for Stripe Elements, Braintree Hosted Fields, and other framework-driven iframes.
145
+
146
+ ### Added — serve mode and CLI
147
+
148
+ - **Bundled JSON responses** — every `send` command returns `{ok, result, url, title, screenshotPath, screenshotB64, screenshotBytes, durationMs, error?}` in one frame. Cuts round-trips by ~60% for linear flows.
149
+ - **Batched commands** — `BATCH\n<json array>` runs multiple commands sequentially and returns an array of bundles, stopping on first failure.
150
+ - **CLI flags** — `--file <path>`, `--stdin`, `--pretty`, `--port` eliminate shell-escape hell for complex JavaScript commands.
151
+ - **`npx blacktip` subcommands** — `serve`, `send`, `batch`, `resume`, `pending`, `exec` with per-command help.
152
+
153
+ ### Added — infrastructure
154
+
155
+ - **`ProxyPool`** — round-robin / random / least-used strategies, per-domain ban tracking with decay window, reputation snapshot for observability, URL formatters for BrightData / Oxylabs / Smartproxy.
156
+ - **`SnapshotManager`** — `capture()` and `restore()` for session migration across proxies or machines. Serializes cookies, localStorage, sessionStorage, and active URL into a JSON blob.
157
+ - **Observability** — `attachObservability(bt, exporters)` wires BlackTip's EventEmitter events into a `StructuredEvent` shape compatible with OpenTelemetry attribute data model. Ships `JsonlFileExporter` and `ConsoleExporter` as reference implementations. No OTel SDK dependency.
158
+ - **Behavioral calibration scaffold** — `src/behavioral/calibration.ts` with `fitDistribution`, `fitFittsLaw` (OLS), `fitMouseDynamics`, `fitTypingDynamics`, `fitFromSamples`, `deriveProfileConfig`. Ready to ingest real mouse-dynamics and keystroke-dynamics datasets (Balabit Mouse Dynamics Challenge, CMU Keystroke Dynamics, GREYC-NISLAB) via user-supplied parser wrappers.
159
+
160
+ ### Detector results
161
+
162
+ Captured against free public detectors as of the release:
163
+
164
+ - **bot.sannysoft.com** — 31 passed / 0 failed across 57 rows
165
+ - **tls.peet.ws** — JA4 `t13d1516h2_...`, first cipher rotates `TLS_GREASE (0x...)` matching real Chrome 122/124
166
+ - **CreepJS** — Grade A, 0% headless, 0% stealth, 31% like-headless (unavoidable desktop false-positive)
167
+ - **browserleaks.com/canvas** — signature shared with 100 of 298,939 browsers (blending in, not unique)
168
+ - **browserleaks.com/webgl** — real GPU via ANGLE, not SwiftShader
169
+ - **browserleaks.com/webrtc** — no RFC1918 local IP leak
170
+ - **browserleaks.com/javascript** — Chrome 122 on Win32, en-US, America/New_York, 1920×1080 all consistent
171
+ - **fingerprint.com/demo** — passes
172
+ - **pixelscan.net** — passes
173
+ - **browserscan.net** — identified as "Chrome on Windows 10", not flagged
174
+
175
+ Real-target validation:
176
+
177
+ - **nowsecure.nl** (Cloudflare bot-fight test, nodriver author's public benchmark) — passes without challenge
178
+ - **antoinevastel.com/bots** (ex-DataDome VP of Research) — loads without block
179
+ - **Anthem.com** (Okta MFA, Angular SPA, real medical claim submission) — full end-to-end flow successful
180
+
181
+ ### Notes
182
+
183
+ - Always runs headful. `headless: true` in config is silently ignored — there is no real-headless path that passes serious detectors.
184
+ - Real Chrome must be installed on the host for the preferred `channel: 'chrome'` path. patchright's bundled Chromium is the fallback.
185
+ - Scoped-name fix: the initial planned unscoped name `blacktip` was blocked by npm's anti-typosquatting policy (too similar to the pre-existing `black-tip` package). Released as `@rester159/blacktip` instead.
186
+
187
+ [Unreleased]: https://github.com/rester159/blacktip/compare/v0.4.0...HEAD
188
+ [0.4.0]: https://github.com/rester159/blacktip/compare/v0.2.0...v0.4.0
189
+ [0.2.0]: https://github.com/rester159/blacktip/compare/v0.1.0...v0.2.0
190
+ [0.1.0]: https://github.com/rester159/blacktip/releases/tag/v0.1.0
package/README.md CHANGED
@@ -49,6 +49,22 @@ BlackTip's architecture:
49
49
  - nowsecure.nl (Cloudflare bot-fight test, nodriver author's public benchmark) — passes without challenge
50
50
  - antoinevastel.com/bots (ex-DataDome VP of Research) — loads without block
51
51
  - Anthem.com (Okta MFA, Angular SPA, real insurance claim submission) — end-to-end flow successful
52
+ - Walmart.com (Akamai + PerimeterX simultaneously) — passes both layers
53
+ - BestBuy.com (Akamai) — passes
54
+ - Vinted.com (DataDome) — real catalog renders, datadome cookie present
55
+ - Crunchbase.com (Cloudflare) — passes, cf_clearance issued silently
56
+ - ChatGPT.com (Cloudflare) — passes, cf_clearance issued silently
57
+ - Twitch.tv (Kasada) — passes, kasada client script detected on the page
58
+ - Ticketmaster.com — passes
59
+ - OpenTable.com Gjelina deep link (Akamai Bot Manager) — passes; full booking endpoint with real time slots
60
+
61
+ **v0.4.0 also ships:**
62
+ - **`IdentityPool`** — long-running session and identity rotation. An identity is the union of cookies, localStorage, proxy, device profile, behavior profile, locale, and timezone. Persists to a JSON file so identities survive restarts. Per-domain burn list — an identity blocked on one site is still eligible for others. Composes `SnapshotManager` and `ProxyPool` with a feedback loop that auto-bans burned identities' proxies in the pool. See **[docs/identity-pool.md](docs/identity-pool.md)**.
63
+ - **`BlackTipConfig.requireResidentialIp`** — `'throw'` / `'warn'` / `false`. Runs `bt.checkIpReputation()` on launch and refuses (or warns) if the egress IP is on a known datacenter ASN. The launch-time defensive companion to `IdentityPool`'s outbound proxy selection.
64
+ - **Go-based TLS side-channel daemon (`bt.fetchWithTls`)** that performs HTTP requests with a real Chrome TLS ClientHello, H2 frame settings, and frame order via `bogdanfinn/tls-client`. Use to make gating requests the browser can't make through itself, then inject the resulting cookies into the browser session before navigating. JA4 `t13d1516h2_8daaf6152771_d8a2da3f94cd`, GREASE first cipher, exact Chrome H2 fingerprint. See **[docs/tls-side-channel.md](docs/tls-side-channel.md)**.
65
+ - **Behavioral profile calibrated against the real CMU Keystroke Dynamics dataset.** **53% closer to real human hold-time distribution** than the canonical defaults on a held-out subject set (51 subjects, 80/20 split). See **[docs/calibration-validation.md](docs/calibration-validation.md)** for methodology and reproduction.
66
+
67
+ See **[docs/anti-bot-validation.md](docs/anti-bot-validation.md)** for the live multi-vendor scoreboard with vendor-signal verification (proves each target is actually protected, not a false negative on an unprotected URL).
52
68
 
53
69
  ---
54
70
 
@@ -162,6 +178,11 @@ See `AGENTS.md` for the full agent-facing reference, including the decision tree
162
178
  | `bt.captureFingerprint()` | TLS / HTTP2 / header snapshot via tls.peet.ws + httpbin (v0.2.0) |
163
179
  | `bt.checkIpReputation()` | Egress IP, ASN, datacenter/residential heuristic (v0.2.0) |
164
180
  | `bt.testAgainstAkamai(url)` | Akamai-protected target probe with diagnosis (v0.2.0) |
181
+ | `bt.testAgainstAntiBot(url)` | Multi-vendor probe — detects Akamai, DataDome, Cloudflare, PerimeterX, Imperva, Kasada, Arkose, plus vendor signals on a passing page (v0.2.0) |
182
+ | `bt.fetchWithTls(req)` | Perform an HTTP request via a Go-based `bogdanfinn/tls-client` daemon with a real Chrome TLS ClientHello, H2 frame settings, and frame order. Use for first-request edge gating and cross-platform UA spoofing. Requires the daemon binary at `native/tls-client/` (build with `go build .`) (v0.3.0) |
183
+ | `bt.injectTlsCookies(resp, targetUrl?)` | Inject cookies returned by `fetchWithTls()` into the browser session, filtered by target eTLD+1 (v0.3.0) |
184
+
185
+ Plus `IdentityPool` (v0.4.0) for long-running session and identity rotation across many flows. See **[docs/identity-pool.md](docs/identity-pool.md)**.
165
186
  | `bt.warmSession({sites?, dwellMsRange?})` | Pre-target warm-up — visit normal sites first (v0.2.0) |
166
187
  | `bt.serve(port?)` | Start TCP command server |
167
188
 
@@ -0,0 +1,89 @@
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
+ import type { MouseMovement, TypingSession } from './calibration.js';
35
+ /** The fixed phrase typed by every CMU subject. Used to map column index → key. */
36
+ export declare 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 declare function parseCmuKeystrokeCsv(csvText: string): TypingSession[];
55
+ /**
56
+ * Parse a single Balabit Mouse Dynamics session CSV.
57
+ *
58
+ * Each row is one mouse event:
59
+ * record_timestamp,client_timestamp,button,state,x,y
60
+ * 1424866316.93,0.000,NoButton,Move,1192,529
61
+ * 1424866316.99,0.064,NoButton,Move,1183,532
62
+ * ...
63
+ * 1424866317.84,0.911,Left,Pressed,820,440
64
+ *
65
+ * We segment into movements: each contiguous run of `Move`/`Drag` rows
66
+ * followed by a `Pressed` row becomes one `MouseMovement`. The press row
67
+ * is the click target. Movements that don't end in a press are also
68
+ * recorded but with `endedWithClick: false` — these are navigation moves.
69
+ *
70
+ * Time is normalized to milliseconds since the first sample of the
71
+ * movement.
72
+ */
73
+ export declare function parseBalabitMouseCsv(csvText: string): MouseMovement[];
74
+ /**
75
+ * Generic loader for users who already export their telemetry in the
76
+ * normalized shapes. Accepts JSON of the form:
77
+ *
78
+ * { "movements": MouseMovement[], "sessions": TypingSession[] }
79
+ *
80
+ * Either field may be absent. Returns the parsed object with empty
81
+ * defaults filled in. This is the "bring your own data" path — most
82
+ * production users will write a tiny exporter on their own telemetry
83
+ * pipeline that emits this shape, then feed it through `fitFromSamples()`.
84
+ */
85
+ export declare function parseGenericTelemetryJson(jsonText: string): {
86
+ movements: MouseMovement[];
87
+ sessions: TypingSession[];
88
+ };
89
+ //# sourceMappingURL=parsers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../../src/behavioral/parsers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAmB,aAAa,EAAe,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAInG,mFAAmF;AACnF,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAoDrE;AAoBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CA0DrE;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAC3D,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CASA"}
@@ -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,7 +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 } from './diagnostics.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';
8
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';
9
10
  /**
10
11
  * BlackTip — Stealth browser instrument for AI agents.
@@ -54,6 +55,7 @@ export declare class BlackTip extends EventEmitter {
54
55
  private core;
55
56
  private engine;
56
57
  private finder;
58
+ private tlsChannel;
57
59
  private logger;
58
60
  private config;
59
61
  private customProfiles;
@@ -232,6 +234,37 @@ export declare class BlackTip extends EventEmitter {
232
234
  * trying to figure out why a specific target is blocking you.
233
235
  */
234
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>;
235
268
  /**
236
269
  * Visit a sequence of "normal" sites with realistic dwell times before
237
270
  * the target navigation. Accumulates cookies, populates History API,