@rester159/blacktip 0.2.0 → 0.5.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 (53) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/README.md +25 -0
  3. package/dist/akamai-sensor.d.ts +128 -0
  4. package/dist/akamai-sensor.d.ts.map +1 -0
  5. package/dist/akamai-sensor.js +190 -0
  6. package/dist/akamai-sensor.js.map +1 -0
  7. package/dist/behavioral/parsers.d.ts +89 -0
  8. package/dist/behavioral/parsers.d.ts.map +1 -0
  9. package/dist/behavioral/parsers.js +223 -0
  10. package/dist/behavioral/parsers.js.map +1 -0
  11. package/dist/blacktip.d.ts +68 -1
  12. package/dist/blacktip.d.ts.map +1 -1
  13. package/dist/blacktip.js +140 -1
  14. package/dist/blacktip.js.map +1 -1
  15. package/dist/browser-core.d.ts +10 -0
  16. package/dist/browser-core.d.ts.map +1 -1
  17. package/dist/browser-core.js +49 -0
  18. package/dist/browser-core.js.map +1 -1
  19. package/dist/diagnostics.d.ts +31 -0
  20. package/dist/diagnostics.d.ts.map +1 -1
  21. package/dist/diagnostics.js +146 -0
  22. package/dist/diagnostics.js.map +1 -1
  23. package/dist/identity-pool.d.ts +160 -0
  24. package/dist/identity-pool.d.ts.map +1 -0
  25. package/dist/identity-pool.js +288 -0
  26. package/dist/identity-pool.js.map +1 -0
  27. package/dist/index.d.ts +11 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +11 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/tls-rewriter.d.ts +74 -0
  32. package/dist/tls-rewriter.d.ts.map +1 -0
  33. package/dist/tls-rewriter.js +203 -0
  34. package/dist/tls-rewriter.js.map +1 -0
  35. package/dist/tls-side-channel.d.ts +91 -0
  36. package/dist/tls-side-channel.d.ts.map +1 -0
  37. package/dist/tls-side-channel.js +248 -0
  38. package/dist/tls-side-channel.js.map +1 -0
  39. package/dist/types.d.ts +46 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/types.js.map +1 -1
  42. package/docs/akamai-bypass.md +257 -0
  43. package/docs/akamai-sensor.md +183 -0
  44. package/docs/anti-bot-validation.md +84 -0
  45. package/docs/calibration-validation.md +93 -0
  46. package/docs/identity-pool.md +176 -0
  47. package/docs/tls-rewriting.md +121 -0
  48. package/docs/tls-side-channel.md +83 -0
  49. package/native/tls-client/go.mod +21 -0
  50. package/native/tls-client/go.sum +36 -0
  51. package/native/tls-client/main.go +216 -0
  52. package/package.json +8 -2
  53. package/scripts/fit-cmu-keystroke.mjs +186 -0
