@browsercore/testing 0.1.3 → 0.2.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 (174) hide show
  1. package/README.md +5 -0
  2. package/captures/DIVERGENCE_REPORT.md +325 -0
  3. package/captures/IMPERSIONATION_GAPS_REPORT.md +367 -0
  4. package/captures/README.md +56 -0
  5. package/captures/REVIEW_FINDINGS.md +71 -0
  6. package/captures/_probe/analyze.py +67 -0
  7. package/captures/_probe/capture_bytes.py +127 -0
  8. package/captures/_probe/chrome131_determinism.py +32 -0
  9. package/captures/_probe/diagnose_capture.py +59 -0
  10. package/captures/_probe/golden_analysis.py +91 -0
  11. package/captures/_probe/inspect_golden.py +55 -0
  12. package/captures/_probe/inspect_sv.py +16 -0
  13. package/captures/_probe/make_golden.py +170 -0
  14. package/captures/_probe/output/chrome100.json +219 -0
  15. package/captures/_probe/output/chrome101.json +219 -0
  16. package/captures/_probe/output/chrome104.json +219 -0
  17. package/captures/_probe/output/chrome107.json +220 -0
  18. package/captures/_probe/output/chrome110.json +220 -0
  19. package/captures/_probe/output/chrome116.json +220 -0
  20. package/captures/_probe/output/chrome119.json +219 -0
  21. package/captures/_probe/output/chrome120.json +219 -0
  22. package/captures/_probe/output/chrome123.json +223 -0
  23. package/captures/_probe/output/chrome124.json +224 -0
  24. package/captures/_probe/output/chrome131.json +224 -0
  25. package/captures/_probe/output/chrome131_fresh.json +224 -0
  26. package/captures/_probe/output/chrome133a.json +224 -0
  27. package/captures/_probe/output/chrome136.json +224 -0
  28. package/captures/_probe/output/chrome99.json +219 -0
  29. package/captures/_probe/output/edge101.json +219 -0
  30. package/captures/_probe/output/edge99.json +219 -0
  31. package/captures/_probe/output/firefox133.json +227 -0
  32. package/captures/_probe/output/firefox135.json +230 -0
  33. package/captures/_probe/output/safari155.json +210 -0
  34. package/captures/_probe/output/safari170.json +214 -0
  35. package/captures/_probe/output/safari170_fresh.json +214 -0
  36. package/captures/_probe/output/safari180.json +216 -0
  37. package/captures/_probe/output/safari180_ios.json +216 -0
  38. package/captures/_probe/output/safari184.json +215 -0
  39. package/captures/_probe/output/safari260.json +211 -0
  40. package/captures/_probe/output/summary.json +2520 -0
  41. package/captures/_probe/parse_existing_settings.py +15 -0
  42. package/captures/_probe/peetprint.py +39 -0
  43. package/captures/_probe/probe.py +137 -0
  44. package/captures/_probe/raw_bytes/chrome-131_client_hello.bin +0 -0
  45. package/captures/_probe/raw_bytes/chrome-131_client_hello.hex +32 -0
  46. package/captures/_probe/raw_bytes/firefox-133_client_hello.bin +0 -0
  47. package/captures/_probe/raw_bytes/firefox-133_client_hello.hex +32 -0
  48. package/captures/_probe/raw_bytes/safari-17_client_hello.bin +0 -0
  49. package/captures/_probe/raw_bytes/safari-17_client_hello.hex +32 -0
  50. package/captures/_probe/relay_capture.py +137 -0
  51. package/captures/_probe/socks_capture.py +138 -0
  52. package/captures/_probe/validate_fresh.py +66 -0
  53. package/captures/_probe/validate_golden.py +97 -0
  54. package/captures/_probe/verify_capture.py +68 -0
  55. package/captures/chrome-131/http2/settings.bin +0 -0
  56. package/captures/chrome-131/http2/settings.meta.json +9 -0
  57. package/captures/chrome-131/oracle_capture.reference.json +224 -0
  58. package/captures/chrome-131/tls/client_hello.bin +0 -0
  59. package/captures/chrome-131/tls/client_hello.meta.json +20 -0
  60. package/captures/chrome-140/http2/settings.bin +0 -0
  61. package/captures/chrome-140/http2/settings.meta.json +9 -0
  62. package/captures/chrome-140/tls/client_hello.bin +0 -0
  63. package/captures/chrome-140/tls/client_hello.meta.json +12 -0
  64. package/captures/firefox-128/tls/client_hello.bin +0 -0
  65. package/captures/firefox-128/tls/client_hello.meta.json +12 -0
  66. package/captures/firefox-133/http2/settings.bin +0 -0
  67. package/captures/firefox-133/http2/settings.meta.json +9 -0
  68. package/captures/firefox-133/oracle_capture.reference.json +227 -0
  69. package/captures/firefox-133/tls/client_hello.bin +0 -0
  70. package/captures/firefox-133/tls/client_hello.meta.json +20 -0
  71. package/captures/safari-17/http2/settings.bin +0 -0
  72. package/captures/safari-17/http2/settings.meta.json +9 -0
  73. package/captures/safari-17/oracle_capture.reference.json +214 -0
  74. package/captures/safari-17/tls/client_hello.bin +0 -0
  75. package/captures/safari-17/tls/client_hello.meta.json +20 -0
  76. package/dist/bench/bench.d.ts +22 -6
  77. package/dist/bench/bench.d.ts.map +1 -1
  78. package/dist/bench/bench.js +105 -10
  79. package/dist/bench/bench.js.map +1 -1
  80. package/dist/captures/manifest.d.ts.map +1 -1
  81. package/dist/captures/manifest.js +2 -3
  82. package/dist/captures/manifest.js.map +1 -1
  83. package/dist/categories/cookies.d.ts +3 -2
  84. package/dist/categories/cookies.d.ts.map +1 -1
  85. package/dist/categories/cookies.js +228 -11
  86. package/dist/categories/cookies.js.map +1 -1
  87. package/dist/categories/redirects.d.ts +8 -2
  88. package/dist/categories/redirects.d.ts.map +1 -1
  89. package/dist/categories/redirects.js +97 -12
  90. package/dist/categories/redirects.js.map +1 -1
  91. package/dist/e2e/cert-gen.d.ts +30 -0
  92. package/dist/e2e/cert-gen.d.ts.map +1 -0
  93. package/dist/e2e/cert-gen.js +158 -0
  94. package/dist/e2e/cert-gen.js.map +1 -0
  95. package/dist/e2e/parse-clienthello.d.ts +177 -0
  96. package/dist/e2e/parse-clienthello.d.ts.map +1 -0
  97. package/dist/e2e/parse-clienthello.js +343 -0
  98. package/dist/e2e/parse-clienthello.js.map +1 -0
  99. package/dist/e2e/sink-server.d.ts +109 -0
  100. package/dist/e2e/sink-server.d.ts.map +1 -0
  101. package/dist/e2e/sink-server.js +292 -0
  102. package/dist/e2e/sink-server.js.map +1 -0
  103. package/dist/errors.d.ts.map +1 -1
  104. package/dist/errors.js.map +1 -1
  105. package/dist/fingerprint/index.d.ts +5 -2
  106. package/dist/fingerprint/index.d.ts.map +1 -1
  107. package/dist/fingerprint/index.js +3 -2
  108. package/dist/fingerprint/index.js.map +1 -1
  109. package/dist/fingerprint/ja3.d.ts.map +1 -1
  110. package/dist/fingerprint/ja3.js +33 -4
  111. package/dist/fingerprint/ja3.js.map +1 -1
  112. package/dist/fingerprint/ja4-errors.d.ts +17 -0
  113. package/dist/fingerprint/ja4-errors.d.ts.map +1 -0
  114. package/dist/fingerprint/ja4-errors.js +20 -0
  115. package/dist/fingerprint/ja4-errors.js.map +1 -0
  116. package/dist/fingerprint/ja4-reader.d.ts +44 -0
  117. package/dist/fingerprint/ja4-reader.d.ts.map +1 -0
  118. package/dist/fingerprint/ja4-reader.js +112 -0
  119. package/dist/fingerprint/ja4-reader.js.map +1 -0
  120. package/dist/fingerprint/ja4.d.ts +11 -43
  121. package/dist/fingerprint/ja4.d.ts.map +1 -1
  122. package/dist/fingerprint/ja4.js +54 -141
  123. package/dist/fingerprint/ja4.js.map +1 -1
  124. package/dist/fingerprint/ja4h.d.ts +46 -0
  125. package/dist/fingerprint/ja4h.d.ts.map +1 -0
  126. package/dist/fingerprint/ja4h.js +50 -0
  127. package/dist/fingerprint/ja4h.js.map +1 -0
  128. package/dist/golden/golden.d.ts +15 -1
  129. package/dist/golden/golden.d.ts.map +1 -1
  130. package/dist/golden/golden.js +35 -22
  131. package/dist/golden/golden.js.map +1 -1
  132. package/dist/index.d.ts +17 -1
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +26 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/reference/browser-provider.d.ts +33 -0
  137. package/dist/reference/browser-provider.d.ts.map +1 -0
  138. package/dist/reference/browser-provider.js +60 -0
  139. package/dist/reference/browser-provider.js.map +1 -0
  140. package/dist/reference/curl-provider.d.ts +40 -0
  141. package/dist/reference/curl-provider.d.ts.map +1 -0
  142. package/dist/reference/curl-provider.js +96 -0
  143. package/dist/reference/curl-provider.js.map +1 -0
  144. package/dist/reference/dump.d.ts +42 -0
  145. package/dist/reference/dump.d.ts.map +1 -0
  146. package/dist/reference/dump.js +102 -0
  147. package/dist/reference/dump.js.map +1 -0
  148. package/dist/reference/node-reference.d.ts.map +1 -1
  149. package/dist/reference/node-reference.js +2 -5
  150. package/dist/reference/node-reference.js.map +1 -1
  151. package/dist/reference/reference-errors.d.ts +36 -0
  152. package/dist/reference/reference-errors.d.ts.map +1 -0
  153. package/dist/reference/reference-errors.js +43 -0
  154. package/dist/reference/reference-errors.js.map +1 -0
  155. package/dist/reference/reference-types.d.ts +33 -0
  156. package/dist/reference/reference-types.d.ts.map +1 -0
  157. package/dist/reference/reference-types.js +9 -0
  158. package/dist/reference/reference-types.js.map +1 -0
  159. package/dist/reference/reference.d.ts +14 -77
  160. package/dist/reference/reference.d.ts.map +1 -1
  161. package/dist/reference/reference.js +43 -207
  162. package/dist/reference/reference.js.map +1 -1
  163. package/dist/rfc/rfcTests.d.ts +46 -8
  164. package/dist/rfc/rfcTests.d.ts.map +1 -1
  165. package/dist/rfc/rfcTests.js +203 -16
  166. package/dist/rfc/rfcTests.js.map +1 -1
  167. package/dist/types.d.ts +2 -1
  168. package/dist/types.d.ts.map +1 -1
  169. package/dist/types.js.map +1 -1
  170. package/dist/utils.d.ts +3 -0
  171. package/dist/utils.d.ts.map +1 -1
  172. package/dist/utils.js +11 -5
  173. package/dist/utils.js.map +1 -1
  174. package/package.json +61 -55
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Low-level TLS ClientHello byte readers shared by JA4 computation.
3
+ *
4
+ * Factored out of `ja4.ts` so the fingerprint module stays focused on the
5
+ * JA4 spec (sorting, hashing, formatting) and the readers live alongside the
6
+ * wire-format constants they depend on. All readers are bounds-checked and
7
+ * throw {@link Ja4ParseError} on truncation.
8
+ */
9
+ import { Ja4ParseError } from "./ja4-errors.js";
10
+ /** GREASE values per RFC 8701 — these are reserved and must be ignored. */
11
+ export const GREASE_VALUES = new Set([
12
+ 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
13
+ 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa,
14
+ ]);
15
+ /** TLS extension type numbers we special-case when parsing. */
16
+ export const EXT_SNI = 0x0000;
17
+ export const EXT_SUPPORTED_GROUPS = 0x000a;
18
+ export const EXT_EC_POINT_FORMATS = 0x000b;
19
+ export const EXT_ALPN = 0x0010;
20
+ export const EXT_SUPPORTED_VERSIONS = 0x002b;
21
+ /** TLS protocol-version codes → human-readable version string. */
22
+ export const TLS_VERSIONS = new Map([
23
+ [0x0304, "13"],
24
+ [0x0303, "12"],
25
+ [0x0302, "11"],
26
+ [0x0301, "10"],
27
+ [0x0300, "09"],
28
+ ]);
29
+ /** Read a big-endian uint16 at `offset` in `buf` (unchecked — caller bounds). */
30
+ export function uint16(buf, offset) {
31
+ const hi = buf[offset];
32
+ const lo = buf[offset + 1];
33
+ if (hi === undefined || lo === undefined) {
34
+ throw new Ja4ParseError(`uint16 read out of bounds at offset ${offset}`);
35
+ }
36
+ return (hi << 8) | lo;
37
+ }
38
+ /** Read a 24-bit big-endian integer at `offset` in `buf`. */
39
+ export function uint24(buf, offset) {
40
+ const hi = buf[offset];
41
+ const mid = buf[offset + 1];
42
+ const lo = buf[offset + 2];
43
+ if (hi === undefined || mid === undefined || lo === undefined) {
44
+ throw new Ja4ParseError(`uint24 read out of bounds at offset ${offset}`);
45
+ }
46
+ return (hi << 16) | (mid << 8) | lo;
47
+ }
48
+ /** Format a number as a 4-char lowercase hex string (`0x1a2b` → `"1a2b"`). */
49
+ export function hex4(value) {
50
+ return value.toString(16).padStart(4, "0");
51
+ }
52
+ /** Human-readable version string for a TLS `client_version` code. */
53
+ export function tlsVersionLabel(versionCode) {
54
+ return TLS_VERSIONS.get(versionCode) ?? versionCode.toString(16).padStart(2, "0");
55
+ }
56
+ /**
57
+ * Read the non-GREASE supported_groups(10) list that starts at `pos`.
58
+ * `pos` points at the 2-byte list length. Throws {@link Ja4ParseError} on
59
+ * truncation.
60
+ */
61
+ export function readSupportedGroups(clientHello, pos) {
62
+ const listLen = uint16(clientHello, pos);
63
+ const groups = [];
64
+ for (let i = 0; i + 1 < listLen; i += 2) {
65
+ const group = uint16(clientHello, pos + 2 + i);
66
+ if (!GREASE_VALUES.has(group)) {
67
+ groups.push(group);
68
+ }
69
+ }
70
+ return groups;
71
+ }
72
+ /**
73
+ * Read the ec_point_formats(11) list that starts at `pos`. `pos` points at the
74
+ * 1-byte list length. Throws {@link Ja4ParseError} on truncation.
75
+ */
76
+ export function readEcPointFormats(clientHello, pos) {
77
+ const listLen = clientHello[pos];
78
+ if (listLen === undefined) {
79
+ throw new Ja4ParseError("ClientHello truncated at ec_point_formats length");
80
+ }
81
+ const formats = [];
82
+ for (let i = 0; i < listLen; i++) {
83
+ const fmt = clientHello[pos + 1 + i];
84
+ if (fmt === undefined) {
85
+ throw new Ja4ParseError("ClientHello truncated in ec_point_formats list");
86
+ }
87
+ formats.push(fmt);
88
+ }
89
+ return formats;
90
+ }
91
+ /**
92
+ * Read the ALPN protocol list that starts at `pos`. `pos` points at the 2-byte
93
+ * list length. Returns the comma-joined protocol string. Throws
94
+ * {@link Ja4ParseError} on truncation.
95
+ */
96
+ export function readAlpnProtocols(clientHello, pos) {
97
+ const listLen = uint16(clientHello, pos);
98
+ let cursor = pos + 2;
99
+ const protocols = [];
100
+ const listEnd = cursor + listLen;
101
+ while (cursor < listEnd) {
102
+ const protoLen = clientHello[cursor];
103
+ if (protoLen === undefined) {
104
+ throw new Ja4ParseError("ClientHello truncated at ALPN protocol length");
105
+ }
106
+ cursor += 1;
107
+ protocols.push(new TextDecoder().decode(clientHello.subarray(cursor, cursor + protoLen)));
108
+ cursor += protoLen;
109
+ }
110
+ return protocols.join(",");
111
+ }
112
+ //# sourceMappingURL=ja4-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ja4-reader.js","sourceRoot":"","sources":["../../src/fingerprint/ja4-reader.ts"],"names":[],"mappings":"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"}
@@ -15,17 +15,11 @@
15
15
  *
