tunnelfetch 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/package.json +1 -1
- package/src/client.js +9 -0
- package/src/profiles.js +18 -1
package/README.md
CHANGED
|
@@ -427,7 +427,8 @@ pinned in `test/tls/fingerprint.test.js` and `test/http2/fingerprint.test.js`.
|
|
|
427
427
|
| Signature algorithms | ECDSA and RSA-PSS/PKCS#1 over SHA-256/384/512 | `tls.sigSchemes` |
|
|
428
428
|
| ALPN | `h2, http/1.1` | `tls.alpn` |
|
|
429
429
|
| HTTP/2 `SETTINGS` ids **and order** | curl's: `MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, ENABLE_PUSH` | `http2Settings` |
|
|
430
|
-
| h2 preface, `WINDOW_UPDATE`, pseudo-header order
|
|
430
|
+
| h2 preface, `WINDOW_UPDATE`, pseudo-header order | curl's, byte-for-byte | `http2ConnectionWindow`, `http2PseudoHeaderOrder` |
|
|
431
|
+
| HPACK representation | curl's (`:path` without indexing, the rest incremental) | `http2HpackIndexing` |
|
|
431
432
|
| `Accept-Encoding` | `gzip, deflate` — curl's | `decoders` appends |
|
|
432
433
|
|
|
433
434
|
Extension order matters because JA3 and JA4 hash the extension list **in wire order**, so it is most
|
package/README.zh-CN.md
CHANGED
|
@@ -335,7 +335,8 @@ curl(8.7.1 / nghttp2),照线上抓包原样复刻。这么做是实证需
|
|
|
335
335
|
| 签名算法 | SHA-256/384/512 上的 ECDSA 与 RSA-PSS/PKCS#1 | `tls.sigSchemes` |
|
|
336
336
|
| ALPN | `h2, http/1.1` | `tls.alpn` |
|
|
337
337
|
| HTTP/2 `SETTINGS` 的 id **与顺序** | curl 的:`MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, ENABLE_PUSH` | `http2Settings` |
|
|
338
|
-
| h2 前导、`WINDOW_UPDATE
|
|
338
|
+
| h2 前导、`WINDOW_UPDATE`、伪头顺序 | curl 的,逐字节一致 | `http2ConnectionWindow`、`http2PseudoHeaderOrder` |
|
|
339
|
+
| HPACK 表示 | curl 的(`:path` 不索引,其余 incremental) | `http2HpackIndexing` |
|
|
339
340
|
| `Accept-Encoding` | `gzip, deflate`——curl 的 | `decoders` 会追加 |
|
|
340
341
|
|
|
341
342
|
扩展顺序之所以要紧,是因为 JA3 和 JA4 哈希的正是**线上顺序**的扩展列表,那是指纹识别读到的主要内容。`pre_shared_key` 无论你怎么配都强制排最后:RFC 8446 §4.2.11 把 binder 的转录定义为"截到 binder 之前的那段 hello",只有后面不跟东西时这个范围才成立。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunnelfetch",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "A fetch-shaped HTTP client that can route through HTTP CONNECT / HTTPS / SOCKS5 proxies on runtimes with only raw TCP, such as Cloudflare Workers. Implements TLS in userland because the runtime cannot verify a tunnelled peer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
package/src/client.js
CHANGED
|
@@ -501,6 +501,15 @@ function registerHttp2(client, key, conn) {
|
|
|
501
501
|
// `tls`, and one being reachable while the other was not made "the fingerprint is
|
|
502
502
|
// configurable" only half true.
|
|
503
503
|
...(client.options.http2Settings ? { settings: client.options.http2Settings } : {}),
|
|
504
|
+
// The connection-level WINDOW_UPDATE that follows the preface. It is part of the h2
|
|
505
|
+
// fingerprint and it was DEAD: `profiles.chrome` declared `http2ConnectionWindow`, nothing
|
|
506
|
+
// copied it out of the profile, nothing passed it here, and the connection reads an option
|
|
507
|
+
// spelled `connectionWindow`. The identifier appeared in exactly two places in the whole
|
|
508
|
+
// repository — the profile and its type declaration — so a Chromium ClientHello was followed
|
|
509
|
+
// by curl's 1000 MiB window increment, which is about as distinctive as an h2 client gets.
|
|
510
|
+
...(client.options.http2ConnectionWindow
|
|
511
|
+
? { connectionWindow: client.options.http2ConnectionWindow }
|
|
512
|
+
: {}),
|
|
504
513
|
...(client.options.http2PseudoHeaderOrder
|
|
505
514
|
? { pseudoHeaderOrder: client.options.http2PseudoHeaderOrder }
|
|
506
515
|
: {}),
|
package/src/profiles.js
CHANGED
|
@@ -95,6 +95,19 @@ export const chrome = Object.freeze({
|
|
|
95
95
|
// both fingerprints are exactly what the browser normally sends.
|
|
96
96
|
http2Settings: Object.freeze([[1, 65536], [2, 0], [4, 6291456], [6, 262144]]),
|
|
97
97
|
http2ConnectionWindow: 15663105 + 65535,
|
|
98
|
+
// NOT captured: http2HpackIndexing. So this identity currently presents curl's HPACK
|
|
99
|
+
// representation (`:path` without indexing, everything else incremental) under a Chromium
|
|
100
|
+
// ClientHello, which is the split identity this whole module exists to prevent — narrowed to one
|
|
101
|
+
// field, but real, and named here rather than left to be discovered. It is not in `requires`
|
|
102
|
+
// because refusing the profile outright over one uncaptured field would take away the working
|
|
103
|
+
// 90% of the identity, and because the honest fix is a capture rather than a guess: this package
|
|
104
|
+
// does not invent fingerprint values. See test/http2/fingerprint.test.js, which pins the absence
|
|
105
|
+
// so that supplying it later is a deliberate act.
|
|
106
|
+
//
|
|
107
|
+
// The connection window immediately above was DEAD until 1.6.1 — declared here, copied by
|
|
108
|
+
// nothing, passed by nothing, and read under a different name — so every connection using this
|
|
109
|
+
// profile sent curl's 1000 MiB increment. Four places had to agree and no test checked that they
|
|
110
|
+
// did. There is one now.
|
|
98
111
|
// m,a,s,p — NOT curl's m,s,a,p. Measured, and a difference that would have been easy to miss.
|
|
99
112
|
http2PseudoHeaderOrder: Object.freeze([':method', ':authority', ':scheme', ':path']),
|
|
100
113
|
headerOrder: Object.freeze([
|
|
@@ -174,7 +187,11 @@ export function applyProfile(options) {
|
|
|
174
187
|
for (const kind of ['ciphers', 'groups', 'decoders']) {
|
|
175
188
|
if (p[kind]) out[kind] = { ...p[kind], ...(options[kind] ?? {}) };
|
|
176
189
|
}
|
|
177
|
-
|
|
190
|
+
// Every h2 fingerprint field a profile can carry must be listed here. `http2ConnectionWindow`
|
|
191
|
+
// was missing, which made it dead config: the chrome profile declared Chromium's ~15 MiB window
|
|
192
|
+
// and every chrome connection sent curl's 1000 MiB one.
|
|
193
|
+
for (const key of ['headerOrder', 'http2Settings', 'http2ConnectionWindow',
|
|
194
|
+
'http2PseudoHeaderOrder', 'http2HpackIndexing']) {
|
|
178
195
|
if (options[key] === undefined && p[key] != null) out[key] = p[key];
|
|
179
196
|
}
|
|
180
197
|
// Profile headers are DEFAULTS: a request that sets its own User-Agent keeps it. They are folded
|