@@ -0,0 +1,248 @@
1
+ /**
2
+ * TLS side-channel — spawns the Go-based bogdanfinn/tls-client daemon
3
+ * and exposes a `fetchWithTls()` primitive that performs HTTP requests
4
+ * with a real Chrome TLS ClientHello, real H2 frame settings, and real
5
+ * H2 frame order. Used to make gating requests that the browser can't
6
+ * make through itself, then inject the resulting cookies into the
7
+ * BlackTip browser session.
8
+ *
9
+ * This is the v0.3.0 answer to "an edge gates the very first request
10
+ * before BlackTip's browser even has a session." Use it as follows:
11
+ *
12
+ * const channel = await TlsSideChannel.spawn();
13
+ * const resp = await channel.fetch('https://protected.example.com/');
14
+ * await bt.setCookies(resp.cookies.map(c => ({ name: c.name, value: c.value, domain: c.domain, path: c.path })));
15
+ * await bt.navigate('https://protected.example.com/');
16
+ * // ... browser session now has the cookies the gating request earned
17
+ * await channel.close();
18
+ *
19
+ * The daemon binary lives in `native/tls-client/blacktip-tls` (Linux/macOS)
20
+ * or `native/tls-client/blacktip-tls.exe` (Windows). Build it once with
21
+ * `cd native/tls-client && go build -o blacktip-tls .` — Go is the only
22
+ * build dependency, and you can grab it from https://go.dev/dl/.
23
+ *
24
+ * The daemon stays alive across many requests so we don't pay subprocess
25
+ * startup cost per call. It uses a newline-delimited JSON wire protocol
26
+ * with per-request IDs, so multiple `fetch()` calls can be in flight
27
+ * concurrently without interleaving issues.
28
+ */
29
+ import { spawn } from 'node:child_process';
30
+ import { existsSync } from 'node:fs';
31
+ import { join, dirname } from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { createInterface } from 'node:readline';
34
+ // ── Resolution of the daemon binary ──
35
+ /**
36
+ * Resolves the path to the prebuilt blacktip-tls binary. Looks first
37
+ * in `native/tls-client/` next to the package source, then falls back
38
+ * to a process-env override (`BLACKTIP_TLS_BIN`) for users who want
39
+ * to ship the binary somewhere else.
40
+ */
41
+ function resolveBinaryPath() {
42
+ const env = process.env.BLACKTIP_TLS_BIN;
43
+ if (env && existsSync(env))
44
+ return env;
45
+ // From src/tls-side-channel.ts (or its compiled equivalent in dist/)
46
+ // → ../native/tls-client/blacktip-tls[.exe]
47
+ const here = dirname(fileURLToPath(import.meta.url));
48
+ const exe = process.platform === 'win32' ? 'blacktip-tls.exe' : 'blacktip-tls';
49
+ const candidates = [
50
+ join(here, '..', 'native', 'tls-client', exe),
51
+ join(here, '..', '..', 'native', 'tls-client', exe), // when running from dist/
52
+ ];
53
+ for (const c of candidates) {
54
+ if (existsSync(c))
55
+ return c;
56
+ }
57
+ throw new Error(`BlackTip TLS daemon binary not found. Build it with:\n` +
58
+ ` cd native/tls-client && go build -o ${exe} .\n` +
59
+ `Or set BLACKTIP_TLS_BIN to an absolute path.`);
60
+ }
61
+ // ── Cookie parsing ──
62
+ //
63
+ // Set-Cookie parsing is gnarly: commas can appear inside `Expires`
64
+ // values ("Sun, 06 Nov 1994 08:49:37 GMT"), so a naive split-on-comma
65
+ // breaks. We split on the literal `\n` boundary that bogdanfinn/fhttp
66
+ // uses when joining multiple Set-Cookie headers... but the Go daemon
67
+ // returns headers as `string[]`, so each cookie is already its own
68
+ // array element and we don't need to split at all.
69
+ function parseCookie(setCookie, defaultDomain) {
70
+ // First chunk before `;` is `name=value`. Subsequent chunks are attributes.
71
+ const parts = setCookie.split(';').map((p) => p.trim());
72
+ const first = parts[0];
73
+ if (!first)
74
+ return null;
75
+ const eq = first.indexOf('=');
76
+ if (eq < 0)
77
+ return null;
78
+ const name = first.slice(0, eq).trim();
79
+ const value = first.slice(eq + 1).trim();
80
+ if (!name)
81
+ return null;
82
+ let domain = defaultDomain;
83
+ let path = '/';
84
+ let httpOnly = false;
85
+ let secure = false;
86
+ let sameSite;
87
+ let expires;
88
+ for (let i = 1; i < parts.length; i++) {
89
+ const attr = parts[i];
90
+ const ai = attr.indexOf('=');
91
+ const key = (ai < 0 ? attr : attr.slice(0, ai)).trim().toLowerCase();
92
+ const val = ai < 0 ? '' : attr.slice(ai + 1).trim();
93
+ if (key === 'domain')
94
+ domain = val.replace(/^\./, '');
95
+ else if (key === 'path')
96
+ path = val;
97
+ else if (key === 'httponly')
98
+ httpOnly = true;
99
+ else if (key === 'secure')
100
+ secure = true;
101
+ else if (key === 'samesite') {
102
+ const v = val.toLowerCase();
103
+ if (v === 'strict')
104
+ sameSite = 'Strict';
105
+ else if (v === 'lax')
106
+ sameSite = 'Lax';
107
+ else if (v === 'none')
108
+ sameSite = 'None';
109
+ }
110
+ else if (key === 'expires') {
111
+ const t = Date.parse(val);
112
+ if (!Number.isNaN(t))
113
+ expires = t / 1000;
114
+ }
115
+ }
116
+ return { name, value, domain, path, httpOnly, secure, sameSite, expires };
117
+ }
118
+ export class TlsSideChannel {
119
+ proc;
120
+ rl;
121
+ pending = new Map();
122
+ nextId = 1;
123
+ closed = false;
124
+ constructor(proc) {
125
+ this.proc = proc;
126
+ this.rl = createInterface({ input: proc.stdout });
127
+ this.rl.on('line', (line) => this.handleLine(line));
128
+ proc.on('exit', (code) => {
129
+ this.closed = true;
130
+ const err = new Error(`TLS daemon exited with code ${code}`);
131
+ for (const p of this.pending.values())
132
+ p.reject(err);
133
+ this.pending.clear();
134
+ });
135
+ }
136
+ /**
137
+ * Spawn the daemon. Throws if the binary isn't built or can't start.
138
+ * The daemon stays alive until you call `close()`.
139
+ */
140
+ static async spawn() {
141
+ const bin = resolveBinaryPath();
142
+ const proc = spawn(bin, [], { stdio: ['pipe', 'pipe', 'pipe'] });
143
+ // Forward daemon stderr to ours so we see crashes in dev.
144
+ proc.stderr.on('data', (chunk) => {
145
+ process.stderr.write(`[blacktip-tls] ${chunk.toString()}`);
146
+ });
147
+ return new TlsSideChannel(proc);
148
+ }
149
+ handleLine(line) {
150
+ if (!line.trim())
151
+ return;
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(line);
155
+ }
156
+ catch {
157
+ process.stderr.write(`[blacktip-tls] unparseable line: ${line}\n`);
158
+ return;
159
+ }
160
+ const pending = this.pending.get(parsed.id);
161
+ if (!pending)
162
+ return;
163
+ this.pending.delete(parsed.id);
164
+ if (!parsed.ok) {
165
+ pending.reject(new Error(parsed.error ?? 'unknown daemon error'));
166
+ return;
167
+ }
168
+ const finalUrl = parsed.finalUrl ?? '';
169
+ const headers = parsed.headers ?? {};
170
+ const bodyB64 = parsed.body ?? '';
171
+ const bodyBuffer = Buffer.from(bodyB64, 'base64');
172
+ const body = bodyBuffer.toString('utf-8');
173
+ // Parse Set-Cookie headers. The header key may be `Set-Cookie`
174
+ // or `set-cookie` depending on the daemon's Go HTTP version.
175
+ const cookies = [];
176
+ const cookieHeaders = headers['Set-Cookie'] ?? headers['set-cookie'] ?? [];
177
+ let defaultDomain = '';
178
+ try {
179
+ defaultDomain = new URL(finalUrl).hostname;
180
+ }
181
+ catch { /* leave empty */ }
182
+ for (const sc of cookieHeaders) {
183
+ const c = parseCookie(sc, defaultDomain);
184
+ if (c)
185
+ cookies.push(c);
186
+ }
187
+ pending.resolve({
188
+ status: parsed.status ?? 0,
189
+ headers,
190
+ body,
191
+ bodyBuffer,
192
+ finalUrl,
193
+ durationMs: parsed.durationMs,
194
+ cookies,
195
+ });
196
+ }
197
+ /**
198
+ * Perform a single TLS-impersonated request. Multiple `fetch()` calls
199
+ * can be in flight concurrently — the daemon handles each in its own
200
+ * goroutine and matches responses by id.
201
+ */
202
+ async fetch(req) {
203
+ if (this.closed)
204
+ throw new Error('TLS daemon is closed');
205
+ const id = `r${this.nextId++}`;
206
+ let bodyB64 = '';
207
+ if (req.body != null) {
208
+ const buf = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body, 'utf-8');
209
+ bodyB64 = buf.toString('base64');
210
+ }
211
+ const wire = {
212
+ id,
213
+ url: req.url,
214
+ method: req.method ?? 'GET',
215
+ headers: req.headers ?? {},
216
+ body: bodyB64,
217
+ timeoutMs: req.timeoutMs ?? 15000,
218
+ profile: req.profile ?? 'chrome_133',
219
+ };
220
+ return new Promise((resolve, reject) => {
221
+ this.pending.set(id, { resolve, reject });
222
+ this.proc.stdin.write(JSON.stringify(wire) + '\n', (err) => {
223
+ if (err) {
224
+ this.pending.delete(id);
225
+ reject(err);
226
+ }
227
+ });
228
+ });
229
+ }
230
+ /** Shut down the daemon and clean up the subprocess. */
231
+ async close() {
232
+ if (this.closed)
233
+ return;
234
+ this.closed = true;
235
+ this.rl.close();
236
+ return new Promise((resolve) => {
237
+ this.proc.once('exit', () => resolve());
238
+ this.proc.stdin.end();
239
+ // Hard-kill if it doesn't exit gracefully within a second.
240
+ setTimeout(() => {
241
+ if (!this.proc.killed)
242
+ this.proc.kill('SIGKILL');
243
+ resolve();
244
+ }, 1000).unref();
245
+ });
246
+ }
247
+ }
248
+ //# sourceMappingURL=tls-side-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tls-side-channel.js","sourceRoot":"","sources":["../src/tls-side-channel.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -25,6 +25,52 @@ export interface BlackTipConfig {
25
25
  * If unset, Chrome runs with a fresh profile each launch (default).
