@jshookmcp/jshook 0.2.8 → 0.3.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 (162) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-CqGDXmfc.mjs} +160 -54
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/ConsoleMonitor-DykL3IAw.mjs +2269 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-ETyy0xyo.mjs} +1 -1
  7. package/dist/DetailedDataManager-HT49OrvF.mjs +217 -0
  8. package/dist/EventBus-DFKvADm3.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-318Oi0Lf.mjs +153 -0
  10. package/dist/{ExtensionManager-D5-bO9D8.mjs → ExtensionManager-BDMsY2Dz.mjs} +27 -13
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-BN4UQWnX.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-DruMgsgj.mjs} +21 -21
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs +566 -0
  15. package/dist/InstrumentationSession-DLH0vd-z.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-ITgb_NMi.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-CiL7Z3ey.mjs} +50 -21
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +20 -56
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-DMQ44gen.mjs} +16 -16
  22. package/dist/PageController-BPJNqqBN.mjs +431 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  24. package/dist/PrerequisiteError-TuyZIs6n.mjs +20 -0
  25. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  26. package/dist/ResponseBuilder-CJXWmWNw.mjs +143 -0
  27. package/dist/ReverseEvidenceGraph-C02-gXOh.mjs +269 -0
  28. package/dist/ScriptManager-ZuWD-0Jg.mjs +3003 -0
  29. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  30. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  31. package/dist/ToolCatalog-5OJdMiF0.mjs +582 -0
  32. package/dist/ToolError-jh9whhMd.mjs +15 -0
  33. package/dist/ToolProbe-DbCFGyrg.mjs +45 -0
  34. package/dist/ToolRegistry-B9krbTtI.mjs +180 -0
  35. package/dist/ToolRouter.policy-BGDAGyeH.mjs +344 -0
  36. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  37. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-C2kjj0ze.mjs} +19 -13
  38. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-CKrGOTpo.mjs} +3 -3
  39. package/dist/WorkflowEngine-DJ6M4opp.mjs +569 -0
  40. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  41. package/dist/antidebug-BRKeyt27.mjs +1081 -0
  42. package/dist/artifactRetention-CPXkUJXp.mjs +598 -0
  43. package/dist/artifacts-DkfosXH3.mjs +59 -0
  44. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  45. package/dist/betterSqlite3-DLSBZodi.mjs +74 -0
  46. package/dist/binary-instrument--V3MAhJ4.mjs +971 -0
  47. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  48. package/dist/boringssl-inspector-Bo_LOLaS.mjs +180 -0
  49. package/dist/browser-Dx3_S2cG.mjs +4369 -0
  50. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  51. package/dist/concurrency-Drev_Vz9.mjs +41 -0
  52. package/dist/{constants-CCvsN80K.mjs → constants-CDZLOoVv.mjs} +105 -48
  53. package/dist/coordination-DgItD9DL.mjs +259 -0
  54. package/dist/debugger-RS3RSAqs.mjs +1288 -0
  55. package/dist/definitions-BEoYofW5.mjs +47 -0
  56. package/dist/definitions-BRaefg3u.mjs +365 -0
  57. package/dist/definitions-BbkvZkiv.mjs +96 -0
  58. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  59. package/dist/definitions-C1gCHO0i.mjs +43 -0
  60. package/dist/definitions-CDOg_b-l.mjs +138 -0
  61. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  62. package/dist/definitions-Cea8Lgl7.mjs +94 -0
  63. package/dist/definitions-DAgIyjxM.mjs +10 -0
  64. package/dist/definitions-DJA27nsL.mjs +66 -0
  65. package/dist/definitions-DKPFU3LW.mjs +25 -0
  66. package/dist/definitions-DPRpZQ96.mjs +47 -0
  67. package/dist/definitions-DUE5gmdn.mjs +18 -0
  68. package/dist/definitions-DYVjOtxa.mjs +26 -0
  69. package/dist/definitions-DcYLVLCo.mjs +37 -0
  70. package/dist/definitions-Pp5LI2H4.mjs +27 -0
  71. package/dist/definitions-j9KdHVNR.mjs +14 -0
  72. package/dist/definitions-uzkjBwa7.mjs +258 -0
  73. package/dist/definitions-va-AnLuQ.mjs +28 -0
  74. package/dist/encoding-DJeqHmpd.mjs +1079 -0
  75. package/dist/evidence-graph-bridge-DcYizFk2.mjs +136 -0
  76. package/dist/{factory-CibqTNC8.mjs → factory-C90tBff6.mjs} +41 -56
  77. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  78. package/dist/graphql-CoHrhweh.mjs +1197 -0
  79. package/dist/handlers-4jmR0nMs.mjs +898 -0
  80. package/dist/handlers-BAHPxcch.mjs +789 -0
  81. package/dist/handlers-BOs9b907.mjs +2600 -0
  82. package/dist/handlers-BWXEy6ef.mjs +917 -0
  83. package/dist/handlers-Bndn6QvE.mjs +111 -0
  84. package/dist/handlers-BqC4bD4s.mjs +681 -0
  85. package/dist/handlers-BtYq60bM2.mjs +276 -0
  86. package/dist/handlers-BzgcB4iv.mjs +799 -0
  87. package/dist/handlers-CRyRWj2b.mjs +859 -0
  88. package/dist/handlers-CVv2H1uq.mjs +592 -0
  89. package/dist/handlers-Dl5a7JS4.mjs +572 -0
  90. package/dist/handlers-Dx2d7jt7.mjs +2537 -0
  91. package/dist/handlers-Dz9PYsCa.mjs +2805 -0
  92. package/dist/handlers-HujRKC3b.mjs +661 -0
  93. package/dist/handlers.impl-XWXkQfyi.mjs +807 -0
  94. package/dist/hooks-B1B8NRHL.mjs +898 -0
  95. package/dist/index.mjs +491 -259
  96. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  97. package/dist/maintenance-PRMkLVRW.mjs +835 -0
  98. package/dist/manifest-67Bok-Si.mjs +58 -0
  99. package/dist/manifest-6lNTMZAB2.mjs +87 -0
  100. package/dist/manifest-B2duEHiH.mjs +90 -0
  101. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  102. package/dist/manifest-B6nKSbyY.mjs +95 -0
  103. package/dist/manifest-BL8AQNPF.mjs +106 -0
  104. package/dist/manifest-BSZvJJmV.mjs +47 -0
  105. package/dist/manifest-BU7qzUyX.mjs +418 -0
  106. package/dist/manifest-Bl62e8WK.mjs +49 -0
  107. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  108. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  109. package/dist/manifest-Bv65_e2W.mjs +101 -0
  110. package/dist/manifest-BytNIF4Z.mjs +117 -0
  111. package/dist/manifest-C-xtsjS3.mjs +81 -0
  112. package/dist/manifest-CDYl7OhA.mjs +66 -0
  113. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  114. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  115. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  116. package/dist/manifest-D7YZM_2e.mjs +194 -0
  117. package/dist/manifest-DE_VrAeQ.mjs +314 -0
  118. package/dist/manifest-DGsXSCpT.mjs +39 -0
  119. package/dist/manifest-DJ2vfEuW.mjs +156 -0
  120. package/dist/manifest-DPXDYhEu.mjs +80 -0
  121. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  122. package/dist/manifest-Deq6opGg.mjs +223 -0
  123. package/dist/manifest-DfJTafJK.mjs +37 -0
  124. package/dist/manifest-DgOdgN_j.mjs +50 -0
  125. package/dist/manifest-DlbMW4v4.mjs +47 -0
  126. package/dist/manifest-DmVfbH0w.mjs +374 -0
  127. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  128. package/dist/manifest-DvgU5FWb.mjs +58 -0
  129. package/dist/manifest-HsfDBs7j.mjs +50 -0
  130. package/dist/manifest-I8oQHvCG.mjs +186 -0
  131. package/dist/manifest-NvH_a-av.mjs +786 -0
  132. package/dist/manifest-cEJU1v0Z.mjs +129 -0
  133. package/dist/manifest-wOl5XLB12.mjs +112 -0
  134. package/dist/modules-tZozf0LQ.mjs +10635 -0
  135. package/dist/mojo-ipc-DXNEXEqb.mjs +640 -0
  136. package/dist/network-CPVvwvFg.mjs +3852 -0
  137. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-um7lCRY3.mjs} +219 -216
  138. package/dist/parse-args-B4cY5Vx5.mjs +39 -0
  139. package/dist/platform-CYeFoTWp.mjs +2161 -0
  140. package/dist/process-BTbgcVc6.mjs +1306 -0
  141. package/dist/proxy-r8YN6nP1.mjs +192 -0
  142. package/dist/registry-Bl8ZQW61.mjs +34 -0
  143. package/dist/response-CWhh2aLo.mjs +34 -0
  144. package/dist/server/plugin-api.mjs +2 -2
  145. package/dist/shared-state-board-BoZnSoj-.mjs +586 -0
  146. package/dist/sourcemap-BIDHUVXy.mjs +934 -0
  147. package/dist/ssrf-policy-Dsqd-DTX.mjs +166 -0
  148. package/dist/streaming-Dal6utPp.mjs +725 -0
  149. package/dist/tool-builder-BHJp32mV.mjs +186 -0
  150. package/dist/transform-DRVgGG90.mjs +1011 -0
  151. package/dist/types-Bx92KJfT.mjs +4 -0
  152. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  153. package/dist/webcrack-Be0_FccV.mjs +747 -0
  154. package/dist/workflow-BpuKEtvn.mjs +725 -0
  155. package/package.json +82 -49
  156. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  157. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  158. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  159. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  160. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  161. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  162. package/dist/{types-BBjOqye-.mjs → types-DDBWs9UP.mjs} +1 -1
