@jshookmcp/jshook 0.2.9 → 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 (187) hide show
  1. package/README.md +2 -2
  2. package/README.zh.md +2 -2
  3. package/dist/{AntiCheatDetector-BNk-EoBt.mjs → AntiCheatDetector-CqGDXmfc.mjs} +159 -53
  4. package/dist/{CodeInjector-Cq8q01kp.mjs → CodeInjector-BdjRfNx7.mjs} +5 -5
  5. package/dist/{ConsoleMonitor-CPVQW1Y-.mjs → ConsoleMonitor-DykL3IAw.mjs} +85 -17
  6. package/dist/{DetailedDataManager-BQQcxh64.mjs → DetailedDataManager-HT49OrvF.mjs} +1 -1
  7. package/dist/{ExtensionManager-CWYgw0YW.mjs → ExtensionManager-BDMsY2Dz.mjs} +15 -8
  8. package/dist/{HardwareBreakpoint-B9gZCdFP.mjs → HardwareBreakpoint-Cc2AFq1Y.mjs} +3 -3
  9. package/dist/{HeapAnalyzer-BLDH0dCv.mjs → HeapAnalyzer-DruMgsgj.mjs} +20 -20
  10. package/dist/{HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs → HookGeneratorBuilders.core.generators.storage-CTbB4Lcx.mjs} +1 -74
  11. package/dist/{InstrumentationSession-CvPC7Jwy.mjs → InstrumentationSession-DLH0vd-z.mjs} +2 -2
  12. package/dist/{MemoryController-CbVdCIJF.mjs → MemoryController-CMtviNW_.mjs} +3 -3
  13. package/dist/{MemoryScanSession-BsDZbLYm.mjs → MemoryScanSession-ITgb_NMi.mjs} +2 -2
  14. package/dist/{MemoryScanner-Bcpml6II.mjs → MemoryScanner-CiL7Z3ey.mjs} +12 -9
  15. package/dist/{NativeMemoryManager.impl-dZtA1ZGn.mjs → NativeMemoryManager.impl-D9Lkovvn.mjs} +13 -10
  16. package/dist/{NativeMemoryManager.utils-B-FjA2mJ.mjs → NativeMemoryManager.utils-BBlAixF5.mjs} +1 -1
  17. package/dist/{PEAnalyzer-D1lzJ_VG.mjs → PEAnalyzer-DMQ44gen.mjs} +15 -15
  18. package/dist/{PageController-Bqm2kZ_X.mjs → PageController-BPJNqqBN.mjs} +18 -4
  19. package/dist/{PointerChainEngine-BOhyVsjx.mjs → PointerChainEngine-K7wN8Z-w.mjs} +10 -7
  20. package/dist/ProcessRegistry-zGg12QbE.mjs +74 -0
  21. package/dist/{ResponseBuilder-D3iFYx2N.mjs → ResponseBuilder-CJXWmWNw.mjs} +10 -10
  22. package/dist/{ScriptManager-aHHq0X7U.mjs → ScriptManager-ZuWD-0Jg.mjs} +195 -192
  23. package/dist/{Speedhack-CqdIFlQl.mjs → Speedhack-D-z0umeT.mjs} +2 -2
  24. package/dist/{StructureAnalyzer-DhFaPvRO.mjs → StructureAnalyzer-Cav5AVSL.mjs} +9 -6
  25. package/dist/{ToolCatalog-C0JGZoOm.mjs → ToolCatalog-5OJdMiF0.mjs} +81 -81
  26. package/dist/{ToolProbe-oC7aPrkv.mjs → ToolProbe-DbCFGyrg.mjs} +1 -1
  27. package/dist/{ToolRegistry-BjaF4oNz.mjs → ToolRegistry-B9krbTtI.mjs} +51 -2
  28. package/dist/{ToolRouter.policy-BWV67ZK-.mjs → ToolRouter.policy-BGDAGyeH.mjs} +60 -20
  29. package/dist/TraceRecorder-B41Z5XBj.mjs +1286 -0
  30. package/dist/{Win32API-CePkipZY.mjs → Win32API-C2kjj0ze.mjs} +18 -12
  31. package/dist/{Win32Debug-BvKs-gxc.mjs → Win32Debug-CKrGOTpo.mjs} +2 -2
  32. package/dist/{WorkflowEngine-CuvkZtWu.mjs → WorkflowEngine-DJ6M4opp.mjs} +226 -255
  33. package/dist/analysis-BHeJW2Nb.mjs +1234 -0
  34. package/dist/{antidebug-CqDTB_uk.mjs → antidebug-BRKeyt27.mjs} +3 -3
  35. package/dist/{artifactRetention-CFEprwPw.mjs → artifactRetention-CPXkUJXp.mjs} +13 -6
  36. package/dist/{artifacts-Bk2-_uPq.mjs → artifacts-DkfosXH3.mjs} +1 -1
  37. package/dist/authorization-schema-DRqyJMSk.mjs +31 -0
  38. package/dist/{binary-instrument-CXfpx6fT.mjs → binary-instrument--V3MAhJ4.mjs} +19 -27
  39. package/dist/bind-helpers-ClV34xdn.mjs +42 -0
  40. package/dist/{boringssl-inspector-BH2D3VKc.mjs → boringssl-inspector-Bo_LOLaS.mjs} +1 -1
  41. package/dist/{browser-BpOr5PEx.mjs → browser-Dx3_S2cG.mjs} +324 -37
  42. package/dist/capabilities-CcHlvWgK.mjs +33 -0
  43. package/dist/{constants-B0OANIBL.mjs → constants-CDZLOoVv.mjs} +18 -3
  44. package/dist/{coordination-qUbyF8KU.mjs → coordination-DgItD9DL.mjs} +2 -2
  45. package/dist/{debugger-gnKxRSN0.mjs → debugger-RS3RSAqs.mjs} +30 -13
  46. package/dist/definitions-BEoYofW5.mjs +47 -0
  47. package/dist/{definitions-bAhHQJq9.mjs → definitions-BRaefg3u.mjs} +11 -5
  48. package/dist/{definitions-DVGfrn7y.mjs → definitions-BbkvZkiv.mjs} +2 -2
  49. package/dist/definitions-BtWSHJ3o.mjs +17 -0
  50. package/dist/{definitions-BMfYXoNC.mjs → definitions-C1gCHO0i.mjs} +1 -1
  51. package/dist/{definitions-C1UvM5Iy.mjs → definitions-CDOg_b-l.mjs} +14 -2
  52. package/dist/definitions-CVPD9hzZ.mjs +54 -0
  53. package/dist/{definitions-Cke7zEb8.mjs → definitions-Cea8Lgl7.mjs} +1 -1
  54. package/dist/definitions-DAgIyjxM.mjs +10 -0
  55. package/dist/{definitions-B4rAvHNZ.mjs → definitions-DJA27nsL.mjs} +12 -9
  56. package/dist/{definitions-ClJLzsJQ.mjs → definitions-DKPFU3LW.mjs} +1 -1
  57. package/dist/{definitions-D3VsGcvz.mjs → definitions-DPRpZQ96.mjs} +7 -7
  58. package/dist/{definitions-B18eyf0B.mjs → definitions-DUE5gmdn.mjs} +1 -1
  59. package/dist/definitions-DYVjOtxa.mjs +26 -0
  60. package/dist/{definitions-BB_4jnmy.mjs → definitions-DcYLVLCo.mjs} +1 -1
  61. package/dist/{definitions-Beid2EB3.mjs → definitions-Pp5LI2H4.mjs} +1 -1
  62. package/dist/definitions-j9KdHVNR.mjs +14 -0
  63. package/dist/definitions-uzkjBwa7.mjs +258 -0
  64. package/dist/{definitions-Cq-zroAU.mjs → definitions-va-AnLuQ.mjs} +4 -4
  65. package/dist/{encoding-Bvz5jLRv.mjs → encoding-DJeqHmpd.mjs} +18 -4
  66. package/dist/{evidence-graph-bridge-C_fv9PuC.mjs → evidence-graph-bridge-DcYizFk2.mjs} +1 -0
  67. package/dist/{factory-DxlGh9Xf.mjs → factory-C90tBff6.mjs} +6 -6
  68. package/dist/flat-target-session-Dgax2Cy3.mjs +29 -0
  69. package/dist/{graphql-DYWzJ29s.mjs → graphql-CoHrhweh.mjs} +205 -34
  70. package/dist/{handlers-C67ktuRN.mjs → handlers-4jmR0nMs.mjs} +220 -32
  71. package/dist/{handlers-DlCJN4Td.mjs → handlers-BAHPxcch.mjs} +122 -90
  72. package/dist/{handlers-9sAbfIg-.mjs → handlers-BOs9b907.mjs} +849 -801
  73. package/dist/{handlers-DxGIq15_2.mjs → handlers-BWXEy6ef.mjs} +16 -16
  74. package/dist/{handlers-tB9Mp9ZK.mjs → handlers-Bndn6QvE.mjs} +31 -4
  75. package/dist/{handlers-CTsDAO6p.mjs → handlers-BqC4bD4s.mjs} +1 -1
  76. package/dist/{handlers-C87g8oCe.mjs → handlers-BtYq60bM2.mjs} +1 -1
  77. package/dist/{handlers-DeLOCd5m.mjs → handlers-BzgcB4iv.mjs} +17 -17
  78. package/dist/{handlers-Cgyg6c0U.mjs → handlers-CRyRWj2b.mjs} +237 -23
  79. package/dist/{handlers-U6L4xhuF.mjs → handlers-CVv2H1uq.mjs} +24 -17
  80. package/dist/{handlers-tiy7EIBp.mjs → handlers-Dl5a7JS4.mjs} +3 -3
  81. package/dist/{handlers-D6j6yka7.mjs → handlers-Dx2d7jt7.mjs} +1893 -1480
  82. package/dist/{handlers-Bl8zkwz1.mjs → handlers-Dz9PYsCa.mjs} +95 -6
  83. package/dist/handlers-HujRKC3b.mjs +661 -0
  84. package/dist/{handlers.impl-DS0d9fUw.mjs → handlers.impl-XWXkQfyi.mjs} +70 -24
  85. package/dist/{hooks-CzCWByww.mjs → hooks-B1B8NRHL.mjs} +3 -3
  86. package/dist/index.mjs +154 -144
  87. package/dist/{maintenance-P7ePRXQC.mjs → maintenance-PRMkLVRW.mjs} +35 -30
  88. package/dist/manifest-67Bok-Si.mjs +58 -0
  89. package/dist/{manifest-B3QVVeBS.mjs → manifest-6lNTMZAB2.mjs} +33 -28
  90. package/dist/manifest-B2duEHiH.mjs +90 -0
  91. package/dist/manifest-B6EY9Vm8.mjs +57 -0
  92. package/dist/{manifest-gZ4s_UtG.mjs → manifest-B6nKSbyY.mjs} +32 -33
  93. package/dist/{manifest-2ToTpjv8.mjs → manifest-BL8AQNPF.mjs} +31 -31
  94. package/dist/{manifest-DzwvxPJX.mjs → manifest-BSZvJJmV.mjs} +23 -14
  95. package/dist/{manifest-Sc_0JQ13.mjs → manifest-BU7qzUyX.mjs} +23 -23
  96. package/dist/{manifest-CT7zZBV1.mjs → manifest-Bl62e8WK.mjs} +24 -23
  97. package/dist/manifest-Bo5cXjdt.mjs +82 -0
  98. package/dist/manifest-BpS4gtUK.mjs +1347 -0
  99. package/dist/manifest-Bv65_e2W.mjs +101 -0
  100. package/dist/manifest-BytNIF4Z.mjs +117 -0
  101. package/dist/{manifest-BqrQ4Tpj.mjs → manifest-C-xtsjS3.mjs} +23 -23
  102. package/dist/{manifest-NXctwWQq.mjs → manifest-CDYl7OhA.mjs} +36 -38
  103. package/dist/manifest-CRZ3xmkD.mjs +61 -0
  104. package/dist/manifest-CoW6u4Tp.mjs +132 -0
  105. package/dist/manifest-Cq5zN_8A.mjs +50 -0
  106. package/dist/{manifest-CAhOuvSl.mjs → manifest-D7YZM_2e.mjs} +75 -85
  107. package/dist/{manifest-DCyjf4n2.mjs → manifest-DE_VrAeQ.mjs} +27 -7
  108. package/dist/manifest-DGsXSCpT.mjs +39 -0
  109. package/dist/{manifest-BB2J8IMJ.mjs → manifest-DJ2vfEuW.mjs} +48 -41
  110. package/dist/{manifest-3g71z6Bg.mjs → manifest-DPXDYhEu.mjs} +26 -25
  111. package/dist/manifest-Dd4fQb0a.mjs +322 -0
  112. package/dist/{manifest-CXsRWjjI.mjs → manifest-Deq6opGg.mjs} +95 -96
  113. package/dist/{manifest-C9RT5nk32.mjs → manifest-DfJTafJK.mjs} +14 -11
  114. package/dist/manifest-DgOdgN_j.mjs +50 -0
  115. package/dist/{manifest-BmtZzQiQ2.mjs → manifest-DlbMW4v4.mjs} +17 -15
  116. package/dist/{manifest-DrbmZcFl2.mjs → manifest-DmVfbH0w.mjs} +212 -91
  117. package/dist/manifest-Dog6Ddjr.mjs +109 -0
  118. package/dist/manifest-DvgU5FWb.mjs +58 -0
  119. package/dist/manifest-HsfDBs7j.mjs +50 -0
  120. package/dist/manifest-I8oQHvCG.mjs +186 -0
  121. package/dist/manifest-NvH_a-av.mjs +786 -0
  122. package/dist/{manifest-Dh8WBmEW.mjs → manifest-cEJU1v0Z.mjs} +24 -24
  123. package/dist/manifest-wOl5XLB12.mjs +112 -0
  124. package/dist/{modules-C184v-S9.mjs → modules-tZozf0LQ.mjs} +130 -860
  125. package/dist/{mojo-ipc-B_H61Afw.mjs → mojo-ipc-DXNEXEqb.mjs} +141 -26
  126. package/dist/{network-671Cw6hV.mjs → network-CPVvwvFg.mjs} +1329 -823
  127. package/dist/{outputPaths-B1uGmrWZ.mjs → outputPaths-um7lCRY3.mjs} +4 -8
  128. package/dist/{platform-WmNn8Sxb.mjs → platform-CYeFoTWp.mjs} +101 -10
  129. package/dist/{process-QcbIy5Zq.mjs → process-BTbgcVc6.mjs} +251 -346
  130. package/dist/{proxy-DqNs0bAd.mjs → proxy-r8YN6nP1.mjs} +30 -8
  131. package/dist/{registry-D-6e18lB.mjs → registry-Bl8ZQW61.mjs} +3 -3
  132. package/dist/{response-BQVP-xUn.mjs → response-CWhh2aLo.mjs} +7 -1
  133. package/dist/{shared-state-board-DV-dpHFJ.mjs → shared-state-board-BoZnSoj-.mjs} +2 -2
  134. package/dist/{sourcemap-Dq8ez8vS.mjs → sourcemap-BIDHUVXy.mjs} +350 -66
  135. package/dist/{streaming-BUQ0VJsg.mjs → streaming-Dal6utPp.mjs} +13 -13
  136. package/dist/{tool-builder-DCbIC5Eo.mjs → tool-builder-BHJp32mV.mjs} +1 -1
  137. package/dist/{transform-CiYJfNX0.mjs → transform-DRVgGG90.mjs} +18 -14
  138. package/dist/wasm-BYx5UOeG.mjs +1044 -0
  139. package/dist/webcrack-Be0_FccV.mjs +747 -0
  140. package/dist/{workflow-f3xJOcjx.mjs → workflow-BpuKEtvn.mjs} +8 -8
  141. package/package.json +76 -43
  142. package/dist/TraceRecorder-DgxyVbdQ.mjs +0 -519
  143. package/dist/analysis-CL9uACt9.mjs +0 -463
  144. package/dist/bind-helpers-xFfRF-qm.mjs +0 -22
  145. package/dist/definitions-6M-eejaT.mjs +0 -53
  146. package/dist/definitions-B3QdlrHv.mjs +0 -34
  147. package/dist/definitions-CXEI7QC72.mjs +0 -216
  148. package/dist/definitions-C_4r7Fo-2.mjs +0 -14
  149. package/dist/definitions-CkFDALoa.mjs +0 -26
  150. package/dist/definitions-Cy3Sl6gV.mjs +0 -34
  151. package/dist/definitions-LKpC3-nL.mjs +0 -9
  152. package/dist/handlers-DdFzXLvF.mjs +0 -446
  153. package/dist/manifest-82baTv4U.mjs +0 -45
  154. package/dist/manifest-BKbgbSiY.mjs +0 -60
  155. package/dist/manifest-Bcf-TJzH.mjs +0 -848
  156. package/dist/manifest-Bnd7kqEY.mjs +0 -55
  157. package/dist/manifest-BqQX6OQC2.mjs +0 -65
  158. package/dist/manifest-Br4RPFt5.mjs +0 -370
  159. package/dist/manifest-C5qDjysN.mjs +0 -107
  160. package/dist/manifest-CBYWCUBJ.mjs +0 -51
  161. package/dist/manifest-CFADCRa1.mjs +0 -37
  162. package/dist/manifest-CQVhavRF.mjs +0 -114
  163. package/dist/manifest-CV12bcrF.mjs +0 -121
  164. package/dist/manifest-CZLUCfG02.mjs +0 -95
  165. package/dist/manifest-D6phHKFd.mjs +0 -131
  166. package/dist/manifest-DHsnKgP6.mjs +0 -60
  167. package/dist/manifest-Df_dliIe.mjs +0 -55
  168. package/dist/manifest-DhKRAT8_.mjs +0 -92
  169. package/dist/manifest-DlpTj4ic2.mjs +0 -193
  170. package/dist/manifest-DuwHjUa5.mjs +0 -70
  171. package/dist/manifest-qSleDqdO.mjs +0 -1023
  172. package/dist/wasm-DQTnHDs4.mjs +0 -531
  173. /package/dist/{CacheAdapters-CDe5WPSV.mjs → CacheAdapters-jJFy20G-.mjs} +0 -0
  174. /package/dist/{DarwinAPI-BNPxu0RH.mjs → DarwinAPI-ETyy0xyo.mjs} +0 -0
  175. /package/dist/{EventBus-DgPmwpeu.mjs → EventBus-DFKvADm3.mjs} +0 -0
  176. /package/dist/{EvidenceGraphBridge-SFesNera.mjs → EvidenceGraphBridge-318Oi0Lf.mjs} +0 -0
  177. /package/dist/{FingerprintManager-gzWtkKuf.mjs → FingerprintManager-BN4UQWnX.mjs} +0 -0
  178. /package/dist/{PrerequisiteError-Dl33Svkz.mjs → PrerequisiteError-TuyZIs6n.mjs} +0 -0
  179. /package/dist/{ReverseEvidenceGraph-Dlsk94LC.mjs → ReverseEvidenceGraph-C02-gXOh.mjs} +0 -0
  180. /package/dist/{StealthVerifier-Bo4T3bz8.mjs → StealthVerifier-BWmPgQsv.mjs} +0 -0
  181. /package/dist/{VersionDetector-CwVLVdDM.mjs → VersionDetector-K3V4vGsw.mjs} +0 -0
  182. /package/dist/{betterSqlite3-0pqusHHH.mjs → betterSqlite3-DLSBZodi.mjs} +0 -0
  183. /package/dist/{concurrency-Bt0yv1kJ.mjs → concurrency-Drev_Vz9.mjs} +0 -0
  184. /package/dist/{formatAddress-DVkj9kpI.mjs → formatAddress-nnMvEohD.mjs} +0 -0
  185. /package/dist/{parse-args-BlRjqlkL.mjs → parse-args-B4cY5Vx5.mjs} +0 -0
  186. /package/dist/{ssrf-policy-ZaUfvhq7.mjs → ssrf-policy-Dsqd-DTX.mjs} +0 -0
  187. /package/dist/{types-CPhOReNX.mjs → types-DDBWs9UP.mjs} +0 -0
