@rester159/blacktip 0.4.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.
- package/CHANGELOG.md +33 -1
- package/README.md +4 -0
- package/dist/akamai-sensor.d.ts +128 -0
- package/dist/akamai-sensor.d.ts.map +1 -0
- package/dist/akamai-sensor.js +190 -0
- package/dist/akamai-sensor.js.map +1 -0
- package/dist/blacktip.d.ts +34 -0
- package/dist/blacktip.d.ts.map +1 -1
- package/dist/blacktip.js +35 -0
- package/dist/blacktip.js.map +1 -1
- package/dist/browser-core.d.ts +10 -0
- package/dist/browser-core.d.ts.map +1 -1
- package/dist/browser-core.js +49 -0
- package/dist/browser-core.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/tls-rewriter.d.ts +74 -0
- package/dist/tls-rewriter.d.ts.map +1 -0
- package/dist/tls-rewriter.js +203 -0
- package/dist/tls-rewriter.js.map +1 -0
- package/dist/tls-side-channel.d.ts +13 -4
- package/dist/tls-side-channel.d.ts.map +1 -1
- package/dist/tls-side-channel.js +9 -2
- package/dist/tls-side-channel.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/akamai-sensor.md +183 -0
- package/docs/tls-rewriting.md +121 -0
- package/package.json +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Full TLS rewriting (v0.5.0)
|
|
2
|
+
|
|
3
|
+
The v0.5.0 answer to "every wire request should present a real Chrome TLS fingerprint, not just the gating ones." Without a TCP-level MITM proxy and the OS-specific cert installation hell that entails, BlackTip uses Chrome DevTools Protocol's `Fetch.enable` to pause every HTTP request the browser issues, hand it to the Go `bogdanfinn/tls-client` daemon for upstream execution, and fulfill the response back through CDP. **The browser never opens an upstream TCP connection. Every wire request presents real Chrome TLS via Go.**
|
|
4
|
+
|
|
5
|
+
## What this gives you over the v0.3.0 side-channel
|
|
6
|
+
|
|
7
|
+
The v0.3.0 `bt.fetchWithTls()` only handled gating requests the caller made explicitly. Page subresources, XHR, `fetch()` from page JS — all of those went through Chrome's own TLS, meaning the host OS's Chrome fingerprint reached the wire on every subresource. With the v0.5.0 rewriter installed, **every** subresource also goes through Go.
|
|
8
|
+
|
|
9
|
+
What changes:
|
|
10
|
+
- **Cross-platform UA spoofing is restored.** v0.2.0's L016 fix removed the broken context-level UA override because it caused User-Agent / Sec-Ch-Ua mismatch. With the rewriter, the daemon controls every header on the wire — spoof to your heart's content.
|
|
11
|
+
- **JA4 / GREASE / H2 fingerprint of every request matches real Chrome 133** (or whatever profile you select), regardless of what Chrome the host OS has installed. Useful when you need to impersonate a specific Chrome version that doesn't ship for your platform.
|
|
12
|
+
- **One source of truth for TLS.** No more "the gating request matched but the subresource didn't" inconsistency that anti-bot vendors can fingerprint at the session level.
|
|
13
|
+
|
|
14
|
+
## Validated end-to-end
|
|
15
|
+
|
|
16
|
+
Run `npx vitest run tests/tls-rewriter.integration.test.ts` (requires the Go daemon binary). The load-bearing assertion is:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
JA4: t13d1516h2_8daaf6152771_d8a2da3f94cd (textbook Chrome 133)
|
|
20
|
+
First cipher: TLS_GREASE (0x3A3A) (rotated each connection)
|
|
21
|
+
HTTP/2 fingerprint: 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This is the JA4 reaching tls.peet.ws **via the browser navigation** (not via a direct daemon call). The browser navigated to `tls.peet.ws/api/all`, the JSON it received back came from an upstream connection that the Go daemon opened, not Chrome.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { BlackTip } from '@rester159/blacktip';
|
|
30
|
+
|
|
31
|
+
const bt = new BlackTip({
|
|
32
|
+
logLevel: 'info',
|
|
33
|
+
timeout: 30_000,
|
|
34
|
+
// The headline knob — when set to 'all', every request the browser
|
|
35
|
+
// issues is intercepted via CDP Fetch and forwarded through the Go
|
|
36
|
+
// daemon for upstream execution.
|
|
37
|
+
tlsRewriting: 'all',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await bt.launch();
|
|
41
|
+
await bt.navigate('https://www.opentable.com/');
|
|
42
|
+
|
|
43
|
+
// Verify the rewriter is doing what you expect
|
|
44
|
+
console.log(bt.getTlsRewriterStats());
|
|
45
|
+
// {
|
|
46
|
+
// intercepted: 47,
|
|
47
|
+
// fulfilled: 45,
|
|
48
|
+
// fellThrough: 0,
|
|
49
|
+
// webSocketLeaks: 0,
|
|
50
|
+
// avgDurationMs: 73
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
await bt.close();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The daemon binary must be built first. Go is the only build dependency:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd native/tls-client
|
|
60
|
+
go build -o blacktip-tls . # Linux / macOS
|
|
61
|
+
go build -o blacktip-tls.exe . # Windows
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If the daemon binary is missing when `tlsRewriting: 'all'` is set, `bt.launch()` throws rather than silently falling back to native Chrome TLS that the caller didn't ask for.
|
|
65
|
+
|
|
66
|
+
## Architecture
|
|
67
|
+
|
|
68
|
+
The rewriter is installed as a Patchright/Playwright `context.route('**/*', handler)` hook, which is the high-level wrapper around CDP `Fetch.enable`. We use the route handler so we don't need to manage CDP sessions ourselves.
|
|
69
|
+
|
|
70
|
+
For each intercepted request, the handler:
|
|
71
|
+
|
|
72
|
+
1. Reads URL, method, headers, body via Playwright's `Request` API.
|
|
73
|
+
2. Strips request headers Chrome's lifecycle owns: `Host`, `Content-Length`, `Connection`, `Keep-Alive`, `Transfer-Encoding`, etc.
|
|
74
|
+
3. Calls `TlsSideChannel.fetch()` with the cleaned-up request — the daemon makes the upstream call with real Chrome TLS.
|
|
75
|
+
4. Strips response headers Chrome re-computes: `Content-Length`, `Content-Encoding`, `Transfer-Encoding`, `Connection`, `Keep-Alive`.
|
|
76
|
+
5. Multi-valued headers are joined: `Set-Cookie` with `\n` (so each cookie stays separate), everything else with `, `.
|
|
77
|
+
6. Calls `route.fulfill({ status, headers, body })` with the daemon's response — the browser receives it as if Chrome had made the request itself.
|
|
78
|
+
|
|
79
|
+
The daemon stays alive for the lifetime of the browser context. On `bt.close()`, the rewriter is uninstalled and the daemon is shut down via its existing `TlsSideChannel.close()` path.
|
|
80
|
+
|
|
81
|
+
## Chrome flags
|
|
82
|
+
|
|
83
|
+
When `tlsRewriting: 'all'` is set, BlackTip automatically launches Chrome with `--disable-quic` to force HTTP/1.1 and HTTP/2 only. This is required because Chrome handles QUIC at a layer below CDP Fetch — QUIC requests would bypass the rewriter entirely and present Chrome's native TLS to the wire. With QUIC disabled, every HTTP request flows through Fetch and through us.
|
|
84
|
+
|
|
85
|
+
Real Chrome users disable QUIC routinely (corporate network policies, debugging) so this isn't a fingerprinting tell on its own. If you need QUIC for some reason (rare), set `tlsRewriting: 'off'` and use the v0.3.0 side-channel for the requests that matter most.
|
|
86
|
+
|
|
87
|
+
## Limitations
|
|
88
|
+
|
|
89
|
+
### WebSocket leaks
|
|
90
|
+
|
|
91
|
+
Chrome handles WebSocket frames at a layer below CDP `Fetch.enable`. The initial HTTP `Upgrade: websocket` request can be intercepted, but the actual WebSocket frames after the upgrade go straight through Chrome's native TLS. The rewriter detects WebSocket upgrades, logs a warning, and falls through to `route.continue()` so the upgrade succeeds (and the WebSocket works) — but those frames present Chrome's host-OS TLS, not the daemon's.
|
|
92
|
+
|
|
93
|
+
Mitigation: if your target uses WebSockets and you need full TLS rewriting on them, the only honest answer today is "use a proxy that handles WebSocket framing" (which is back to TCP-level MITM). The rewriter's `webSocketLeaks` stat counts how many you saw so you can decide whether to care.
|
|
94
|
+
|
|
95
|
+
### Streaming responses
|
|
96
|
+
|
|
97
|
+
CDP `Fetch.fulfillRequest` requires a complete body. The rewriter buffers each response fully before fulfilling, which is fine for HTML, JSON, CSS, JS, fonts, images, and typical web pages — but bad for video streams, large file downloads, and Server-Sent Events. For those, set `tlsRewriting: 'off'` and use the v0.3.0 side-channel selectively for the gating requests.
|
|
98
|
+
|
|
99
|
+
### HTTP/3 / QUIC
|
|
100
|
+
|
|
101
|
+
Disabled via `--disable-quic` automatically when the rewriter is on (see above). If a server only serves HTTP/3, the rewriter can't reach it.
|
|
102
|
+
|
|
103
|
+
### Per-request overhead
|
|
104
|
+
|
|
105
|
+
Each intercepted request adds 5–10ms of round-trip overhead through the daemon (measured: `avgDurationMs ≈ 70-100ms` for typical small responses, dominated by the actual upstream network latency). On a typical page with 50 subresources that's 250–500ms added to the total page load. Acceptable for stealth-critical use cases; not acceptable for high-throughput crawling.
|
|
106
|
+
|
|
107
|
+
### Stats accounting
|
|
108
|
+
|
|
109
|
+
`intercepted` may be slightly higher than `fulfilled + fellThrough + webSocketLeaks` (off by 1–5 on a typical page) when requests are aborted by Chrome's navigation lifecycle before the route handler completes. The functional behavior is correct — the page renders properly — but the counter doesn't perfectly account for the cancelled-in-flight cases. Don't use the stats for billing.
|
|
110
|
+
|
|
111
|
+
## When to use which
|
|
112
|
+
|
|
113
|
+
| Need | Use |
|
|
114
|
+
|---|---|
|
|
115
|
+
| Make a single gating request before launching the browser | `bt.fetchWithTls()` (v0.3.0 side-channel) |
|
|
116
|
+
| Make sure every subresource also presents real Chrome TLS | `tlsRewriting: 'all'` (v0.5.0 rewriter) |
|
|
117
|
+
| Cross-platform UA spoofing (Linux pretending to be Mac) | `tlsRewriting: 'all'` |
|
|
118
|
+
| WebSocket-heavy app (chat, real-time stocks) | `tlsRewriting: 'off'` + selective side-channel |
|
|
119
|
+
| Video streaming or large downloads | `tlsRewriting: 'off'` |
|
|
120
|
+
| Maximum throughput crawling | `tlsRewriting: 'off'` |
|
|
121
|
+
| Stealth-critical, throughput-flexible | `tlsRewriting: 'all'` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rester159/blacktip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Stealth browser instrument for AI agents. Real Chrome + patchright CDP stealth + human-calibrated behavioral simulation. Every action is indistinguishable from a human.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|