@@ -0,0 +1,2600 @@
1
+ import { n as asJsonResponse } from "./response-CWhh2aLo.mjs";
2
+ import { a as enableKeyLog, c as parseKeyLog, i as disableKeyLog, l as summarizeKeyLog, n as TLSKeyLogExtractor, o as getKeyLogFilePath, r as decryptPayload, s as lookupSecret } from "./boringssl-inspector-Bo_LOLaS.mjs";
3
+ import { a as argString, n as argEnum, o as argStringArray, r as argNumber, t as argBool } from "./parse-args-B4cY5Vx5.mjs";
4
+ import { a as isLoopbackHost, s as isPrivateHost } from "./ssrf-policy-Dsqd-DTX.mjs";
5
+ import { X509Certificate, createHash, randomBytes, randomUUID } from "node:crypto";
6
+ import { readFile } from "node:fs/promises";
7
+ import { Socket, createServer, isIP } from "node:net";
8
+ import { createSocket } from "node:dgram";
9
+ import { checkServerIdentity, connect } from "node:tls";
10
+ //#region src/server/domains/boringssl-inspector/handlers/shared/types.ts
11
+ const TLS_VERSION_SET = new Set([
12
+ "TLSv1",
13
+ "TLSv1.1",
14
+ "TLSv1.2",
15
+ "TLSv1.3"
16
+ ]);
17
+ //#endregion
18
+ //#region src/server/domains/boringssl-inspector/handlers/shared/common.ts
19
+ function errorMessage(error) {
20
+ return error instanceof Error ? error.message : String(error);
21
+ }
22
+ function normalizeHex(value) {
23
+ return value.replace(/\s+/g, "").toUpperCase();
24
+ }
25
+ function isHex(value) {
26
+ return value.length > 0 && value.length % 2 === 0 && /^[0-9A-F]+$/i.test(value);
27
+ }
28
+ //#endregion
29
+ //#region src/server/domains/boringssl-inspector/handlers/shared/tls-probe.ts
30
+ function validateNetworkTarget(host) {
31
+ if (isPrivateHost(host) && !isLoopbackHost(host)) return {
32
+ ok: false,
33
+ error: `Blocked: target host "${host}" resolves to a private/internal address. SSRF protection applies.`
34
+ };
35
+ return null;
36
+ }
37
+ function normalizeSocketServername(servername) {
38
+ return typeof servername === "string" && servername.length > 0 ? servername : null;
39
+ }
40
+ function normalizeAlpnProtocol(protocol) {
41
+ return typeof protocol === "string" && protocol.length > 0 ? protocol : null;
42
+ }
43
+ function applyTlsValidationPolicy(options, allowInvalidCertificates) {
44
+ const next = { ...options };
45
+ Reflect.set(next, "rejectUnauthorized", !allowInvalidCertificates);
46
+ return next;
47
+ }
48
+ async function loadProbeCaBundle(args) {
49
+ const caPem = argString(args, "caPem") ?? null;
50
+ const caPath = argString(args, "caPath") ?? null;
51
+ if (caPem && caPath) return {
52
+ ok: false,
53
+ error: "caPem and caPath are mutually exclusive"
54
+ };
55
+ if (caPem) return {
56
+ ok: true,
57
+ ca: caPem,
58
+ source: "inline",
59
+ path: null,
60
+ bytes: Buffer.byteLength(caPem)
61
+ };
62
+ if (caPath) try {
63
+ const ca = await readFile(caPath, "utf8");
64
+ return {
65
+ ok: true,
66
+ ca,
67
+ source: "path",
68
+ path: caPath,
69
+ bytes: Buffer.byteLength(ca)
70
+ };
71
+ } catch (error) {
72
+ return {
73
+ ok: false,
74
+ error: `Failed to read caPath "${caPath}": ${errorMessage(error)}`
75
+ };
76
+ }
77
+ return {
78
+ ok: true,
79
+ ca: void 0,
80
+ source: null,
81
+ path: null,
82
+ bytes: null
83
+ };
84
+ }
85
+ //#endregion
86
+ //#region src/server/domains/boringssl-inspector/handlers/shared/certificates.ts
87
+ function isNonEmptyObject(value) {
88
+ return value !== null && typeof value === "object" && Object.keys(value).length > 0;
89
+ }
90
+ function hasPeerCertificate(value) {
91
+ return isNonEmptyObject(value);
92
+ }
93
+ function summarizePeerCertificate(cert, depth) {
94
+ const raw = Buffer.isBuffer(cert.raw) ? cert.raw : null;
95
+ const x509 = raw ? new X509Certificate(raw) : null;
96
+ const subject = x509?.subject ?? null;
97
+ const issuer = x509?.issuer ?? null;
98
+ return {
99
+ depth,
100
+ subject,
101
+ issuer,
102
+ subjectAltName: x509?.subjectAltName ?? cert.subjectaltname ?? null,
103
+ serialNumber: x509?.serialNumber ?? cert.serialNumber ?? null,
104
+ validFrom: x509?.validFrom ?? cert.valid_from ?? null,
105
+ validTo: x509?.validTo ?? cert.valid_to ?? null,
106
+ fingerprint256: x509?.fingerprint256 ?? cert.fingerprint256 ?? null,
107
+ fingerprint512: x509?.fingerprint512 ?? cert.fingerprint512 ?? null,
108
+ rawLength: raw?.length ?? null,
109
+ isCA: x509?.ca ?? cert.ca ?? null,
110
+ selfIssued: subject && issuer ? subject === issuer : null
111
+ };
112
+ }
113
+ function buildPeerCertificateChain(peerCertificate) {
114
+ if (!peerCertificate) return [];
115
+ const chain = [];
116
+ const seen = /* @__PURE__ */ new Set();
117
+ let current = peerCertificate;
118
+ let depth = 0;
119
+ while (current && hasPeerCertificate(current)) {
120
+ const summary = summarizePeerCertificate(current, depth);
121
+ const dedupeKey = summary.fingerprint256 ?? `${summary.subject ?? "unknown-subject"}:${summary.serialNumber ?? "unknown-serial"}:${depth}`;
122
+ if (seen.has(dedupeKey)) break;
123
+ seen.add(dedupeKey);
124
+ chain.push(summary);
125
+ if (!("issuerCertificate" in current)) break;
126
+ const issuerCertificate = current.issuerCertificate;
127
+ if (!issuerCertificate || issuerCertificate === current || !hasPeerCertificate(issuerCertificate)) break;
128
+ current = issuerCertificate;
129
+ depth += 1;
130
+ }
131
+ return chain;
132
+ }
133
+ function parseDerCertificate(der) {
134
+ const sha256 = createHash("sha256").update(der).digest("hex").toUpperCase();
135
+ try {
136
+ const cert = new X509Certificate(der);
137
+ return {
138
+ subject: cert.subject || void 0,
139
+ issuer: cert.issuer || void 0,
140
+ serialNumber: cert.serialNumber || void 0,
141
+ validFrom: cert.validFrom || void 0,
142
+ validTo: cert.validTo || void 0,
143
+ sha256,
144
+ length: der.length
145
+ };
146
+ } catch {
147
+ return {
148
+ sha256,
149
+ length: der.length
150
+ };
151
+ }
152
+ }
153
+ function parseCertificateChain(hexPayload) {
154
+ const buffer = Buffer.from(normalizeHex(hexPayload), "hex");
155
+ const certs = [];
156
+ let cursor = 0;
157
+ while (cursor < buffer.length - 4) if (buffer[cursor] === 48) {
158
+ const info = parseDerCertificate(buffer.subarray(cursor));
159
+ certs.push({
160
+ sha256: info.sha256,
161
+ length: info.length
162
+ });
163
+ cursor += info.length;
164
+ } else cursor += 1;
165
+ if (certs.length === 0 && buffer.length > 0) certs.push({
166
+ sha256: createHash("sha256").update(buffer).digest("hex").toUpperCase(),
167
+ length: buffer.length
168
+ });
169
+ return certs;
170
+ }
171
+ //#endregion
172
+ //#region src/server/domains/boringssl-inspector/handlers/shared/session-buffer.ts
173
+ function makeSessionId(kind) {
174
+ return `${kind}_${randomUUID()}`;
175
+ }
176
+ function serializeSocketAddresses(socket) {
177
+ return {
178
+ localAddress: socket.localAddress ?? null,
179
+ localPort: socket.localPort ?? null,
180
+ remoteAddress: socket.remoteAddress ?? null,
181
+ remotePort: socket.remotePort ?? null
182
+ };
183
+ }
184
+ function serializeSessionState(session) {
185
+ return {
186
+ bufferedBytes: session.buffer.length,
187
+ remoteEnded: session.ended,
188
+ socketClosed: session.closed,
189
+ error: session.error
190
+ };
191
+ }
192
+ function wakeSessionWaiters(session) {
193
+ for (const waiter of session.waiters) waiter();
194
+ session.waiters.clear();
195
+ }
196
+ function attachBufferedSession(session) {
197
+ session.socket.on("data", (chunk) => {
198
+ session.buffer = Buffer.concat([session.buffer, chunk]);
199
+ wakeSessionWaiters(session);
200
+ });
201
+ session.socket.on("end", () => {
202
+ session.ended = true;
203
+ wakeSessionWaiters(session);
204
+ });
205
+ session.socket.on("close", () => {
206
+ session.closed = true;
207
+ wakeSessionWaiters(session);
208
+ });
209
+ session.socket.on("error", (error) => {
210
+ session.error = error.message;
211
+ wakeSessionWaiters(session);
212
+ });
213
+ }
214
+ function waitForSessionActivity(session, timeoutMs) {
215
+ return new Promise((resolve) => {
216
+ const onActivity = () => {
217
+ clearTimeout(timer);
218
+ session.waiters.delete(onActivity);
219
+ resolve(true);
220
+ };
221
+ const timer = setTimeout(() => {
222
+ session.waiters.delete(onActivity);
223
+ resolve(false);
224
+ }, timeoutMs);
225
+ session.waiters.add(onActivity);
226
+ });
227
+ }
228
+ function consumeSessionBuffer(session, delimiter, includeDelimiter, maxBytes) {
229
+ const delimiterHex = delimiter ? delimiter.toString("hex").toUpperCase() : null;
230
+ if (delimiter) {
231
+ const matchIndex = session.buffer.indexOf(delimiter);
232
+ if (matchIndex >= 0) {
233
+ const consumedBytes = matchIndex + delimiter.length;
234
+ const data = includeDelimiter ? session.buffer.subarray(0, consumedBytes) : session.buffer.subarray(0, matchIndex);
235
+ session.buffer = session.buffer.subarray(consumedBytes);
236
+ return {
237
+ data,
238
+ matchedDelimiter: true,
239
+ stopReason: "delimiter",
240
+ delimiterHex
241
+ };
242
+ }
243
+ }
244
+ if (typeof maxBytes === "number" && session.buffer.length >= maxBytes) {
245
+ const data = session.buffer.subarray(0, maxBytes);
246
+ session.buffer = session.buffer.subarray(maxBytes);
247
+ return {
248
+ data,
249
+ matchedDelimiter: false,
250
+ stopReason: "maxBytes",
251
+ delimiterHex
252
+ };
253
+ }
254
+ if ((session.error || session.ended || session.closed) && session.buffer.length > 0) {
255
+ const data = session.buffer;
256
+ session.buffer = Buffer.alloc(0);
257
+ return {
258
+ data,
259
+ matchedDelimiter: false,
260
+ stopReason: session.error ? "error" : "closed",
261
+ delimiterHex
262
+ };
263
+ }
264
+ return null;
265
+ }
266
+ //#endregion
267
+ //#region src/server/domains/boringssl-inspector/handlers/shared/websocket.ts
268
+ const WEBSOCKET_ACCEPT_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
269
+ function serializeWebSocketSessionState(session) {
270
+ return {
271
+ bufferedBytes: session.parserBuffer.length,
272
+ queuedFrames: session.frames.length,
273
+ remoteEnded: session.ended,
274
+ socketClosed: session.closed,
275
+ closeSent: session.closeSent,
276
+ closeReceived: session.closeReceived,
277
+ error: session.error
278
+ };
279
+ }
280
+ function normalizeWebSocketPath(path) {
281
+ if (!path || path.trim().length === 0) return "/";
282
+ return path.startsWith("/") ? path : `/${path}`;
283
+ }
284
+ function websocketOpcodeName(opcode) {
285
+ return {
286
+ 1: "text",
287
+ 2: "binary",
288
+ 8: "close",
289
+ 9: "ping",
290
+ 10: "pong"
291
+ }[opcode] ?? null;
292
+ }
293
+ function computeWebSocketAccept(requestKey) {
294
+ return createHash("sha1").update(`${requestKey}${WEBSOCKET_ACCEPT_SUFFIX}`, "utf8").digest("base64");
295
+ }
296
+ function encodeWebSocketFrame(type, payload, closeCode, closeReason) {
297
+ const opcodeByType = {
298
+ text: 1,
299
+ binary: 2,
300
+ close: 8,
301
+ ping: 9,
302
+ pong: 10
303
+ };
304
+ let framePayload = payload;
305
+ if (type === "close") {
306
+ if (closeCode !== void 0 && closeCode !== null) {
307
+ const reasonBuffer = closeReason ? Buffer.from(closeReason, "utf8") : Buffer.alloc(0);
308
+ framePayload = Buffer.alloc(2 + reasonBuffer.length);
309
+ framePayload.writeUInt16BE(closeCode, 0);
310
+ reasonBuffer.copy(framePayload, 2);
311
+ } else if (closeReason) framePayload = Buffer.from(closeReason, "utf8");
312
+ }
313
+ const maskKey = randomBytes(4);
314
+ const payloadLength = framePayload.length;
315
+ let header;
316
+ if (payloadLength < 126) {
317
+ header = Buffer.alloc(2);
318
+ header[1] = 128 | payloadLength;
319
+ } else if (payloadLength <= 65535) {
320
+ header = Buffer.alloc(4);
321
+ header[1] = 254;
322
+ header.writeUInt16BE(payloadLength, 2);
323
+ } else {
324
+ header = Buffer.alloc(10);
325
+ header[1] = 255;
326
+ header.writeBigUInt64BE(BigInt(payloadLength), 2);
327
+ }
328
+ header[0] = 128 | opcodeByType[type];
329
+ const maskedPayload = Buffer.alloc(payloadLength);
330
+ for (let index = 0; index < payloadLength; index += 1) maskedPayload[index] = framePayload[index] ^ maskKey[index % 4];
331
+ return Buffer.concat([
332
+ header,
333
+ maskKey,
334
+ maskedPayload
335
+ ]);
336
+ }
337
+ function tryConsumeWebSocketFrame(buffer) {
338
+ if (buffer.length < 2) return null;
339
+ const first = buffer[0];
340
+ const second = buffer[1];
341
+ const fin = (first & 128) !== 0;
342
+ const opcode = first & 15;
343
+ const masked = (second & 128) !== 0;
344
+ let payloadLength = second & 127;
345
+ let cursor = 2;
346
+ if (payloadLength === 126) {
347
+ if (buffer.length < cursor + 2) return null;
348
+ payloadLength = buffer.readUInt16BE(cursor);
349
+ cursor += 2;
350
+ } else if (payloadLength === 127) {
351
+ if (buffer.length < cursor + 8) return null;
352
+ const bigLength = buffer.readBigUInt64BE(cursor);
353
+ if (bigLength > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("WebSocket frame payload length exceeds supported limits");
354
+ payloadLength = Number(bigLength);
355
+ cursor += 8;
356
+ }
357
+ const maskKey = masked ? buffer.subarray(cursor, cursor + 4) : null;
358
+ if (masked) {
359
+ if (buffer.length < cursor + 4) return null;
360
+ cursor += 4;
361
+ }
362
+ if (buffer.length < cursor + payloadLength) return null;
363
+ const payload = buffer.subarray(cursor, cursor + payloadLength);
364
+ const data = Buffer.alloc(payload.length);
365
+ if (masked && maskKey) for (let index = 0; index < payload.length; index += 1) data[index] = payload[index] ^ maskKey[index % 4];
366
+ else payload.copy(data);
367
+ const type = websocketOpcodeName(opcode);
368
+ if (!type) throw new Error(`Unsupported WebSocket opcode 0x${opcode.toString(16)}`);
369
+ let closeCode = null;
370
+ let closeReason = null;
371
+ if (type === "close" && data.length >= 2) {
372
+ closeCode = data.readUInt16BE(0);
373
+ closeReason = data.subarray(2).toString("utf8");
374
+ }
375
+ return {
376
+ frame: {
377
+ type,
378
+ fin,
379
+ opcode,
380
+ masked,
381
+ data,
382
+ closeCode,
383
+ closeReason,
384
+ receivedAt: Date.now()
385
+ },
386
+ bytesConsumed: cursor + payloadLength
387
+ };
388
+ }
389
+ function wakeWebSocketWaiters(session) {
390
+ for (const waiter of session.waiters) waiter();
391
+ session.waiters.clear();
392
+ }
393
+ function waitForWebSocketActivity(session, timeoutMs) {
394
+ return new Promise((resolve) => {
395
+ const onActivity = () => {
396
+ clearTimeout(timer);
397
+ session.waiters.delete(onActivity);
398
+ resolve(true);
399
+ };
400
+ const timer = setTimeout(() => {
401
+ session.waiters.delete(onActivity);
402
+ resolve(false);
403
+ }, timeoutMs);
404
+ session.waiters.add(onActivity);
405
+ });
406
+ }
407
+ //#endregion
408
+ //#region src/server/domains/boringssl-inspector/handlers/shared/tls-parse.ts
409
+ const TLS_VERSION_NAMES = {
410
+ "3:1": "TLS 1.0",
411
+ "3:2": "TLS 1.1",
412
+ "3:3": "TLS 1.2",
413
+ "3:4": "TLS 1.3"
414
+ };
415
+ const CONTENT_TYPE_NAMES = {
416
+ 20: "change_cipher_spec",
417
+ 21: "alert",
418
+ 22: "handshake",
419
+ 23: "application_data",
420
+ 24: "heartbeat"
421
+ };
422
+ const CIPHER_SUITES_BY_ID = {
423
+ 156: "TLS_RSA_WITH_AES_128_GCM_SHA256",
424
+ 157: "TLS_RSA_WITH_AES_256_GCM_SHA384",
425
+ 52392: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
426
+ 52393: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
427
+ 4865: "TLS_AES_128_GCM_SHA256",
428
+ 4866: "TLS_AES_256_GCM_SHA384",
429
+ 4867: "TLS_CHACHA20_POLY1305_SHA256",
430
+ 4868: "TLS_AES_128_CCM_SHA256",
431
+ 4869: "TLS_AES_128_CCM_8_SHA256",
432
+ 49195: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
433
+ 49196: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
434
+ 49199: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
435
+ 49200: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
436
+ };
437
+ const EXTENSION_NAMES = {
438
+ 0: "server_name",
439
+ 1: "max_fragment_length",
440
+ 5: "status_request",
441
+ 10: "supported_groups",
442
+ 13: "signature_algorithms",
443
+ 16: "application_layer_protocol_negotiation",
444
+ 18: "signed_certificate_timestamp",
445
+ 23: "record_size_limit",
446
+ 27: "compress_certificate",
447
+ 35: "session_ticket",
448
+ 43: "supported_versions",
449
+ 44: "cookie",
450
+ 45: "psk_key_exchange_modes",
451
+ 49: "post_handshake_auth",
452
+ 51: "key_share"
453
+ };
454
+ function tlsVersionName(major, minor) {
455
+ return TLS_VERSION_NAMES[`${major}:${minor}`] ?? `0x${major.toString(16).padStart(2, "0")}${minor.toString(16).padStart(2, "0")}`;
456
+ }
457
+ function contentTypeName(contentType) {
458
+ return CONTENT_TYPE_NAMES[contentType] ?? "unknown";
459
+ }
460
+ function parseClientHello(payload) {
461
+ const result = {
462
+ cipherSuites: [],
463
+ extensions: []
464
+ };
465
+ const bodyOffset = payload[0] !== void 0 && payload[0] < 25 ? 4 : 0;
466
+ if (payload.length < bodyOffset + 38) return result;
467
+ const sessionIdOffset = bodyOffset + 34;
468
+ const sessionIdLength = payload[sessionIdOffset] ?? 0;
469
+ let cursor = sessionIdOffset + 1 + sessionIdLength;
470
+ if (cursor + 2 > payload.length) return result;
471
+ const cipherSuitesLength = payload.readUInt16BE(cursor);
472
+ cursor += 2;
473
+ const cipherSuitesEnd = cursor + cipherSuitesLength;
474
+ while (cursor + 2 <= cipherSuitesEnd) {
475
+ const suiteId = payload.readUInt16BE(cursor);
476
+ result.cipherSuites.push(CIPHER_SUITES_BY_ID[suiteId] ?? `0x${suiteId.toString(16).padStart(4, "0")}`);
477
+ cursor += 2;
478
+ }
479
+ cursor = cipherSuitesEnd + 1;
480
+ if (cursor < payload.length) {
481
+ const compLength = payload[cursor];
482
+ if (compLength !== void 0) cursor += 1 + compLength;
483
+ }
484
+ if (cursor + 2 <= payload.length) {
485
+ const extensionsLength = payload.readUInt16BE(cursor);
486
+ cursor += 2;
487
+ const extensionsEnd = cursor + extensionsLength;
488
+ while (cursor + 4 <= extensionsEnd) {
489
+ const extType = payload.readUInt16BE(cursor);
490
+ const extLength = payload.readUInt16BE(cursor + 2);
491
+ cursor += 4;
492
+ const extName = EXTENSION_NAMES[extType] ?? `unknown(0x${extType.toString(16)})`;
493
+ result.extensions.push({
494
+ type: extType,
495
+ name: extName,
496
+ length: extLength
497
+ });
498
+ if (extType === 0 && cursor + 2 <= extensionsEnd) {
499
+ const sniCursor = cursor + 2;
500
+ if (sniCursor + 3 <= extensionsEnd) {
501
+ if (payload[sniCursor] === 0) {
502
+ const sniLength = payload.readUInt16BE(sniCursor + 1);
503
+ const sniStart = sniCursor + 3;
504
+ if (sniStart + sniLength <= extensionsEnd) result.serverName = payload.subarray(sniStart, sniStart + sniLength).toString("utf8");
505
+ }
506
+ }
507
+ }
508
+ cursor += extLength;
509
+ }
510
+ }
511
+ return result;
512
+ }
513
+ //#endregion
514
+ //#region src/server/domains/boringssl-inspector/handlers/base.ts
515
+ /**
516
+ * BoringsslInspectorBaseHandlers — shared state and transport helpers.
517
+ */
518
+ var BoringsslInspectorBaseHandlers = class {
519
+ extensionInvoke;
520
+ eventBus;
521
+ tcpSessions = /* @__PURE__ */ new Map();
522
+ tlsSessions = /* @__PURE__ */ new Map();
523
+ websocketSessions = /* @__PURE__ */ new Map();
524
+ constructor(keyLogExtractor = new TLSKeyLogExtractor()) {
525
+ this.keyLogExtractor = keyLogExtractor;
526
+ }
527
+ setExtensionInvoke(invoke) {
528
+ this.extensionInvoke = invoke;
529
+ }
530
+ setEventBus(eventBus) {
531
+ this.eventBus = eventBus;
532
+ }
533
+ getTcpSession(sessionId) {
534
+ return this.tcpSessions.get(sessionId) ?? null;
535
+ }
536
+ getTlsSession(sessionId) {
537
+ return this.tlsSessions.get(sessionId) ?? null;
538
+ }
539
+ getWebSocketSession(sessionId) {
540
+ return this.websocketSessions.get(sessionId) ?? null;
541
+ }
542
+ parseWritePayload(args) {
543
+ const dataHex = argString(args, "dataHex");
544
+ const dataText = argString(args, "dataText");
545
+ if (!dataHex && !dataText) return {
546
+ ok: false,
547
+ error: "dataHex or dataText is required"
548
+ };
549
+ if (dataHex && dataText) return {
550
+ ok: false,
551
+ error: "dataHex and dataText are mutually exclusive"
552
+ };
553
+ if (dataHex) {
554
+ const normalized = normalizeHex(dataHex);
555
+ if (!isHex(normalized)) return {
556
+ ok: false,
557
+ error: "dataHex must be valid even-length hexadecimal data"
558
+ };
559
+ return {
560
+ ok: true,
561
+ data: Buffer.from(normalized, "hex"),
562
+ inputEncoding: "hex"
563
+ };
564
+ }
565
+ return {
566
+ ok: true,
567
+ data: Buffer.from(dataText ?? "", "utf8"),
568
+ inputEncoding: "utf8"
569
+ };
570
+ }
571
+ async writeBufferedSession(session, args) {
572
+ if (session.socket.destroyed || session.closed) return {
573
+ ok: false,
574
+ error: `Session "${session.id}" is already closed`,
575
+ sessionId: session.id,
576
+ kind: session.kind,
577
+ state: serializeSessionState(session)
578
+ };
579
+ const payload = this.parseWritePayload(args);
580
+ if (!payload.ok) return {
581
+ ok: false,
582
+ error: payload.error,
583
+ sessionId: session.id,
584
+ kind: session.kind
585
+ };
586
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
587
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
588
+ ok: false,
589
+ error: "timeoutMs must be a positive number"
590
+ };
591
+ return new Promise((resolve) => {
592
+ let settled = false;
593
+ const finish = (result) => {
594
+ if (settled) return;
595
+ settled = true;
596
+ clearTimeout(timer);
597
+ session.socket.off("error", onError);
598
+ resolve(result);
599
+ };
600
+ const timer = setTimeout(() => {
601
+ finish({
602
+ ok: false,
603
+ error: "write timed out",
604
+ sessionId: session.id,
605
+ kind: session.kind,
606
+ state: serializeSessionState(session)
607
+ });
608
+ }, timeoutMs);
609
+ const onError = (error) => {
610
+ finish({
611
+ ok: false,
612
+ error: error.message,
613
+ sessionId: session.id,
614
+ kind: session.kind,
615
+ state: serializeSessionState(session)
616
+ });
617
+ };
618
+ session.socket.once("error", onError);
619
+ session.socket.write(payload.data, () => {
620
+ if (session.kind === "tcp") this.eventBus?.emit("tcp:session_written", {
621
+ sessionId: session.id,
622
+ byteLength: payload.data.length,
623
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
624
+ });
625
+ else this.eventBus?.emit("tls:session_written", {
626
+ sessionId: session.id,
627
+ byteLength: payload.data.length,
628
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
629
+ });
630
+ finish({
631
+ ok: true,
632
+ sessionId: session.id,
633
+ kind: session.kind,
634
+ inputEncoding: payload.inputEncoding,
635
+ bytesWritten: payload.data.length,
636
+ transport: serializeSocketAddresses(session.socket),
637
+ state: serializeSessionState(session)
638
+ });
639
+ });
640
+ });
641
+ }
642
+ async readBufferedSessionUntil(session, args) {
643
+ const delimiterHex = argString(args, "delimiterHex");
644
+ const delimiterText = argString(args, "delimiterText");
645
+ if (delimiterHex && delimiterText) return {
646
+ ok: false,
647
+ error: "delimiterHex and delimiterText are mutually exclusive"
648
+ };
649
+ let delimiter = null;
650
+ if (delimiterHex) {
651
+ const normalized = normalizeHex(delimiterHex);
652
+ if (!isHex(normalized)) return {
653
+ ok: false,
654
+ error: "delimiterHex must be valid even-length hexadecimal data"
655
+ };
656
+ delimiter = Buffer.from(normalized, "hex");
657
+ } else if (delimiterText !== void 0) delimiter = Buffer.from(delimiterText, "utf8");
658
+ if (delimiter && delimiter.length === 0) return {
659
+ ok: false,
660
+ error: "delimiter must not be empty"
661
+ };
662
+ const includeDelimiter = argBool(args, "includeDelimiter") ?? true;
663
+ const rawMaxBytes = argNumber(args, "maxBytes");
664
+ const maxBytes = rawMaxBytes === void 0 ? void 0 : Math.trunc(rawMaxBytes);
665
+ if (maxBytes !== void 0 && (!Number.isFinite(maxBytes) || maxBytes <= 0)) return {
666
+ ok: false,
667
+ error: "maxBytes must be a positive integer when provided"
668
+ };
669
+ if (!delimiter && maxBytes === void 0) return {
670
+ ok: false,
671
+ error: "delimiterHex, delimiterText, or maxBytes is required"
672
+ };
673
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
674
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
675
+ ok: false,
676
+ error: "timeoutMs must be a positive number"
677
+ };
678
+ if (session.activeRead) return {
679
+ ok: false,
680
+ error: `Session "${session.id}" already has a pending read`,
681
+ sessionId: session.id,
682
+ kind: session.kind,
683
+ state: serializeSessionState(session)
684
+ };
685
+ session.activeRead = true;
686
+ const startedAt = Date.now();
687
+ try {
688
+ while (true) {
689
+ const consumed = consumeSessionBuffer(session, delimiter, includeDelimiter, maxBytes);
690
+ if (consumed) {
691
+ if (session.kind === "tcp") this.eventBus?.emit("tcp:session_read", {
692
+ sessionId: session.id,
693
+ byteLength: consumed.data.length,
694
+ matched: consumed.matchedDelimiter,
695
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
696
+ });
697
+ else this.eventBus?.emit("tls:session_read", {
698
+ sessionId: session.id,
699
+ byteLength: consumed.data.length,
700
+ matched: consumed.matchedDelimiter,
701
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
702
+ });
703
+ return {
704
+ ok: true,
705
+ sessionId: session.id,
706
+ kind: session.kind,
707
+ bytesRead: consumed.data.length,
708
+ matchedDelimiter: consumed.matchedDelimiter,
709
+ stopReason: consumed.stopReason,
710
+ delimiterHex: consumed.delimiterHex,
711
+ dataHex: consumed.data.toString("hex").toUpperCase(),
712
+ dataText: consumed.data.toString("utf8"),
713
+ elapsedMs: Date.now() - startedAt,
714
+ state: serializeSessionState(session)
715
+ };
716
+ }
717
+ if (session.error) return {
718
+ ok: false,
719
+ error: session.error,
720
+ sessionId: session.id,
721
+ kind: session.kind,
722
+ state: serializeSessionState(session)
723
+ };
724
+ if (session.ended || session.closed) return {
725
+ ok: false,
726
+ error: "socket closed before the requested read condition was satisfied",
727
+ sessionId: session.id,
728
+ kind: session.kind,
729
+ state: serializeSessionState(session)
730
+ };
731
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
732
+ if (remainingMs <= 0) return {
733
+ ok: false,
734
+ error: "read timed out",
735
+ sessionId: session.id,
736
+ kind: session.kind,
737
+ state: serializeSessionState(session)
738
+ };
739
+ if (!await waitForSessionActivity(session, remainingMs)) return {
740
+ ok: false,
741
+ error: "read timed out",
742
+ sessionId: session.id,
743
+ kind: session.kind,
744
+ state: serializeSessionState(session)
745
+ };
746
+ }
747
+ } finally {
748
+ session.activeRead = false;
749
+ }
750
+ }
751
+ async closeBufferedSession(sessionId, sessions, kind, args) {
752
+ const session = sessions.get(sessionId);
753
+ if (!session) return {
754
+ ok: false,
755
+ error: `Unknown ${kind} sessionId "${sessionId}"`
756
+ };
757
+ const force = argBool(args, "force") ?? false;
758
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
759
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
760
+ ok: false,
761
+ error: "timeoutMs must be a positive number"
762
+ };
763
+ const bufferedBytesDiscarded = session.buffer.length;
764
+ if (session.closed || session.socket.destroyed) {
765
+ sessions.delete(sessionId);
766
+ return {
767
+ ok: true,
768
+ sessionId,
769
+ kind,
770
+ force,
771
+ closed: true,
772
+ bufferedBytesDiscarded,
773
+ state: serializeSessionState(session)
774
+ };
775
+ }
776
+ return new Promise((resolve) => {
777
+ let settled = false;
778
+ const finish = (closed) => {
779
+ if (settled) return;
780
+ settled = true;
781
+ clearTimeout(timer);
782
+ session.socket.off("close", onClose);
783
+ session.socket.off("error", onError);
784
+ sessions.delete(sessionId);
785
+ if (kind === "tcp") this.eventBus?.emit("tcp:session_closed", {
786
+ sessionId,
787
+ reason: session.error,
788
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
789
+ });
790
+ else this.eventBus?.emit("tls:session_closed", {
791
+ sessionId,
792
+ reason: session.error,
793
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
794
+ });
795
+ resolve({
796
+ ok: true,
797
+ sessionId,
798
+ kind,
799
+ force,
800
+ closed,
801
+ bufferedBytesDiscarded,
802
+ state: serializeSessionState(session)
803
+ });
804
+ };
805
+ const onClose = () => finish(true);
806
+ const onError = () => finish(session.socket.destroyed || session.closed);
807
+ const timer = setTimeout(() => {
808
+ session.socket.destroy();
809
+ finish(session.socket.destroyed || session.closed);
810
+ }, timeoutMs);
811
+ session.socket.once("close", onClose);
812
+ session.socket.once("error", onError);
813
+ if (force) {
814
+ session.socket.destroy();
815
+ return;
816
+ }
817
+ session.socket.end();
818
+ });
819
+ }
820
+ };
821
+ //#endregion
822
+ //#region src/server/domains/boringssl-inspector/handlers/tls-handlers.ts
823
+ /**
824
+ * BoringsslInspectorTlsHandlers — keylog and TLS parsing helpers.
825
+ */
826
+ var BoringsslInspectorTlsHandlers = class extends BoringsslInspectorBaseHandlers {
827
+ async handleTlsKeylogEnable(_args) {
828
+ return {
829
+ enabled: true,
830
+ keyLogPath: await this.keyLogExtractor.enableKeyLog(),
831
+ environmentVariable: "SSLKEYLOGFILE"
832
+ };
833
+ }
834
+ async handleTlsKeylogDisable(args) {
835
+ const path = argString(args, "path") ?? null;
836
+ if (path) await this.keyLogExtractor.disableKeyLog();
837
+ else disableKeyLog();
838
+ return {
839
+ disabled: true,
840
+ previousPath: path ?? getKeyLogFilePath()
841
+ };
842
+ }
843
+ async handleTlsKeylogParse(args) {
844
+ const path = argString(args, "path") ?? null;
845
+ const entries = this.keyLogExtractor.parseKeyLog(path ?? void 0);
846
+ const summary = this.keyLogExtractor.summarizeKeyLog(path ?? void 0);
847
+ return {
848
+ path: path ?? this.keyLogExtractor.getKeyLogFilePath(),
849
+ entries,
850
+ summary
851
+ };
852
+ }
853
+ async handleTlsDecryptPayload(args) {
854
+ const encryptedHex = argString(args, "encryptedHex") ?? null;
855
+ const keyHex = argString(args, "keyHex") ?? null;
856
+ const nonceHex = argString(args, "nonceHex") ?? null;
857
+ const algorithm = argString(args, "algorithm") ?? "aes-256-gcm";
858
+ const authTagHex = argString(args, "authTagHex") ?? null;
859
+ if (!encryptedHex || !keyHex || !nonceHex) return {
860
+ ok: false,
861
+ error: "encryptedHex, keyHex, and nonceHex are required"
862
+ };
863
+ const decrypted = decryptPayload(encryptedHex, keyHex, nonceHex, algorithm, authTagHex ?? void 0);
864
+ return {
865
+ ok: true,
866
+ algorithm,
867
+ decrypted,
868
+ isFailed: decrypted.startsWith("DECRYPTION_FAILED:")
869
+ };
870
+ }
871
+ async handleTlsKeylogSummarize(args) {
872
+ const content = argString(args, "content") ?? null;
873
+ if (content) return summarizeKeyLog(parseKeyLog(content));
874
+ this.keyLogExtractor.parseKeyLog();
875
+ return this.keyLogExtractor.summarizeKeyLog();
876
+ }
877
+ async handleTlsKeylogLookupSecret(args) {
878
+ const clientRandom = argString(args, "clientRandom") ?? null;
879
+ const label = argString(args, "label") ?? void 0;
880
+ if (!clientRandom) return {
881
+ ok: false,
882
+ error: "clientRandom is required"
883
+ };
884
+ const cached = this.keyLogExtractor.lookupSecret(clientRandom);
885
+ if (cached) return {
886
+ ok: true,
887
+ clientRandom: normalizeHex(clientRandom),
888
+ secret: cached
889
+ };
890
+ const secret = lookupSecret(this.keyLogExtractor.parseKeyLog(), clientRandom, label);
891
+ return {
892
+ ok: secret !== null,
893
+ clientRandom: normalizeHex(clientRandom),
894
+ secret: secret ?? null
895
+ };
896
+ }
897
+ async handleTlsCertPinBypass(args) {
898
+ const target = argString(args, "target") ?? null;
899
+ if (target !== "android" && target !== "ios" && target !== "desktop") return { error: "target must be one of android, ios, or desktop" };
900
+ return {
901
+ bypassStrategy: {
902
+ android: "hook-trust-manager",
903
+ ios: "replace-sec-trust-evaluation",
904
+ desktop: "patch-custom-verifier"
905
+ }[target],
906
+ affectedDomains: ["*"],
907
+ instructions: {
908
+ android: ["Inject a Frida script that overrides X509TrustManager checks.", "Re-run the target flow after SSLKEYLOGFILE capture is enabled."],
909
+ ios: ["Hook SecTrustEvaluateWithError and return success for the target session.", "Collect TLS keys after the app resumes the failing request."],
910
+ desktop: ["Patch the custom verifier callback or disable pin comparison in the client.", "Capture a fresh handshake after the patched build starts."]
911
+ }[target]
912
+ };
913
+ }
914
+ async handleParseHandshake(args) {
915
+ const rawHex = argString(args, "rawHex") ?? null;
916
+ const decrypt = args.decrypt === true;
917
+ if (!rawHex) return asJsonResponse({
918
+ success: false,
919
+ error: "rawHex is required"
920
+ });
921
+ const normalizedHex = normalizeHex(rawHex);
922
+ if (!/^(?:[0-9a-f]{2})+$/i.test(normalizedHex)) return asJsonResponse({
923
+ success: false,
924
+ error: "Invalid hex payload"
925
+ });
926
+ const record = Buffer.from(normalizedHex, "hex");
927
+ if (record.length < 5) return asJsonResponse({
928
+ success: false,
929
+ error: "TLS record is too short"
930
+ });
931
+ const contentType = record[0];
932
+ const versionMajor = record[1];
933
+ const versionMinor = record[2];
934
+ const declaredLength = record.readUInt16BE(3);
935
+ const payload = record.subarray(5);
936
+ const clientHello = contentType === 22 && payload.length > 0 && payload[0] === 1 ? parseClientHello(payload) : void 0;
937
+ const decryptedPreviewHex = decrypt ? (() => {
938
+ const decrypted = this.keyLogExtractor.decryptPayload(normalizedHex, this.keyLogExtractor.parseKeyLog());
939
+ return decrypted ? decrypted.subarray(0, 16).toString("hex").toUpperCase() : null;
940
+ })() : void 0;
941
+ return asJsonResponse({
942
+ success: true,
943
+ record: {
944
+ contentType,
945
+ contentTypeName: contentTypeName(contentType),
946
+ version: tlsVersionName(versionMajor, versionMinor),
947
+ declaredLength,
948
+ actualLength: payload.length
949
+ },
950
+ handshake: {
951
+ version: tlsVersionName(versionMajor, versionMinor),
952
+ contentType: contentTypeName(contentType),
953
+ ...clientHello ? {
954
+ type: "client_hello",
955
+ serverName: clientHello.serverName,
956
+ cipherSuites: clientHello.cipherSuites,
957
+ extensions: clientHello.extensions
958
+ } : {
959
+ cipherSuite: [],
960
+ extensions: []
961
+ }
962
+ },
963
+ sni: clientHello?.serverName ? { serverName: clientHello.serverName } : void 0,
964
+ ...decryptedPreviewHex !== void 0 ? { decryptedPreviewHex } : {}
965
+ });
966
+ }
967
+ async handleKeyLogEnable(args) {
968
+ const filePath = argString(args, "filePath") ?? "/tmp/sslkeylog.log";
969
+ enableKeyLog(filePath);
970
+ this.eventBus?.emit("tls:keylog_started", {
971
+ filePath,
972
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
973
+ });
974
+ return asJsonResponse({
975
+ success: true,
976
+ filePath,
977
+ currentFilePath: getKeyLogFilePath()
978
+ });
979
+ }
980
+ async handleCipherSuites(args) {
981
+ const filter = argString(args, "filter") ?? null;
982
+ const allSuites = [
983
+ "TLS_AES_128_GCM_SHA256",
984
+ "TLS_AES_256_GCM_SHA384",
985
+ "TLS_CHACHA20_POLY1305_SHA256",
986
+ "TLS_AES_128_CCM_SHA256",
987
+ "TLS_AES_128_CCM_8_SHA256",
988
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
989
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
990
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
991
+ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
992
+ "TLS_RSA_WITH_AES_128_GCM_SHA256",
993
+ "TLS_RSA_WITH_AES_256_GCM_SHA384"
994
+ ];
995
+ const filteredSuites = filter ? allSuites.filter((suite) => suite.includes(filter)) : allSuites;
996
+ return asJsonResponse({
997
+ success: true,
998
+ filter,
999
+ total: filteredSuites.length,
1000
+ suites: filteredSuites
1001
+ });
1002
+ }
1003
+ async handleParseCertificate(args) {
1004
+ const rawHex = argString(args, "rawHex") ?? null;
1005
+ if (!rawHex) return asJsonResponse({
1006
+ success: false,
1007
+ error: "rawHex is required"
1008
+ });
1009
+ const certs = parseCertificateChain(rawHex);
1010
+ return asJsonResponse({
1011
+ success: true,
1012
+ certificateCount: certs.length,
1013
+ fingerprints: certs.map((cert) => ({
1014
+ sha256: cert.sha256,
1015
+ length: cert.length
1016
+ }))
1017
+ });
1018
+ }
1019
+ };
1020
+ //#endregion
1021
+ //#region src/server/domains/boringssl-inspector/handlers/tls-probe-handlers.ts
1022
+ /**
1023
+ * BoringsslInspectorTlsProbeHandlers — active TLS probe handler.
1024
+ */
1025
+ const TLS_VERSION_ORDER$2 = [
1026
+ "TLSv1",
1027
+ "TLSv1.1",
1028
+ "TLSv1.2",
1029
+ "TLSv1.3"
1030
+ ];
1031
+ var BoringsslInspectorTlsProbeHandlers = class extends BoringsslInspectorTlsHandlers {
1032
+ async handleTlsProbeEndpoint(args) {
1033
+ const host = argString(args, "host")?.trim() ?? null;
1034
+ if (!host) return {
1035
+ ok: false,
1036
+ error: "host is required"
1037
+ };
1038
+ const port = argNumber(args, "port") ?? 443;
1039
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return {
1040
+ ok: false,
1041
+ error: "port must be an integer between 1 and 65535"
1042
+ };
1043
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
1044
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1045
+ ok: false,
1046
+ error: "timeoutMs must be a positive number"
1047
+ };
1048
+ const allowInvalidCertificates = argBool(args, "allowInvalidCertificates") ?? false;
1049
+ const skipHostnameCheck = argBool(args, "skipHostnameCheck") ?? false;
1050
+ const servernameArg = argString(args, "servername")?.trim() ?? null;
1051
+ const alpnProtocols = [...new Set(argStringArray(args, "alpnProtocols").map((v) => v.trim()))].filter((v) => v.length > 0);
1052
+ let minVersion;
1053
+ let maxVersion;
1054
+ try {
1055
+ minVersion = argEnum(args, "minVersion", TLS_VERSION_SET);
1056
+ maxVersion = argEnum(args, "maxVersion", TLS_VERSION_SET);
1057
+ } catch (error) {
1058
+ return {
1059
+ ok: false,
1060
+ error: errorMessage(error)
1061
+ };
1062
+ }
1063
+ if (minVersion && maxVersion && TLS_VERSION_ORDER$2.indexOf(minVersion) > TLS_VERSION_ORDER$2.indexOf(maxVersion)) return {
1064
+ ok: false,
1065
+ error: "minVersion must not be greater than maxVersion"
1066
+ };
1067
+ const ssrfCheck = validateNetworkTarget(host);
1068
+ if (ssrfCheck) return ssrfCheck;
1069
+ const caBundle = await loadProbeCaBundle(args);
1070
+ if (!caBundle.ok) return {
1071
+ ok: false,
1072
+ error: caBundle.error
1073
+ };
1074
+ const validationTarget = servernameArg ?? host;
1075
+ const requestedServername = servernameArg ?? (isIP(host) === 0 ? host : void 0);
1076
+ const startedAt = Date.now();
1077
+ return new Promise((resolve) => {
1078
+ let settled = false;
1079
+ const socket = connect(applyTlsValidationPolicy({
1080
+ host,
1081
+ port,
1082
+ servername: requestedServername,
1083
+ ...minVersion ? { minVersion } : {},
1084
+ ...maxVersion ? { maxVersion } : {},
1085
+ ...alpnProtocols.length > 0 ? { ALPNProtocols: alpnProtocols } : {},
1086
+ ...caBundle.ca ? { ca: caBundle.ca } : {}
1087
+ }, allowInvalidCertificates));
1088
+ const finish = (payload) => {
1089
+ if (settled) return;
1090
+ settled = true;
1091
+ clearTimeout(timer);
1092
+ socket.removeAllListeners();
1093
+ socket.destroy();
1094
+ resolve(payload);
1095
+ };
1096
+ const timer = setTimeout(() => {
1097
+ this.eventBus?.emit("tls:probe_completed", {
1098
+ host,
1099
+ port,
1100
+ success: false,
1101
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1102
+ });
1103
+ finish({
1104
+ ok: false,
1105
+ error: "TLS probe timed out",
1106
+ target: {
1107
+ host,
1108
+ port,
1109
+ requestedServername: requestedServername ?? null,
1110
+ validationTarget
1111
+ },
1112
+ policy: {
1113
+ allowInvalidCertificates,
1114
+ skipHostnameCheck,
1115
+ timeoutMs,
1116
+ minVersion: minVersion ?? null,
1117
+ maxVersion: maxVersion ?? null,
1118
+ alpnProtocols,
1119
+ customCa: {
1120
+ source: caBundle.source,
1121
+ path: caBundle.path,
1122
+ bytes: caBundle.bytes
1123
+ }
1124
+ }
1125
+ });
1126
+ }, timeoutMs);
1127
+ socket.once("error", (error) => {
1128
+ this.eventBus?.emit("tls:probe_completed", {
1129
+ host,
1130
+ port,
1131
+ success: false,
1132
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1133
+ });
1134
+ finish({
1135
+ ok: false,
1136
+ error: error.message,
1137
+ errorCode: error.code ?? null,
1138
+ target: {
1139
+ host,
1140
+ port,
1141
+ requestedServername: requestedServername ?? null,
1142
+ validationTarget
1143
+ },
1144
+ policy: {
1145
+ allowInvalidCertificates,
1146
+ skipHostnameCheck,
1147
+ timeoutMs,
1148
+ minVersion: minVersion ?? null,
1149
+ maxVersion: maxVersion ?? null,
1150
+ alpnProtocols,
1151
+ customCa: {
1152
+ source: caBundle.source,
1153
+ path: caBundle.path,
1154
+ bytes: caBundle.bytes
1155
+ }
1156
+ }
1157
+ });
1158
+ });
1159
+ socket.once("secureConnect", () => {
1160
+ const handshakeMs = Date.now() - startedAt;
1161
+ const peerCertificate = socket.getPeerCertificate(true);
1162
+ const hasLeafCertificate = hasPeerCertificate(peerCertificate);
1163
+ const certificateChain = hasLeafCertificate ? buildPeerCertificateChain(peerCertificate) : [];
1164
+ const leafCertificate = certificateChain[0] ?? null;
1165
+ const hostnameError = skipHostnameCheck || !hasLeafCertificate ? void 0 : checkServerIdentity(validationTarget, peerCertificate);
1166
+ const hostnameValidation = {
1167
+ checked: !skipHostnameCheck,
1168
+ target: skipHostnameCheck ? null : validationTarget,
1169
+ matched: skipHostnameCheck ? null : hostnameError === void 0,
1170
+ error: !skipHostnameCheck && !hasLeafCertificate ? "Peer certificate was not presented by the server" : hostnameError?.message ?? null
1171
+ };
1172
+ const authorizationReasons = [
1173
+ socket.authorized ? "Certificate chain validated against the active trust store." : `Certificate chain validation failed: ${socket.authorizationError ?? "unknown_authority"}`,
1174
+ skipHostnameCheck ? "Hostname validation was skipped by request." : hostnameValidation.matched ? "Hostname validation passed." : `Hostname validation failed: ${hostnameValidation.error ?? "unknown_error"}`,
1175
+ !socket.authorized && allowInvalidCertificates ? "Policy allowed the probe to continue despite certificate trust failure." : null
1176
+ ].filter((reason) => Boolean(reason));
1177
+ const cipher = socket.getCipher();
1178
+ this.eventBus?.emit("tls:probe_completed", {
1179
+ host,
1180
+ port,
1181
+ success: true,
1182
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1183
+ });
1184
+ finish({
1185
+ ok: true,
1186
+ target: {
1187
+ host,
1188
+ port,
1189
+ requestedServername: requestedServername ?? null,
1190
+ validationTarget
1191
+ },
1192
+ policy: {
1193
+ allowInvalidCertificates,
1194
+ skipHostnameCheck,
1195
+ timeoutMs,
1196
+ minVersion: minVersion ?? null,
1197
+ maxVersion: maxVersion ?? null,
1198
+ alpnProtocols,
1199
+ customCa: {
1200
+ source: caBundle.source,
1201
+ path: caBundle.path,
1202
+ bytes: caBundle.bytes
1203
+ }
1204
+ },
1205
+ transport: {
1206
+ protocol: socket.getProtocol() ?? null,
1207
+ alpnProtocol: normalizeAlpnProtocol(socket.alpnProtocol),
1208
+ cipher: {
1209
+ name: cipher.name,
1210
+ standardName: cipher.standardName,
1211
+ version: cipher.version
1212
+ },
1213
+ localAddress: socket.localAddress ?? null,
1214
+ localPort: socket.localPort ?? null,
1215
+ remoteAddress: socket.remoteAddress ?? null,
1216
+ remotePort: socket.remotePort ?? null,
1217
+ servernameSent: normalizeSocketServername(socket.servername),
1218
+ sessionReused: socket.isSessionReused()
1219
+ },
1220
+ authorization: {
1221
+ socketAuthorized: socket.authorized,
1222
+ authorizationError: typeof socket.authorizationError === "string" ? socket.authorizationError : socket.authorizationError?.message ?? null,
1223
+ hostnameValidation,
1224
+ policyAllowed: (socket.authorized || allowInvalidCertificates) && (skipHostnameCheck || hostnameValidation.matched === true),
1225
+ reasons: authorizationReasons
1226
+ },
1227
+ certificates: {
1228
+ leaf: leafCertificate,
1229
+ chain: certificateChain
1230
+ },
1231
+ timing: { handshakeMs }
1232
+ });
1233
+ });
1234
+ });
1235
+ }
1236
+ };
1237
+ //#endregion
1238
+ //#region src/server/domains/boringssl-inspector/handlers/session-handlers.ts
1239
+ /**
1240
+ * BoringsslInspectorSessionHandlers — stateful TCP and TLS session handlers.
1241
+ */
1242
+ const TLS_VERSION_ORDER$1 = [
1243
+ "TLSv1",
1244
+ "TLSv1.1",
1245
+ "TLSv1.2",
1246
+ "TLSv1.3"
1247
+ ];
1248
+ var BoringsslInspectorSessionHandlers = class extends BoringsslInspectorTlsProbeHandlers {
1249
+ async handleTcpOpen(args) {
1250
+ const host = argString(args, "host") ?? "127.0.0.1";
1251
+ const port = argNumber(args, "port");
1252
+ if (port === void 0 || !Number.isInteger(port) || port < 1 || port > 65535) return {
1253
+ ok: false,
1254
+ error: "port must be an integer between 1 and 65535"
1255
+ };
1256
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
1257
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1258
+ ok: false,
1259
+ error: "timeoutMs must be a positive number"
1260
+ };
1261
+ const noDelay = argBool(args, "noDelay") ?? true;
1262
+ const ssrfCheck = validateNetworkTarget(host);
1263
+ if (ssrfCheck) return ssrfCheck;
1264
+ return new Promise((resolve) => {
1265
+ let settled = false;
1266
+ const socket = new Socket();
1267
+ const finish = (payload) => {
1268
+ if (settled) return;
1269
+ settled = true;
1270
+ clearTimeout(timer);
1271
+ socket.off("connect", onConnect);
1272
+ socket.off("error", onError);
1273
+ resolve(payload);
1274
+ };
1275
+ const timer = setTimeout(() => {
1276
+ socket.destroy();
1277
+ finish({
1278
+ ok: false,
1279
+ error: "TCP connect timed out",
1280
+ target: {
1281
+ host,
1282
+ port
1283
+ }
1284
+ });
1285
+ }, timeoutMs);
1286
+ const onError = (error) => {
1287
+ finish({
1288
+ ok: false,
1289
+ error: error.message,
1290
+ target: {
1291
+ host,
1292
+ port
1293
+ }
1294
+ });
1295
+ };
1296
+ const onConnect = () => {
1297
+ socket.setNoDelay(noDelay);
1298
+ const sessionId = makeSessionId("tcp");
1299
+ const session = {
1300
+ id: sessionId,
1301
+ kind: "tcp",
1302
+ socket,
1303
+ host,
1304
+ port,
1305
+ createdAt: Date.now(),
1306
+ buffer: Buffer.alloc(0),
1307
+ ended: false,
1308
+ closed: false,
1309
+ error: null,
1310
+ waiters: /* @__PURE__ */ new Set(),
1311
+ activeRead: false
1312
+ };
1313
+ attachBufferedSession(session);
1314
+ this.tcpSessions.set(sessionId, session);
1315
+ this.eventBus?.emit("tcp:session_opened", {
1316
+ sessionId,
1317
+ host,
1318
+ port,
1319
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1320
+ });
1321
+ finish({
1322
+ ok: true,
1323
+ sessionId,
1324
+ kind: "tcp",
1325
+ target: {
1326
+ host,
1327
+ port
1328
+ },
1329
+ createdAt: new Date(session.createdAt).toISOString(),
1330
+ transport: serializeSocketAddresses(socket),
1331
+ state: serializeSessionState(session)
1332
+ });
1333
+ };
1334
+ socket.once("connect", onConnect);
1335
+ socket.once("error", onError);
1336
+ socket.connect(port, host);
1337
+ });
1338
+ }
1339
+ async handleTcpWrite(args) {
1340
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1341
+ if (!sessionId) return {
1342
+ ok: false,
1343
+ error: "sessionId is required"
1344
+ };
1345
+ const session = this.getTcpSession(sessionId);
1346
+ if (!session) return {
1347
+ ok: false,
1348
+ error: `Unknown tcp sessionId "${sessionId}"`
1349
+ };
1350
+ return this.writeBufferedSession(session, args);
1351
+ }
1352
+ async handleTcpReadUntil(args) {
1353
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1354
+ if (!sessionId) return {
1355
+ ok: false,
1356
+ error: "sessionId is required"
1357
+ };
1358
+ const session = this.getTcpSession(sessionId);
1359
+ if (!session) return {
1360
+ ok: false,
1361
+ error: `Unknown tcp sessionId "${sessionId}"`
1362
+ };
1363
+ return this.readBufferedSessionUntil(session, args);
1364
+ }
1365
+ async handleTcpClose(args) {
1366
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1367
+ if (!sessionId) return {
1368
+ ok: false,
1369
+ error: "sessionId is required"
1370
+ };
1371
+ return this.closeBufferedSession(sessionId, this.tcpSessions, "tcp", args);
1372
+ }
1373
+ async handleTlsOpen(args) {
1374
+ const host = argString(args, "host")?.trim() ?? null;
1375
+ if (!host) return {
1376
+ ok: false,
1377
+ error: "host is required"
1378
+ };
1379
+ const port = argNumber(args, "port") ?? 443;
1380
+ if (!Number.isInteger(port) || port < 1 || port > 65535) return {
1381
+ ok: false,
1382
+ error: "port must be an integer between 1 and 65535"
1383
+ };
1384
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
1385
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1386
+ ok: false,
1387
+ error: "timeoutMs must be a positive number"
1388
+ };
1389
+ const allowInvalidCertificates = argBool(args, "allowInvalidCertificates") ?? false;
1390
+ const skipHostnameCheck = argBool(args, "skipHostnameCheck") ?? false;
1391
+ const servernameArg = argString(args, "servername")?.trim() ?? null;
1392
+ const alpnProtocols = [...new Set(argStringArray(args, "alpnProtocols").map((value) => value.trim()))].filter((value) => value.length > 0);
1393
+ let minVersion;
1394
+ let maxVersion;
1395
+ try {
1396
+ minVersion = argEnum(args, "minVersion", TLS_VERSION_SET);
1397
+ maxVersion = argEnum(args, "maxVersion", TLS_VERSION_SET);
1398
+ } catch (error) {
1399
+ return {
1400
+ ok: false,
1401
+ error: errorMessage(error)
1402
+ };
1403
+ }
1404
+ if (minVersion && maxVersion && TLS_VERSION_ORDER$1.indexOf(minVersion) > TLS_VERSION_ORDER$1.indexOf(maxVersion)) return {
1405
+ ok: false,
1406
+ error: "minVersion must not be greater than maxVersion"
1407
+ };
1408
+ const ssrfCheck = validateNetworkTarget(host);
1409
+ if (ssrfCheck) return ssrfCheck;
1410
+ const caBundle = await loadProbeCaBundle(args);
1411
+ if (!caBundle.ok) return {
1412
+ ok: false,
1413
+ error: caBundle.error
1414
+ };
1415
+ const target = {
1416
+ host,
1417
+ port,
1418
+ requestedServername: servernameArg ?? (isIP(host) === 0 ? host : void 0) ?? null,
1419
+ validationTarget: servernameArg ?? host
1420
+ };
1421
+ const policy = {
1422
+ allowInvalidCertificates,
1423
+ skipHostnameCheck,
1424
+ timeoutMs,
1425
+ minVersion: minVersion ?? null,
1426
+ maxVersion: maxVersion ?? null,
1427
+ alpnProtocols,
1428
+ customCa: {
1429
+ source: caBundle.source,
1430
+ path: caBundle.path,
1431
+ bytes: caBundle.bytes
1432
+ }
1433
+ };
1434
+ const startedAt = Date.now();
1435
+ return new Promise((resolve) => {
1436
+ let settled = false;
1437
+ const socket = connect(applyTlsValidationPolicy({
1438
+ host,
1439
+ port,
1440
+ servername: target.requestedServername ?? void 0,
1441
+ ...minVersion ? { minVersion } : {},
1442
+ ...maxVersion ? { maxVersion } : {},
1443
+ ...alpnProtocols.length > 0 ? { ALPNProtocols: alpnProtocols } : {},
1444
+ ...caBundle.ca ? { ca: caBundle.ca } : {}
1445
+ }, allowInvalidCertificates));
1446
+ const finish = (payload) => {
1447
+ if (settled) return;
1448
+ settled = true;
1449
+ clearTimeout(timer);
1450
+ socket.off("error", onError);
1451
+ socket.off("secureConnect", onSecureConnect);
1452
+ resolve(payload);
1453
+ };
1454
+ const timer = setTimeout(() => {
1455
+ socket.destroy();
1456
+ this.eventBus?.emit("tls:probe_completed", {
1457
+ host,
1458
+ port,
1459
+ success: false,
1460
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1461
+ });
1462
+ finish({
1463
+ ok: false,
1464
+ error: "TLS open timed out",
1465
+ target,
1466
+ policy
1467
+ });
1468
+ }, timeoutMs);
1469
+ const onError = (error) => {
1470
+ this.eventBus?.emit("tls:probe_completed", {
1471
+ host,
1472
+ port,
1473
+ success: false,
1474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1475
+ });
1476
+ finish({
1477
+ ok: false,
1478
+ error: error.message,
1479
+ errorCode: error.code ?? null,
1480
+ target,
1481
+ policy
1482
+ });
1483
+ };
1484
+ const onSecureConnect = () => {
1485
+ const handshakeMs = Date.now() - startedAt;
1486
+ const peerCertificate = socket.getPeerCertificate(true);
1487
+ const hasLeafCertificate = hasPeerCertificate(peerCertificate);
1488
+ const certificateChain = hasLeafCertificate ? buildPeerCertificateChain(peerCertificate) : [];
1489
+ const leafCertificate = certificateChain[0] ?? null;
1490
+ const hostnameError = skipHostnameCheck || !hasLeafCertificate ? void 0 : checkServerIdentity(target.validationTarget, peerCertificate);
1491
+ const hostnameValidation = {
1492
+ checked: !skipHostnameCheck,
1493
+ target: skipHostnameCheck ? null : target.validationTarget,
1494
+ matched: skipHostnameCheck ? null : hostnameError === void 0,
1495
+ error: !skipHostnameCheck && !hasLeafCertificate ? "Peer certificate was not presented by the server" : hostnameError?.message ?? null
1496
+ };
1497
+ const authorizationReasons = [
1498
+ socket.authorized ? "Certificate chain validated against the active trust store." : `Certificate chain validation failed: ${socket.authorizationError ?? "unknown_authority"}`,
1499
+ skipHostnameCheck ? "Hostname validation was skipped by request." : hostnameValidation.matched ? "Hostname validation passed." : `Hostname validation failed: ${hostnameValidation.error ?? "unknown_error"}`,
1500
+ !socket.authorized && allowInvalidCertificates ? "Policy allowed the session to continue despite certificate trust failure." : null
1501
+ ].filter((reason) => Boolean(reason));
1502
+ const cipher = socket.getCipher();
1503
+ const metadata = {
1504
+ target,
1505
+ policy,
1506
+ transport: {
1507
+ protocol: socket.getProtocol() ?? null,
1508
+ alpnProtocol: normalizeAlpnProtocol(socket.alpnProtocol),
1509
+ cipher: {
1510
+ name: cipher.name,
1511
+ standardName: cipher.standardName,
1512
+ version: cipher.version
1513
+ },
1514
+ localAddress: socket.localAddress ?? null,
1515
+ localPort: socket.localPort ?? null,
1516
+ remoteAddress: socket.remoteAddress ?? null,
1517
+ remotePort: socket.remotePort ?? null,
1518
+ servernameSent: normalizeSocketServername(socket.servername),
1519
+ sessionReused: socket.isSessionReused()
1520
+ },
1521
+ authorization: {
1522
+ socketAuthorized: socket.authorized,
1523
+ authorizationError: typeof socket.authorizationError === "string" ? socket.authorizationError : socket.authorizationError?.message ?? null,
1524
+ hostnameValidation,
1525
+ policyAllowed: (socket.authorized || allowInvalidCertificates) && (skipHostnameCheck || hostnameValidation.matched === true),
1526
+ reasons: authorizationReasons
1527
+ },
1528
+ certificates: {
1529
+ leaf: leafCertificate,
1530
+ chain: certificateChain
1531
+ }
1532
+ };
1533
+ if (!metadata.authorization.policyAllowed) {
1534
+ socket.destroy();
1535
+ this.eventBus?.emit("tls:probe_completed", {
1536
+ host,
1537
+ port,
1538
+ success: false,
1539
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1540
+ });
1541
+ finish({
1542
+ ok: false,
1543
+ error: "TLS session authorization failed",
1544
+ ...metadata,
1545
+ timing: { handshakeMs }
1546
+ });
1547
+ return;
1548
+ }
1549
+ const sessionId = makeSessionId("tls");
1550
+ const session = {
1551
+ id: sessionId,
1552
+ kind: "tls",
1553
+ socket,
1554
+ host,
1555
+ port,
1556
+ createdAt: Date.now(),
1557
+ buffer: Buffer.alloc(0),
1558
+ ended: false,
1559
+ closed: false,
1560
+ error: null,
1561
+ waiters: /* @__PURE__ */ new Set(),
1562
+ activeRead: false,
1563
+ metadata
1564
+ };
1565
+ attachBufferedSession(session);
1566
+ this.tlsSessions.set(sessionId, session);
1567
+ this.eventBus?.emit("tls:session_opened", {
1568
+ sessionId,
1569
+ host,
1570
+ port,
1571
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1572
+ });
1573
+ this.eventBus?.emit("tls:probe_completed", {
1574
+ host,
1575
+ port,
1576
+ success: true,
1577
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1578
+ });
1579
+ finish({
1580
+ ok: true,
1581
+ sessionId,
1582
+ kind: "tls",
1583
+ ...metadata,
1584
+ timing: { handshakeMs },
1585
+ state: serializeSessionState(session)
1586
+ });
1587
+ };
1588
+ socket.once("error", onError);
1589
+ socket.once("secureConnect", onSecureConnect);
1590
+ });
1591
+ }
1592
+ async handleTlsWrite(args) {
1593
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1594
+ if (!sessionId) return {
1595
+ ok: false,
1596
+ error: "sessionId is required"
1597
+ };
1598
+ const session = this.getTlsSession(sessionId);
1599
+ if (!session) return {
1600
+ ok: false,
1601
+ error: `Unknown tls sessionId "${sessionId}"`
1602
+ };
1603
+ return this.writeBufferedSession(session, args);
1604
+ }
1605
+ async handleTlsReadUntil(args) {
1606
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1607
+ if (!sessionId) return {
1608
+ ok: false,
1609
+ error: "sessionId is required"
1610
+ };
1611
+ const session = this.getTlsSession(sessionId);
1612
+ if (!session) return {
1613
+ ok: false,
1614
+ error: `Unknown tls sessionId "${sessionId}"`
1615
+ };
1616
+ return this.readBufferedSessionUntil(session, args);
1617
+ }
1618
+ async handleTlsClose(args) {
1619
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1620
+ if (!sessionId) return {
1621
+ ok: false,
1622
+ error: "sessionId is required"
1623
+ };
1624
+ return this.closeBufferedSession(sessionId, this.tlsSessions, "tls", args);
1625
+ }
1626
+ };
1627
+ //#endregion
1628
+ //#region src/server/domains/boringssl-inspector/handlers/websocket-frame-handlers.ts
1629
+ /**
1630
+ * BoringsslInspectorWebSocketFrameHandlers — WebSocket session/frame operations.
1631
+ */
1632
+ var BoringsslInspectorWebSocketFrameHandlers = class extends BoringsslInspectorSessionHandlers {
1633
+ emitWebSocketEvent(event, payload) {
1634
+ this.eventBus?.emit(event, payload);
1635
+ }
1636
+ attachWebSocketSession(session) {
1637
+ const parseBufferedFrames = () => {
1638
+ while (session.parserBuffer.length > 0) {
1639
+ let consumed;
1640
+ try {
1641
+ consumed = tryConsumeWebSocketFrame(session.parserBuffer);
1642
+ } catch (error) {
1643
+ session.error = errorMessage(error);
1644
+ session.socket.destroy();
1645
+ break;
1646
+ }
1647
+ if (!consumed) break;
1648
+ session.parserBuffer = session.parserBuffer.subarray(consumed.bytesConsumed);
1649
+ const frame = consumed.frame;
1650
+ session.frames.push(frame);
1651
+ if (frame.type === "ping" && !session.closeSent && !session.socket.destroyed) {
1652
+ const pongFrame = encodeWebSocketFrame("pong", frame.data);
1653
+ session.socket.write(pongFrame);
1654
+ this.emitWebSocketEvent("websocket:session_written", {
1655
+ sessionId: session.id,
1656
+ frameType: "pong",
1657
+ byteLength: frame.data.length,
1658
+ automatic: true,
1659
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1660
+ });
1661
+ }
1662
+ if (frame.type === "close") {
1663
+ session.closeReceived = true;
1664
+ if (!session.closeSent && !session.socket.destroyed) {
1665
+ session.closeSent = true;
1666
+ session.socket.write(encodeWebSocketFrame("close", frame.data, frame.closeCode, frame.closeReason));
1667
+ this.emitWebSocketEvent("websocket:session_written", {
1668
+ sessionId: session.id,
1669
+ frameType: "close",
1670
+ byteLength: frame.data.length,
1671
+ automatic: true,
1672
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1673
+ });
1674
+ }
1675
+ }
1676
+ }
1677
+ wakeWebSocketWaiters(session);
1678
+ };
1679
+ session.socket.on("data", (chunk) => {
1680
+ session.parserBuffer = Buffer.concat([session.parserBuffer, chunk]);
1681
+ parseBufferedFrames();
1682
+ });
1683
+ session.socket.on("end", () => {
1684
+ session.ended = true;
1685
+ wakeWebSocketWaiters(session);
1686
+ });
1687
+ session.socket.on("close", () => {
1688
+ session.closed = true;
1689
+ wakeWebSocketWaiters(session);
1690
+ });
1691
+ session.socket.on("error", (error) => {
1692
+ session.error = error.message;
1693
+ wakeWebSocketWaiters(session);
1694
+ });
1695
+ parseBufferedFrames();
1696
+ }
1697
+ async readWebSocketFrame(session, args) {
1698
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
1699
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1700
+ ok: false,
1701
+ error: "timeoutMs must be a positive number"
1702
+ };
1703
+ if (session.activeRead) return {
1704
+ ok: false,
1705
+ error: `Session "${session.id}" already has a pending read`,
1706
+ sessionId: session.id,
1707
+ kind: session.kind,
1708
+ state: serializeWebSocketSessionState(session)
1709
+ };
1710
+ session.activeRead = true;
1711
+ const startedAt = Date.now();
1712
+ try {
1713
+ while (true) {
1714
+ const frame = session.frames.shift();
1715
+ if (frame) {
1716
+ this.emitWebSocketEvent("websocket:frame_read", {
1717
+ sessionId: session.id,
1718
+ frameType: frame.type,
1719
+ byteLength: frame.data.length,
1720
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1721
+ });
1722
+ return {
1723
+ ok: true,
1724
+ sessionId: session.id,
1725
+ kind: session.kind,
1726
+ scheme: session.scheme,
1727
+ frameType: frame.type,
1728
+ fin: frame.fin,
1729
+ opcode: frame.opcode,
1730
+ masked: frame.masked,
1731
+ byteLength: frame.data.length,
1732
+ dataHex: frame.data.toString("hex").toUpperCase(),
1733
+ dataText: frame.type === "binary" ? null : frame.data.toString("utf8"),
1734
+ closeCode: frame.closeCode,
1735
+ closeReason: frame.closeReason,
1736
+ elapsedMs: Date.now() - startedAt,
1737
+ state: serializeWebSocketSessionState(session)
1738
+ };
1739
+ }
1740
+ if (session.error) return {
1741
+ ok: false,
1742
+ error: session.error,
1743
+ sessionId: session.id,
1744
+ kind: session.kind,
1745
+ state: serializeWebSocketSessionState(session)
1746
+ };
1747
+ if (session.closed || session.ended) return {
1748
+ ok: false,
1749
+ error: "socket closed before a WebSocket frame was available",
1750
+ sessionId: session.id,
1751
+ kind: session.kind,
1752
+ state: serializeWebSocketSessionState(session)
1753
+ };
1754
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
1755
+ if (remainingMs <= 0) return {
1756
+ ok: false,
1757
+ error: "read timed out",
1758
+ sessionId: session.id,
1759
+ kind: session.kind,
1760
+ state: serializeWebSocketSessionState(session)
1761
+ };
1762
+ if (!await waitForWebSocketActivity(session, remainingMs)) return {
1763
+ ok: false,
1764
+ error: "read timed out",
1765
+ sessionId: session.id,
1766
+ kind: session.kind,
1767
+ state: serializeWebSocketSessionState(session)
1768
+ };
1769
+ }
1770
+ } finally {
1771
+ session.activeRead = false;
1772
+ }
1773
+ }
1774
+ async sendWebSocketFrame(session, args) {
1775
+ if (session.closed || session.socket.destroyed) return {
1776
+ ok: false,
1777
+ error: `Session "${session.id}" is already closed`,
1778
+ sessionId: session.id,
1779
+ kind: session.kind,
1780
+ state: serializeWebSocketSessionState(session)
1781
+ };
1782
+ const frameType = argEnum(args, "frameType", new Set([
1783
+ "text",
1784
+ "binary",
1785
+ "ping",
1786
+ "pong",
1787
+ "close"
1788
+ ]));
1789
+ if (!frameType) return {
1790
+ ok: false,
1791
+ error: "frameType is required"
1792
+ };
1793
+ const dataHex = argString(args, "dataHex");
1794
+ const dataText = argString(args, "dataText");
1795
+ if (dataHex && dataText) return {
1796
+ ok: false,
1797
+ error: "dataHex and dataText are mutually exclusive"
1798
+ };
1799
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
1800
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1801
+ ok: false,
1802
+ error: "timeoutMs must be a positive number"
1803
+ };
1804
+ let payload = Buffer.alloc(0);
1805
+ if (dataHex) {
1806
+ const normalized = normalizeHex(dataHex);
1807
+ if (!isHex(normalized)) return {
1808
+ ok: false,
1809
+ error: "dataHex must be valid even-length hexadecimal data"
1810
+ };
1811
+ payload = Buffer.from(normalized, "hex");
1812
+ } else if (dataText !== void 0) payload = Buffer.from(dataText, "utf8");
1813
+ let closeCode = null;
1814
+ let closeReason = null;
1815
+ if (frameType === "close") {
1816
+ const rawCloseCode = argNumber(args, "closeCode");
1817
+ if (rawCloseCode !== void 0) {
1818
+ if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
1819
+ ok: false,
1820
+ error: "closeCode must be an integer between 1000 and 4999"
1821
+ };
1822
+ closeCode = rawCloseCode;
1823
+ }
1824
+ closeReason = argString(args, "closeReason") ?? null;
1825
+ if (dataHex || dataText) return {
1826
+ ok: false,
1827
+ error: "close frames use closeCode/closeReason instead of dataHex/dataText"
1828
+ };
1829
+ session.closeSent = true;
1830
+ }
1831
+ if (frameType === "text" && dataHex) return {
1832
+ ok: false,
1833
+ error: "text frames require UTF-8 dataText instead of dataHex"
1834
+ };
1835
+ const frameBuffer = encodeWebSocketFrame(frameType, payload, closeCode, closeReason);
1836
+ return new Promise((resolve) => {
1837
+ let settled = false;
1838
+ const finish = (result) => {
1839
+ if (settled) return;
1840
+ settled = true;
1841
+ clearTimeout(timer);
1842
+ session.socket.off("error", onError);
1843
+ resolve(result);
1844
+ };
1845
+ const timer = setTimeout(() => {
1846
+ finish({
1847
+ ok: false,
1848
+ error: "write timed out",
1849
+ sessionId: session.id,
1850
+ kind: session.kind,
1851
+ state: serializeWebSocketSessionState(session)
1852
+ });
1853
+ }, timeoutMs);
1854
+ const onError = (error) => {
1855
+ finish({
1856
+ ok: false,
1857
+ error: error.message,
1858
+ sessionId: session.id,
1859
+ kind: session.kind,
1860
+ state: serializeWebSocketSessionState(session)
1861
+ });
1862
+ };
1863
+ session.socket.once("error", onError);
1864
+ session.socket.write(frameBuffer, () => {
1865
+ this.emitWebSocketEvent("websocket:session_written", {
1866
+ sessionId: session.id,
1867
+ frameType,
1868
+ byteLength: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
1869
+ automatic: false,
1870
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1871
+ });
1872
+ finish({
1873
+ ok: true,
1874
+ sessionId: session.id,
1875
+ kind: session.kind,
1876
+ scheme: session.scheme,
1877
+ frameType,
1878
+ bytesWritten: frameBuffer.length,
1879
+ payloadBytes: frameType === "close" ? closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0 : payload.length,
1880
+ state: serializeWebSocketSessionState(session)
1881
+ });
1882
+ });
1883
+ });
1884
+ }
1885
+ async closeWebSocketSession(sessionId, args) {
1886
+ const session = this.websocketSessions.get(sessionId);
1887
+ if (!session) return {
1888
+ ok: false,
1889
+ error: `Unknown websocket sessionId "${sessionId}"`
1890
+ };
1891
+ const force = argBool(args, "force") ?? false;
1892
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 1e3;
1893
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
1894
+ ok: false,
1895
+ error: "timeoutMs must be a positive number"
1896
+ };
1897
+ const queuedFramesDiscarded = session.frames.length;
1898
+ if (session.closed || session.socket.destroyed) {
1899
+ this.websocketSessions.delete(sessionId);
1900
+ return {
1901
+ ok: true,
1902
+ sessionId,
1903
+ kind: session.kind,
1904
+ force,
1905
+ closed: true,
1906
+ queuedFramesDiscarded,
1907
+ state: serializeWebSocketSessionState(session)
1908
+ };
1909
+ }
1910
+ let closeCode = null;
1911
+ const rawCloseCode = argNumber(args, "closeCode");
1912
+ if (rawCloseCode !== void 0) {
1913
+ if (!Number.isInteger(rawCloseCode) || rawCloseCode < 1e3 || rawCloseCode > 4999) return {
1914
+ ok: false,
1915
+ error: "closeCode must be an integer between 1000 and 4999"
1916
+ };
1917
+ closeCode = rawCloseCode;
1918
+ }
1919
+ const closeReason = argString(args, "closeReason") ?? null;
1920
+ return new Promise((resolve) => {
1921
+ let settled = false;
1922
+ const finish = (closed) => {
1923
+ if (settled) return;
1924
+ settled = true;
1925
+ clearTimeout(timer);
1926
+ session.socket.off("close", onClose);
1927
+ session.socket.off("error", onError);
1928
+ this.websocketSessions.delete(sessionId);
1929
+ this.emitWebSocketEvent("websocket:session_closed", {
1930
+ sessionId,
1931
+ reason: session.error,
1932
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1933
+ });
1934
+ resolve({
1935
+ ok: true,
1936
+ sessionId,
1937
+ kind: session.kind,
1938
+ force,
1939
+ closed,
1940
+ queuedFramesDiscarded,
1941
+ state: serializeWebSocketSessionState(session)
1942
+ });
1943
+ };
1944
+ const onClose = () => finish(true);
1945
+ const onError = () => finish(session.socket.destroyed || session.closed);
1946
+ const timer = setTimeout(() => {
1947
+ session.socket.destroy();
1948
+ finish(session.socket.destroyed || session.closed);
1949
+ }, timeoutMs);
1950
+ session.socket.once("close", onClose);
1951
+ session.socket.once("error", onError);
1952
+ if (force) {
1953
+ session.socket.destroy();
1954
+ return;
1955
+ }
1956
+ if (!session.closeSent) {
1957
+ session.closeSent = true;
1958
+ session.socket.write(encodeWebSocketFrame("close", Buffer.alloc(0), closeCode, closeReason));
1959
+ this.emitWebSocketEvent("websocket:session_written", {
1960
+ sessionId,
1961
+ frameType: "close",
1962
+ byteLength: closeReason ? Buffer.byteLength(closeReason) + 2 : closeCode ? 2 : 0,
1963
+ automatic: false,
1964
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1965
+ });
1966
+ }
1967
+ });
1968
+ }
1969
+ async handleWebSocketSendFrame(args) {
1970
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1971
+ if (!sessionId) return {
1972
+ ok: false,
1973
+ error: "sessionId is required"
1974
+ };
1975
+ const session = this.getWebSocketSession(sessionId);
1976
+ if (!session) return {
1977
+ ok: false,
1978
+ error: `Unknown websocket sessionId "${sessionId}"`
1979
+ };
1980
+ return this.sendWebSocketFrame(session, args);
1981
+ }
1982
+ async handleWebSocketReadFrame(args) {
1983
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1984
+ if (!sessionId) return {
1985
+ ok: false,
1986
+ error: "sessionId is required"
1987
+ };
1988
+ const session = this.getWebSocketSession(sessionId);
1989
+ if (!session) return {
1990
+ ok: false,
1991
+ error: `Unknown websocket sessionId "${sessionId}"`
1992
+ };
1993
+ return this.readWebSocketFrame(session, args);
1994
+ }
1995
+ async handleWebSocketClose(args) {
1996
+ const sessionId = argString(args, "sessionId")?.trim() ?? null;
1997
+ if (!sessionId) return {
1998
+ ok: false,
1999
+ error: "sessionId is required"
2000
+ };
2001
+ return this.closeWebSocketSession(sessionId, args);
2002
+ }
2003
+ };
2004
+ //#endregion
2005
+ //#region src/server/domains/boringssl-inspector/handlers/websocket-handlers.ts
2006
+ /**
2007
+ * BoringsslInspectorWebSocketHandlers — WebSocket upgrade/open handlers and bypass helpers.
2008
+ */
2009
+ const TLS_VERSION_ORDER = [
2010
+ "TLSv1",
2011
+ "TLSv1.1",
2012
+ "TLSv1.2",
2013
+ "TLSv1.3"
2014
+ ];
2015
+ var BoringsslInspectorWebSocketHandlers = class extends BoringsslInspectorWebSocketFrameHandlers {
2016
+ async handleWebSocketOpen(args) {
2017
+ const rawUrl = argString(args, "url")?.trim() ?? null;
2018
+ const rawHost = argString(args, "host")?.trim() ?? null;
2019
+ const rawPath = argString(args, "path")?.trim() ?? null;
2020
+ const rawPort = argNumber(args, "port");
2021
+ const rawScheme = argString(args, "scheme")?.trim() ?? null;
2022
+ if (rawUrl && (rawHost || rawPath || rawPort !== void 0 || rawScheme)) return {
2023
+ ok: false,
2024
+ error: "url is mutually exclusive with explicit scheme/host/port/path inputs"
2025
+ };
2026
+ let scheme = "ws";
2027
+ let host = rawHost;
2028
+ let port = rawPort ?? void 0;
2029
+ let path = normalizeWebSocketPath(rawPath);
2030
+ let url;
2031
+ if (rawUrl) {
2032
+ let parsedUrl;
2033
+ try {
2034
+ parsedUrl = new URL(rawUrl);
2035
+ } catch (error) {
2036
+ return {
2037
+ ok: false,
2038
+ error: `Invalid url: ${errorMessage(error)}`
2039
+ };
2040
+ }
2041
+ if (parsedUrl.protocol !== "ws:" && parsedUrl.protocol !== "wss:") return {
2042
+ ok: false,
2043
+ error: "url must use ws:// or wss:// protocol"
2044
+ };
2045
+ scheme = parsedUrl.protocol === "wss:" ? "wss" : "ws";
2046
+ host = parsedUrl.hostname;
2047
+ port = parsedUrl.port.length > 0 ? Number(parsedUrl.port) : scheme === "wss" ? 443 : 80;
2048
+ path = normalizeWebSocketPath(`${parsedUrl.pathname}${parsedUrl.search}`);
2049
+ url = `${scheme}://${parsedUrl.host}${path}`;
2050
+ } else {
2051
+ if (!host) return {
2052
+ ok: false,
2053
+ error: "host or url is required"
2054
+ };
2055
+ if (rawScheme) {
2056
+ if (rawScheme !== "ws" && rawScheme !== "wss") return {
2057
+ ok: false,
2058
+ error: "scheme must be ws or wss"
2059
+ };
2060
+ scheme = rawScheme;
2061
+ }
2062
+ port ??= scheme === "wss" ? 443 : 80;
2063
+ const authority = port === (scheme === "wss" ? 443 : 80) ? host : `${host}:${String(port)}`;
2064
+ url = `${scheme}://${authority}${path}`;
2065
+ }
2066
+ if (!host || !port || !Number.isInteger(port) || port < 1 || port > 65535) return {
2067
+ ok: false,
2068
+ error: "port must be an integer between 1 and 65535"
2069
+ };
2070
+ const timeoutMs = argNumber(args, "timeoutMs") ?? 5e3;
2071
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return {
2072
+ ok: false,
2073
+ error: "timeoutMs must be a positive number"
2074
+ };
2075
+ const subprotocols = [...new Set(argStringArray(args, "subprotocols").map((value) => value.trim()))].filter((value) => value.length > 0);
2076
+ const ssrfCheck = validateNetworkTarget(host);
2077
+ if (ssrfCheck) return ssrfCheck;
2078
+ const allowInvalidCertificates = argBool(args, "allowInvalidCertificates") ?? false;
2079
+ const skipHostnameCheck = argBool(args, "skipHostnameCheck") ?? false;
2080
+ const servernameArg = argString(args, "servername")?.trim() ?? null;
2081
+ const alpnProtocols = [...new Set(argStringArray(args, "alpnProtocols").map((value) => value.trim()))].filter((value) => value.length > 0);
2082
+ let minVersion;
2083
+ let maxVersion;
2084
+ try {
2085
+ minVersion = argEnum(args, "minVersion", TLS_VERSION_SET);
2086
+ maxVersion = argEnum(args, "maxVersion", TLS_VERSION_SET);
2087
+ } catch (error) {
2088
+ return {
2089
+ ok: false,
2090
+ error: errorMessage(error)
2091
+ };
2092
+ }
2093
+ if (minVersion && maxVersion && TLS_VERSION_ORDER.indexOf(minVersion) > TLS_VERSION_ORDER.indexOf(maxVersion)) return {
2094
+ ok: false,
2095
+ error: "minVersion must not be greater than maxVersion"
2096
+ };
2097
+ const caBundle = scheme === "wss" ? await loadProbeCaBundle(args) : {
2098
+ ok: true,
2099
+ ca: void 0,
2100
+ source: null,
2101
+ path: null,
2102
+ bytes: null
2103
+ };
2104
+ if (!caBundle.ok) return {
2105
+ ok: false,
2106
+ error: caBundle.error
2107
+ };
2108
+ const target = {
2109
+ scheme,
2110
+ url,
2111
+ host,
2112
+ port,
2113
+ path,
2114
+ requestedServername: scheme === "wss" ? servernameArg ?? (isIP(host) === 0 ? host : void 0) ?? null : null,
2115
+ validationTarget: scheme === "wss" ? servernameArg ?? host : null
2116
+ };
2117
+ const requestKey = randomBytes(16).toString("base64");
2118
+ const acceptKey = computeWebSocketAccept(requestKey);
2119
+ const startedAt = Date.now();
2120
+ return new Promise((resolve) => {
2121
+ let settled = false;
2122
+ let handshakeBuffer = Buffer.alloc(0);
2123
+ let transport = null;
2124
+ let authorization = null;
2125
+ let certificates = null;
2126
+ const socket = scheme === "wss" ? connect(applyTlsValidationPolicy({
2127
+ host,
2128
+ port,
2129
+ servername: target.requestedServername ?? void 0,
2130
+ ...minVersion ? { minVersion } : {},
2131
+ ...maxVersion ? { maxVersion } : {},
2132
+ ...alpnProtocols.length > 0 ? { ALPNProtocols: alpnProtocols } : {},
2133
+ ...caBundle.ca ? { ca: caBundle.ca } : {}
2134
+ }, allowInvalidCertificates)) : new Socket();
2135
+ const finish = (payload) => {
2136
+ if (settled) return;
2137
+ settled = true;
2138
+ clearTimeout(timer);
2139
+ socket.off("error", onError);
2140
+ socket.off("connect", onConnect);
2141
+ socket.off("secureConnect", onSecureConnect);
2142
+ socket.off("data", onHandshakeData);
2143
+ resolve(payload);
2144
+ };
2145
+ const buildHandshakeRequest = () => {
2146
+ const hostHeader = port === (scheme === "wss" ? 443 : 80) ? host : `${host}:${String(port)}`;
2147
+ const lines = [
2148
+ `GET ${path} HTTP/1.1`,
2149
+ `Host: ${hostHeader}`,
2150
+ "Upgrade: websocket",
2151
+ "Connection: Upgrade",
2152
+ `Sec-WebSocket-Key: ${requestKey}`,
2153
+ "Sec-WebSocket-Version: 13"
2154
+ ];
2155
+ if (subprotocols.length > 0) lines.push(`Sec-WebSocket-Protocol: ${subprotocols.join(", ")}`);
2156
+ lines.push("", "");
2157
+ return Buffer.from(lines.join("\r\n"), "utf8");
2158
+ };
2159
+ const timer = setTimeout(() => {
2160
+ socket.destroy();
2161
+ finish({
2162
+ ok: false,
2163
+ error: "WebSocket open timed out",
2164
+ target
2165
+ });
2166
+ }, timeoutMs);
2167
+ const onError = (error) => {
2168
+ finish({
2169
+ ok: false,
2170
+ error: error.message,
2171
+ errorCode: error.code ?? null,
2172
+ target
2173
+ });
2174
+ };
2175
+ const sendHandshake = () => {
2176
+ socket.write(buildHandshakeRequest());
2177
+ };
2178
+ const onConnect = () => {
2179
+ if (socket instanceof Socket) socket.setNoDelay(true);
2180
+ transport = {
2181
+ ...serializeSocketAddresses(socket),
2182
+ protocol: null,
2183
+ alpnProtocol: null,
2184
+ servernameSent: null,
2185
+ sessionReused: null
2186
+ };
2187
+ sendHandshake();
2188
+ };
2189
+ const onSecureConnect = () => {
2190
+ const tlsSocket = socket;
2191
+ const peerCertificate = tlsSocket.getPeerCertificate(true);
2192
+ const hasLeafCertificate = hasPeerCertificate(peerCertificate);
2193
+ const certificateChain = hasLeafCertificate ? buildPeerCertificateChain(peerCertificate) : [];
2194
+ const leafCertificate = certificateChain[0] ?? null;
2195
+ const hostnameError = skipHostnameCheck || !hasLeafCertificate || !target.validationTarget ? void 0 : checkServerIdentity(target.validationTarget, peerCertificate);
2196
+ const hostnameValidation = {
2197
+ checked: !skipHostnameCheck,
2198
+ target: skipHostnameCheck ? null : target.validationTarget,
2199
+ matched: skipHostnameCheck ? null : hostnameError === void 0,
2200
+ error: !skipHostnameCheck && !hasLeafCertificate ? "Peer certificate was not presented by the server" : hostnameError?.message ?? null
2201
+ };
2202
+ const authorizationReasons = [
2203
+ tlsSocket.authorized ? "Certificate chain validated against the active trust store." : `Certificate chain validation failed: ${tlsSocket.authorizationError ?? "unknown_authority"}`,
2204
+ skipHostnameCheck ? "Hostname validation was skipped by request." : hostnameValidation.matched ? "Hostname validation passed." : `Hostname validation failed: ${hostnameValidation.error ?? "unknown_error"}`,
2205
+ !tlsSocket.authorized && allowInvalidCertificates ? "Policy allowed the session to continue despite certificate trust failure." : null
2206
+ ].filter((reason) => Boolean(reason));
2207
+ authorization = {
2208
+ socketAuthorized: tlsSocket.authorized,
2209
+ authorizationError: typeof tlsSocket.authorizationError === "string" ? tlsSocket.authorizationError : tlsSocket.authorizationError?.message ?? null,
2210
+ hostnameValidation,
2211
+ policyAllowed: (tlsSocket.authorized || allowInvalidCertificates) && (skipHostnameCheck || hostnameValidation.matched === true),
2212
+ reasons: authorizationReasons
2213
+ };
2214
+ certificates = {
2215
+ leaf: leafCertificate,
2216
+ chain: certificateChain
2217
+ };
2218
+ transport = {
2219
+ ...serializeSocketAddresses(tlsSocket),
2220
+ protocol: tlsSocket.getProtocol() ?? null,
2221
+ alpnProtocol: normalizeAlpnProtocol(tlsSocket.alpnProtocol),
2222
+ servernameSent: normalizeSocketServername(tlsSocket.servername),
2223
+ sessionReused: tlsSocket.isSessionReused()
2224
+ };
2225
+ if (!authorization.policyAllowed) {
2226
+ tlsSocket.destroy();
2227
+ finish({
2228
+ ok: false,
2229
+ error: "WebSocket TLS authorization failed",
2230
+ target,
2231
+ authorization,
2232
+ certificates
2233
+ });
2234
+ return;
2235
+ }
2236
+ sendHandshake();
2237
+ };
2238
+ const onHandshakeData = (chunk) => {
2239
+ handshakeBuffer = Buffer.concat([handshakeBuffer, chunk]);
2240
+ const headerEnd = handshakeBuffer.indexOf("\r\n\r\n");
2241
+ if (headerEnd < 0) return;
2242
+ const lines = handshakeBuffer.subarray(0, headerEnd).toString("utf8").split("\r\n");
2243
+ const statusLine = lines.shift() ?? "";
2244
+ if (!/^HTTP\/1\.1 101\b/.test(statusLine)) {
2245
+ socket.destroy();
2246
+ finish({
2247
+ ok: false,
2248
+ error: `Unexpected WebSocket upgrade response: ${statusLine}`,
2249
+ target
2250
+ });
2251
+ return;
2252
+ }
2253
+ const headers = /* @__PURE__ */ new Map();
2254
+ for (const line of lines) {
2255
+ const separator = line.indexOf(":");
2256
+ if (separator <= 0) continue;
2257
+ const name = line.slice(0, separator).trim().toLowerCase();
2258
+ const value = line.slice(separator + 1).trim();
2259
+ headers.set(name, value);
2260
+ }
2261
+ const upgrade = headers.get("upgrade")?.toLowerCase() ?? "";
2262
+ const connection = headers.get("connection")?.toLowerCase() ?? "";
2263
+ const responseAcceptKey = headers.get("sec-websocket-accept") ?? null;
2264
+ if (upgrade !== "websocket") {
2265
+ socket.destroy();
2266
+ finish({
2267
+ ok: false,
2268
+ error: "Upgrade header did not confirm websocket",
2269
+ target
2270
+ });
2271
+ return;
2272
+ }
2273
+ if (!connection.split(",").map((part) => part.trim()).includes("upgrade")) {
2274
+ socket.destroy();
2275
+ finish({
2276
+ ok: false,
2277
+ error: "Connection header did not confirm upgrade",
2278
+ target
2279
+ });
2280
+ return;
2281
+ }
2282
+ if (responseAcceptKey !== acceptKey) {
2283
+ socket.destroy();
2284
+ finish({
2285
+ ok: false,
2286
+ error: "sec-websocket-accept did not match the client key",
2287
+ target
2288
+ });
2289
+ return;
2290
+ }
2291
+ const negotiatedSubprotocol = headers.get("sec-websocket-protocol") ?? null;
2292
+ if (negotiatedSubprotocol && !subprotocols.includes(negotiatedSubprotocol)) {
2293
+ socket.destroy();
2294
+ finish({
2295
+ ok: false,
2296
+ error: `Server selected unexpected subprotocol "${negotiatedSubprotocol}"`,
2297
+ target
2298
+ });
2299
+ return;
2300
+ }
2301
+ const sessionId = makeSessionId("websocket");
2302
+ const session = {
2303
+ id: sessionId,
2304
+ kind: "websocket",
2305
+ scheme,
2306
+ socket,
2307
+ host,
2308
+ port,
2309
+ path,
2310
+ createdAt: Date.now(),
2311
+ parserBuffer: handshakeBuffer.subarray(headerEnd + 4),
2312
+ frames: [],
2313
+ ended: false,
2314
+ closed: false,
2315
+ error: null,
2316
+ waiters: /* @__PURE__ */ new Set(),
2317
+ activeRead: false,
2318
+ closeSent: false,
2319
+ closeReceived: false,
2320
+ metadata: {
2321
+ target,
2322
+ handshake: {
2323
+ requestKey,
2324
+ acceptKey,
2325
+ responseAcceptKey,
2326
+ subprotocol: negotiatedSubprotocol
2327
+ },
2328
+ transport: transport ?? {
2329
+ ...serializeSocketAddresses(socket),
2330
+ protocol: null,
2331
+ alpnProtocol: null,
2332
+ servernameSent: null,
2333
+ sessionReused: null
2334
+ },
2335
+ authorization,
2336
+ certificates
2337
+ }
2338
+ };
2339
+ this.attachWebSocketSession(session);
2340
+ this.websocketSessions.set(sessionId, session);
2341
+ this.emitWebSocketEvent("websocket:session_opened", {
2342
+ sessionId,
2343
+ scheme,
2344
+ host,
2345
+ port,
2346
+ path,
2347
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2348
+ });
2349
+ finish({
2350
+ ok: true,
2351
+ sessionId,
2352
+ kind: session.kind,
2353
+ scheme,
2354
+ target,
2355
+ handshake: session.metadata.handshake,
2356
+ transport: session.metadata.transport,
2357
+ authorization: session.metadata.authorization,
2358
+ certificates: session.metadata.certificates,
2359
+ timing: { handshakeMs: Date.now() - startedAt },
2360
+ state: serializeWebSocketSessionState(session)
2361
+ });
2362
+ };
2363
+ socket.once("error", onError);
2364
+ socket.on("data", onHandshakeData);
2365
+ if (scheme === "wss") socket.once("secureConnect", onSecureConnect);
2366
+ else {
2367
+ socket.once("connect", onConnect);
2368
+ socket.connect(port, host);
2369
+ }
2370
+ });
2371
+ }
2372
+ async handleBypassCertPinning(args) {
2373
+ if (this.extensionInvoke) try {
2374
+ const result = await this.extensionInvoke(args);
2375
+ if (result) return asJsonResponse({
2376
+ success: true,
2377
+ strategy: "frida-injection",
2378
+ result
2379
+ });
2380
+ } catch {}
2381
+ return asJsonResponse({
2382
+ success: true,
2383
+ strategy: "manual-bypass",
2384
+ instructions: {
2385
+ android: ["Use Frida to hook X509TrustManager.checkServerTrusted and return without throwing.", "Alternatively, use OkHttp CertificatePinner.Builder().add() with the target cert."],
2386
+ ios: ["Hook SecTrustEvaluateWithError to always return true.", "Or use SSLSetSessionOption to disable certificate validation."],
2387
+ desktop: ["Set NODE_TLS_REJECT_UNAUTHORIZED=0 for Node.js targets.", "Or patch the certificate comparison function in the HTTP client."]
2388
+ },
2389
+ args
2390
+ });
2391
+ }
2392
+ };
2393
+ //#endregion
2394
+ //#region src/server/domains/boringssl-inspector/handlers/raw-socket-handlers.ts
2395
+ /**
2396
+ * BoringsslInspectorRawSocketHandlers — stateless raw TCP/UDP helpers.
2397
+ */
2398
+ var BoringsslInspectorRawSocketHandlers = class extends BoringsslInspectorWebSocketHandlers {
2399
+ async handleRawTcpSend(args) {
2400
+ const host = argString(args, "host") ?? "127.0.0.1";
2401
+ const port = argNumber(args, "port");
2402
+ if (port === void 0 || port < 1 || port > 65535) return {
2403
+ ok: false,
2404
+ error: "port must be a number between 1 and 65535"
2405
+ };
2406
+ const ssrfCheck = validateNetworkTarget(host);
2407
+ if (ssrfCheck) return ssrfCheck;
2408
+ const dataHex = argString(args, "dataHex");
2409
+ const dataText = argString(args, "dataText");
2410
+ if (!dataHex && !dataText) return {
2411
+ ok: false,
2412
+ error: "dataHex or dataText is required"
2413
+ };
2414
+ const data = dataHex ? Buffer.from(normalizeHex(dataHex), "hex") : Buffer.from(dataText ?? "", "utf8");
2415
+ const timeout = argNumber(args, "timeout") ?? 5e3;
2416
+ return new Promise((resolve) => {
2417
+ const socket = new Socket();
2418
+ const timer = setTimeout(() => {
2419
+ socket.destroy();
2420
+ resolve({
2421
+ ok: false,
2422
+ error: "Connection timed out"
2423
+ });
2424
+ }, timeout);
2425
+ socket.on("connect", () => {
2426
+ socket.write(data, () => {
2427
+ socket.end();
2428
+ });
2429
+ });
2430
+ socket.on("data", (chunk) => {
2431
+ clearTimeout(timer);
2432
+ resolve({
2433
+ ok: true,
2434
+ host,
2435
+ port,
2436
+ sentBytes: data.length,
2437
+ responseHex: chunk.toString("hex").toUpperCase(),
2438
+ responseText: chunk.toString("utf8")
2439
+ });
2440
+ socket.destroy();
2441
+ });
2442
+ socket.on("error", (error) => {
2443
+ clearTimeout(timer);
2444
+ resolve({
2445
+ ok: false,
2446
+ error: error.message
2447
+ });
2448
+ });
2449
+ socket.connect(port, host);
2450
+ });
2451
+ }
2452
+ async handleRawTcpListen(args) {
2453
+ const port = argNumber(args, "port");
2454
+ if (port === void 0 || port < 1 || port > 65535) return {
2455
+ ok: false,
2456
+ error: "port must be a number between 1 and 65535"
2457
+ };
2458
+ const timeout = argNumber(args, "timeout") ?? 1e4;
2459
+ return new Promise((resolve) => {
2460
+ const server = createServer();
2461
+ const timer = setTimeout(() => {
2462
+ server.close();
2463
+ resolve({
2464
+ ok: false,
2465
+ error: "Listen timed out - no connection received"
2466
+ });
2467
+ }, timeout);
2468
+ server.on("connection", (socket) => {
2469
+ clearTimeout(timer);
2470
+ const chunks = [];
2471
+ socket.on("data", (chunk) => {
2472
+ chunks.push(chunk);
2473
+ });
2474
+ socket.on("end", () => {
2475
+ const data = Buffer.concat(chunks);
2476
+ server.close();
2477
+ resolve({
2478
+ ok: true,
2479
+ port,
2480
+ receivedBytes: data.length,
2481
+ dataHex: data.toString("hex").toUpperCase(),
2482
+ dataText: data.toString("utf8")
2483
+ });
2484
+ });
2485
+ socket.on("error", (error) => {
2486
+ clearTimeout(timer);
2487
+ server.close();
2488
+ resolve({
2489
+ ok: false,
2490
+ error: error.message
2491
+ });
2492
+ });
2493
+ });
2494
+ server.on("error", (error) => {
2495
+ clearTimeout(timer);
2496
+ resolve({
2497
+ ok: false,
2498
+ error: error.message
2499
+ });
2500
+ });
2501
+ server.listen(port, "127.0.0.1");
2502
+ });
2503
+ }
2504
+ async handleRawUdpSend(args) {
2505
+ const host = argString(args, "host") ?? "127.0.0.1";
2506
+ const port = argNumber(args, "port");
2507
+ if (port === void 0 || port < 1 || port > 65535) return {
2508
+ ok: false,
2509
+ error: "port must be a number between 1 and 65535"
2510
+ };
2511
+ const ssrfCheck = validateNetworkTarget(host);
2512
+ if (ssrfCheck) return ssrfCheck;
2513
+ const dataHex = argString(args, "dataHex");
2514
+ const dataText = argString(args, "dataText");
2515
+ if (!dataHex && !dataText) return {
2516
+ ok: false,
2517
+ error: "dataHex or dataText is required"
2518
+ };
2519
+ const data = dataHex ? Buffer.from(normalizeHex(dataHex), "hex") : Buffer.from(dataText ?? "", "utf8");
2520
+ const timeout = argNumber(args, "timeout") ?? 5e3;
2521
+ return new Promise((resolve) => {
2522
+ const socket = createSocket("udp4");
2523
+ const timer = setTimeout(() => {
2524
+ socket.close();
2525
+ resolve({
2526
+ ok: false,
2527
+ error: "UDP response timed out"
2528
+ });
2529
+ }, timeout);
2530
+ socket.on("message", (msg) => {
2531
+ clearTimeout(timer);
2532
+ socket.close();
2533
+ resolve({
2534
+ ok: true,
2535
+ host,
2536
+ port,
2537
+ sentBytes: data.length,
2538
+ responseHex: msg.toString("hex").toUpperCase(),
2539
+ responseText: msg.toString("utf8")
2540
+ });
2541
+ });
2542
+ socket.on("error", (error) => {
2543
+ clearTimeout(timer);
2544
+ socket.close();
2545
+ resolve({
2546
+ ok: false,
2547
+ error: error.message
2548
+ });
2549
+ });
2550
+ socket.send(data, 0, data.length, port, host);
2551
+ });
2552
+ }
2553
+ async handleRawUdpListen(args) {
2554
+ const port = argNumber(args, "port");
2555
+ if (port === void 0 || port < 1 || port > 65535) return {
2556
+ ok: false,
2557
+ error: "port must be a number between 1 and 65535"
2558
+ };
2559
+ const timeout = argNumber(args, "timeout") ?? 1e4;
2560
+ return new Promise((resolve) => {
2561
+ const socket = createSocket("udp4");
2562
+ const timer = setTimeout(() => {
2563
+ socket.close();
2564
+ resolve({
2565
+ ok: false,
2566
+ error: "UDP listen timed out"
2567
+ });
2568
+ }, timeout);
2569
+ socket.on("message", (msg, rinfo) => {
2570
+ clearTimeout(timer);
2571
+ socket.close();
2572
+ resolve({
2573
+ ok: true,
2574
+ localPort: port,
2575
+ receivedBytes: msg.length,
2576
+ from: rinfo,
2577
+ dataHex: msg.toString("hex").toUpperCase(),
2578
+ dataText: msg.toString("utf8")
2579
+ });
2580
+ });
2581
+ socket.on("error", (error) => {
2582
+ clearTimeout(timer);
2583
+ socket.close();
2584
+ resolve({
2585
+ ok: false,
2586
+ error: error.message
2587
+ });
2588
+ });
2589
+ socket.bind(port, "127.0.0.1");
2590
+ });
2591
+ }
2592
+ };
2593
+ //#endregion
2594
+ //#region src/server/domains/boringssl-inspector/handlers/handler-class.ts
2595
+ /**
2596
+ * BoringsslInspectorHandlers — thin facade over the split handler chain.
2597
+ */
2598
+ var BoringsslInspectorHandlers = class extends BoringsslInspectorRawSocketHandlers {};
2599
+ //#endregion
2600
+ export { BoringsslInspectorHandlers };