26
26
  */
27
27
  userDataDir?: string;
28
+ /**
29
+ * IP reputation gate. When set, BlackTip queries `bt.checkIpReputation()`
30
+ * immediately after `launch()` and acts based on the verdict:
31
+ *
32
+ * - `false` / unset (default): no check.
33
+ * - `'warn'`: log a warning if the egress IP is on a known datacenter
34
+ * ASN, but allow the launch to proceed.
35
+ * - `'throw'` / `true`: throw on launch if the egress IP is datacenter.
36
+ * This is the safest setting for high-stakes flows where a flagged
37
+ * IP would burn a real account.
38
+ *
39
+ * Use `'throw'` in CI / production. Use `'warn'` for local dev. Leave
40
+ * unset for offline / air-gapped use.
41
+ */
42
+ requireResidentialIp?: boolean | 'warn' | 'throw';
43
+ /**
44
+ * TLS rewriting (v0.5.0). When set to `'all'`, every browser request is
45
+ * intercepted via Chrome DevTools Protocol's `Fetch.enable` and forwarded
46
+ * through the Go-based `bogdanfinn/tls-client` daemon, which makes the
47
+ * upstream call with a real Chrome TLS ClientHello, real H2 frame
48
+ * settings, and real H2 frame order. The browser never opens an upstream
49
+ * TCP connection — all its HTTP is fulfilled by the daemon.
50
+ *
51
+ * This restores cross-platform UA spoofing (run on Linux, present as
52
+ * Windows or macOS — the daemon controls every header on the wire) and
53
+ * gives total fingerprint control without the cert-installation hell of
54
+ * a TCP-level MITM proxy.
55
+ *
56
+ * Tradeoffs:
57
+ * - WebSocket upgrades cannot be intercepted via Fetch and leak
58
+ * Chrome's native TLS. Mitigation: BlackTip auto-disables QUIC/HTTP3
59
+ * (`--disable-quic`) and the rewriter logs WS leaks for awareness.
60
+ * - Streaming/large response bodies are fully buffered (Fetch.fulfill
61
+ * takes a complete body). Bad for video; fine for HTML pages.
62
+ * - 5–10ms round-trip overhead per request. ~250–500ms added on a
63
+ * typical page with 50 subresources.
64
+ *
65
+ * Requires the Go daemon binary at `native/tls-client/blacktip-tls[.exe]`
66
+ * — see `docs/tls-side-channel.md` for build instructions. If the daemon
67
+ * binary is missing, launch will throw rather than silently falling back.
68
+ *
69
+ * Set to `'off'` (default) for normal operation, `'all'` for full
70
+ * rewriting. Future versions may add `'selective:<domain-glob>'` for
71
+ * per-domain rewriting.
72
+ */
73
+ tlsRewriting?: 'off' | 'all';
28
74
  }
