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 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, HPACK representation | curl's, byte-for-byte | fixed |
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`、伪头顺序、HPACK 表示 | curl 的,逐字节一致 | 固定 |
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.0",
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
- for (const key of ['headerOrder', 'http2Settings', 'http2PseudoHeaderOrder', 'http2HpackIndexing']) {
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