@@ -1,19 +1,20 @@
1
1
  import { t as logger } from "./logger-Dh_xb7_2.mjs";
2
- import { Gt as NETWORK_REPLAY_MAX_REDIRECTS, Wt as NETWORK_HAR_BODY_CONCURRENCY, dt as ICMP_DEFAULT_PACKET_SIZE, ft as ICMP_PROBE_TIMEOUT_MS, pt as ICMP_TRACEROUTE_MAX_HOPS } from "./constants-B0OANIBL.mjs";
3
- import { t as DetailedDataManager } from "./DetailedDataManager-BQQcxh64.mjs";
4
- import { a as PerformanceMonitor } from "./modules-C184v-S9.mjs";
5
- import { n as argEnum, t as argBool } from "./parse-args-BlRjqlkL.mjs";
6
- import { a as isLoopbackHost, c as isSsrfTarget, i as isLocalSsrfBypassEnabled, l as resolveNetworkTarget, n as hasAuthorizedTargets, o as isNetworkAuthorizationExpired, r as isAuthorizedNetworkTarget, s as isPrivateHost, t as createNetworkAuthorizationPolicy } from "./ssrf-policy-ZaUfvhq7.mjs";
7
- import { t as R } from "./ResponseBuilder-D3iFYx2N.mjs";
8
- import "./definitions-CXEI7QC72.mjs";
2
+ import { Gt as NETWORK_HAR_BODY_CONCURRENCY, Kt as NETWORK_REPLAY_MAX_REDIRECTS, ft as ICMP_DEFAULT_PACKET_SIZE, g as BOT_DETECT_LIMIT_DEFAULT, mt as ICMP_TRACEROUTE_MAX_HOPS, pt as ICMP_PROBE_TIMEOUT_MS } from "./constants-CDZLOoVv.mjs";
3
+ import { t as DetailedDataManager } from "./DetailedDataManager-HT49OrvF.mjs";
4
+ import { a as PerformanceMonitor } from "./modules-tZozf0LQ.mjs";
5
+ import { n as argEnum, t as argBool } from "./parse-args-B4cY5Vx5.mjs";
6
+ import { a as isLoopbackHost, c as isSsrfTarget, i as isLocalSsrfBypassEnabled, l as resolveNetworkTarget, n as hasAuthorizedTargets, o as isNetworkAuthorizationExpired, r as isAuthorizedNetworkTarget, s as isPrivateHost, t as createNetworkAuthorizationPolicy } from "./ssrf-policy-Dsqd-DTX.mjs";
7
+ import { t as R } from "./ResponseBuilder-CJXWmWNw.mjs";
8
+ import "./definitions-uzkjBwa7.mjs";
9
9
  import * as http$1 from "node:http";
10
+ import { createHash } from "node:crypto";
10
11
  import { promises } from "node:fs";
11
12
  import koffi from "koffi";
12
13
  import * as net from "node:net";
13
- import * as tls from "node:tls";
14
14
  import * as dns from "node:dns/promises";
15
- import * as https from "node:https";
15
+ import * as tls from "node:tls";
16
16
  import * as http2 from "node:http2";
17
+ import * as https from "node:https";
17
18
  //#region src/server/domains/network/handlers.base.types.ts
18
19
  /** Resource types excluded by default when no explicit filters are set. */