29
75
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
30
76
  export interface ProfileConfig {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAsQtB,oBAAoB;AAEpB,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,SAAS;IAClB,iBAAiB,EAAE,mBAAmB;IACtC,iBAAiB,EAAE,mBAAmB;IACtC,QAAQ,EAAE,UAAU;IACpB,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;CACpB,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAoTtB,oBAAoB;AAEpB,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,SAAS;IAClB,iBAAiB,EAAE,mBAAmB;IACtC,iBAAiB,EAAE,mBAAmB;IACtC,QAAQ,EAAE,UAAU;IACpB,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;CACpB,CAAC"}
@@ -0,0 +1,257 @@
1
+ # Defeating Akamai Bot Manager
2
+
3
+ > Status as of v0.2.0: **passing on the User-Agent / Sec-Ch-Ua consistency layer** that previously blocked us. Validated against OpenTable (which uses Akamai Bot Manager). Future detection layers (sensor data, behavioral biometrics, IP reputation) are tracked below as the next areas to harden.
4
+
5
+ This is the BlackTip team's working plan against Akamai Bot Manager, the most layered commercial anti-bot service in the wild. It's structured so you can use it as a reference whether you're a contributor improving BlackTip or a user diagnosing why a specific Akamai-protected target isn't working for you.
6
+
7
+ ## What Akamai Bot Manager actually is
8
+
9
+ Akamai Bot Manager runs a stack of detection layers, scored independently and combined into a "bot probability" that decides whether you get the page, get a JavaScript challenge, or get blocked at the edge with `Access Denied`. The layers, in the order they fire:
10
+
11
+ 1. **TCP/IP layer** — IP reputation database. Datacenter ASNs (AWS, GCP, OVH, DigitalOcean) flagged automatically. Residential IPs scored by historical bot behavior on the same /24 block. Tor exit nodes blocked outright. **Cheapest signal, runs first.**
12
+ 2. **TLS layer** — JA3, JA4, GREASE position and rotation pattern, cipher ordering, extension ordering, signature algorithms, EC curves, ALPN. Akamai is one of the few that checks GREASE *position* (Chrome puts GREASE first in both ciphers and extensions).
13
+ 3. **HTTP/2 layer** — Akamai's own fingerprint format: `s[settings];w[window_update];p[priority_frames];h[header_order]`. Tracks SETTINGS values (HEADER_TABLE_SIZE, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE), WINDOW_UPDATE size, PRIORITY frame patterns, and pseudo-header order. Chrome's signature is `m,a,s,p` (method/authority/scheme/path).
14
+ 4. **HTTP header layer** — header order, presence and consistency of `Sec-Fetch-*`, `Sec-Ch-Ua-*`, `Accept-Language`, `Accept-Encoding`, `User-Agent`. **This is where v0.1.0 was being caught.** See L016 below.
15
+ 5. **Sensor data (the JavaScript challenge)** — Akamai injects a script that collects ~80 browser signals (mouse traces, keystroke timings, performance.now() resolution, Battery API, screen properties, WebGL info, canvas hash, audio fingerprint, plugins, fonts, timezone math, navigator properties) and POSTs them as a 30–50 KB blob to `/akam/11/...`. The server validates the blob and either sets a valid `_abck` cookie or marks the session as a bot. **All subsequent requests need a valid `_abck` cookie.**
16
+ 6. **Cookie continuity** — `_abck`, `bm_sz`, `bm_sv`, `_bm_sz`. They expire, rotate, and need session affinity. Sessions that don't carry the cookies properly are flagged on the next request.
17
+ 7. **Behavioral patterns** — after passing the initial gate, Akamai still profiles mouse dynamics, keystroke flight times, scroll patterns, and click timing distributions. Bot-like distributions get reclassified as bots even after passing the initial probe.
18
+
19
+ If your block happens **before any JavaScript runs** (you see the `Access Denied` page directly with a `Reference #...` and `errors.edgesuite.net` URL), Akamai flagged you at one of layers 1–4. The sensor never executed.
20
+
21
+ If your block happens **after the page partially loads** or you get a CAPTCHA challenge, you made it past layers 1–4 but the sensor data validation failed.
22
+
23
+ ## Why v0.1.0 was blocked
24
+
25
+ When the BlackTip team first ran v0.1.0 against OpenTable in development, every request was rejected at the edge with Akamai's `Access Denied` page. We spent 30 minutes ruling out hypotheses one by one:
26
+
27
+ - **TLS fingerprint:** Captured via `tls.peet.ws/api/all`. Result: **byte-perfect match** for real Chrome 125 on Windows. JA4 `t13d1516h2_8daaf6152771_d8a2da3f94cd`, GREASE in position 0 of both ciphers and extensions, 16 ciphers, 18 extensions. Not the issue.
28
+ - **HTTP/2 fingerprint:** Akamai HTTP/2 string `1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p`. **Byte-perfect match** for real Chrome. Not the issue.
29
+ - **IP reputation:** Residential Frontier Communications IP in Los Angeles. Not on a known datacenter ASN. Plausible signal but couldn't confirm via free tools.
30
+ - **HTTP headers:** Captured via `httpbin.org/headers`. **FOUND IT.**
31
+
32
+ The `httpbin.org/headers` capture showed:
33
+
34
+ ```
35
+ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/125.0.0.0 ...
36
+ Sec-Ch-Ua: "Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"
37
+ ```
38
+
39
+ **Chrome/125 in `User-Agent` but Chrome/146 in `Sec-Ch-Ua`.** Real Chrome NEVER has these inconsistent. Akamai catches the mismatch as a textbook spoofing tell — they don't even need to run JavaScript, this header alone is enough.
40
+
41
+ ### Root cause
42
+
43
+ `browser-core.ts` was setting `userAgent` at the Playwright context level via `newContext({userAgent: ...})`. Playwright's `userAgent` option overrides the `User-Agent` HTTP header value, but it 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 (Chromium 146, the version patchright bundles, OR the version of Chrome Stable installed via `channel: 'chrome'`).
44
+
45
+ The result: BlackTip was broadcasting "I am Chrome 125 (UA) but also Chrome 146 (client hints)" to every site since v0.1.0. Detectors that don't cross-check (CreepJS, bot.sannysoft, browserleaks) didn't notice. Detectors that do (Akamai, DataDome, PerimeterX) flagged it instantly.
46
+
47
+ ### The v0.2.0 fix (L016)
48
+
49
+ Remove the `userAgent` context override entirely. Let real Chrome's natural User-Agent come through. UA and Sec-Ch-Ua match because they come from the same source (the actual Chromium binary).
50
+
51
+ ```typescript
52
+ // browser-core.ts
53
+ this.context = await this.browser.newContext({
54
+ viewport: {...},
55
+ // userAgent: this.deviceProfile.userAgent, // ← REMOVED in v0.2.0
56
+ locale: this.config.locale,
57
+ timezoneId: this.config.timezone,
58
+ ...
59
+ });
60
+ ```
61
+
62
+ **Result:** OpenTable's Akamai Bot Manager went from blocking us at the edge to letting us into the booking flow on the very next request. Same machine, same network, same IP — only the UA override removed.
63
+
64
+ ### Side effect: cross-platform UA spoofing is no longer supported
65
+
66
+ Previously you could declare a `desktop-macos` device profile while running on Linux and BlackTip would set the User-Agent to a macOS Chrome string. That doesn't work in v0.2.0 — your reported UA matches the actual Chrome binary on the host machine.
67
+
68
+ If you need cross-platform spoofing, you have to override BOTH the User-Agent header AND all Sec-Ch-Ua-* headers in lockstep using `setExtraHTTPHeaders`. v0.2.0 doesn't ship a helper for this; v0.3.0 will.
69
+
70
+ For most production use cases, you want Chrome-on-your-platform anyway, so this isn't a meaningful loss.
71
+
72
+ ## The phased response plan against Akamai
73
+
74
+ This is the BlackTip team's running plan against Akamai's full layer stack. Phases marked DONE shipped in the version noted; phases marked NEXT are the team's next priorities.
75
+
76
+ ### Phase 1 — Diagnostics (DONE in v0.2.0)
77
+
78
+ You can't fix what you can't see. v0.2.0 ships diagnostic primitives that capture exactly what BlackTip is sending across the TLS, HTTP/2, and HTTP header layers, plus IP reputation queries.
79
+
80
+ ```typescript
81
+ // Capture our actual TLS / HTTP2 / header fingerprint
82
+ const fp = await bt.captureFingerprint();
83
+ console.log(fp.tls.ja4); // 't13d1516h2_8daaf6152771_d8a2da3f94cd'
84
+ console.log(fp.http2.akamaiFingerprint); // '1:65536;2:0;4:6291456;...'
85
+ console.log(fp.headers.userAgent); // 'Mozilla/5.0 ... Chrome/146.0.0.0 ...'
86
+ console.log(fp.headers.secChUa); // '"Google Chrome";v="146", ...'
87
+ console.log(fp.headers.uaConsistent); // true (the L016 check)
88
+
89
+ // Check our IP reputation
90
+ const ip = await bt.checkIpReputation();
91
+ console.log(ip.ip); // '47.150.34.38'
92
+ console.log(ip.asn); // 'AS5650'
93
+ console.log(ip.org); // 'Frontier Communications of America, Inc.'
94
+ console.log(ip.isDatacenter); // false
95
+ console.log(ip.isResidential); // true
96
+
97
+ // Test against an Akamai-protected URL with diagnosis
98
+ const result = await bt.testAgainstAkamai('https://www.opentable.com/');
99
+ console.log(result.passed); // true
100
+ console.log(result.title); // 'Restaurants and Restaurant Bookings | OpenTable'
101
+ console.log(result.akamaiReference); // null (no block)
102
+ ```
103
+
104
+ ### Phase 2 — Quick wins (DONE in v0.2.0)
105
+
106
+ Cheap fixes applied directly:
107
+
108
+ 1. **L016 (UA / Sec-Ch-Ua consistency)** — described above, the load-bearing fix
109
+ 2. **Aggressive Chrome flag cleanup** — minimum flags only, match Chrome's natural launch
110
+ 3. **Optional persistent user-data-dir** — `BlackTipConfig.userDataDir` lets you carry cookies, history, and visited-sites context across sessions, which makes Akamai's "first request from unknown session" challenge less likely to fire
111
+
112
+ ### Phase 3 — Session warming (DONE in v0.2.0)
113
+
114
+ Akamai's "first request" challenge is harder to pass than the second. Solution: warm the session before hitting the target.
115
+
116
+ ```typescript
117
+ await bt.launch();
118
+ await bt.warmSession({
119
+ sites: [
120
+ 'https://www.google.com/',
121
+ 'https://www.wikipedia.org/',
122
+ 'https://news.ycombinator.com/',
123
+ ],
124
+ dwellMsRange: [3000, 8000], // human-like reading time on each site
125
+ });
126
+ // Now navigate to the target — the browser has cookies, history, and a
127
+ // realistic activity pattern.
128
+ await bt.navigate('https://target-protected-by-akamai.com/');
129
+ ```
130
+
131
+ The warming visits accumulate cookies, populate the History API, and trigger the natural behavioral signals Akamai's profiler expects to see from a real user.
132
+
133
+ ### Phase 4 — TLS-rewriting proxy (DEFERRED to v0.3.0)
134
+
135
+ For cases where the host machine's installed Chrome version is OLDER than what we want to declare, OR where the host has no Chrome installed at all and we're falling back to patchright's bundled Chromium with a different TLS profile, we need byte-level TLS impersonation. The plan:
136
+
137
+ - **Use [bogdanfinn/tls-client](https://github.com/bogdanfinn/tls-client)** as a local MITM proxy
138
+ - Spawn it as a subprocess on `bt.launch()`
139
+ - Generate a self-signed root CA, install it into Chrome's cert store at launch
140
+ - Point Chrome via `--proxy-server` at the local proxy
141
+ - Verify via `bt.captureFingerprint()` that the JA4 matches the desired Chrome version
142
+
143
+ Latency cost: ~5–20 ms per connection. Platform binaries: separate Linux/macOS/Windows × x64/arm64 builds. Will ship as an **optional dependency** so users who don't need this don't pay for it.
144
+
145
+ ### Phase 5 — Sensor data (DEFERRED to v0.3.0+)
146
+
147
+ Akamai's JavaScript challenge collects ~80 signals and POSTs a 30–50 KB blob. To pass:
148
+
149
+ - Either let the real script run with a real environment (best, but requires every JS-level signal to be perfect)
150
+ - Or replay a pre-captured sensor payload from a real Chrome session (works once, then session expires)
151
+
152
+ Plan:
153
+
154
+ 1. Run the Akamai sensor script in a controlled BlackTip session against a known-protected URL
155
+ 2. Capture the full payload and the resulting `_abck` cookie
156
+ 3. Identify which signals are flagged by analyzing the payload bytes
157
+ 4. Patch those specific signals at the patchright layer
158
+ 5. Re-test, repeat
159
+
160
+ This is reverse-engineering work and takes weeks. Until then, BlackTip relies on its native browser environment being good enough to pass the sensor naturally — which it does in many cases now that L016 is fixed.
161
+
162
+ ### Phase 6 — Behavioral biometrics (DEFERRED to v0.3.0+)
163
+
164
+ Once past the gate, Akamai still profiles mouse dynamics and keystroke timing. BlackTip's `BehavioralEngine` already handles this with Bézier mouse paths, Fitts' Law movement time, and digraph-aware typing. Tier 2 calibration against real datasets (Balabit, CMU Keystroke) will tighten the distributions further.
165
+
166
+ The current behavioral engine is sufficient for most Akamai targets. The Tier 2 calibration is a "best-in-the-world" upgrade, not a "passes Akamai" requirement.
167
+
168
+ ### Phase 7 — IP reputation (USER-PROVIDED)
169
+
170
+ This is the one layer BlackTip can't fix in code. If your IP is on Akamai's flagged list, no amount of fingerprint patching will help — you need a different network. Options:
171
+
172
+ 1. **Use a different connection** (mobile hotspot, different ISP) for the affected sessions
173
+ 2. **Use a residential proxy provider** (BrightData, Oxylabs, Smartproxy) — `BlackTipConfig.proxy` accepts the URL, and the `ProxyPool` class handles per-domain affinity
174
+ 3. **Wait 24–48 hours** for Akamai's reputation cache to expire if you've been hammering a target
175
+
176
+ BlackTip's `bt.checkIpReputation()` will tell you if your current IP is on a known flagged list, but it can't fix it.
177
+
178
+ ## Currently passing / failing matrix
179
+
180
+ As of v0.2.0:
181
+
182
+ | Akamai layer | Status | Notes |
183
+ |---|---|---|
184
+ | TCP/IP reputation | User-dependent | BlackTip can't fix; use `checkIpReputation()` to diagnose |
185
+ | TLS fingerprint | ✓ Passing | Real Chrome via `channel: 'chrome'` provides byte-perfect Chrome TLS |
186
+ | HTTP/2 fingerprint | ✓ Passing | Same — real Chrome HTTP/2 stack |
187
+ | HTTP headers (UA / Sec-Ch-Ua consistency) | ✓ Passing in v0.2.0 | The L016 fix |
188
+ | HTTP headers (Sec-Fetch-*, order) | ✓ Passing | Real Chrome emits these naturally |
189
+ | Sensor data validation | Best-effort | Native browser environment passes most Akamai sensors; sites with deeper sensor analysis may still flag |
190
+ | Cookie continuity | ✓ Passing | Real Chrome handles cookies normally; persistent profile via `userDataDir` improves it further |
191
+ | Behavioral patterns | Mostly passing | Behavioral engine generates plausible distributions; Tier 2 dataset calibration tightens further |
192
+
193
+ **Validated against:**
194
+
195
+ - ✓ **OpenTable** (Akamai Bot Manager) — passing as of v0.2.0
196
+ - (More targets to be added as the team validates)
197
+
198
+ ## Recipe: get into an Akamai-protected target
199
+
200
+ ```typescript
201
+ import { BlackTip } from '@rester159/blacktip';
202
+
203
+ async function bookOnAkamaiSite() {
204
+ const bt = new BlackTip({
205
+ logLevel: 'info',
206
+ timeout: 15_000,
207
+ retryAttempts: 2,
208
+ behaviorProfile: 'human',
209
+ // userDataDir: './.bt-profile', // optional: persist Chrome state across runs
210
+ });
211
+ await bt.launch();
212
+
213
+ // Verify we're set up correctly before touching the target
214
+ const fp = await bt.captureFingerprint();
215
+ if (!fp.headers.uaConsistent) {
216
+ throw new Error('UA / Sec-Ch-Ua mismatch — upgrade BlackTip to v0.2.0+');
217
+ }
218
+
219
+ const ip = await bt.checkIpReputation();
220
+ if (ip.isDatacenter) {
221
+ console.warn(`IP is on a datacenter ASN (${ip.asn}). Akamai will likely block.`);
222
+ }
223
+
224
+ // Warm the session before the target
225
+ await bt.warmSession({
226
+ sites: ['https://www.google.com/', 'https://en.wikipedia.org/wiki/Special:Random'],
227
+ dwellMsRange: [3000, 6000],
228
+ });
229
+
230
+ // Navigate to the target
231
+ await bt.navigate('https://www.opentable.com/');
232
+ await bt.waitForStable({ networkIdleMs: 1000, maxMs: 10_000 });
233
+
234
+ // Drive the booking flow as a normal user would
235
+ // ...
236
+ }
237
+ ```
238
+
239
+ ## When BlackTip is NOT enough
240
+
241
+ If `bt.testAgainstAkamai(targetUrl)` reports a block, walk this checklist:
242
+
243
+ 1. **Run `bt.captureFingerprint()`** — does `headers.uaConsistent` say `true`? If `false`, you're on v0.1.0 or older — upgrade.
244
+ 2. **Run `bt.checkIpReputation()`** — is `isDatacenter: true`? Then the IP itself is the problem. Switch networks or use a residential proxy.
245
+ 3. **Test in your normal Chrome from the same machine.** If your normal Chrome ALSO gets blocked, the IP is flagged regardless of what BlackTip does. You need a different network.
246
+ 4. **Try a session warm-up** — call `bt.warmSession()` before the target navigation.
247
+ 5. **Try a persistent profile** — set `userDataDir` in `BlackTipConfig` and let cookies accumulate across runs.
248
+ 6. **Try with a residential proxy** — configure via `BlackTipConfig.proxy`.
249
+ 7. **If all else fails, file an issue** at https://github.com/rester159/blacktip/issues with the output of `bt.captureFingerprint()` and `bt.checkIpReputation()` so the team can investigate.
250
+
251
+ ## What we learned
252
+
253
+ The most important lesson from the v0.1.0 → v0.2.0 transition is that **fingerprint consistency matters more than fingerprint stealth**. We were emitting byte-perfect Chrome TLS, byte-perfect Chrome HTTP/2, and byte-perfect Chrome headers — except for ONE inconsistency between User-Agent and Sec-Ch-Ua. That single bug invalidated everything else against the highest-tier detectors.
254
+
255
+ Top-tier commercial detectors (Akamai, DataDome, PerimeterX) don't just look at individual fingerprint values — they cross-check that values from different layers tell the same story. A "Chrome 125" UA and "Chrome 146" client hints together is a louder signal than either value being slightly off would be alone.
256
+
257
+ **Implication:** if you're building stealth, prioritize consistency over richness. A complete, internally-consistent Chrome 125 fingerprint beats a perfectly-tuned Chrome 130 fingerprint that disagrees with itself somewhere.