16
16
  * The concatenated `JA4_a_JA4_b_JA4_c_JA4_f` string is the canonical JA4 tag.
17
17
  *
18
- * JA4H (the HTTP-layer sibling) is computed separately by {@link computeJa4h}
19
- * from method/version/headers/cookies — it is the HTTP analogue used by the
20
- * reference provider to fingerprint a captured request.
18
+ * JA4H (the HTTP-layer sibling) is computed separately in {@link ./ja4h.ts}.
21
19
  *
22
20
  * Reference: https://github.com/FoxIO-LLC/JA4
23
21
  */
24
- /** Reasons a ClientHello cannot be parsed into a JA4 input. */
25
- export declare class Ja4ParseError extends Error {
26
- readonly kind: "Ja4ParseError";
27
- constructor(message: string);
28
- }
22
+ import { Ja4ParseError } from "./ja4-errors.js";
29
23
  /**
30
24
  * Parsed fields from a ClientHello relevant to JA4.
31
25
  *
@@ -35,6 +29,12 @@ export declare class Ja4ParseError extends Error {
35
29
  */
36
30
  export interface Ja4ClientHello {
37
31
  readonly tlsVersion: string;
32
+ /**
33
+ * Raw numeric `client_version` code parsed from the ClientHello (e.g.
34
+ * `0x0304` for TLS 1.3). Used by JA4_f so that byte-identical ClientHellos
35
+ * produce the same fingerprint regardless of TLS-record wrapping.
36
+ */
37
+ readonly versionCode: number;
38
38
  readonly sniPresent: boolean;
39
39
  readonly cipherSuites: readonly number[];
40
40
  readonly extensions: readonly number[];
@@ -51,15 +51,9 @@ export interface Ja4Fingerprint {
51
51
  /** Canonical `JA4_a_JA4_b_JA4_c_JA4_f` tag. */
52
52
  readonly tag: string;
53
53
  }
54
- /** The three computed JA4H parts. */
55
- export interface Ja4hFingerprint {
56
- readonly a: string;
57
- readonly b: string;
58
- readonly c: string;
59
- readonly d: string;
60
- /** Canonical `JA4H_a_JA4H_b_JA4H_c_JA4H_d` tag. */
61
- readonly tag: string;
62
- }
54
+ export { Ja4ParseError };
55
+ /** First 12 hex chars of the SHA-256 digest of `input`. */
56
+ export declare function sha256First12(input: string): string;
63
57
  /**
64
58
  * Parse a TLS ClientHello (either wrapped in a TLS record or bare) into the
65
59
  * fields JA4 needs. Throws {@link Ja4ParseError} on malformed input.
@@ -80,30 +74,4 @@ export declare function computeJa4(clientHello: Uint8Array): string;
80
74
  * full tag or against JA4_a (the connection prefix) for grouping.
81
75
  */
82
76
  export declare function computeJa4Fingerprint(clientHello: Uint8Array): Ja4Fingerprint;
83
- /**
84
- * HTTP request fields needed to compute a JA4H fingerprint.
85
- *
86
- * Only the parts JA4H inspects are required — method, HTTP version, header
87
- * names (in order), cookies, and the Accept-Language prefix.
88
- */
89
- export interface Ja4hRequest {
90
- readonly method: "GET" | "POST" | "HEAD" | "PUT" | "DELETE" | "OPTIONS" | "PATCH";
91
- readonly httpVersion: "1.1" | "2" | "3";
92
- /** Header names in the order they are serialized (lowercased). */
93
- readonly headerNames: readonly string[];
94
- /** Cookie name=value pairs (as they appear in the Cookie header). */
95
- readonly cookies: readonly string[];
96
- /** The Accept-Language header value (only its 4-char prefix is used). */
97
- readonly acceptLanguage?: string;
98
- }
99
- /**
100
- * Compute the four-part JA4H HTTP fingerprint from a captured request.
101
- *
102
- * Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
103
- * - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
104
- * - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
105
- * - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
106
- * - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
107
- */
108
- export declare function computeJa4h(request: Ja4hRequest): Ja4hFingerprint;
109
77
  //# sourceMappingURL=ja4.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ja4.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,+DAA+D;AAC/D,qBAAa,aAAc,SAAQ,KAAK;IACpC,SAAgB,IAAI,EAAG,eAAe,CAAU;gBACpC,OAAO,EAAE,MAAM;CAI9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAED,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAgL3E;AAeD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,UAAU,GAAG,MAAM,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAgC7E;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAClF,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAkCjE"}
1
+ {"version":3,"file":"ja4.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAgBhD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC3B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAID,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAeD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAyJ3E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,UAAU,GAAG,MAAM,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,UAAU,GAAG,cAAc,CAqC7E"}
@@ -15,56 +15,32 @@
15
15
  *
16
16
  * The concatenated `JA4_a_JA4_b_JA4_c_JA4_f` string is the canonical JA4 tag.
17
17
  *
18
- * JA4H (the HTTP-layer sibling) is computed separately by {@link computeJa4h}
19
- * from method/version/headers/cookies — it is the HTTP analogue used by the
20
- * reference provider to fingerprint a captured request.
18
+ * JA4H (the HTTP-layer sibling) is computed separately in {@link ./ja4h.ts}.
21
19
  *
22
20
  * Reference: https://github.com/FoxIO-LLC/JA4
23
21
  */
24
22
  import { createHash } from "node:crypto";
25
- /** Reasons a ClientHello cannot be parsed into a JA4 input. */
26
- export class Ja4ParseError extends Error {
27
- kind = "Ja4ParseError";
28
- constructor(message) {
29
- super(message);
30
- this.name = "Ja4ParseError";
31
- }
32
- }
33
- /** GREASE values per RFC 8701 — these are reserved and must be ignored. */
34
- const GREASE_VALUES = new Set([
35
- 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
36
- 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa,
37
- ]);
38
- /** TLS extension type numbers we special-case when parsing. */
39
- const EXT_SNI = 0x0000;
40
- const EXT_SUPPORTED_GROUPS = 0x000a;
41
- const EXT_EC_POINT_FORMATS = 0x000b;
42
- const EXT_ALPN = 0x0010;
43
- const EXT_SUPPORTED_VERSIONS = 0x002b;
44
- /** TLS protocol-version codes → human-readable version string. */
45
- const TLS_VERSIONS = new Map([
46
- [0x0304, "13"],
47
- [0x0303, "12"],
48
- [0x0302, "11"],
49
- [0x0301, "10"],
50
- [0x0300, "09"],
51
- ]);
52
- /** Read a big-endian uint16 at `offset` in `buf` (unchecked — caller bounds). */
53
- function uint16(buf, offset) {
54
- return (buf[offset] << 8) | buf[offset + 1];
55
- }
56
- /** Read a 24-bit big-endian integer at `offset` in `buf`. */
57
- function uint24(buf, offset) {
58
- return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
59
- }
60
- /** Format a number as a 4-char lowercase hex string (`0x1a2b` → `"1a2b"`). */
61
- function hex4(value) {
62
- return value.toString(16).padStart(4, "0");
63
- }
23
+ import { Ja4ParseError } from "./ja4-errors.js";
24
+ import { EXT_ALPN, EXT_EC_POINT_FORMATS, EXT_SNI, EXT_SUPPORTED_GROUPS, GREASE_VALUES, hex4, readAlpnProtocols, readEcPointFormats, readSupportedGroups, tlsVersionLabel, uint16, uint24, } from "./ja4-reader.js";
25
+ // Re-export the parse error so existing import sites (`from "./ja4.js"`) keep
26
+ // resolving after the type moved to `ja4-errors.ts`.
27
+ export { Ja4ParseError };
64
28
  /** First 12 hex chars of the SHA-256 digest of `input`. */
65
- function sha256First12(input) {
29
+ export function sha256First12(input) {
66
30
  return createHash("sha256").update(input).digest("hex").slice(0, 12);
67
31
  }
32
+ /** ALPN code: first char of first ALPN + first char of last ALPN, or "00". */
33
+ function alpnCode(alpnRaw) {
34
+ if (alpnRaw.length === 0) {
35
+ return "00";
36
+ }
37
+ const protocols = alpnRaw.split(",");
38
+ const first = protocols[0] ?? "";
39
+ const last = protocols.at(-1) ?? "";
40
+ const a = first.length > 0 ? first[0] ?? "0" : "0";
41
+ const b = last.length > 0 ? last[0] ?? "0" : "0";
42
+ return `${a}${b}`.toLowerCase();
43
+ }
68
44
  /**
69
45
  * Parse a TLS ClientHello (either wrapped in a TLS record or bare) into the
70
46
  * fields JA4 needs. Throws {@link Ja4ParseError} on malformed input.
@@ -120,6 +96,9 @@ export function parseJa4ClientHello(clientHello) {
120
96
  }
121
97
  // session_id(variable)
122
98
  const sessionIdLen = clientHello[pos];
99
+ if (sessionIdLen === undefined) {
100
+ throw new Ja4ParseError("ClientHello truncated at session id length");
101
+ }
123
102
  pos += 1 + sessionIdLen;
124
103
  if (pos + 2 > end) {
125
104
  throw new Ja4ParseError("ClientHello truncated before cipher suites");
@@ -143,11 +122,15 @@ export function parseJa4ClientHello(clientHello) {
143
122
  }
144
123
  // compression_methods(variable)
145
124
  const compLen = clientHello[pos];
125
+ if (compLen === undefined) {
126
+ throw new Ja4ParseError("ClientHello truncated at compression methods length");
127
+ }
146
128
  pos += 1 + compLen;
147
129
  if (pos + 2 > end) {
148
130
  // No extensions present.
149
131
  return {
150
- tlsVersion: TLS_VERSIONS.get(versionCode) ?? versionCode.toString(16).padStart(2, "0"),
132
+ tlsVersion: tlsVersionLabel(versionCode),
133
+ versionCode,
151
134
  sniPresent: false,
152
135
  cipherSuites,
153
136
  extensions: [],
@@ -172,54 +155,29 @@ export function parseJa4ClientHello(clientHello) {
172
155
  if (!GREASE_VALUES.has(extType)) {
173
156
  extensions.push(extType);
174
157
  }
175
- switch (extType) {
176
- case EXT_SNI:
177
- sniPresent = true;
178
- break;
179
- case EXT_SUPPORTED_GROUPS:
180
- if (extLen >= 4) {
181
- const listLen = uint16(clientHello, pos);
182
- for (let i = 0; i + 1 < listLen; i += 2) {
183
- const group = uint16(clientHello, pos + 2 + i);
184
- if (!GREASE_VALUES.has(group)) {
185
- supportedGroups.push(group);
186
- }
187
- }
188
- }
189
- break;
190
- case EXT_EC_POINT_FORMATS:
191
- if (extLen >= 1) {
192
- const listLen = clientHello[pos];
193
- for (let i = 0; i < listLen; i++) {
194
- ecPointFormats.push(clientHello[pos + 1 + i]);
195
- }
196
- }
197
- break;
198
- case EXT_ALPN:
199
- if (extLen >= 2) {
200
- const listLen = uint16(clientHello, pos);
201
- let cursor = pos + 2;
202
- const protocols = [];
203
- const listEnd = cursor + listLen;
204
- while (cursor < listEnd) {
205
- const protoLen = clientHello[cursor];
206
- cursor += 1;
207
- protocols.push(new TextDecoder().decode(clientHello.subarray(cursor, cursor + protoLen)));
208
- cursor += protoLen;
209
- }
210
- alpnRaw = protocols.join(",");
211
- }
212
- break;
213
- case EXT_SUPPORTED_VERSIONS:
214
- // JA4 uses the highest supported version from this extension.
215
- break;
216
- default:
217
- break;
158
+ if (extType === EXT_SNI) {
159
+ sniPresent = true;
218
160
  }
161
+ else if (extType === EXT_SUPPORTED_GROUPS) {
162
+ if (extLen >= 4) {
163
+ supportedGroups.push(...readSupportedGroups(clientHello, pos));
164
+ }
165
+ }
166
+ else if (extType === EXT_EC_POINT_FORMATS) {
167
+ if (extLen >= 1) {
168
+ ecPointFormats.push(...readEcPointFormats(clientHello, pos));
169
+ }
170
+ }
171
+ else if (extType === EXT_ALPN && extLen >= 2) {
172
+ alpnRaw = readAlpnProtocols(clientHello, pos);
173
+ }
174
+ // EXT_SUPPORTED_VERSIONS: JA4 uses the highest supported version from
175
+ // this extension — no parsing needed here.
219
176
  pos += extLen;
220
177
  }
221
178
  return {
222
- tlsVersion: TLS_VERSIONS.get(versionCode) ?? versionCode.toString(16).padStart(2, "0"),
179
+ tlsVersion: tlsVersionLabel(versionCode),
180
+ versionCode,
223
181
  sniPresent,
224
182
  cipherSuites,
225
183
  extensions,
@@ -228,18 +186,6 @@ export function parseJa4ClientHello(clientHello) {
228
186
  alpnRaw,
229
187
  };
230
188
  }
231
- /** ALPN code: first char of first ALPN + first char of last ALPN, or "00". */
232
- function alpnCode(alpnRaw) {
233
- if (alpnRaw.length === 0) {
234
- return "00";
235
- }
236
- const protocols = alpnRaw.split(",");
237
- const first = protocols[0] ?? "";
238
- const last = protocols[protocols.length - 1] ?? "";
239
- const a = first.length > 0 ? first[0] : "0";
240
- const b = last.length > 0 ? last[0] : "0";
241
- return `${a}${b}`.toLowerCase();
242
- }
243
189
  /**
244
190
  * Compute the canonical JA4 tag (`JA4_a_JA4_b_JA4_c_JA4_f`) from a ClientHello.
245
191
  *
@@ -264,18 +210,23 @@ export function computeJa4Fingerprint(clientHello) {
264
210
  .toString()
265
211
  .padStart(2, "0")}${sniFlag}${hello.tlsVersion}${alpnCode(hello.alpnRaw)}`;
266
212
  // JA4_b: sorted cipher suites, 4-char hex, SHA-256, first 12 hex.
267
- const sortedCiphers = [...hello.cipherSuites].sort((x, y) => x - y).map(hex4).join("");
213
+ const sortedCiphers = [...hello.cipherSuites].sort((x, y) => x - y).map((s) => hex4(s)).join("");
268
214
  const b = sortedCiphers.length > 0 ? sha256First12(sortedCiphers) : "000000000000";
269
215
  // JA4_c: sorted extensions (excluding SNI=0, ALPN=16), 4-char hex.
270
216
  const filteredExts = hello.extensions
271
217
  .filter((e) => e !== EXT_SNI && e !== EXT_ALPN)
272
218
  .sort((x, y) => x - y)
273
- .map(hex4)
219
+ .map((e) => hex4(e))
274
220
  .join("");
275
221
  const c = filteredExts.length > 0 ? sha256First12(filteredExts) : "000000000000";
276
222
  // JA4_f: raw fields — version, ciphers, extensions, supported groups, ec.
223
+ // Use the parsed client_version (`hello.versionCode`), NOT the first two
224
+ // bytes of the buffer: those are the TLS record header (0x16 0x03) for a
225
+ // record-wrapped hello and the handshake type byte (0x01 ...) for a bare
226
+ // hello, so a bare and a record-wrapped ClientHello that are otherwise
227
+ // byte-identical would otherwise hash differently.
277
228
  const raw = [
278
- hex4(clientHello.length > 1 ? uint16(clientHello, 0) : 0),
229
+ hex4(hello.versionCode),
279
230
  hello.cipherSuites.map(hex4).join(""),
280
231
  hello.extensions.map(hex4).join(""),
281
232
  hello.supportedGroups.map(hex4).join(""),
@@ -284,42 +235,4 @@ export function computeJa4Fingerprint(clientHello) {
284
235
  const f = sha256First12(raw);
285
236
  return { a, b, c, f, tag: `${a}_${b}_${c}_${f}` };
286
237
  }
287
- /**
288
- * Compute the four-part JA4H HTTP fingerprint from a captured request.
289
- *
290
- * Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
291
- * - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
292
- * - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
293
- * - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
294
- * - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
295
- */
296
- export function computeJa4h(request) {
297
- const methodCode = request.method.slice(0, 2).toLowerCase();
298
- const versionCode = request.httpVersion === "1.1"
299
- ? "11"
300
- : request.httpVersion === "2"
301
- ? "02"
302
- : request.httpVersion === "3"
303
- ? "03"
304
- : "00";
305
- const hasCookies = request.cookies.length > 0 ? "c" : "n";
306
- const hasReferer = request.headerNames.some((h) => h === "referer") ? "r" : "n";
307
- const headerCount = request.headerNames.length.toString().padStart(2, "0");
308
- const langPrefix = (request.acceptLanguage ?? "").slice(0, 4).padEnd(4, "0").toLowerCase();
309
- const a = `${methodCode}${versionCode}${hasCookies}${hasReferer}${headerCount}${langPrefix}`;
310
- const sortedCookies = [...request.cookies].sort().join(",");
311
- const b = sortedCookies.length > 0 ? sha256First12(sortedCookies) : "000000000000";
312
- const sortedCookieNames = [...request.cookies]
313
- .map((c) => c.split("=")[0] ?? "")
314
- .filter((n) => n.length > 0)
315
- .sort()
316
- .join(",");
317
- const c = sortedCookieNames.length > 0 ? sha256First12(sortedCookieNames) : "000000000000";
318
- const sortedHeaders = [...request.headerNames]
319
- .filter((h) => h !== "cookie")
320
- .sort()
321
- .join(",");
322
- const d = sortedHeaders.length > 0 ? sha256First12(sortedHeaders) : "000000000000";
323
- return { a, b, c, d, tag: `${a}_${b}_${c}_${d}` };
324
- }
325
238
  //# sourceMappingURL=ja4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ja4.js","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ja4.js","sourceRoot":"","sources":["../../src/fingerprint/ja4.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * JA4H — the HTTP-layer sibling of JA4 (docs/TEST-SUITE.md, Category 4).
3
+ *
4
+ * JA4H fingerprints an HTTP request from its method, version, header order,
5
+ * cookies, and Accept-Language prefix. It is the HTTP analogue computed by the
6
+ * reference provider to fingerprint a captured request. Independent of the TLS
7
+ * ClientHello parsing in `ja4.ts`.
8
+ *
9
+ * Reference: https://github.com/FoxIO-LLC/JA4
10
+ */
11
+ /** The four computed JA4H parts. */
12
+ export interface Ja4hFingerprint {
13
+ readonly a: string;
14
+ readonly b: string;
15
+ readonly c: string;
16
+ readonly d: string;
17
+ /** Canonical `JA4H_a_JA4H_b_JA4H_c_JA4H_d` tag. */
18
+ readonly tag: string;
19
+ }
20
+ /**
21
+ * HTTP request fields needed to compute a JA4H fingerprint.
22
+ *
23
+ * Only the parts JA4H inspects are required — method, HTTP version, header
24
+ * names (in order), cookies, and the Accept-Language prefix.
25
+ */
26
+ export interface Ja4hRequest {
27
+ readonly method: "GET" | "POST" | "HEAD" | "PUT" | "DELETE" | "OPTIONS" | "PATCH";
28
+ readonly httpVersion: "1.1" | "2" | "3";
29
+ /** Header names in the order they are serialized (lowercased). */
30
+ readonly headerNames: readonly string[];
31
+ /** Cookie name=value pairs (as they appear in the Cookie header). */
32
+ readonly cookies: readonly string[];
33
+ /** The Accept-Language header value (only its 4-char prefix is used). */
34
+ readonly acceptLanguage?: string;
35
+ }
36
+ /**
37
+ * Compute the four-part JA4H HTTP fingerprint from a captured request.
38
+ *
39
+ * Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
40
+ * - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
41
+ * - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
42
+ * - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
43
+ * - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
44
+ */
45
+ export declare function computeJa4h(request: Ja4hRequest): Ja4hFingerprint;
46
+ //# sourceMappingURL=ja4h.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ja4h.d.ts","sourceRoot":"","sources":["../../src/fingerprint/ja4h.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;IAClF,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,GAAG,GAAG,GAAG,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAkCjE"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * JA4H — the HTTP-layer sibling of JA4 (docs/TEST-SUITE.md, Category 4).
3
+ *
4
+ * JA4H fingerprints an HTTP request from its method, version, header order,
5
+ * cookies, and Accept-Language prefix. It is the HTTP analogue computed by the
6
+ * reference provider to fingerprint a captured request. Independent of the TLS
7
+ * ClientHello parsing in `ja4.ts`.
8
+ *
9
+ * Reference: https://github.com/FoxIO-LLC/JA4
10
+ */
11
+ import { sha256First12 } from "./ja4.js";
12
+ /**
13
+ * Compute the four-part JA4H HTTP fingerprint from a captured request.
14
+ *
15
+ * Format: `JA4H_a_JA4H_b_JA4H_c_JA4H_d` where:
16
+ * - JA4H_a: `{method:02s}{version}{has_cookies:d}{has_referer:d}{header_count:02d}{lang_prefix}`
17
+ * - JA4H_b: sorted cookie name=value pairs, SHA-256, first 12 hex.
18
+ * - JA4H_c: sorted cookie names, SHA-256, first 12 hex.
19
+ * - JA4H_d: sorted header names (excluding cookie), SHA-256, first 12 hex.
20
+ */
21
+ export function computeJa4h(request) {
22
+ const methodCode = request.method.slice(0, 2).toLowerCase();
23
+ const versionCode = request.httpVersion === "1.1"
24
+ ? "11"
25
+ : request.httpVersion === "2"
26
+ ? "02"
27
+ : request.httpVersion === "3"
28
+ ? "03"
29
+ : "00";
30
+ const hasCookies = request.cookies.length > 0 ? "c" : "n";
31
+ const hasReferer = request.headerNames.some((h) => h === "referer") ? "r" : "n";
32
+ const headerCount = request.headerNames.length.toString().padStart(2, "0");
33
+ const langPrefix = (request.acceptLanguage ?? "").slice(0, 4).padEnd(4, "0").toLowerCase();
34
+ const a = `${methodCode}${versionCode}${hasCookies}${hasReferer}${headerCount}${langPrefix}`;
35
+ const sortedCookies = [...request.cookies].sort().join(",");
36
+ const b = sortedCookies.length > 0 ? sha256First12(sortedCookies) : "000000000000";
37
+ const sortedCookieNames = [...request.cookies]
38
+ .map((c) => c.split("=")[0] ?? "")
39
+ .filter((n) => n.length > 0)
40
+ .sort()
41
+ .join(",");
42
+ const c = sortedCookieNames.length > 0 ? sha256First12(sortedCookieNames) : "000000000000";
43
+ const sortedHeaders = [...request.headerNames]
44
+ .filter((h) => h !== "cookie")
45
+ .sort()
46
+ .join(",");
47
+ const d = sortedHeaders.length > 0 ? sha256First12(sortedHeaders) : "000000000000";
48
+ return { a, b, c, d, tag: `${a}_${b}_${c}_${d}` };
49
+ }
50
+ //# sourceMappingURL=ja4h.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ja4h.js","sourceRoot":"","sources":["../../src/fingerprint/ja4h.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA6BzC;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,OAAoB;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,MAAM,WAAW,GACb,OAAO,CAAC,WAAW,KAAK,KAAK;QACzB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG;YAC3B,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,OAAO,CAAC,WAAW,KAAK,GAAG;gBAC3B,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI,CAAC;IACnB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAChF,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAE3F,MAAM,CAAC,GAAG,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,EAAE,CAAC;IAE7F,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnF,MAAM,iBAAiB,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;SACzC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,CAAC,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAE3F,MAAM,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;SACzC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC;SAC7B,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IAEnF,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;AACtD,CAAC"}
@@ -11,7 +11,21 @@
11
11
  * (e.g. `chrome-140/tls/client_hello`) that maps directly onto the
12
12
  * `captures/<profile>/<protocol>/<record>.{bin,meta.json}` layout.
13
13
  */
14
- import type { CaptureId, CaptureMeta, ComparisonResult, ComparisonResultWithIgnore, GoldenCapture } from "../types.js";
14
+ import type { CaptureId, CaptureMeta, ComparisonResult, ComparisonResultWithIgnore, GoldenCapture, RandomizedField } from "../types.js";
15
+ /**
16
+ * Validate unknown JSON data as a {@link CaptureMeta} (no zod dep here).
17
+ *
18
+ * Exported for unit testing the validation branches — the error paths are only
19
+ * reachable with a malformed sidecar, which the on-disk captures are not.
20
+ */
21
+ export declare function parseCaptureMeta(raw: unknown, captureId: string): CaptureMeta;
22
+ /**
23
+ * Validate unknown data as a list of {@link RandomizedField}.
24
+ *
25
+ * Exported for unit testing the validation branches — the error paths are only
26
+ * reachable with a malformed sidecar, which the on-disk captures are not.
27
+ */
28
+ export declare function parseRandomizedFields(raw: unknown, captureId: string): readonly RandomizedField[];
15
29
  /**
16
30
  * Load a golden capture by id.
17
31
  *