19
20
  const EXCLUDED_RESOURCE_TYPES = new Set([
@@ -564,9 +565,27 @@ var PerformanceHandlers = class {
564
565
  }
565
566
  async handlePerformanceTakeHeapSnapshot(_args) {
566
567
  try {
568
+ const traceRecorder = this.deps.getTraceRecorder?.() ?? null;
569
+ if (traceRecorder?.getState() === "recording") try {
570
+ const snapshotSize = await traceRecorder.captureActiveHeapSnapshot();
571
+ return R.ok().merge({
572
+ snapshotSize,
573
+ persistedToTrace: true,
574
+ message: "Heap snapshot taken and persisted to the active trace recording (data too large to return)"
575
+ }).json();
576
+ } catch (traceError) {
577
+ const snapshotSize = await this.deps.getPerformanceMonitor().takeHeapSnapshot();
578
+ return R.ok().merge({
579
+ snapshotSize,
580
+ persistedToTrace: false,
581
+ tracePersistenceError: traceError instanceof Error ? traceError.message : String(traceError),
582
+ message: "Heap snapshot taken, but the active trace recording could not persist it (data too large to return)"
583
+ }).json();
584
+ }
567
585
  const snapshotSize = await this.deps.getPerformanceMonitor().takeHeapSnapshot();
568
586
  return R.ok().merge({
569
587
  snapshotSize,
588
+ persistedToTrace: false,
570
589
  message: "Heap snapshot taken (data too large to return, saved internally)"
571
590
  }).json();
572
591
  } catch (error) {
@@ -582,7 +601,7 @@ var PerformanceHandlers = class {
582
601
  categories,
583
602
  screenshots
584
603
  });
585
- return R.ok().set("message", "Performance tracing started. Call performance_trace_stop to save the trace.").json();
604
+ return R.ok().set("message", "Performance tracing started. Call performance_trace with action=\"stop\" to save the trace.").json();
586
605
  } catch (error) {
587
606
  return R.fail(error).json();
588
607
  }
@@ -606,7 +625,7 @@ var PerformanceHandlers = class {
606
625
  async handleProfilerCpuStart(_args) {
607
626
  try {
608
627
  await this.deps.getPerformanceMonitor().startCPUProfiling();
609
- return R.ok().set("message", "CPU profiling started. Call profiler_cpu_stop to save the profile.").json();
628
+ return R.ok().set("message", "CPU profiling started. Call profiler_cpu with action=\"stop\" to save the profile.").json();
610
629
  } catch (error) {
611
630
  return R.fail(error).json();
612
631
  }
@@ -616,7 +635,7 @@ var PerformanceHandlers = class {
616
635
  const profileRaw = await this.deps.getPerformanceMonitor().stopCPUProfiling();
617
636
  const profile = toCpuProfilePayload(profileRaw) || profileRaw;
618
637
  const { writeFile } = await import("node:fs/promises");
619
- const { resolveArtifactPath } = await import("./artifacts-Bk2-_uPq.mjs").then((n) => n.t);
638
+ const { resolveArtifactPath } = await import("./artifacts-DkfosXH3.mjs").then((n) => n.t);
620
639
  const artifactPath = asOptionalString(args.artifactPath);
621
640
  const profileJson = JSON.stringify(profile, null, 2);
622
641
  let savedPath;
@@ -655,7 +674,7 @@ var PerformanceHandlers = class {
655
674
  const monitor = this.deps.getPerformanceMonitor();
656
675
  const samplingInterval = asOptionalNumber(args.samplingInterval);
657
676
  await monitor.startHeapSampling({ samplingInterval });
658
- return R.ok().set("message", "Heap sampling started. Call profiler_heap_sampling_stop to save the report.").json();
677
+ return R.ok().set("message", "Heap sampling started. Call profiler_heap_sampling with action=\"stop\" to save the report.").json();
659
678
  } catch (error) {
660
679
  return R.fail(error).json();
661
680
  }
@@ -1030,6 +1049,14 @@ const DANGEROUS_KEYS = new Set([
1030
1049
  "constructor",
1031
1050
  "prototype"
1032
1051
  ]);
1052
+ function buildCookieHeader(profile) {
1053
+ const parts = [];
1054
+ for (const cookie of profile.cookies) {
1055
+ if (!cookie.name) continue;
1056
+ parts.push(`${cookie.name}=${cookie.value}`);
1057
+ }
1058
+ return parts.length > 0 ? parts.join("; ") : void 0;
1059
+ }
1033
1060
  function sanitizeHeaders(headers) {
1034
1061
  const out = Object.create(null);
1035
1062
  for (const [k, v] of Object.entries(headers)) if (!STRIPPED_HEADERS.has(k.toLowerCase()) && !DANGEROUS_KEYS.has(k)) out[k] = v;
@@ -1042,6 +1069,11 @@ async function replayRequest(base, args, maxBodyBytes = 512e3) {
1042
1069
  ...base.headers,
1043
1070
  ...args.headerPatch
1044
1071
  });
1072
+ const cookieHeader = args.sessionProfile ? buildCookieHeader(args.sessionProfile) : void 0;
1073
+ if (cookieHeader) mergedHeaders.Cookie = cookieHeader;
1074
+ if (args.sessionProfile?.userAgent && !mergedHeaders["User-Agent"] && !mergedHeaders["user-agent"]) mergedHeaders["User-Agent"] = args.sessionProfile.userAgent;
1075
+ if (args.sessionProfile?.acceptLanguage && !mergedHeaders["Accept-Language"] && !mergedHeaders["accept-language"]) mergedHeaders["Accept-Language"] = args.sessionProfile.acceptLanguage;
1076
+ if (args.sessionProfile?.referer && !mergedHeaders["Referer"] && !mergedHeaders["referer"]) mergedHeaders.Referer = args.sessionProfile.referer;
1045
1077
  const body = args.bodyPatch !== void 0 ? args.bodyPatch : base.postData;
1046
1078
  const authorizationPolicy = createNetworkAuthorizationPolicy(args.authorization);
1047
1079
  const allowLegacyLocalSsrf = !authorizationPolicy && isLocalSsrfBypassEnabled();
@@ -1310,6 +1342,7 @@ var ReplayHandlers = class {
1310
1342
  const result = await replayRequest(base, {
1311
1343
  requestId,
1312
1344
  headerPatch: args.headerPatch,
1345
+ sessionProfile: args.sessionProfile,
1313
1346
  bodyPatch: args.bodyPatch,
1314
1347
  methodOverride: args.methodOverride,
1315
1348
  urlOverride: args.urlOverride,
@@ -2164,755 +2197,205 @@ function performHttp2ProbeInternal(options) {
2164
2197
  });
2165
2198
  }
2166
2199
  //#endregion
2167
- //#region src/native/IcmpProbe.ts
2168
- /**
2169
- * Cross-platform ICMP probe and traceroute via koffi FFI.
2170
- *
2171
- * Windows: IcmpSendEcho from iphlpapi.dll (no admin required).
2172
- * Linux/macOS: Raw ICMP sockets via libc (requires root/CAP_NET_RAW).
2173
- *
2174
- * Uses Buffer-based struct parsing (same pattern as Win32API.ts)
2175
- * to avoid koffi struct registration issues in test environments.
2176
- */
2177
- function ipToString(addr) {
2178
- return `${addr & 255}.${addr >>> 8 & 255}.${addr >>> 16 & 255}.${addr >>> 24 & 255}`;
2179
- }
2180
- function isValidIpv4(ip) {
2181
- const parts = ip.split(".");
2182
- if (parts.length !== 4) return false;
2183
- return parts.every((p) => {
2184
- const n = parseInt(p, 10);
2185
- return !isNaN(n) && n >= 0 && n <= 255 && p === String(n);
2186
- });
2187
- }
2188
- let _available = null;
2189
- const IP_STATUS = {
2190
- 0: "SUCCESS",
2191
- 11001: "BUF_TOO_SMALL",
2192
- 11002: "DEST_NET_UNREACHABLE",
2193
- 11003: "DEST_HOST_UNREACHABLE",
2194
- 11004: "DEST_PROT_UNREACHABLE",
2195
- 11005: "DEST_PORT_UNREACHABLE",
2196
- 11009: "PACKET_TOO_BIG",
2197
- 11010: "REQ_TIMED_OUT",
2198
- 11013: "TTL_EXPIRED_TRANSIT",
2199
- 11014: "TTL_EXPIRED_REASSEM",
2200
- 11015: "PARAM_PROBLEM",
2201
- 11016: "SOURCE_QUENCH",
2202
- 11050: "GENERAL_FAILURE"
2203
- };
2204
- function winStatusLabel(s) {
2205
- return IP_STATUS[s] ?? `UNKNOWN_${s}`;
2206
- }
2207
- function winStatusClass(s) {
2208
- if (s === 0) return "success";
2209
- if (s === 11010) return "timeout";
2210
- if (s === 11013 || s === 11014) return "time_exceeded";
2211
- if (s >= 11002 && s <= 11005) return "destination_unreachable";
2212
- if (s === 11016) return "source_quench";
2213
- if (s === 11009) return "packet_too_big";
2214
- if (s === 11015) return "parameter_problem";
2215
- return "error";
2216
- }
2217
- let iphlpapi = null;
2218
- let ws2_32 = null;
2219
- function getIphlpapi() {
2220
- if (!iphlpapi) {
2221
- iphlpapi = koffi.load("iphlpapi.dll");
2222
- logger.debug("Loaded iphlpapi.dll via koffi");
2223
- }
2224
- return iphlpapi;
2225
- }
2226
- function getWs2_32() {
2227
- if (!ws2_32) {
2228
- ws2_32 = koffi.load("ws2_32.dll");
2229
- logger.debug("Loaded ws2_32.dll via koffi");
2200
+ //#region src/server/domains/network/handlers/raw-dns-http-handlers.ts
2201
+ var RawDnsHttpHandlers = class {
2202
+ constructor(eventBus) {
2203
+ this.eventBus = eventBus;
2230
2204
  }
2231
- return ws2_32;
2232
- }
2233
- const IP_OPT_SIZE = 16;
2234
- const MIN_REPLY_BUF_SIZE = 256;
2235
- const ICMP_REPLY_OVERHEAD = 64;
2236
- function getReplyBufferSize(packetSize) {
2237
- return Math.max(MIN_REPLY_BUF_SIZE, packetSize + ICMP_REPLY_OVERHEAD);
2238
- }
2239
- function buildOptionBuf(ttl) {
2240
- const buf = Buffer.alloc(IP_OPT_SIZE, 0);
2241
- buf.writeUInt8(ttl, 0);
2242
- return buf;
2243
- }
2244
- function parseReply(buf) {
2245
- return {
2246
- address: buf.readUInt32LE(0),
2247
- status: buf.readUInt32LE(4),
2248
- rtt: buf.readUInt32LE(8)
2249
- };
2250
- }
2251
- function win_inet_addr(ip) {
2252
- return getWs2_32().func("uint32 inet_addr(char *)")(ip);
2253
- }
2254
- function win_IcmpCreateFile() {
2255
- return getIphlpapi().func("void * IcmpCreateFile()")();
2256
- }
2257
- function win_IcmpCloseHandle(h) {
2258
- return getIphlpapi().func("int IcmpCloseHandle(void *)")(h) !== 0;
2259
- }
2260
- function win_IcmpSendEcho(handle, destAddr, sendData, optionBuf, timeoutMs) {
2261
- const fn = getIphlpapi().func("uint32 IcmpSendEcho(void *, uint32, void *, uint16, void *, void *, uint32, uint32)");
2262
- const replyBuf = Buffer.alloc(getReplyBufferSize(sendData.length));
2263
- const n = fn(handle, destAddr, sendData, sendData.length, optionBuf, replyBuf, replyBuf.length, timeoutMs);
2264
- return {
2265
- numReplies: Number(n),
2266
- replyBuf
2267
- };
2268
- }
2269
- function winIcmpProbe(params) {
2270
- const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2271
- const destAddr = win_inet_addr(target);
2272
- if (destAddr === 4294967295) return {
2273
- target,
2274
- ip: "",
2275
- alive: false,
2276
- rtt: null,
2277
- ttl,
2278
- icmpStatus: "INVALID_ADDRESS",
2279
- errorClass: "error",
2280
- packetSize
2281
- };
2282
- const handle = win_IcmpCreateFile();
2283
- try {
2284
- const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2285
- if (numReplies === 0) return {
2286
- target,
2287
- ip: ipToString(destAddr),
2288
- alive: false,
2289
- rtt: null,
2290
- ttl,
2291
- icmpStatus: "REQ_TIMED_OUT",
2292
- errorClass: "timeout",
2293
- packetSize
2294
- };
2295
- const reply = parseReply(replyBuf);
2296
- return {
2297
- target,
2298
- ip: ipToString(reply.address),
2299
- alive: reply.status === 0,
2300
- rtt: reply.status === 0 ? reply.rtt : null,
2301
- ttl,
2302
- icmpStatus: winStatusLabel(reply.status),
2303
- errorClass: winStatusClass(reply.status),
2304
- packetSize
2305
- };
2306
- } finally {
2307
- win_IcmpCloseHandle(handle);
2205
+ async handleDnsResolve(args) {
2206
+ try {
2207
+ const hostname = parseOptionalString(args.hostname, "hostname");
2208
+ if (!hostname) return R.text("hostname is required", true);
2209
+ const rrType = parseOptionalString(args.rrType, "rrType") ?? "A";
2210
+ const validTypes = [
2211
+ "A",
2212
+ "AAAA",
2213
+ "MX",
2214
+ "TXT",
2215
+ "NS",
2216
+ "CNAME",
2217
+ "SOA",
2218
+ "PTR",
2219
+ "SRV",
2220
+ "ANY"
2221
+ ];
2222
+ if (!validTypes.includes(rrType)) return R.text(`Invalid rrType: "${rrType}". Expected one of: ${validTypes.join(", ")}`, true);
2223
+ const start = performance.now();
2224
+ const records = await dns.resolve(hostname, rrType);
2225
+ const timing = Math.round((performance.now() - start) * 100) / 100;
2226
+ return R.ok().json({
2227
+ hostname,
2228
+ rrType,
2229
+ records,
2230
+ timing
2231
+ });
2232
+ } catch (err) {
2233
+ const message = err instanceof Error ? err.message : String(err);
2234
+ return R.fail(`DNS resolve failed: ${message}`).json();
2235
+ }
2308
2236
  }
2309
- }
2310
- function winTraceroute(params) {
2311
- const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2312
- const destAddr = win_inet_addr(target);
2313
- if (destAddr === 4294967295) return {
2314
- target,
2315
- ip: "",
2316
- hops: [],
2317
- reached: false,
2318
- totalHops: 0,
2319
- totalTime: 0
2320
- };
2321
- const handle = win_IcmpCreateFile();
2322
- const hops = [];
2323
- const t0 = performance.now();
2324
- try {
2325
- for (let ttl = 1; ttl <= maxHops; ttl++) {
2326
- const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2327
- if (numReplies === 0) {
2328
- hops.push({
2329
- hop: ttl,
2330
- ip: null,
2331
- rtt: null,
2332
- status: "REQ_TIMED_OUT",
2333
- errorClass: "timeout"
2334
- });
2335
- continue;
2336
- }
2337
- const reply = parseReply(replyBuf);
2338
- const hopIp = ipToString(reply.address);
2339
- hops.push({
2340
- hop: ttl,
2341
- ip: hopIp,
2342
- rtt: reply.rtt,
2343
- status: winStatusLabel(reply.status),
2344
- errorClass: winStatusClass(reply.status)
2237
+ async handleDnsReverse(args) {
2238
+ try {
2239
+ const ip = parseOptionalString(args.ip, "ip");
2240
+ if (!ip) return R.text("ip is required", true);
2241
+ const start = performance.now();
2242
+ const hostnames = await dns.reverse(ip);
2243
+ const timing = Math.round((performance.now() - start) * 100) / 100;
2244
+ return R.ok().json({
2245
+ ip,
2246
+ hostnames,
2247
+ timing
2345
2248
  });
2346
- if (reply.status === 0) break;
2249
+ } catch (err) {
2250
+ const message = err instanceof Error ? err.message : String(err);
2251
+ return R.fail(`DNS reverse lookup failed: ${message}`).json();
2347
2252
  }
2348
- } finally {
2349
- win_IcmpCloseHandle(handle);
2350
2253
  }
2351
- const last = hops[hops.length - 1];
2352
- return {
2353
- target,
2354
- ip: ipToString(destAddr),
2355
- hops,
2356
- reached: last?.status === "SUCCESS",
2357
- totalHops: hops.length,
2358
- totalTime: Math.round((performance.now() - t0) * 100) / 100
2359
- };
2360
- }
2361
- const AF_INET = 2;
2362
- const SOCK_RAW = 3;
2363
- const IPPROTO_ICMP = 1;
2364
- const IPPROTO_IP = 0;
2365
- const IP_TTL = 2;
2366
- const SOL_SOCKET = 1;
2367
- const SO_RCVTIMEO = process.platform === "darwin" ? 4102 : 20;
2368
- const POSIX_LIB = process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6";
2369
- let posixLib = null;
2370
- function getPosixLib() {
2371
- if (!posixLib) {
2372
- posixLib = koffi.load(POSIX_LIB);
2373
- logger.debug(`Loaded ${POSIX_LIB} via koffi for ICMP`);
2254
+ async handleHttpRequestBuild(args) {
2255
+ try {
2256
+ const method = parseOptionalString(args.method, "method");
2257
+ const target = parseOptionalString(args.target, "target");
2258
+ if (!method) throw new Error("method is required");
2259
+ if (!target) throw new Error("target is required");
2260
+ const built = buildHttpRequest({
2261
+ method,
2262
+ target,
2263
+ host: parseOptionalString(args.host, "host"),
2264
+ headers: parseHeaderRecord(args.headers, "headers"),
2265
+ body: parseRawString(args.body, "body", { allowEmpty: true }),
2266
+ httpVersion: parseOptionalString(args.httpVersion, "httpVersion") ?? "1.1",
2267
+ addHostHeader: parseBooleanArg(args.addHostHeader, true),
2268
+ addContentLength: parseBooleanArg(args.addContentLength, true),
2269
+ addConnectionClose: parseBooleanArg(args.addConnectionClose, true)
2270
+ });
2271
+ emitEvent(this.eventBus, "network:http_request_built", {
2272
+ method: built.startLine.split(" ", 1)[0] ?? "UNKNOWN",
2273
+ target,
2274
+ byteLength: built.requestBytes,
2275
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2276
+ });
2277
+ return R.ok().merge(built).json();
2278
+ } catch (error) {
2279
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
2280
+ }
2374
2281
  }
2375
- return posixLib;
2376
- }
2377
- function posixSocket(domain, type, protocol) {
2378
- return getPosixLib().func("int socket(int, int, int)")(domain, type, protocol);
2379
- }
2380
- function posixSetsockopt(fd, level, optname, optval, optlen) {
2381
- return getPosixLib().func("int setsockopt(int, int, int, void *, int)")(fd, level, optname, optval, optlen);
2382
- }
2383
- function posixSendto(fd, buf, addr) {
2384
- return getPosixLib().func("int sendto(int, void *, int, int, void *, int)")(fd, buf, buf.length, 0, addr, 16);
2385
- }
2386
- function posixRecv(fd, buf) {
2387
- return getPosixLib().func("int recv(int, void *, int, int)")(fd, buf, buf.length, 0);
2388
- }
2389
- function posixClose(fd) {
2390
- return getPosixLib().func("int close(int)")(fd);
2391
- }
2392
- function computeChecksum(buf) {
2393
- let sum = 0;
2394
- for (let i = 0; i < buf.length - 1; i += 2) sum += buf.readUInt16BE(i);
2395
- if (buf.length & 1) sum += (buf[buf.length - 1] ?? 0) << 8;
2396
- while (sum > 65535) sum = (sum & 65535) + (sum >>> 16);
2397
- return ~sum & 65535;
2398
- }
2399
- function buildIcmpEcho(id, seq, payloadSize) {
2400
- const buf = Buffer.alloc(8 + payloadSize);
2401
- buf[0] = 8;
2402
- buf[1] = 0;
2403
- buf.writeUInt16BE(id & 65535, 4);
2404
- buf.writeUInt16BE(seq & 65535, 6);
2405
- for (let i = 8; i < buf.length; i++) buf[i] = 170;
2406
- buf.writeUInt16BE(computeChecksum(buf), 2);
2407
- return buf;
2408
- }
2409
- function buildSockaddrIn(ip) {
2410
- const buf = Buffer.alloc(16, 0);
2411
- buf.writeUInt16LE(AF_INET, 0);
2412
- const parts = ip.split(".").map(Number);
2413
- buf[4] = parts[0] ?? 0;
2414
- buf[5] = parts[1] ?? 0;
2415
- buf[6] = parts[2] ?? 0;
2416
- buf[7] = parts[3] ?? 0;
2417
- return buf;
2418
- }
2419
- function parseIcmpPacket(buf, n, expectedId) {
2420
- if (n < 20) return null;
2421
- const ihl = ((buf[0] ?? 0) & 15) * 4;
2422
- if (n < ihl + 8) return null;
2423
- const icmpType = buf[ihl] ?? 0;
2424
- const icmpCode = buf[ihl + 1] ?? 0;
2425
- const fromIp = buf.readUInt32LE(12);
2426
- if (icmpType === 0) {
2427
- if (buf.readUInt16BE(ihl + 4) !== expectedId) return null;
2428
- return {
2429
- type: icmpType,
2430
- code: icmpCode,
2431
- fromIp
2432
- };
2282
+ async handleHttpPlainRequest(args) {
2283
+ try {
2284
+ const hostArg = parseOptionalString(args.host, "host");
2285
+ const requestText = parseRawString(args.requestText, "requestText");
2286
+ if (!hostArg) throw new Error("host is required");
2287
+ if (!requestText) throw new Error("requestText is required");
2288
+ const host = normalizeTargetHost(hostArg);
2289
+ const port = parseNumberArg(args.port, {
2290
+ defaultValue: 80,
2291
+ min: 1,
2292
+ max: 65535,
2293
+ integer: true
2294
+ });
2295
+ const timeoutMs = parseNumberArg(args.timeoutMs, {
2296
+ defaultValue: 3e4,
2297
+ min: 1,
2298
+ max: 12e4,
2299
+ integer: true
2300
+ });
2301
+ const maxResponseBytes = parseNumberArg(args.maxResponseBytes, {
2302
+ defaultValue: 512e3,
2303
+ min: 256,
2304
+ max: 5242880,
2305
+ integer: true
2306
+ });
2307
+ const requestMethod = getRequestMethod(requestText);
2308
+ const authorization = parseNetworkAuthorization(args.authorization);
2309
+ const { target } = await resolveAuthorizedTransportTarget(`http://${formatHostForUrl(host)}:${String(port)}/`, authorization, "HTTP request");
2310
+ if (authorization) {
2311
+ const requestTarget = (requestText.split(/\r?\n/, 1)[0] ?? "").split(/\s+/)[1] ?? "";
2312
+ if (requestTarget.includes("://")) try {
2313
+ const targetHost = normalizeTargetHost(new URL(requestTarget).hostname);
2314
+ if (targetHost !== host && targetHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: request-line target host "${targetHost}" does not match authorized host "${host}"`);
2315
+ } catch (e) {
2316
+ if (e instanceof Error && e.message.startsWith("HTTP request blocked:")) throw e;
2317
+ }
2318
+ const hostHeaderValue = requestText.match(/^Host:\s*(\S+)/im)?.[1];
2319
+ if (hostHeaderValue) {
2320
+ const declaredHost = normalizeTargetHost(hostHeaderValue.replace(/:\d+$/, ""));
2321
+ if (declaredHost !== host && declaredHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: Host header "${hostHeaderValue}" does not match authorized host "${host}"`);
2322
+ }
2323
+ }
2324
+ const exchange = await exchangePlainHttp(target.resolvedAddress ?? target.hostname, port, Buffer.from(requestText, "utf8"), requestMethod, timeoutMs, maxResponseBytes);
2325
+ const analysis = analyzeHttpResponse(exchange.rawResponse, requestMethod);
2326
+ if (!analysis) throw new Error("Received data but could not parse complete HTTP response headers.");
2327
+ const bodyIsText = isLikelyTextHttpBody(analysis.rawHeaders.find((header) => header.name.toLowerCase() === "content-type")?.value ?? null, analysis.bodyBuffer);
2328
+ const complete = analysis.complete || analysis.bodyMode === "until-close" && exchange.endedBy === "socket-close";
2329
+ emitEvent(this.eventBus, "network:http_plain_request_completed", {
2330
+ host,
2331
+ port,
2332
+ statusCode: analysis.statusCode,
2333
+ byteLength: exchange.rawResponse.length,
2334
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2335
+ });
2336
+ return R.ok().merge({
2337
+ host,
2338
+ port,
2339
+ resolvedAddress: target.resolvedAddress ?? target.hostname,
2340
+ requestBytes: Buffer.byteLength(requestText, "utf8"),
2341
+ response: {
2342
+ statusLine: analysis.statusLine,
2343
+ httpVersion: analysis.httpVersion,
2344
+ statusCode: analysis.statusCode,
2345
+ statusText: analysis.statusText,
2346
+ headers: analysis.headers,
2347
+ rawHeaders: analysis.rawHeaders,
2348
+ headerBytes: analysis.headerBytes,
2349
+ bodyBytes: analysis.bodyBytes,
2350
+ bodyMode: analysis.bodyMode,
2351
+ chunkedDecoded: analysis.chunkedDecoded,
2352
+ complete,
2353
+ truncated: exchange.endedBy === "max-bytes",
2354
+ endedBy: exchange.endedBy,
2355
+ bodyText: bodyIsText ? analysis.bodyBuffer.toString("utf8") : void 0,
2356
+ bodyBase64: bodyIsText ? void 0 : analysis.bodyBuffer.toString("base64")
2357
+ }
2358
+ }).json();
2359
+ } catch (error) {
2360
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
2361
+ }
2433
2362
  }
2434
- if (icmpType === 11 || icmpType === 3) {
2435
- const origStart = ihl + 8;
2436
- if (n < origStart + 28) return null;
2437
- const origIhl = ((buf[origStart] ?? 0) & 15) * 4;
2438
- if (n < origStart + origIhl + 8) return null;
2439
- if (buf.readUInt16BE(origStart + origIhl + 4) !== expectedId) return null;
2440
- return {
2441
- type: icmpType,
2442
- code: icmpCode,
2443
- fromIp
2444
- };
2363
+ };
2364
+ //#endregion
2365
+ //#region src/server/domains/network/handlers/raw-http2-handlers.ts
2366
+ var RawHttp2Handlers = class {
2367
+ constructor(eventBus) {
2368
+ this.eventBus = eventBus;
2445
2369
  }
2446
- return null;
2447
- }
2448
- function posixStatusLabel(type, code, timedOut) {
2449
- if (type === 0) return "SUCCESS";
2450
- if (timedOut) return "REQ_TIMED_OUT";
2451
- if (type === 11 && code === 0) return "TTL_EXPIRED_TRANSIT";
2452
- if (type === 11 && code === 1) return "TTL_EXPIRED_REASSEM";
2453
- if (type === 3 && code === 0) return "DEST_NET_UNREACHABLE";
2454
- if (type === 3 && code === 1) return "DEST_HOST_UNREACHABLE";
2455
- if (type === 3 && code === 2) return "DEST_PROT_UNREACHABLE";
2456
- if (type === 3 && code === 3) return "DEST_PORT_UNREACHABLE";
2457
- return `UNKNOWN_${type}_${code}`;
2458
- }
2459
- function posixErrorClass(type, _code, timedOut) {
2460
- if (type === 0) return "success";
2461
- if (timedOut) return "timeout";
2462
- if (type === 11) return "time_exceeded";
2463
- if (type === 3) return "destination_unreachable";
2464
- return "error";
2465
- }
2466
- function posixSetTtl(fd, ttl) {
2467
- const buf = Buffer.alloc(4);
2468
- buf.writeInt32LE(ttl);
2469
- posixSetsockopt(fd, IPPROTO_IP, IP_TTL, buf, 4);
2470
- }
2471
- function posixSetRecvTimeout(fd, timeoutMs) {
2472
- const tv = Buffer.alloc(16, 0);
2473
- tv.writeInt32LE(Math.floor(timeoutMs / 1e3), 0);
2474
- tv.writeInt32LE(timeoutMs % 1e3 * 1e3, 8);
2475
- posixSetsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv, 16);
2476
- }
2477
- function posixIcmpProbe(params) {
2478
- const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2479
- if (!isValidIpv4(target)) return {
2480
- target,
2481
- ip: "",
2482
- alive: false,
2483
- rtt: null,
2484
- ttl,
2485
- icmpStatus: "INVALID_ADDRESS",
2486
- errorClass: "error",
2487
- packetSize
2488
- };
2489
- const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2490
- if (fd < 0) return {
2491
- target,
2492
- ip: "",
2493
- alive: false,
2494
- rtt: null,
2495
- ttl,
2496
- icmpStatus: "SOCKET_ERROR",
2497
- errorClass: "error",
2498
- packetSize
2499
- };
2500
- try {
2501
- posixSetTtl(fd, ttl);
2502
- posixSetRecvTimeout(fd, timeout);
2503
- const id = process.pid & 65535;
2504
- const packet = buildIcmpEcho(id, 1, packetSize);
2505
- const destAddr = buildSockaddrIn(target);
2506
- const t0 = performance.now();
2507
- if (posixSendto(fd, packet, destAddr) < 0) return {
2508
- target,
2509
- ip: target,
2510
- alive: false,
2511
- rtt: null,
2512
- ttl,
2513
- icmpStatus: "SEND_ERROR",
2514
- errorClass: "error",
2515
- packetSize
2516
- };
2517
- const recvBuf = Buffer.alloc(512);
2518
- const n = posixRecv(fd, recvBuf);
2519
- const rtt = Math.round(performance.now() - t0);
2520
- if (n <= 0) return {
2521
- target,
2522
- ip: target,
2523
- alive: false,
2524
- rtt: null,
2525
- ttl,
2526
- icmpStatus: "REQ_TIMED_OUT",
2527
- errorClass: "timeout",
2528
- packetSize
2529
- };
2530
- const reply = parseIcmpPacket(recvBuf, n, id);
2531
- if (!reply) return {
2532
- target,
2533
- ip: target,
2534
- alive: false,
2535
- rtt: null,
2536
- ttl,
2537
- icmpStatus: "UNEXPECTED_REPLY",
2538
- errorClass: "error",
2539
- packetSize
2540
- };
2541
- const alive = reply.type === 0;
2542
- return {
2543
- target,
2544
- ip: ipToString(reply.fromIp),
2545
- alive,
2546
- rtt: alive ? rtt : null,
2547
- ttl,
2548
- icmpStatus: posixStatusLabel(reply.type, reply.code, false),
2549
- errorClass: posixErrorClass(reply.type, reply.code, false),
2550
- packetSize
2551
- };
2552
- } finally {
2553
- posixClose(fd);
2554
- }
2555
- }
2556
- function posixTraceroute(params) {
2557
- const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2558
- if (!isValidIpv4(target)) return {
2559
- target,
2560
- ip: "",
2561
- hops: [],
2562
- reached: false,
2563
- totalHops: 0,
2564
- totalTime: 0
2565
- };
2566
- const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2567
- if (fd < 0) return {
2568
- target,
2569
- ip: "",
2570
- hops: [],
2571
- reached: false,
2572
- totalHops: 0,
2573
- totalTime: 0
2574
- };
2575
- const hops = [];
2576
- const id = process.pid & 65535;
2577
- const destAddr = buildSockaddrIn(target);
2578
- const t0 = performance.now();
2579
- const MAX_CONSECUTIVE_SEND_ERRORS = 5;
2580
- let consecutiveSendErrors = 0;
2581
- try {
2582
- posixSetRecvTimeout(fd, timeout);
2583
- for (let ttl = 1; ttl <= maxHops; ttl++) {
2584
- posixSetTtl(fd, ttl);
2585
- const packet = buildIcmpEcho(id, ttl, packetSize);
2586
- const sendT0 = performance.now();
2587
- if (posixSendto(fd, packet, destAddr) < 0) {
2588
- consecutiveSendErrors++;
2589
- hops.push({
2590
- hop: ttl,
2591
- ip: null,
2592
- rtt: null,
2593
- status: "SEND_ERROR",
2594
- errorClass: "error"
2595
- });
2596
- if (consecutiveSendErrors >= MAX_CONSECUTIVE_SEND_ERRORS) break;
2597
- continue;
2598
- }
2599
- consecutiveSendErrors = 0;
2600
- const recvBuf = Buffer.alloc(512);
2601
- const n = posixRecv(fd, recvBuf);
2602
- const rtt = Math.round(performance.now() - sendT0);
2603
- if (n <= 0) {
2604
- hops.push({
2605
- hop: ttl,
2606
- ip: null,
2607
- rtt: null,
2608
- status: "REQ_TIMED_OUT",
2609
- errorClass: "timeout"
2610
- });
2611
- continue;
2612
- }
2613
- const reply = parseIcmpPacket(recvBuf, n, id);
2614
- if (!reply) {
2615
- hops.push({
2616
- hop: ttl,
2617
- ip: null,
2618
- rtt: null,
2619
- status: "UNEXPECTED_REPLY",
2620
- errorClass: "error"
2621
- });
2622
- continue;
2623
- }
2624
- const status = posixStatusLabel(reply.type, reply.code, false);
2625
- const errorCls = posixErrorClass(reply.type, reply.code, false);
2626
- hops.push({
2627
- hop: ttl,
2628
- ip: ipToString(reply.fromIp),
2629
- rtt,
2630
- status,
2631
- errorClass: errorCls
2632
- });
2633
- if (reply.type === 0) break;
2634
- }
2635
- } finally {
2636
- posixClose(fd);
2637
- }
2638
- return {
2639
- target,
2640
- ip: target,
2641
- hops,
2642
- reached: hops[hops.length - 1]?.status === "SUCCESS",
2643
- totalHops: hops.length,
2644
- totalTime: Math.round((performance.now() - t0) * 100) / 100
2645
- };
2646
- }
2647
- const isPosix = process.platform === "linux" || process.platform === "darwin";
2648
- function isIcmpAvailable() {
2649
- if (_available !== null) return _available;
2650
- if (process.platform === "win32") try {
2651
- koffi.load("iphlpapi.dll").unload();
2652
- _available = true;
2653
- return true;
2654
- } catch {
2655
- _available = false;
2656
- return false;
2657
- }
2658
- if (isPosix) {
2659
- try {
2660
- const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2661
- if (fd >= 0) {
2662
- posixClose(fd);
2663
- _available = true;
2664
- } else _available = false;
2665
- } catch {
2666
- _available = false;
2667
- }
2668
- return _available;
2669
- }
2670
- _available = false;
2671
- return false;
2672
- }
2673
- function icmpProbe(params) {
2674
- const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2675
- if (!isIcmpAvailable()) return {
2676
- target,
2677
- ip: "",
2678
- alive: false,
2679
- rtt: null,
2680
- ttl,
2681
- icmpStatus: "PLATFORM_NOT_SUPPORTED",
2682
- errorClass: "error",
2683
- packetSize
2684
- };
2685
- if (process.platform === "win32") return winIcmpProbe({
2686
- target,
2687
- ttl,
2688
- packetSize,
2689
- timeout
2690
- });
2691
- return posixIcmpProbe({
2692
- target,
2693
- ttl,
2694
- packetSize,
2695
- timeout
2696
- });
2697
- }
2698
- function traceroute(params) {
2699
- const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2700
- if (!isIcmpAvailable()) return {
2701
- target,
2702
- ip: "",
2703
- hops: [],
2704
- reached: false,
2705
- totalHops: 0,
2706
- totalTime: 0
2707
- };
2708
- if (process.platform === "win32") return winTraceroute({
2709
- target,
2710
- maxHops,
2711
- timeout,
2712
- packetSize
2713
- });
2714
- return posixTraceroute({
2715
- target,
2716
- maxHops,
2717
- timeout,
2718
- packetSize
2719
- });
2720
- }
2721
- //#endregion
2722
- //#region src/server/domains/network/handlers/raw-handlers.ts
2723
- /**
2724
- * Raw HTTP/HTTP2/DNS/RTT handlers — standalone class using composition.
2725
- *
2726
- * Extracted from AdvancedToolHandlersRaw (handlers.impl.core.runtime.raw.ts).
2727
- * Uses helpers from ./raw-helpers.ts and ./shared.ts instead of inheritance.
2728
- */
2729
- var RawHandlers = class {
2730
- constructor(eventBus) {
2731
- this.eventBus = eventBus;
2732
- }
2733
- async handleDnsResolve(args) {
2370
+ async handleHttp2Probe(args) {
2371
+ const rawUrl = parseOptionalString(args.url, "url");
2372
+ let eventUrl = rawUrl ?? "";
2373
+ let eventStatusCode = null;
2374
+ let eventAlpnProtocol = null;
2375
+ let eventSuccess = false;
2734
2376
  try {
2735
- const hostname = parseOptionalString(args.hostname, "hostname");
2736
- if (!hostname) return R.text("hostname is required", true);
2737
- const rrType = parseOptionalString(args.rrType, "rrType") ?? "A";
2738
- const validTypes = [
2739
- "A",
2740
- "AAAA",
2741
- "MX",
2742
- "TXT",
2743
- "NS",
2744
- "CNAME",
2745
- "SOA",
2746
- "PTR",
2747
- "SRV",
2748
- "ANY"
2749
- ];
2750
- if (!validTypes.includes(rrType)) return R.text(`Invalid rrType: "${rrType}". Expected one of: ${validTypes.join(", ")}`, true);
2751
- const start = performance.now();
2752
- const records = await dns.resolve(hostname, rrType);
2753
- const timing = roundMs(performance.now() - start);
2754
- return R.ok().json({
2755
- hostname,
2756
- rrType,
2757
- records,
2758
- timing
2377
+ if (!rawUrl) throw new Error("url is required");
2378
+ const method = (parseOptionalString(args.method, "method") ?? "GET").toUpperCase();
2379
+ if (!HTTP_TOKEN_RE.test(method)) throw new Error("method must be a valid HTTP token");
2380
+ const timeoutMs = parseNumberArg(args.timeoutMs, {
2381
+ defaultValue: 3e4,
2382
+ min: 1,
2383
+ max: 12e4,
2384
+ integer: true
2759
2385
  });
2760
- } catch (err) {
2761
- const message = err instanceof Error ? err.message : String(err);
2762
- return R.fail(`DNS resolve failed: ${message}`).json();
2763
- }
2764
- }
2765
- async handleDnsReverse(args) {
2766
- try {
2767
- const ip = parseOptionalString(args.ip, "ip");
2768
- if (!ip) return R.text("ip is required", true);
2769
- const start = performance.now();
2770
- const hostnames = await dns.reverse(ip);
2771
- const timing = roundMs(performance.now() - start);
2772
- return R.ok().json({
2773
- ip,
2774
- hostnames,
2775
- timing
2386
+ const maxBodyBytes = parseNumberArg(args.maxBodyBytes, {
2387
+ defaultValue: 32768,
2388
+ min: 128,
2389
+ max: 1048576,
2390
+ integer: true
2776
2391
  });
2777
- } catch (err) {
2778
- const message = err instanceof Error ? err.message : String(err);
2779
- return R.fail(`DNS reverse lookup failed: ${message}`).json();
2780
- }
2781
- }
2782
- async handleHttpRequestBuild(args) {
2783
- try {
2784
- const method = parseOptionalString(args.method, "method");
2785
- const target = parseOptionalString(args.target, "target");
2786
- if (!method) throw new Error("method is required");
2787
- if (!target) throw new Error("target is required");
2788
- const built = buildHttpRequest({
2789
- method,
2790
- target,
2791
- host: parseOptionalString(args.host, "host"),
2792
- headers: parseHeaderRecord(args.headers, "headers"),
2793
- body: parseRawString(args.body, "body", { allowEmpty: true }),
2794
- httpVersion: parseOptionalString(args.httpVersion, "httpVersion") ?? "1.1",
2795
- addHostHeader: parseBooleanArg(args.addHostHeader, true),
2796
- addContentLength: parseBooleanArg(args.addContentLength, true),
2797
- addConnectionClose: parseBooleanArg(args.addConnectionClose, true)
2798
- });
2799
- emitEvent(this.eventBus, "network:http_request_built", {
2800
- method: built.startLine.split(" ", 1)[0] ?? "UNKNOWN",
2801
- target,
2802
- byteLength: built.requestBytes,
2803
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2804
- });
2805
- return R.ok().merge(built).json();
2806
- } catch (error) {
2807
- return R.fail(error instanceof Error ? error.message : String(error)).json();
2808
- }
2809
- }
2810
- async handleHttpPlainRequest(args) {
2811
- try {
2812
- const hostArg = parseOptionalString(args.host, "host");
2813
- const requestText = parseRawString(args.requestText, "requestText");
2814
- if (!hostArg) throw new Error("host is required");
2815
- if (!requestText) throw new Error("requestText is required");
2816
- const host = normalizeTargetHost(hostArg);
2817
- const port = parseNumberArg(args.port, {
2818
- defaultValue: 80,
2819
- min: 1,
2820
- max: 65535,
2821
- integer: true
2822
- });
2823
- const timeoutMs = parseNumberArg(args.timeoutMs, {
2824
- defaultValue: 3e4,
2825
- min: 1,
2826
- max: 12e4,
2827
- integer: true
2828
- });
2829
- const maxResponseBytes = parseNumberArg(args.maxResponseBytes, {
2830
- defaultValue: 512e3,
2831
- min: 256,
2832
- max: 5242880,
2833
- integer: true
2834
- });
2835
- const requestMethod = getRequestMethod(requestText);
2836
- const authorization = parseNetworkAuthorization(args.authorization);
2837
- const { target } = await resolveAuthorizedTransportTarget(`http://${formatHostForUrl(host)}:${String(port)}/`, authorization, "HTTP request");
2838
- if (authorization) {
2839
- const requestTarget = (requestText.split(/\r?\n/, 1)[0] ?? "").split(/\s+/)[1] ?? "";
2840
- if (requestTarget.includes("://")) try {
2841
- const targetHost = normalizeTargetHost(new URL(requestTarget).hostname);
2842
- if (targetHost !== host && targetHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: request-line target host "${targetHost}" does not match authorized host "${host}"`);
2843
- } catch (e) {
2844
- if (e instanceof Error && e.message.startsWith("HTTP request blocked:")) throw e;
2845
- }
2846
- const hostHeaderValue = requestText.match(/^Host:\s*(\S+)/im)?.[1];
2847
- if (hostHeaderValue) {
2848
- const declaredHost = normalizeTargetHost(hostHeaderValue.replace(/:\d+$/, ""));
2849
- if (declaredHost !== host && declaredHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: Host header "${hostHeaderValue}" does not match authorized host "${host}"`);
2850
- }
2851
- }
2852
- const exchange = await exchangePlainHttp(target.resolvedAddress ?? target.hostname, port, Buffer.from(requestText, "utf8"), requestMethod, timeoutMs, maxResponseBytes);
2853
- const analysis = analyzeHttpResponse(exchange.rawResponse, requestMethod);
2854
- if (!analysis) throw new Error("Received data but could not parse complete HTTP response headers.");
2855
- const bodyIsText = isLikelyTextHttpBody(analysis.rawHeaders.find((h) => h.name.toLowerCase() === "content-type")?.value ?? null, analysis.bodyBuffer);
2856
- const complete = analysis.complete || analysis.bodyMode === "until-close" && exchange.endedBy === "socket-close";
2857
- emitEvent(this.eventBus, "network:http_plain_request_completed", {
2858
- host,
2859
- port,
2860
- statusCode: analysis.statusCode,
2861
- byteLength: exchange.rawResponse.length,
2862
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2863
- });
2864
- return R.ok().merge({
2865
- host,
2866
- port,
2867
- resolvedAddress: target.resolvedAddress ?? target.hostname,
2868
- requestBytes: Buffer.byteLength(requestText, "utf8"),
2869
- response: {
2870
- statusLine: analysis.statusLine,
2871
- httpVersion: analysis.httpVersion,
2872
- statusCode: analysis.statusCode,
2873
- statusText: analysis.statusText,
2874
- headers: analysis.headers,
2875
- rawHeaders: analysis.rawHeaders,
2876
- headerBytes: analysis.headerBytes,
2877
- bodyBytes: analysis.bodyBytes,
2878
- bodyMode: analysis.bodyMode,
2879
- chunkedDecoded: analysis.chunkedDecoded,
2880
- complete,
2881
- truncated: exchange.endedBy === "max-bytes",
2882
- endedBy: exchange.endedBy,
2883
- bodyText: bodyIsText ? analysis.bodyBuffer.toString("utf8") : void 0,
2884
- bodyBase64: bodyIsText ? void 0 : analysis.bodyBuffer.toString("base64")
2885
- }
2886
- }).json();
2887
- } catch (error) {
2888
- return R.fail(error instanceof Error ? error.message : String(error)).json();
2889
- }
2890
- }
2891
- async handleHttp2Probe(args) {
2892
- const rawUrl = parseOptionalString(args.url, "url");
2893
- let eventUrl = rawUrl ?? "";
2894
- let eventStatusCode = null;
2895
- let eventAlpnProtocol = null;
2896
- let eventSuccess = false;
2897
- try {
2898
- if (!rawUrl) throw new Error("url is required");
2899
- const method = (parseOptionalString(args.method, "method") ?? "GET").toUpperCase();
2900
- if (!HTTP_TOKEN_RE.test(method)) throw new Error("method must be a valid HTTP token");
2901
- const timeoutMs = parseNumberArg(args.timeoutMs, {
2902
- defaultValue: 3e4,
2903
- min: 1,
2904
- max: 12e4,
2905
- integer: true
2906
- });
2907
- const maxBodyBytes = parseNumberArg(args.maxBodyBytes, {
2908
- defaultValue: 32768,
2909
- min: 128,
2910
- max: 1048576,
2911
- integer: true
2912
- });
2913
- const bodyBuffer = Buffer.from(parseRawString(args.body, "body", { allowEmpty: true }) ?? "", "utf8");
2392
+ const rawBody = args.body;
2393
+ const bodyIsObject = rawBody !== null && rawBody !== void 0 && typeof rawBody !== "string";
2394
+ const bodyString = typeof rawBody === "string" ? rawBody : bodyIsObject ? JSON.stringify(rawBody) : "";
2395
+ const bodyBuffer = Buffer.from(bodyString, "utf8");
2914
2396
  const alpnProtocols = parseStringArray(args.alpnProtocols, "alpnProtocols");
2915
2397
  const requestHeaders = toHttp2RequestHeaders(parseHeaderRecord(args.headers, "headers"));
2398
+ if (bodyIsObject && bodyBuffer.length > 0 && !("content-type" in requestHeaders)) requestHeaders["content-type"] = "application/json";
2916
2399
  const { url, target } = await resolveAuthorizedTransportTarget(rawUrl, parseNetworkAuthorization(args.authorization), "HTTP/2 probe");
2917
2400
  eventUrl = url.toString();
2918
2401
  if (!("content-length" in requestHeaders) && bodyBuffer.length > 0) requestHeaders["content-length"] = String(bodyBuffer.length);
@@ -3024,32 +2507,594 @@ var RawHandlers = class {
3024
2507
  };
3025
2508
  });
3026
2509
  }
3027
- const result = buildHttp2Frame({
3028
- frameType,
3029
- ...streamId !== void 0 && { streamId },
3030
- ...flags !== void 0 && { flags },
3031
- ...frameTypeCode !== void 0 && { frameTypeCode },
3032
- ...payloadHex !== void 0 && { payloadHex },
3033
- ...payloadText !== void 0 && { payloadText },
3034
- ...payloadEncoding !== void 0 && { payloadEncoding },
3035
- ...settings !== void 0 && { settings },
3036
- ...ack !== void 0 && { ack },
3037
- ...pingOpaqueDataHex !== void 0 && { pingOpaqueDataHex },
3038
- ...windowSizeIncrement !== void 0 && { windowSizeIncrement },
3039
- ...errorCode !== void 0 && { errorCode },
3040
- ...lastStreamId !== void 0 && { lastStreamId },
3041
- ...debugDataText !== void 0 && { debugDataText },
3042
- ...debugDataEncoding !== void 0 && { debugDataEncoding }
3043
- });
3044
- emitEvent(this.eventBus, "network:http2_frame_build_completed", {
3045
- frameType: result.frameType,
3046
- typeCode: result.typeCode,
3047
- streamId: result.streamId,
3048
- flags: result.flags,
3049
- payloadBytes: result.payloadBytes,
3050
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3051
- });
3052
- return R.ok().merge(result).json();
2510
+ const result = buildHttp2Frame({
2511
+ frameType,
2512
+ ...streamId !== void 0 && { streamId },
2513
+ ...flags !== void 0 && { flags },
2514
+ ...frameTypeCode !== void 0 && { frameTypeCode },
2515
+ ...payloadHex !== void 0 && { payloadHex },
2516
+ ...payloadText !== void 0 && { payloadText },
2517
+ ...payloadEncoding !== void 0 && { payloadEncoding },
2518
+ ...settings !== void 0 && { settings },
2519
+ ...ack !== void 0 && { ack },
2520
+ ...pingOpaqueDataHex !== void 0 && { pingOpaqueDataHex },
2521
+ ...windowSizeIncrement !== void 0 && { windowSizeIncrement },
2522
+ ...errorCode !== void 0 && { errorCode },
2523
+ ...lastStreamId !== void 0 && { lastStreamId },
2524
+ ...debugDataText !== void 0 && { debugDataText },
2525
+ ...debugDataEncoding !== void 0 && { debugDataEncoding }
2526
+ });
2527
+ emitEvent(this.eventBus, "network:http2_frame_build_completed", {
2528
+ frameType: result.frameType,
2529
+ typeCode: result.typeCode,
2530
+ streamId: result.streamId,
2531
+ flags: result.flags,
2532
+ payloadBytes: result.payloadBytes,
2533
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2534
+ });
2535
+ return R.ok().merge(result).json();
2536
+ }
2537
+ };
2538
+ //#endregion
2539
+ //#region src/native/IcmpProbe.ts
2540
+ /**
2541
+ * Cross-platform ICMP probe and traceroute via koffi FFI.
2542
+ *
2543
+ * Windows: IcmpSendEcho from iphlpapi.dll (no admin required).
2544
+ * Linux/macOS: Raw ICMP sockets via libc (requires root/CAP_NET_RAW).
2545
+ *
2546
+ * Uses Buffer-based struct parsing (same pattern as Win32API.ts)
2547
+ * to avoid koffi struct registration issues in test environments.
2548
+ */
2549
+ function ipToString(addr) {
2550
+ return `${addr & 255}.${addr >>> 8 & 255}.${addr >>> 16 & 255}.${addr >>> 24 & 255}`;
2551
+ }
2552
+ function isValidIpv4(ip) {
2553
+ const parts = ip.split(".");
2554
+ if (parts.length !== 4) return false;
2555
+ return parts.every((p) => {
2556
+ const n = parseInt(p, 10);
2557
+ return !isNaN(n) && n >= 0 && n <= 255 && p === String(n);
2558
+ });
2559
+ }
2560
+ let available = null;
2561
+ const IP_STATUS = {
2562
+ 0: "SUCCESS",
2563
+ 11001: "BUF_TOO_SMALL",
2564
+ 11002: "DEST_NET_UNREACHABLE",
2565
+ 11003: "DEST_HOST_UNREACHABLE",
2566
+ 11004: "DEST_PROT_UNREACHABLE",
2567
+ 11005: "DEST_PORT_UNREACHABLE",
2568
+ 11009: "PACKET_TOO_BIG",
2569
+ 11010: "REQ_TIMED_OUT",
2570
+ 11013: "TTL_EXPIRED_TRANSIT",
2571
+ 11014: "TTL_EXPIRED_REASSEM",
2572
+ 11015: "PARAM_PROBLEM",
2573
+ 11016: "SOURCE_QUENCH",
2574
+ 11050: "GENERAL_FAILURE"
2575
+ };
2576
+ function winStatusLabel(s) {
2577
+ return IP_STATUS[s] ?? `UNKNOWN_${s}`;
2578
+ }
2579
+ function winStatusClass(s) {
2580
+ if (s === 0) return "success";
2581
+ if (s === 11010) return "timeout";
2582
+ if (s === 11013 || s === 11014) return "time_exceeded";
2583
+ if (s >= 11002 && s <= 11005) return "destination_unreachable";
2584
+ if (s === 11016) return "source_quench";
2585
+ if (s === 11009) return "packet_too_big";
2586
+ if (s === 11015) return "parameter_problem";
2587
+ return "error";
2588
+ }
2589
+ let iphlpapi = null;
2590
+ let ws2_32 = null;
2591
+ function getIphlpapi() {
2592
+ if (!iphlpapi) {
2593
+ iphlpapi = koffi.load("iphlpapi.dll");
2594
+ logger.debug("Loaded iphlpapi.dll via koffi");
2595
+ }
2596
+ return iphlpapi;
2597
+ }
2598
+ function getWs2_32() {
2599
+ if (!ws2_32) {
2600
+ ws2_32 = koffi.load("ws2_32.dll");
2601
+ logger.debug("Loaded ws2_32.dll via koffi");
2602
+ }
2603
+ return ws2_32;
2604
+ }
2605
+ const IP_OPT_SIZE = 16;
2606
+ const MIN_REPLY_BUF_SIZE = 256;
2607
+ const ICMP_REPLY_OVERHEAD = 64;
2608
+ function getReplyBufferSize(packetSize) {
2609
+ return Math.max(MIN_REPLY_BUF_SIZE, packetSize + ICMP_REPLY_OVERHEAD);
2610
+ }
2611
+ function buildOptionBuf(ttl) {
2612
+ const buf = Buffer.alloc(IP_OPT_SIZE, 0);
2613
+ buf.writeUInt8(ttl, 0);
2614
+ return buf;
2615
+ }
2616
+ function parseReply(buf) {
2617
+ return {
2618
+ address: buf.readUInt32LE(0),
2619
+ status: buf.readUInt32LE(4),
2620
+ rtt: buf.readUInt32LE(8)
2621
+ };
2622
+ }
2623
+ function win_inet_addr(ip) {
2624
+ return getWs2_32().func("uint32 inet_addr(char *)")(ip);
2625
+ }
2626
+ function win_IcmpCreateFile() {
2627
+ return getIphlpapi().func("void * IcmpCreateFile()")();
2628
+ }
2629
+ function win_IcmpCloseHandle(h) {
2630
+ return getIphlpapi().func("int IcmpCloseHandle(void *)")(h) !== 0;
2631
+ }
2632
+ function win_IcmpSendEcho(handle, destAddr, sendData, optionBuf, timeoutMs) {
2633
+ const fn = getIphlpapi().func("uint32 IcmpSendEcho(void *, uint32, void *, uint16, void *, void *, uint32, uint32)");
2634
+ const replyBuf = Buffer.alloc(getReplyBufferSize(sendData.length));
2635
+ const n = fn(handle, destAddr, sendData, sendData.length, optionBuf, replyBuf, replyBuf.length, timeoutMs);
2636
+ return {
2637
+ numReplies: Number(n),
2638
+ replyBuf
2639
+ };
2640
+ }
2641
+ function winIcmpProbe(params) {
2642
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2643
+ const destAddr = win_inet_addr(target);
2644
+ if (destAddr === 4294967295) return {
2645
+ target,
2646
+ ip: "",
2647
+ alive: false,
2648
+ rtt: null,
2649
+ ttl,
2650
+ icmpStatus: "INVALID_ADDRESS",
2651
+ errorClass: "error",
2652
+ packetSize
2653
+ };
2654
+ const handle = win_IcmpCreateFile();
2655
+ try {
2656
+ const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2657
+ if (numReplies === 0) return {
2658
+ target,
2659
+ ip: ipToString(destAddr),
2660
+ alive: false,
2661
+ rtt: null,
2662
+ ttl,
2663
+ icmpStatus: "REQ_TIMED_OUT",
2664
+ errorClass: "timeout",
2665
+ packetSize
2666
+ };
2667
+ const reply = parseReply(replyBuf);
2668
+ return {
2669
+ target,
2670
+ ip: ipToString(reply.address),
2671
+ alive: reply.status === 0,
2672
+ rtt: reply.status === 0 ? reply.rtt : null,
2673
+ ttl,
2674
+ icmpStatus: winStatusLabel(reply.status),
2675
+ errorClass: winStatusClass(reply.status),
2676
+ packetSize
2677
+ };
2678
+ } finally {
2679
+ win_IcmpCloseHandle(handle);
2680
+ }
2681
+ }
2682
+ function winTraceroute(params) {
2683
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2684
+ const destAddr = win_inet_addr(target);
2685
+ if (destAddr === 4294967295) return {
2686
+ target,
2687
+ ip: "",
2688
+ hops: [],
2689
+ reached: false,
2690
+ totalHops: 0,
2691
+ totalTime: 0
2692
+ };
2693
+ const handle = win_IcmpCreateFile();
2694
+ const hops = [];
2695
+ const t0 = performance.now();
2696
+ try {
2697
+ for (let ttl = 1; ttl <= maxHops; ttl++) {
2698
+ const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2699
+ if (numReplies === 0) {
2700
+ hops.push({
2701
+ hop: ttl,
2702
+ ip: null,
2703
+ rtt: null,
2704
+ status: "REQ_TIMED_OUT",
2705
+ errorClass: "timeout"
2706
+ });
2707
+ continue;
2708
+ }
2709
+ const reply = parseReply(replyBuf);
2710
+ const hopIp = ipToString(reply.address);
2711
+ hops.push({
2712
+ hop: ttl,
2713
+ ip: hopIp,
2714
+ rtt: reply.rtt,
2715
+ status: winStatusLabel(reply.status),
2716
+ errorClass: winStatusClass(reply.status)
2717
+ });
2718
+ if (reply.status === 0) break;
2719
+ }
2720
+ } finally {
2721
+ win_IcmpCloseHandle(handle);
2722
+ }
2723
+ const last = hops[hops.length - 1];
2724
+ return {
2725
+ target,
2726
+ ip: ipToString(destAddr),
2727
+ hops,
2728
+ reached: last?.status === "SUCCESS",
2729
+ totalHops: hops.length,
2730
+ totalTime: Math.round((performance.now() - t0) * 100) / 100
2731
+ };
2732
+ }
2733
+ const AF_INET = 2;
2734
+ const SOCK_RAW = 3;
2735
+ const IPPROTO_ICMP = 1;
2736
+ const IPPROTO_IP = 0;
2737
+ const IP_TTL = 2;
2738
+ const SOL_SOCKET = 1;
2739
+ const SO_RCVTIMEO = process.platform === "darwin" ? 4102 : 20;
2740
+ const POSIX_LIB = process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6";
2741
+ let posixLib = null;
2742
+ function getPosixLib() {
2743
+ if (!posixLib) {
2744
+ posixLib = koffi.load(POSIX_LIB);
2745
+ logger.debug(`Loaded ${POSIX_LIB} via koffi for ICMP`);
2746
+ }
2747
+ return posixLib;
2748
+ }
2749
+ function posixSocket(domain, type, protocol) {
2750
+ return getPosixLib().func("int socket(int, int, int)")(domain, type, protocol);
2751
+ }
2752
+ function posixSetsockopt(fd, level, optname, optval, optlen) {
2753
+ return getPosixLib().func("int setsockopt(int, int, int, void *, int)")(fd, level, optname, optval, optlen);
2754
+ }
2755
+ function posixSendto(fd, buf, addr) {
2756
+ return getPosixLib().func("int sendto(int, void *, int, int, void *, int)")(fd, buf, buf.length, 0, addr, 16);
2757
+ }
2758
+ function posixRecv(fd, buf) {
2759
+ return getPosixLib().func("int recv(int, void *, int, int)")(fd, buf, buf.length, 0);
2760
+ }
2761
+ function posixClose(fd) {
2762
+ return getPosixLib().func("int close(int)")(fd);
2763
+ }
2764
+ function computeChecksum(buf) {
2765
+ let sum = 0;
2766
+ for (let i = 0; i < buf.length - 1; i += 2) sum += buf.readUInt16BE(i);
2767
+ if (buf.length & 1) sum += (buf[buf.length - 1] ?? 0) << 8;
2768
+ while (sum > 65535) sum = (sum & 65535) + (sum >>> 16);
2769
+ return ~sum & 65535;
2770
+ }
2771
+ function buildIcmpEcho(id, seq, payloadSize) {
2772
+ const buf = Buffer.alloc(8 + payloadSize);
2773
+ buf[0] = 8;
2774
+ buf[1] = 0;
2775
+ buf.writeUInt16BE(id & 65535, 4);
2776
+ buf.writeUInt16BE(seq & 65535, 6);
2777
+ for (let i = 8; i < buf.length; i++) buf[i] = 170;
2778
+ buf.writeUInt16BE(computeChecksum(buf), 2);
2779
+ return buf;
2780
+ }
2781
+ function buildSockaddrIn(ip) {
2782
+ const buf = Buffer.alloc(16, 0);
2783
+ buf.writeUInt16LE(AF_INET, 0);
2784
+ const parts = ip.split(".").map(Number);
2785
+ buf[4] = parts[0] ?? 0;
2786
+ buf[5] = parts[1] ?? 0;
2787
+ buf[6] = parts[2] ?? 0;
2788
+ buf[7] = parts[3] ?? 0;
2789
+ return buf;
2790
+ }
2791
+ function parseIcmpPacket(buf, n, expectedId) {
2792
+ if (n < 20) return null;
2793
+ const ihl = ((buf[0] ?? 0) & 15) * 4;
2794
+ if (n < ihl + 8) return null;
2795
+ const icmpType = buf[ihl] ?? 0;
2796
+ const icmpCode = buf[ihl + 1] ?? 0;
2797
+ const fromIp = buf.readUInt32LE(12);
2798
+ if (icmpType === 0) {
2799
+ if (buf.readUInt16BE(ihl + 4) !== expectedId) return null;
2800
+ return {
2801
+ type: icmpType,
2802
+ code: icmpCode,
2803
+ fromIp
2804
+ };
2805
+ }
2806
+ if (icmpType === 11 || icmpType === 3) {
2807
+ const origStart = ihl + 8;
2808
+ if (n < origStart + 28) return null;
2809
+ const origIhl = ((buf[origStart] ?? 0) & 15) * 4;
2810
+ if (n < origStart + origIhl + 8) return null;
2811
+ if (buf.readUInt16BE(origStart + origIhl + 4) !== expectedId) return null;
2812
+ return {
2813
+ type: icmpType,
2814
+ code: icmpCode,
2815
+ fromIp
2816
+ };
2817
+ }
2818
+ return null;
2819
+ }
2820
+ function posixStatusLabel(type, code, timedOut) {
2821
+ if (type === 0) return "SUCCESS";
2822
+ if (timedOut) return "REQ_TIMED_OUT";
2823
+ if (type === 11 && code === 0) return "TTL_EXPIRED_TRANSIT";
2824
+ if (type === 11 && code === 1) return "TTL_EXPIRED_REASSEM";
2825
+ if (type === 3 && code === 0) return "DEST_NET_UNREACHABLE";
2826
+ if (type === 3 && code === 1) return "DEST_HOST_UNREACHABLE";
2827
+ if (type === 3 && code === 2) return "DEST_PROT_UNREACHABLE";
2828
+ if (type === 3 && code === 3) return "DEST_PORT_UNREACHABLE";
2829
+ return `UNKNOWN_${type}_${code}`;
2830
+ }
2831
+ function posixErrorClass(type, _exitCodeValue, timedOut) {
2832
+ if (type === 0) return "success";
2833
+ if (timedOut) return "timeout";
2834
+ if (type === 11) return "time_exceeded";
2835
+ if (type === 3) return "destination_unreachable";
2836
+ return "error";
2837
+ }
2838
+ function posixSetTtl(fd, ttl) {
2839
+ const buf = Buffer.alloc(4);
2840
+ buf.writeInt32LE(ttl);
2841
+ posixSetsockopt(fd, IPPROTO_IP, IP_TTL, buf, 4);
2842
+ }
2843
+ function posixSetRecvTimeout(fd, timeoutMs) {
2844
+ const tv = Buffer.alloc(16, 0);
2845
+ tv.writeInt32LE(Math.floor(timeoutMs / 1e3), 0);
2846
+ tv.writeInt32LE(timeoutMs % 1e3 * 1e3, 8);
2847
+ posixSetsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv, 16);
2848
+ }
2849
+ function posixIcmpProbe(params) {
2850
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2851
+ if (!isValidIpv4(target)) return {
2852
+ target,
2853
+ ip: "",
2854
+ alive: false,
2855
+ rtt: null,
2856
+ ttl,
2857
+ icmpStatus: "INVALID_ADDRESS",
2858
+ errorClass: "error",
2859
+ packetSize
2860
+ };
2861
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2862
+ if (fd < 0) return {
2863
+ target,
2864
+ ip: "",
2865
+ alive: false,
2866
+ rtt: null,
2867
+ ttl,
2868
+ icmpStatus: "SOCKET_ERROR",
2869
+ errorClass: "error",
2870
+ packetSize
2871
+ };
2872
+ try {
2873
+ posixSetTtl(fd, ttl);
2874
+ posixSetRecvTimeout(fd, timeout);
2875
+ const id = process.pid & 65535;
2876
+ const packet = buildIcmpEcho(id, 1, packetSize);
2877
+ const destAddr = buildSockaddrIn(target);
2878
+ const t0 = performance.now();
2879
+ if (posixSendto(fd, packet, destAddr) < 0) return {
2880
+ target,
2881
+ ip: target,
2882
+ alive: false,
2883
+ rtt: null,
2884
+ ttl,
2885
+ icmpStatus: "SEND_ERROR",
2886
+ errorClass: "error",
2887
+ packetSize
2888
+ };
2889
+ const recvBuf = Buffer.alloc(512);
2890
+ const n = posixRecv(fd, recvBuf);
2891
+ const rtt = Math.round(performance.now() - t0);
2892
+ if (n <= 0) return {
2893
+ target,
2894
+ ip: target,
2895
+ alive: false,
2896
+ rtt: null,
2897
+ ttl,
2898
+ icmpStatus: "REQ_TIMED_OUT",
2899
+ errorClass: "timeout",
2900
+ packetSize
2901
+ };
2902
+ const reply = parseIcmpPacket(recvBuf, n, id);
2903
+ if (!reply) return {
2904
+ target,
2905
+ ip: target,
2906
+ alive: false,
2907
+ rtt: null,
2908
+ ttl,
2909
+ icmpStatus: "UNEXPECTED_REPLY",
2910
+ errorClass: "error",
2911
+ packetSize
2912
+ };
2913
+ const alive = reply.type === 0;
2914
+ return {
2915
+ target,
2916
+ ip: ipToString(reply.fromIp),
2917
+ alive,
2918
+ rtt: alive ? rtt : null,
2919
+ ttl,
2920
+ icmpStatus: posixStatusLabel(reply.type, reply.code, false),
2921
+ errorClass: posixErrorClass(reply.type, reply.code, false),
2922
+ packetSize
2923
+ };
2924
+ } finally {
2925
+ posixClose(fd);
2926
+ }
2927
+ }
2928
+ function posixTraceroute(params) {
2929
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2930
+ if (!isValidIpv4(target)) return {
2931
+ target,
2932
+ ip: "",
2933
+ hops: [],
2934
+ reached: false,
2935
+ totalHops: 0,
2936
+ totalTime: 0
2937
+ };
2938
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2939
+ if (fd < 0) return {
2940
+ target,
2941
+ ip: "",
2942
+ hops: [],
2943
+ reached: false,
2944
+ totalHops: 0,
2945
+ totalTime: 0
2946
+ };
2947
+ const hops = [];
2948
+ const id = process.pid & 65535;
2949
+ const destAddr = buildSockaddrIn(target);
2950
+ const t0 = performance.now();
2951
+ const MAX_CONSECUTIVE_SEND_ERRORS = 5;
2952
+ let consecutiveSendErrors = 0;
2953
+ try {
2954
+ posixSetRecvTimeout(fd, timeout);
2955
+ for (let ttl = 1; ttl <= maxHops; ttl++) {
2956
+ posixSetTtl(fd, ttl);
2957
+ const packet = buildIcmpEcho(id, ttl, packetSize);
2958
+ const sendT0 = performance.now();
2959
+ if (posixSendto(fd, packet, destAddr) < 0) {
2960
+ consecutiveSendErrors++;
2961
+ hops.push({
2962
+ hop: ttl,
2963
+ ip: null,
2964
+ rtt: null,
2965
+ status: "SEND_ERROR",
2966
+ errorClass: "error"
2967
+ });
2968
+ if (consecutiveSendErrors >= MAX_CONSECUTIVE_SEND_ERRORS) break;
2969
+ continue;
2970
+ }
2971
+ consecutiveSendErrors = 0;
2972
+ const recvBuf = Buffer.alloc(512);
2973
+ const n = posixRecv(fd, recvBuf);
2974
+ const rtt = Math.round(performance.now() - sendT0);
2975
+ if (n <= 0) {
2976
+ hops.push({
2977
+ hop: ttl,
2978
+ ip: null,
2979
+ rtt: null,
2980
+ status: "REQ_TIMED_OUT",
2981
+ errorClass: "timeout"
2982
+ });
2983
+ continue;
2984
+ }
2985
+ const reply = parseIcmpPacket(recvBuf, n, id);
2986
+ if (!reply) {
2987
+ hops.push({
2988
+ hop: ttl,
2989
+ ip: null,
2990
+ rtt: null,
2991
+ status: "UNEXPECTED_REPLY",
2992
+ errorClass: "error"
2993
+ });
2994
+ continue;
2995
+ }
2996
+ const status = posixStatusLabel(reply.type, reply.code, false);
2997
+ const errorCls = posixErrorClass(reply.type, reply.code, false);
2998
+ hops.push({
2999
+ hop: ttl,
3000
+ ip: ipToString(reply.fromIp),
3001
+ rtt,
3002
+ status,
3003
+ errorClass: errorCls
3004
+ });
3005
+ if (reply.type === 0) break;
3006
+ }
3007
+ } finally {
3008
+ posixClose(fd);
3009
+ }
3010
+ return {
3011
+ target,
3012
+ ip: target,
3013
+ hops,
3014
+ reached: hops[hops.length - 1]?.status === "SUCCESS",
3015
+ totalHops: hops.length,
3016
+ totalTime: Math.round((performance.now() - t0) * 100) / 100
3017
+ };
3018
+ }
3019
+ const isPosix = process.platform === "linux" || process.platform === "darwin";
3020
+ function isIcmpAvailable() {
3021
+ if (available !== null) return available;
3022
+ if (process.platform === "win32") try {
3023
+ koffi.load("iphlpapi.dll").unload();
3024
+ available = true;
3025
+ return true;
3026
+ } catch {
3027
+ available = false;
3028
+ return false;
3029
+ }
3030
+ if (isPosix) {
3031
+ try {
3032
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
3033
+ if (fd >= 0) {
3034
+ posixClose(fd);
3035
+ available = true;
3036
+ } else available = false;
3037
+ } catch {
3038
+ available = false;
3039
+ }
3040
+ return available;
3041
+ }
3042
+ available = false;
3043
+ return false;
3044
+ }
3045
+ function icmpProbe(params) {
3046
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
3047
+ if (!isIcmpAvailable()) return {
3048
+ target,
3049
+ ip: "",
3050
+ alive: false,
3051
+ rtt: null,
3052
+ ttl,
3053
+ icmpStatus: "PLATFORM_NOT_SUPPORTED",
3054
+ errorClass: "error",
3055
+ packetSize
3056
+ };
3057
+ if (process.platform === "win32") return winIcmpProbe({
3058
+ target,
3059
+ ttl,
3060
+ packetSize,
3061
+ timeout
3062
+ });
3063
+ return posixIcmpProbe({
3064
+ target,
3065
+ ttl,
3066
+ packetSize,
3067
+ timeout
3068
+ });
3069
+ }
3070
+ function traceroute(params) {
3071
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
3072
+ if (!isIcmpAvailable()) return {
3073
+ target,
3074
+ ip: "",
3075
+ hops: [],
3076
+ reached: false,
3077
+ totalHops: 0,
3078
+ totalTime: 0
3079
+ };
3080
+ if (process.platform === "win32") return winTraceroute({
3081
+ target,
3082
+ maxHops,
3083
+ timeout,
3084
+ packetSize
3085
+ });
3086
+ return posixTraceroute({
3087
+ target,
3088
+ maxHops,
3089
+ timeout,
3090
+ packetSize
3091
+ });
3092
+ }
3093
+ //#endregion
3094
+ //#region src/server/domains/network/handlers/raw-latency-handlers.ts
3095
+ var RawLatencyHandlers = class {
3096
+ constructor(eventBus) {
3097
+ this.eventBus = eventBus;
3053
3098
  }
3054
3099
  async handleNetworkRttMeasure(args) {
3055
3100
  const urlRaw = parseOptionalString(args.url, "url");
@@ -3077,7 +3122,7 @@ var RawHandlers = class {
3077
3122
  const useHttps = url.protocol === "https:";
3078
3123
  const samples = [];
3079
3124
  const errors = [];
3080
- for (let i = 0; i < iterations; i++) try {
3125
+ for (let index = 0; index < iterations; index += 1) try {
3081
3126
  const rtt = await this.measureSingleRtt(hostname, resolvedIp, port, probeType, timeoutMs, useHttps);
3082
3127
  samples.push(rtt);
3083
3128
  } catch (err) {
@@ -3106,6 +3151,46 @@ var RawHandlers = class {
3106
3151
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3107
3152
  }).json();
3108
3153
  }
3154
+ async handleNetworkTraceroute(args) {
3155
+ try {
3156
+ if (!isIcmpAvailable()) return R.text("ICMP traceroute not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3157
+ const target = parseOptionalString(args.target, "target");
3158
+ if (!target) return R.text("target is required", true);
3159
+ const resolvedTarget = await resolveHostname(target);
3160
+ if (!resolvedTarget) return R.fail(`Could not resolve hostname: ${target}`).json();
3161
+ const result = traceroute({
3162
+ target: resolvedTarget,
3163
+ maxHops: clamp(args.maxHops !== void 0 ? Number(args.maxHops) : 30, 1, 64),
3164
+ timeout: clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4),
3165
+ packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500)
3166
+ });
3167
+ return R.ok().merge({ resolvedFrom: target }).json(result);
3168
+ } catch (err) {
3169
+ const message = err instanceof Error ? err.message : String(err);
3170
+ return R.fail(`Traceroute failed: ${message}`).json();
3171
+ }
3172
+ }
3173
+ async handleNetworkIcmpProbe(args) {
3174
+ try {
3175
+ if (!isIcmpAvailable()) return R.text("ICMP probe not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3176
+ const target = parseOptionalString(args.target, "target");
3177
+ if (!target) return R.text("target is required", true);
3178
+ const resolvedTarget = await resolveHostname(target);
3179
+ if (!resolvedTarget) return R.fail(`Could not resolve hostname: ${target}`).json();
3180
+ const ttl = clamp(args.ttl !== void 0 ? Number(args.ttl) : 128, 1, 255);
3181
+ const timeout = clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4);
3182
+ const result = icmpProbe({
3183
+ target: resolvedTarget,
3184
+ ttl,
3185
+ packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500),
3186
+ timeout
3187
+ });
3188
+ return R.ok().merge({ resolvedFrom: target }).json(result);
3189
+ } catch (err) {
3190
+ const message = err instanceof Error ? err.message : String(err);
3191
+ return R.fail(`ICMP probe failed: ${message}`).json();
3192
+ }
3193
+ }
3109
3194
  measureSingleRtt(hostname, address, port, probeType, timeoutMs, useHttps) {
3110
3195
  switch (probeType) {
3111
3196
  case "tcp": return this.probeTcp(address, port, timeoutMs);
@@ -3198,42 +3283,458 @@ var RawHandlers = class {
3198
3283
  request.end();
3199
3284
  });
3200
3285
  }
3201
- async handleNetworkTraceroute(args) {
3202
- try {
3203
- if (!isIcmpAvailable()) return R.text("ICMP traceroute not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3204
- const target = parseOptionalString(args.target, "target");
3205
- if (!target) return R.text("target is required", true);
3206
- const result = traceroute({
3207
- target,
3208
- maxHops: clamp(args.maxHops !== void 0 ? Number(args.maxHops) : 30, 1, 64),
3209
- timeout: clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4),
3210
- packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500)
3211
- });
3212
- return R.ok().json(result);
3213
- } catch (err) {
3214
- const message = err instanceof Error ? err.message : String(err);
3215
- return R.fail(`Traceroute failed: ${message}`).json();
3286
+ };
3287
+ async function resolveHostname(target) {
3288
+ if (net.isIPv4(target)) return target;
3289
+ try {
3290
+ return (await dns.resolve(target, "A"))[0] ?? null;
3291
+ } catch {
3292
+ return null;
3293
+ }
3294
+ }
3295
+ //#endregion
3296
+ //#region src/server/domains/network/handlers/raw-handlers.ts
3297
+ var RawHandlers = class extends RawLatencyHandlers {
3298
+ dnsHttp;
3299
+ http2;
3300
+ constructor(eventBus) {
3301
+ super(eventBus);
3302
+ this.dnsHttp = new RawDnsHttpHandlers(eventBus);
3303
+ this.http2 = new RawHttp2Handlers(eventBus);
3304
+ }
3305
+ handleDnsResolve(args) {
3306
+ return this.dnsHttp.handleDnsResolve(args);
3307
+ }
3308
+ handleDnsReverse(args) {
3309
+ return this.dnsHttp.handleDnsReverse(args);
3310
+ }
3311
+ handleHttpRequestBuild(args) {
3312
+ return this.dnsHttp.handleHttpRequestBuild(args);
3313
+ }
3314
+ handleHttpPlainRequest(args) {
3315
+ return this.dnsHttp.handleHttpPlainRequest(args);
3316
+ }
3317
+ handleHttp2Probe(args) {
3318
+ return this.http2.handleHttp2Probe(args);
3319
+ }
3320
+ handleHttp2FrameBuild(args) {
3321
+ return this.http2.handleHttp2FrameBuild(args);
3322
+ }
3323
+ };
3324
+ //#endregion
3325
+ //#region src/server/domains/network/handlers/tls-bot-handlers.ts
3326
+ const GREASE_HEX = new Set([
3327
+ "0a0a",
3328
+ "1a1a",
3329
+ "2a2a",
3330
+ "3a3a",
3331
+ "4a4a",
3332
+ "5a5a",
3333
+ "6a6a",
3334
+ "7a7a",
3335
+ "8a8a",
3336
+ "9a9a",
3337
+ "aaaa",
3338
+ "baba",
3339
+ "caca",
3340
+ "dada",
3341
+ "eaea",
3342
+ "fafa"
3343
+ ]);
3344
+ function isGrease(hex) {
3345
+ return GREASE_HEX.has(hex.replace("0x", "").toLowerCase().padStart(4, "0"));
3346
+ }
3347
+ function sha256trunc12(input) {
3348
+ return createHash("sha256").update(input).digest("hex").substring(0, 12);
3349
+ }
3350
+ function toHex4(val) {
3351
+ return val.replace("0x", "").toLowerCase().padStart(4, "0");
3352
+ }
3353
+ const TLS_VERSION_MAP = {
3354
+ "0304": "13",
3355
+ "0303": "12",
3356
+ "0302": "11",
3357
+ "0301": "10",
3358
+ "0300": "s3",
3359
+ "0002": "s2",
3360
+ feff: "d1",
3361
+ fefd: "d2",
3362
+ fefc: "d3"
3363
+ };
3364
+ function encodeTlsVersion(versionHex) {
3365
+ return TLS_VERSION_MAP[versionHex.toLowerCase()] ?? "00";
3366
+ }
3367
+ function encodeAlpn(alpn) {
3368
+ if (!alpn || alpn.length === 0) return "00";
3369
+ const first = alpn[0];
3370
+ const last = alpn[alpn.length - 1];
3371
+ const isFirstAlphaNum = /[0-9a-zA-Z]/.test(first);
3372
+ const isLastAlphaNum = /[0-9a-zA-Z]/.test(last);
3373
+ if (isFirstAlphaNum && isLastAlphaNum) return `${first}${last}`;
3374
+ const hex = Buffer.from(alpn, "utf8").toString("hex");
3375
+ return `${hex[0] ?? "0"}${hex[hex.length - 1] ?? "0"}`;
3376
+ }
3377
+ function computeTlsFingerprint(opts) {
3378
+ const { protocol, tlsVersion, hasSni, ciphers, extensions, signatureAlgorithms, alpn } = opts;
3379
+ const protoChar = protocol === "quic" ? "q" : protocol === "dtls" ? "d" : "t";
3380
+ const filteredVersions = [tlsVersion].map(toHex4).filter((v) => !isGrease(v) && v !== "0303");
3381
+ const sorted = (tlsVersion.length > 0 ? filteredVersions : ["0303"]).toSorted();
3382
+ const versionStr = encodeTlsVersion(sorted.length > 0 ? sorted[sorted.length - 1] : "0303");
3383
+ const sniChar = hasSni ? "d" : "i";
3384
+ const filteredCiphers = ciphers.map(toHex4).filter((c) => !isGrease(c));
3385
+ const filteredExts = extensions.map(toHex4).filter((e) => !isGrease(e));
3386
+ const a = `${protoChar}${versionStr}${sniChar}${String(Math.min(filteredCiphers.length, 99)).padStart(2, "0")}${String(Math.min(filteredExts.length, 99)).padStart(2, "0")}${encodeAlpn(alpn)}`;
3387
+ const cipherStr = filteredCiphers.toSorted().join(",");
3388
+ const cipherHash = filteredCiphers.length > 0 ? sha256trunc12(cipherStr) : "000000000000";
3389
+ const extsForHash = filteredExts.filter((e) => e !== "0000" && e !== "0010").toSorted();
3390
+ const sigHex = signatureAlgorithms.map(toHex4);
3391
+ let extInput;
3392
+ if (sigHex.length > 0) extInput = `${extsForHash.join(",")}_${sigHex.join(",")}`;
3393
+ else extInput = extsForHash.join(",");
3394
+ return {
3395
+ tls: `${a}_${cipherHash}_${extsForHash.length > 0 || sigHex.length > 0 ? sha256trunc12(extInput) : "000000000000"}`,
3396
+ tls_raw: `${a}_${filteredCiphers.join(",")}_${filteredExts.join(",")}_${sigHex.join(",")}`
3397
+ };
3398
+ }
3399
+ function computeHttpFingerprint(method, headers, httpVersion, cookieHeader, acceptLanguage) {
3400
+ const methodUpper = method.toUpperCase();
3401
+ const methodCode = {
3402
+ GET: "ge",
3403
+ POST: "po",
3404
+ PUT: "pu",
3405
+ DELETE: "de",
3406
+ HEAD: "he",
3407
+ PATCH: "pa",
3408
+ OPTIONS: "ot"
3409
+ }[methodUpper] ?? methodUpper.toLowerCase().substring(0, 2).padEnd(2, methodUpper.charAt(0).toLowerCase());
3410
+ const normalizedHttpVersion = typeof httpVersion === "string" ? httpVersion.trim().toLowerCase() : "";
3411
+ const versionStr = normalizedHttpVersion === "2" || normalizedHttpVersion === "2.0" || normalizedHttpVersion === "h2" || normalizedHttpVersion === "http/2" ? "20" : normalizedHttpVersion === "3" || normalizedHttpVersion === "3.0" || normalizedHttpVersion === "h3" || normalizedHttpVersion === "http/3" ? "30" : normalizedHttpVersion === "1.0" || normalizedHttpVersion === "http/1.0" ? "10" : normalizedHttpVersion === "1.1" || normalizedHttpVersion === "http/1.1" ? "11" : "00";
3412
+ const lowerHeaders = headers.map((h) => h.toLowerCase());
3413
+ const hasCookie = lowerHeaders.includes("cookie") ? "c" : "n";
3414
+ const hasReferer = lowerHeaders.includes("referer") ? "r" : "n";
3415
+ const nonCookieRefererHeaders = lowerHeaders.filter((h) => h !== "cookie" && h !== "referer");
3416
+ const numHeaders = String(Math.min(nonCookieRefererHeaders.length, 99)).padStart(2, "0");
3417
+ let langStr = "0000";
3418
+ if (acceptLanguage && acceptLanguage.length > 0) langStr = (acceptLanguage.split(",")[0] ?? "").replace(/[-;]/g, "").toLowerCase().trim().padEnd(4, "0").substring(0, 4);
3419
+ const a = `${methodCode}${versionStr}${hasCookie}${hasReferer}${numHeaders}${langStr}`;
3420
+ const sortedHeaders = nonCookieRefererHeaders.filter((h) => !h.startsWith(":")).toSorted();
3421
+ const headerHash = sortedHeaders.length > 0 ? sha256trunc12(sortedHeaders.join(",")) : "000000000000";
3422
+ let cookieNamesHash = "000000000000";
3423
+ let cookieValuesHash = "000000000000";
3424
+ if (hasCookie === "c" && cookieHeader) {
3425
+ const cookiePairs = cookieHeader.split(";").map((c) => c.trim()).filter(Boolean);
3426
+ const cookieNames = cookiePairs.map((c) => c.split("=")[0]?.trim() ?? "").filter(Boolean).toSorted();
3427
+ cookieNamesHash = cookieNames.length > 0 ? sha256trunc12(cookieNames.join(",")) : "000000000000";
3428
+ const sortedValues = cookiePairs.map((c) => {
3429
+ const eqIdx = c.indexOf("=");
3430
+ return {
3431
+ name: eqIdx >= 0 ? c.substring(0, eqIdx).trim() : c.trim(),
3432
+ pair: c
3433
+ };
3434
+ }).toSorted((x, y) => x.name.localeCompare(y.name)).map((p) => p.pair);
3435
+ cookieValuesHash = sortedValues.length > 0 ? sha256trunc12(sortedValues.join(",")) : "000000000000";
3436
+ }
3437
+ return { http: `${a}_${headerHash}_${cookieNamesHash}_${cookieValuesHash}` };
3438
+ }
3439
+ function normalizeObservedHttpVersion(httpVersion) {
3440
+ if (typeof httpVersion !== "string") return void 0;
3441
+ const normalized = httpVersion.trim().toLowerCase();
3442
+ if (normalized === "1.0" || normalized === "http/1.0") return "1.0";
3443
+ if (normalized === "1.1" || normalized === "http/1.1") return "1.1";
3444
+ if (normalized === "2" || normalized === "2.0" || normalized === "http/2" || normalized === "h2") return "h2";
3445
+ if (normalized === "3" || normalized === "3.0" || normalized === "http/3" || normalized === "h3") return "h3";
3446
+ }
3447
+ function detectBotSignals(ua, headerNames, tlsSignals) {
3448
+ const signals = [];
3449
+ let score = 0;
3450
+ if (!ua || ua.length === 0) {
3451
+ signals.push("missing-user-agent");
3452
+ score += .3;
3453
+ } else {
3454
+ if (/bot|crawler|spider|headless|selenium|puppeteer|playwright|phantom|curl|wget|python|java|go-http|httpclient|okhttp|requests\/|aiohttp|axios|node-fetch|undici/i.test(ua)) {
3455
+ signals.push(`bot-ua: ${ua.substring(0, 40)}`);
3456
+ score += .5;
3457
+ }
3458
+ if (/headless/i.test(ua)) {
3459
+ signals.push("headless-browser");
3460
+ score += .4;
3461
+ }
3462
+ if (ua.length < 30 && !/bot|curl|wget|python/i.test(ua)) {
3463
+ signals.push("suspiciously-short-ua");
3464
+ score += .2;
3216
3465
  }
3217
3466
  }
3218
- async handleNetworkIcmpProbe(args) {
3467
+ const lowerHeaders = headerNames.map((h) => h.toLowerCase());
3468
+ if (!lowerHeaders.includes("accept")) {
3469
+ signals.push("missing-accept-header");
3470
+ score += .15;
3471
+ }
3472
+ if (!lowerHeaders.includes("accept-language")) {
3473
+ signals.push("missing-accept-language");
3474
+ score += .1;
3475
+ }
3476
+ if (!lowerHeaders.includes("accept-encoding")) {
3477
+ signals.push("missing-accept-encoding");
3478
+ score += .1;
3479
+ }
3480
+ const headerCount = headerNames.length;
3481
+ if (headerCount < 4) {
3482
+ signals.push(`suspicious-few-headers: ${headerCount}`);
3483
+ score += .2;
3484
+ }
3485
+ if (tlsSignals) {
3486
+ if (tlsSignals.cipherCount <= 2) {
3487
+ signals.push(`anomalous-cipher-count: ${tlsSignals.cipherCount} (real browsers typically 5-15)`);
3488
+ score += .3;
3489
+ }
3490
+ if (tlsSignals.extensionCount < 5) {
3491
+ signals.push(`few-tls-extensions: ${tlsSignals.extensionCount} (real browsers 10-25+)`);
3492
+ score += .2;
3493
+ }
3494
+ if (/\bTLS\s*1\.[01]\b|\b1\.0\b|\b1\.1\b|\bSSL/i.test(tlsSignals.tlsVersion)) {
3495
+ signals.push(`outdated-tls-version: ${tlsSignals.tlsVersion}`);
3496
+ score += .25;
3497
+ }
3498
+ }
3499
+ const expectedBrowserOrder = [
3500
+ "host",
3501
+ "connection",
3502
+ "cache-control",
3503
+ "sec-ch-ua",
3504
+ "sec-ch-ua-mobile",
3505
+ "sec-ch-ua-platform",
3506
+ "upgrade-insecure-requests",
3507
+ "user-agent",
3508
+ "accept",
3509
+ "sec-fetch-site",
3510
+ "sec-fetch-mode",
3511
+ "sec-fetch-user",
3512
+ "sec-fetch-dest",
3513
+ "referer",
3514
+ "accept-encoding",
3515
+ "accept-language"
3516
+ ];
3517
+ if (lowerHeaders.length >= 5) {
3518
+ if (lowerHeaders.slice(0, 5).filter((h, i) => h === expectedBrowserOrder[i]).length === 0) {
3519
+ signals.push("header-order-does-not-match-known-browser");
3520
+ score += .1;
3521
+ }
3522
+ }
3523
+ return {
3524
+ score: Math.min(score, 1),
3525
+ signals
3526
+ };
3527
+ }
3528
+ var TlsBotHandlers = class {
3529
+ consoleMonitor;
3530
+ constructor(deps) {
3531
+ this.consoleMonitor = deps.consoleMonitor;
3532
+ }
3533
+ async handleNetworkTlsFingerprint(args) {
3534
+ const mode = args["mode"];
3535
+ const includeAnalysis = args["includeAnalysis"] !== false;
3536
+ const validModes = [
3537
+ "compute_tls",
3538
+ "compute_http",
3539
+ "analyze_request"
3540
+ ];
3541
+ if (!mode || !validModes.includes(mode)) return R.fail(`Invalid mode: "${mode}". Expected one of: ${validModes.join(", ")}`).json();
3219
3542
  try {
3220
- if (!isIcmpAvailable()) return R.text("ICMP probe not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3221
- const target = parseOptionalString(args.target, "target");
3222
- if (!target) return R.text("target is required", true);
3223
- const ttl = clamp(args.ttl !== void 0 ? Number(args.ttl) : 128, 1, 255);
3224
- const timeout = clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4);
3225
- const result = icmpProbe({
3226
- target,
3227
- ttl,
3228
- packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500),
3229
- timeout
3230
- });
3231
- return R.ok().json(result);
3232
- } catch (err) {
3233
- const message = err instanceof Error ? err.message : String(err);
3234
- return R.fail(`ICMP probe failed: ${message}`).json();
3543
+ if (mode === "compute_tls") {
3544
+ const tlsVersions = args["tlsVersions"] || [];
3545
+ const ciphers = args["ciphers"] || [];
3546
+ const extensions = args["extensions"] || [];
3547
+ const signatureAlgorithms = args["signatureAlgorithms"] || [];
3548
+ const protocol = args["protocol"] === "quic" ? "quic" : args["protocol"] === "dtls" ? "dtls" : "tls";
3549
+ const sni = args["sni"] !== false;
3550
+ const alpn = args["alpn"] || "";
3551
+ if (ciphers.length === 0) return R.fail("ciphers array is required for compute_tls mode").json();
3552
+ const sortedVersions = tlsVersions.map(toHex4).filter((v) => !isGrease(v)).toSorted();
3553
+ const tlsVersion = sortedVersions.length > 0 ? sortedVersions[sortedVersions.length - 1] : "0303";
3554
+ const { tls, tls_raw } = computeTlsFingerprint({
3555
+ protocol,
3556
+ tlsVersion,
3557
+ hasSni: sni,
3558
+ ciphers,
3559
+ extensions,
3560
+ signatureAlgorithms,
3561
+ alpn
3562
+ });
3563
+ const result = {
3564
+ success: true,
3565
+ mode: "tls",
3566
+ tls,
3567
+ tls_raw
3568
+ };
3569
+ if (includeAnalysis) {
3570
+ const filteredCiphers = ciphers.map(toHex4).filter((c) => !isGrease(c));
3571
+ const filteredExts = extensions.map(toHex4).filter((e) => !isGrease(e));
3572
+ result.analysis = {
3573
+ protocol: protocol.toUpperCase(),
3574
+ tlsVersion,
3575
+ sni,
3576
+ cipherCount: filteredCiphers.length,
3577
+ extensionCount: filteredExts.length,
3578
+ signatureAlgorithmCount: signatureAlgorithms.length,
3579
+ alpn: alpn || "(none)",
3580
+ sortedCiphers: filteredCiphers.toSorted(),
3581
+ sortedExtensions: filteredExts.filter((e) => e !== "0000" && e !== "0010").toSorted()
3582
+ };
3583
+ }
3584
+ return R.ok().merge(result).json();
3585
+ }
3586
+ if (mode === "compute_http") {
3587
+ const headers = args["httpHeaders"] || [];
3588
+ const ua = args["userAgent"] || "";
3589
+ const method = args["httpMethod"] || "GET";
3590
+ const httpVersion = args["httpVersion"] || "1.1";
3591
+ const cookieHeader = args["cookieHeader"] || "";
3592
+ const acceptLanguage = args["acceptLanguage"] || "";
3593
+ if (headers.length === 0) return R.fail("httpHeaders array is required for compute_http mode").json();
3594
+ const { http } = computeHttpFingerprint(method, headers, httpVersion, cookieHeader, acceptLanguage);
3595
+ const result = {
3596
+ success: true,
3597
+ mode: "http",
3598
+ http
3599
+ };
3600
+ if (includeAnalysis) {
3601
+ const lowerHeaders = headers.map((h) => h.toLowerCase());
3602
+ result.analysis = {
3603
+ method,
3604
+ httpVersion,
3605
+ headerCount: headers.length,
3606
+ nonCookieRefererHeaders: lowerHeaders.filter((h) => h !== "cookie" && h !== "referer").length,
3607
+ hasCookie: lowerHeaders.includes("cookie"),
3608
+ hasAcceptLanguage: lowerHeaders.includes("accept-language"),
3609
+ sortedHeaders: lowerHeaders.filter((h) => h !== "cookie" && h !== "referer" && !h.startsWith(":")).toSorted(),
3610
+ userAgentLength: ua.length
3611
+ };
3612
+ }
3613
+ return R.ok().merge(result).json();
3614
+ }
3615
+ const requestId = args["requestId"];
3616
+ if (!requestId) return R.fail("requestId is required for analyze_request mode").json();
3617
+ const req = this.consoleMonitor.getNetworkRequests().find((r) => r.requestId === requestId);
3618
+ if (!req) return R.fail(`Request ${requestId} not found`).json();
3619
+ const headers = req.headers || {};
3620
+ const headerNames = Object.keys(headers);
3621
+ const ua = headers["user-agent"] || headers["User-Agent"] || "";
3622
+ const method = req.method || "GET";
3623
+ const cookieHeader = headers["cookie"] || headers["Cookie"] || "";
3624
+ const acceptLanguage = headers["accept-language"] || headers["Accept-Language"] || "";
3625
+ const httpVersion = normalizeObservedHttpVersion(req.httpVersion);
3626
+ const { http } = computeHttpFingerprint(method, headerNames, httpVersion, cookieHeader, acceptLanguage);
3627
+ const secDetails = req["securityDetails"];
3628
+ const tlsSignalsForBot = secDetails && typeof secDetails === "object" ? {
3629
+ cipherCount: typeof secDetails["cipherCount"] === "number" ? secDetails["cipherCount"] : 5,
3630
+ extensionCount: typeof secDetails["extensionCount"] === "number" ? secDetails["extensionCount"] : 10,
3631
+ tlsVersion: typeof secDetails["protocol"] === "string" ? secDetails["protocol"] : ""
3632
+ } : void 0;
3633
+ const result = {
3634
+ success: true,
3635
+ mode: "analyze_request",
3636
+ requestId,
3637
+ url: req.url,
3638
+ method,
3639
+ httpVersion: httpVersion ?? "unknown",
3640
+ http
3641
+ };
3642
+ const analysis = {
3643
+ requestId,
3644
+ url: req.url,
3645
+ method,
3646
+ httpVersion: httpVersion ?? "unknown",
3647
+ http,
3648
+ headerCount: headerNames.length,
3649
+ headerOrder: headerNames.join(", "),
3650
+ userAgent: ua.length > 80 ? ua.substring(0, 80) + "..." : ua,
3651
+ securityHeaders: {
3652
+ hasCSP: void 0,
3653
+ hasHSTS: void 0,
3654
+ hasCORS: void 0
3655
+ },
3656
+ botSignals: detectBotSignals(ua, headerNames, tlsSignalsForBot)
3657
+ };
3658
+ if (includeAnalysis) result.analysis = analysis;
3659
+ return R.ok().merge(result).json();
3660
+ } catch (error) {
3661
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
3235
3662
  }
3236
3663
  }
3664
+ async handleNetworkBotDetectAnalyze(args) {
3665
+ const limit = typeof args["limit"] === "number" ? args["limit"] : BOT_DETECT_LIMIT_DEFAULT;
3666
+ const includeDetails = args["includeDetails"] === true;
3667
+ const requests = this.consoleMonitor.getNetworkRequests();
3668
+ const sample = requests.slice(0, limit);
3669
+ if (sample.length === 0) return R.ok().merge({
3670
+ analyzed: 0,
3671
+ summary: "No captured requests to analyze. Enable network monitoring first."
3672
+ }).json();
3673
+ const signals = [];
3674
+ const details = [];
3675
+ let totalBotScore = 0;
3676
+ const httpFingerprints = /* @__PURE__ */ new Map();
3677
+ for (const req of sample) {
3678
+ const headers = req.headers || {};
3679
+ const headerNames = Object.keys(headers);
3680
+ const ua = headers["user-agent"] || headers["User-Agent"] || "";
3681
+ const url = req.url || "";
3682
+ const method = req.method || "GET";
3683
+ const cookieHeader = headers["cookie"] || headers["Cookie"] || "";
3684
+ const acceptLanguage = headers["accept-language"] || headers["Accept-Language"] || "";
3685
+ const httpVersion = normalizeObservedHttpVersion(req.httpVersion);
3686
+ const secDetails = req["securityDetails"];
3687
+ const reqSignals = detectBotSignals(ua, headerNames, secDetails && typeof secDetails === "object" ? {
3688
+ cipherCount: typeof secDetails["cipherCount"] === "number" ? secDetails["cipherCount"] : 5,
3689
+ extensionCount: typeof secDetails["extensionCount"] === "number" ? secDetails["extensionCount"] : 10,
3690
+ tlsVersion: typeof secDetails["protocol"] === "string" ? secDetails["protocol"] : ""
3691
+ } : void 0);
3692
+ const isApiRequest = /\/api\/|\/v\d+\/|\/graphql/i.test(url);
3693
+ const { http } = computeHttpFingerprint(method, headerNames, httpVersion, cookieHeader, acceptLanguage);
3694
+ httpFingerprints.set(http, (httpFingerprints.get(http) ?? 0) + 1);
3695
+ const reqDetail = {
3696
+ requestId: req.requestId,
3697
+ url: url.length > 100 ? url.substring(0, 100) + "..." : url,
3698
+ method,
3699
+ http,
3700
+ botScore: reqSignals.score,
3701
+ signals: reqSignals.signals
3702
+ };
3703
+ totalBotScore += reqSignals.score;
3704
+ if (reqSignals.score > .5) signals.push(`Request ${req.requestId}: ${reqSignals.signals.join(", ")}`);
3705
+ if (isApiRequest) reqDetail.apiPattern = true;
3706
+ if (includeDetails) details.push(reqDetail);
3707
+ }
3708
+ const avgBotScore = sample.length > 0 ? totalBotScore / sample.length : 0;
3709
+ const uniqueFingerprints = httpFingerprints.size;
3710
+ const fingerprintDiversity = uniqueFingerprints / sample.length;
3711
+ const diversitySignals = [];
3712
+ if (fingerprintDiversity > .8) diversitySignals.push(`High fingerprint diversity (${uniqueFingerprints} unique HTTP fingerprints in ${sample.length} requests) — may indicate multiple clients or rotation`);
3713
+ if (fingerprintDiversity === 1 && sample.length > 5) diversitySignals.push(`Every request has a unique HTTP fingerprint — likely automated tool rotating headers`);
3714
+ return R.ok().merge({
3715
+ analyzed: sample.length,
3716
+ totalRequests: requests.length,
3717
+ averageBotScore: Math.round(avgBotScore * 100) / 100,
3718
+ suspiciousRequests: signals.length,
3719
+ httpFingerprintSummary: {
3720
+ uniqueFingerprints,
3721
+ diversity: Math.round(fingerprintDiversity * 100) / 100,
3722
+ topFingerprints: [...httpFingerprints.entries()].toSorted((a, b) => b[1] - a[1]).slice(0, 5).map(([fp, count]) => ({
3723
+ http_fingerprint: fp,
3724
+ count
3725
+ }))
3726
+ },
3727
+ signals: signals.slice(0, 20),
3728
+ ...diversitySignals.length > 0 ? { diversitySignals } : {},
3729
+ details: includeDetails ? details : void 0,
3730
+ recommendations: avgBotScore > .5 ? [
3731
+ "High bot-like signal detected. Consider TLS fingerprint rotation.",
3732
+ "Review User-Agent consistency across requests.",
3733
+ "Check header ordering matches real browser behavior.",
3734
+ "HTTP fingerprint diversity can distinguish botnets from real users."
3735
+ ] : fingerprintDiversity > .8 ? ["Traffic appears human but fingerprint diversity is high — investigate further."] : ["Traffic appears to follow normal browser patterns."]
3736
+ }).json();
3737
+ }
3237
3738
  };
3238
3739
  //#endregion
3239
3740
  //#region src/server/domains/network/handlers.impl.ts
@@ -3245,11 +3746,12 @@ var AdvancedToolHandlers = class {
3245
3746
  detailedDataManager = DetailedDataManager.getInstance();
3246
3747
  core;
3247
3748
  perf;
3248
- console_;
3749
+ consoleHandlers;
3249
3750
  replay;
3250
3751
  intercept;
3251
3752
  raw;
3252
- constructor(collector, consoleMonitor, eventBus) {
3753
+ tlsBot;
3754
+ constructor(collector, consoleMonitor, eventBus, getTraceRecorder) {
3253
3755
  this.collector = collector;
3254
3756
  this.consoleMonitor = consoleMonitor;
3255
3757
  this.eventBus = eventBus;
@@ -3260,15 +3762,17 @@ var AdvancedToolHandlers = class {
3260
3762
  });
3261
3763
  this.perf = new PerformanceHandlers({
3262
3764
  collector,
3263
- getPerformanceMonitor: () => this.getPerformanceMonitor()
3765
+ getPerformanceMonitor: () => this.getPerformanceMonitor(),
3766
+ getTraceRecorder
3264
3767
  });
3265
- this.console_ = new ConsoleHandlers({ consoleMonitor });
3768
+ this.consoleHandlers = new ConsoleHandlers({ consoleMonitor });
3266
3769
  this.replay = new ReplayHandlers({ consoleMonitor });
3267
3770
  this.intercept = new InterceptHandlers({
3268
3771
  consoleMonitor,
3269
3772
  eventBus
3270
3773
  });
3271
3774
  this.raw = new RawHandlers(eventBus);
3775
+ this.tlsBot = new TlsBotHandlers({ consoleMonitor });
3272
3776
  }
3273
3777
  getPerformanceMonitor() {
3274
3778
  if (!this.performanceMonitor) this.performanceMonitor = new PerformanceMonitor(this.collector);
@@ -3295,24 +3799,24 @@ var AdvancedToolHandlers = class {
3295
3799
  handleProfilerHeapSamplingStart = (args) => this.perf.handleProfilerHeapSamplingStart(args);
3296
3800
  handleProfilerHeapSamplingStop = (args) => this.perf.handleProfilerHeapSamplingStop(args);
3297
3801
  handleProfilerHeapSamplingDispatch = (args) => String(args["action"] ?? "") === "stop" ? this.perf.handleProfilerHeapSamplingStop(args) : this.perf.handleProfilerHeapSamplingStart(args);
3298
- handleConsoleGetExceptions = (args) => this.console_.handleConsoleGetExceptions(args);
3802
+ handleConsoleGetExceptions = (args) => this.consoleHandlers.handleConsoleGetExceptions(args);
3299
3803
  handleConsoleInjectDispatch = (args) => {
3300
3804
  switch (String(args["type"] ?? "")) {
3301
- case "xhr": return this.console_.handleConsoleInjectXhrInterceptor(args);
3302
- case "fetch": return this.console_.handleConsoleInjectFetchInterceptor(args);
3303
- case "function": return this.console_.handleConsoleInjectFunctionTracer(args);
3304
- default: return this.console_.handleConsoleInjectScriptMonitor(args);
3805
+ case "xhr": return this.consoleHandlers.handleConsoleInjectXhrInterceptor(args);
3806
+ case "fetch": return this.consoleHandlers.handleConsoleInjectFetchInterceptor(args);
3807
+ case "function": return this.consoleHandlers.handleConsoleInjectFunctionTracer(args);
3808
+ default: return this.consoleHandlers.handleConsoleInjectScriptMonitor(args);
3305
3809
  }
3306
3810
  };
3307
3811
  handleConsoleBuffersDispatch = (args) => {
3308
- return String(args["action"] ?? "") === "reset" ? this.console_.handleConsoleResetInjectedInterceptors(args) : this.console_.handleConsoleClearInjectedBuffers(args);
3812
+ return String(args["action"] ?? "") === "reset" ? this.consoleHandlers.handleConsoleResetInjectedInterceptors(args) : this.consoleHandlers.handleConsoleClearInjectedBuffers(args);
3309
3813
  };
3310
- handleConsoleInjectScriptMonitor = (args) => this.console_.handleConsoleInjectScriptMonitor(args);
3311
- handleConsoleInjectXhrInterceptor = (args) => this.console_.handleConsoleInjectXhrInterceptor(args);
3312
- handleConsoleInjectFetchInterceptor = (args) => this.console_.handleConsoleInjectFetchInterceptor(args);
3313
- handleConsoleClearInjectedBuffers = (args) => this.console_.handleConsoleClearInjectedBuffers(args);
3314
- handleConsoleResetInjectedInterceptors = (args) => this.console_.handleConsoleResetInjectedInterceptors(args);
3315
- handleConsoleInjectFunctionTracer = (args) => this.console_.handleConsoleInjectFunctionTracer(args);
3814
+ handleConsoleInjectScriptMonitor = (args) => this.consoleHandlers.handleConsoleInjectScriptMonitor(args);
3815
+ handleConsoleInjectXhrInterceptor = (args) => this.consoleHandlers.handleConsoleInjectXhrInterceptor(args);
3816
+ handleConsoleInjectFetchInterceptor = (args) => this.consoleHandlers.handleConsoleInjectFetchInterceptor(args);
3817
+ handleConsoleClearInjectedBuffers = (args) => this.consoleHandlers.handleConsoleClearInjectedBuffers(args);
3818
+ handleConsoleResetInjectedInterceptors = (args) => this.consoleHandlers.handleConsoleResetInjectedInterceptors(args);
3819
+ handleConsoleInjectFunctionTracer = (args) => this.consoleHandlers.handleConsoleInjectFunctionTracer(args);
3316
3820
  handleNetworkExtractAuth = (args) => this.replay.handleNetworkExtractAuth(args);
3317
3821
  handleNetworkExportHar = (args) => this.replay.handleNetworkExportHar(args);
3318
3822
  handleNetworkReplayRequest = (args) => this.replay.handleNetworkReplayRequest(args);
@@ -3341,6 +3845,8 @@ var AdvancedToolHandlers = class {
3341
3845
  handleHttp2Probe = (args) => this.raw.handleHttp2Probe(args);
3342
3846
  handleHttp2FrameBuild = (args) => this.raw.handleHttp2FrameBuild(args);
3343
3847
  handleNetworkRttMeasure = (args) => this.raw.handleNetworkRttMeasure(args);
3848
+ handleNetworkTlsFingerprint = (args) => this.tlsBot.handleNetworkTlsFingerprint(args);
3849
+ handleNetworkBotDetectAnalyze = (args) => this.tlsBot.handleNetworkBotDetectAnalyze(args);
3344
3850
  };
3345
3851
  //#endregion
3346
3852
  export { AdvancedToolHandlers };