rea-agents 6.0.0 → 6.1.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 (343) hide show
  1. package/README.md +23 -17
  2. package/bridge/android/ReaJadxBridge.java +14 -5
  3. package/bridge/ghidra/ReaGhidraBridge.java +173 -47
  4. package/bridge/hopper_bridge.py +310 -128
  5. package/bridge/mitmproxy/capture.py +0 -2
  6. package/bridge/native/rea_lldb_tracer.py +630 -94
  7. package/bridge/process/ProcessRunTokenReader.swift +39 -5
  8. package/bridge/pwntools/recorded_crash_impl.py +4 -0
  9. package/dist/android/JadxConfiguration.js +196 -33
  10. package/dist/android/JadxMcpTransport.js +4 -0
  11. package/dist/android/JadxProtocol.js +6 -0
  12. package/dist/android/JadxProvider.js +19 -2
  13. package/dist/android/JadxSession.js +23 -4
  14. package/dist/application/AnalysisContextQueries.js +8 -35
  15. package/dist/application/AnalysisDocument.js +32 -0
  16. package/dist/application/AppBundleExecutable.js +67 -21
  17. package/dist/application/CallPathTracing.js +19 -3
  18. package/dist/application/CapabilityInventory.js +6 -3
  19. package/dist/application/ClientConfigurationDocument.js +871 -6
  20. package/dist/application/ClientRegistrationIdentity.js +26 -5
  21. package/dist/application/ClientRegistrationStatus.js +29 -14
  22. package/dist/application/DirectAnalysis.js +27 -37
  23. package/dist/application/Doctor.js +4 -2
  24. package/dist/application/DoctorScope.js +25 -13
  25. package/dist/application/EnhancedLiteralTracing.js +22 -25
  26. package/dist/application/EnhancedTools.js +13 -11
  27. package/dist/application/EvidenceBundleFiles.js +23 -11
  28. package/dist/application/FunctionAnalysisEvidence.js +2 -3
  29. package/dist/application/InventoryProjectionEvidence.js +4 -3
  30. package/dist/application/InvestigationProviders.js +3 -2
  31. package/dist/application/JsonFiles.js +50 -28
  32. package/dist/application/ProcedureAddressResolution.js +11 -0
  33. package/dist/application/ReconstructionCoverageService.js +7 -4
  34. package/dist/application/ReconstructionObligationLedgerService.js +4 -3
  35. package/dist/application/Setup.js +13 -1
  36. package/dist/application/SetupClientConfiguration.js +10 -15
  37. package/dist/application/SetupHost.js +5 -2
  38. package/dist/application/SetupPlan.js +5 -3
  39. package/dist/application/SupportedClients.js +34 -1
  40. package/dist/application/Uninstall.js +4 -3
  41. package/dist/application/Update.js +18 -3
  42. package/dist/application/UpdateRuntime.js +46 -25
  43. package/dist/application/WebModuleTraceService.js +4 -2
  44. package/dist/application/WebNetworkCaptureService.js +4 -2
  45. package/dist/application/WebRuntimeService.js +5 -3
  46. package/dist/application/WebScriptExportService.js +2 -2
  47. package/dist/application/WebSourceLocationService.js +4 -2
  48. package/dist/application/WorkflowEvidence.js +82 -0
  49. package/dist/application/binary/AnalysisProviderEvaluation.js +21 -5
  50. package/dist/application/binary/AnalysisProviderRegistry.js +2 -0
  51. package/dist/application/binary/AnalysisSnapshotCache.js +3 -6
  52. package/dist/application/binary/BinarySession.js +95 -68
  53. package/dist/application/binary/BinarySessionOpen.js +3 -3
  54. package/dist/application/binary/BinarySessionRecords.js +25 -3
  55. package/dist/application/binary/BinarySessionStatus.js +17 -1
  56. package/dist/application/binary/CompositeProvider.js +13 -0
  57. package/dist/application/binary/LazyAnalysisProvider.js +3 -0
  58. package/dist/application/binaryDiagnostics/BinaryLayoutService.js +4 -2
  59. package/dist/application/binaryDiagnostics/RecordedCrashService.js +4 -2
  60. package/dist/application/evm/EvmInterfaceService.js +4 -2
  61. package/dist/application/investigation/EvidenceLedger.js +21 -7
  62. package/dist/application/investigation/InvestigationRecords.js +8 -0
  63. package/dist/application/javascript/JavaScriptAnalysisControl.js +34 -0
  64. package/dist/application/javascript/JavaScriptApplicationEvidence.js +24 -3
  65. package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +19 -2
  66. package/dist/application/javascript/JavaScriptApplicationService.js +22 -15
  67. package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +5 -5
  68. package/dist/application/javascript/JavaScriptArtifactAnalysis.js +5 -4
  69. package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +39 -7
  70. package/dist/application/javascript/JavaScriptArtifactPathResolution.js +6 -3
  71. package/dist/application/javascript/JavaScriptArtifactReconstruction.js +11 -3
  72. package/dist/application/javascript/JavaScriptModuleRelationships.js +37 -8
  73. package/dist/application/javascript/JavaScriptRecoveryService.js +4 -2
  74. package/dist/application/javascript/JavaScriptRuntimeObservationService.js +3 -1
  75. package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +3 -3
  76. package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +63 -61
  77. package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +23 -1
  78. package/dist/application/managed/ManagedApplicationGraphService.js +4 -3
  79. package/dist/application/managed/ManagedMemberComparisonService.js +7 -4
  80. package/dist/application/managed/ManagedNativeVerificationService.js +4 -3
  81. package/dist/application/managed/ManagedReconstructionService.js +4 -3
  82. package/dist/application/process/ProcessCli.js +14 -6
  83. package/dist/application/process/ProcessEvidence.js +4 -3
  84. package/dist/application/workflowInputError.js +18 -0
  85. package/dist/artifacts/ArtifactProvider.js +12 -2
  86. package/dist/artifacts/MachOSliceArtifactReader.js +98 -14
  87. package/dist/artifacts/NativeDmgArtifactReader.js +13 -6
  88. package/dist/artifacts/apple/AppleAssetCatalogAnalysis.js +22 -3
  89. package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +176 -56
  90. package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +195 -0
  91. package/dist/artifacts/apple/KeyedArchiveReader.js +36 -22
  92. package/dist/artifacts/apple/MachoLoadCommandReader.js +92 -31
  93. package/dist/artifacts/apple/NibArchive.js +81 -14
  94. package/dist/artifacts/apple/NibViewHierarchy.js +10 -3
  95. package/dist/artifacts/apple/PlistNumberLiterals.js +164 -0
  96. package/dist/artifacts/inventory/ArtifactGraphConstruction.js +13 -8
  97. package/dist/artifacts/inventory/scanCanonical.js +11 -14
  98. package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +6 -2
  99. package/dist/browser/CdpCaptureCompleteness.js +2 -0
  100. package/dist/browser/CdpCaptureEventHandlers.js +76 -11
  101. package/dist/browser/CdpCaptureEventTypes.js +5 -1
  102. package/dist/browser/CdpCaptureEvents.js +25 -0
  103. package/dist/browser/CdpConnection.js +30 -11
  104. package/dist/browser/CdpOptionalCommand.js +2 -3
  105. package/dist/browser/CdpPageCapture.js +23 -1
  106. package/dist/browser/PlaywrightElectronActiveActions.js +32 -23
  107. package/dist/browser/PlaywrightElectronActiveProjection.js +141 -0
  108. package/dist/browser/PlaywrightElectronActiveProvider.js +123 -124
  109. package/dist/browser/WebSourceMapFetcher.js +426 -72
  110. package/dist/browser/assets/LocalWebScriptArtifacts.js +2 -2
  111. package/dist/browser/history/CaptureFailures.js +37 -1
  112. package/dist/browser/history/CaptureRelease.js +5 -0
  113. package/dist/browser/history/HarCaptureAdapter.js +3 -3
  114. package/dist/browser/history/HarJson.js +3 -2
  115. package/dist/browser/history/HistoricalCaptureDecoder.js +4 -2
  116. package/dist/cli/analysisSelector.js +23 -0
  117. package/dist/cli/applicationCommands.js +2 -3
  118. package/dist/cli/coreBinaryCommands.js +70 -12
  119. package/dist/cli/electronCommands.js +7 -6
  120. package/dist/cli/javascriptApplicationAnalysis.js +7 -3
  121. package/dist/cli/jsonOutput.js +4 -99
  122. package/dist/cli/nativeCallCommands.js +2 -3
  123. package/dist/cli/webRuntimeCommands.js +8 -8
  124. package/dist/cli.js +2 -1
  125. package/dist/cliCommandNames.js +0 -1
  126. package/dist/cliJsonInput.js +9 -4
  127. package/dist/config/environment.js +4 -0
  128. package/dist/config/ghidraStartupTimeout.js +17 -0
  129. package/dist/config/mcpResponseBudget.js +22 -0
  130. package/dist/config/parseConfig.js +2 -1
  131. package/dist/contracts/enhancedInputs.js +5 -3
  132. package/dist/contracts/enhancedToolContracts.js +3 -3
  133. package/dist/contracts/hopperMemoryContracts.js +2 -5
  134. package/dist/contracts/javascript/electronToolContracts.js +1 -1
  135. package/dist/contracts/managed/managedToolContracts.js +2 -4
  136. package/dist/contracts/native/nativeToolContracts.js +4 -2
  137. package/dist/contracts/officialToolContracts.js +14 -15
  138. package/dist/contracts/promptContracts.js +3 -3
  139. package/dist/contracts/sessionLifecycleInputs.js +3 -7
  140. package/dist/contracts/sessionStatusContract.js +2 -4
  141. package/dist/contracts/sessionToolContracts.js +4 -6
  142. package/dist/contracts/sessionToolSchemas.js +2 -5
  143. package/dist/contracts/toolContractExamples.js +0 -1
  144. package/dist/contracts/toolContractHelpers.js +4 -1
  145. package/dist/contracts/toolEffects.js +0 -1
  146. package/dist/contracts/toolOutputSchemaGroups.js +28 -27
  147. package/dist/contracts/toolOutputSchemaPrimitives.js +18 -0
  148. package/dist/contracts/toolSchemaMetadata.js +69 -10
  149. package/dist/contracts/webNetworkCaptureToolContracts.js +1 -1
  150. package/dist/domain/analysisErrorBase.js +8 -3
  151. package/dist/domain/analysisErrorCore.js +3 -1
  152. package/dist/domain/analysisErrorPresentation.js +72 -17
  153. package/dist/domain/analysisErrorProjection.js +106 -27
  154. package/dist/domain/android/androidAnalysis.js +2 -4
  155. package/dist/domain/android/androidApplication.js +17 -9
  156. package/dist/domain/apple/appleApplication.js +41 -14
  157. package/dist/domain/apple/dyldEnvironment.js +73 -0
  158. package/dist/domain/apple/dylibResolution.js +120 -9
  159. package/dist/domain/apple/dylibResolutionFindings.js +9 -7
  160. package/dist/domain/apple/interfaceBuilderGraph.js +25 -13
  161. package/dist/domain/apple/interfaceBuilderKeyedArchive.js +13 -3
  162. package/dist/domain/apple/keyedArchive.js +9 -1
  163. package/dist/domain/apple/machoContainer.js +54 -0
  164. package/dist/domain/apple/plistNumbers.js +76 -0
  165. package/dist/domain/artifactIdentity.js +29 -0
  166. package/dist/domain/artifactInventoryEvidence.js +19 -24
  167. package/dist/domain/bridgeCandidateProjection.js +71 -0
  168. package/dist/domain/browserCompleteness.js +1 -0
  169. package/dist/domain/browserObservationError.js +24 -5
  170. package/dist/domain/browserScenarioValues.js +2 -6
  171. package/dist/domain/bundleComparison.js +3 -2
  172. package/dist/domain/canonicalJsonDigestSteps.js +133 -0
  173. package/dist/domain/canonicalOrdering.js +9 -5
  174. package/dist/domain/changedBehavior.js +6 -6
  175. package/dist/domain/comparisonSourceEvidence.js +40 -0
  176. package/dist/domain/configurationErrors.js +1 -1
  177. package/dist/domain/evidence.js +64 -9
  178. package/dist/domain/evidenceBundle.js +42 -1
  179. package/dist/domain/evidenceErrors.js +12 -1
  180. package/dist/domain/explicitSensitiveFailure.js +17 -1
  181. package/dist/domain/firmware/firmwareAnalysis.js +6 -7
  182. package/dist/domain/hopperErrors.js +23 -2
  183. package/dist/domain/hopperValues.js +51 -8
  184. package/dist/domain/immutableJson.js +114 -0
  185. package/dist/domain/inputIssueProjection.js +3 -0
  186. package/dist/domain/javascript/electronActiveObservation.js +30 -4
  187. package/dist/domain/javascript/electronStaticAnalysisValues.js +6 -12
  188. package/dist/domain/javascript/javascriptApplicationAnalysis.js +71 -23
  189. package/dist/domain/javascript/javascriptApplicationGraph.js +13 -0
  190. package/dist/domain/javascript/javascriptAstValues.js +20 -0
  191. package/dist/domain/javascript/javascriptRecovery.js +3 -8
  192. package/dist/domain/javascript/javascriptSemanticAnalysis.js +97 -2
  193. package/dist/domain/javascript/javascriptSemanticCoverage.js +6 -5
  194. package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +9 -17
  195. package/dist/domain/javascript/javascriptSemanticDataEffects.js +22 -5
  196. package/dist/domain/javascript/javascriptSemanticGraph.js +13 -0
  197. package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +1 -0
  198. package/dist/domain/javascript/javascriptSemanticPrimitives.js +25 -17
  199. package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -4
  200. package/dist/domain/javascript/javascriptSemanticResourceLimits.js +77 -0
  201. package/dist/domain/javascript/javascriptSemanticValues.js +83 -11
  202. package/dist/domain/javascript/javascriptSourceParser.js +6 -2
  203. package/dist/domain/javascript/javascriptStaticAnalysis.js +22 -17
  204. package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -3
  205. package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +14 -4
  206. package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +66 -7
  207. package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +28 -16
  208. package/dist/domain/javascript/javascriptStaticAnalysisState.js +3 -2
  209. package/dist/domain/jsonSerialization.js +109 -0
  210. package/dist/domain/jsonValue.js +90 -6
  211. package/dist/domain/localPath.js +6 -0
  212. package/dist/domain/managed/managedApplicationGraph.js +10 -10
  213. package/dist/domain/managed/managedInspectionEvidence.js +16 -0
  214. package/dist/domain/managed/managedMemberComparisonMatch.js +5 -9
  215. package/dist/domain/managed/managedNativeVerification.js +3 -9
  216. package/dist/domain/native/fileOffsetPartialObservation.js +1 -0
  217. package/dist/domain/native/nativeCallObservation.js +33 -11
  218. package/dist/domain/native/nativeCallPartialObservation.js +33 -0
  219. package/dist/domain/process/processCapture.js +8 -4
  220. package/dist/domain/process/processComparison.js +38 -4
  221. package/dist/domain/process/processScenario.js +11 -10
  222. package/dist/domain/propertyListKeys.js +1 -1
  223. package/dist/domain/propertyListXmlText.js +19 -0
  224. package/dist/domain/providerAdapterError.js +1 -1
  225. package/dist/domain/providerOperationHealth.js +54 -0
  226. package/dist/domain/reconstructionVerification.js +6 -43
  227. package/dist/domain/reconstructionVerificationSchemas.js +2 -2
  228. package/dist/domain/staticRuntimeCorrelation.js +6 -43
  229. package/dist/domain/webBundleAnalysis.js +20 -8
  230. package/dist/domain/webBundleAnalyzerInspection.js +13 -8
  231. package/dist/domain/webBundleAnalyzerResult.js +41 -2
  232. package/dist/domain/webRuntime.js +19 -5
  233. package/dist/domain/webSourceLocation.js +1 -0
  234. package/dist/firmware/FirmwareFiles.js +1 -1
  235. package/dist/generatedPackageMetadata.js +1 -1
  236. package/dist/ghidra/GhidraAnalysisProfile.js +5 -0
  237. package/dist/ghidra/GhidraClient.js +47 -14
  238. package/dist/ghidra/GhidraDiagnostics.js +7 -7
  239. package/dist/ghidra/GhidraFunctionValues.js +10 -7
  240. package/dist/ghidra/GhidraInstallation.js +18 -21
  241. package/dist/ghidra/GhidraInventoryValues.js +14 -4
  242. package/dist/ghidra/GhidraJavaLaunch.js +62 -0
  243. package/dist/ghidra/GhidraLauncher.js +33 -16
  244. package/dist/ghidra/GhidraProviderCapabilities.js +14 -2
  245. package/dist/ghidra/GhidraProviderClient.js +23 -12
  246. package/dist/ghidra/GhidraSessionError.js +11 -7
  247. package/dist/ghidra/GhidraTargetSnapshot.js +47 -12
  248. package/dist/ghidra/GhidraTransport.js +20 -0
  249. package/dist/hopper/BridgeLauncher.js +31 -11
  250. package/dist/hopper/HopperAnalysisProfile.js +62 -4
  251. package/dist/hopper/HopperCleanup.js +15 -3
  252. package/dist/hopper/HopperClient.js +222 -14
  253. package/dist/hopper/HopperFileOffset.js +237 -0
  254. package/dist/hopper/HopperMachOImage.js +196 -0
  255. package/dist/hopper/HopperOperationStage.js +6 -0
  256. package/dist/hopper/HopperProvider.js +63 -9
  257. package/dist/hopper/HopperProviderCapabilities.js +19 -1
  258. package/dist/hopper/HopperRegexSearch.js +137 -0
  259. package/dist/hopper/HopperRegexWorker.js +27 -0
  260. package/dist/hopper/HopperRequestQueue.js +68 -19
  261. package/dist/hopper/HopperTargetLease.js +3 -2
  262. package/dist/inspector/JavaScriptRuntimeScope.js +9 -1
  263. package/dist/inspector/V8InspectorCaptureProjection.js +8 -3
  264. package/dist/inspector/V8InspectorProvider.js +47 -11
  265. package/dist/javascript/sourceMaps/DecodedSourceMap.js +47 -0
  266. package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -0
  267. package/dist/javascript/sourceMaps/SourceMapFormat.js +79 -31
  268. package/dist/javascript/sourceMaps/TraceSourceMap.js +14 -28
  269. package/dist/mcpDoctor.js +62 -2
  270. package/dist/native/AppleDispatchCoverage.js +47 -6
  271. package/dist/native/AppleDispatchDecodeFacts.js +6 -0
  272. package/dist/native/AppleDispatchMetadata.js +6 -3
  273. package/dist/native/AppleMachoFixups.js +48 -24
  274. package/dist/native/AppleMachoSelection.js +32 -16
  275. package/dist/native/AppleObjcCategories.js +6 -2
  276. package/dist/native/AppleObjcDispatchFacets.js +5 -2
  277. package/dist/native/AppleObjcProperties.js +2 -1
  278. package/dist/native/AppleSwiftDispatchFacets.js +27 -21
  279. package/dist/native/AppleSwiftVtables.js +21 -15
  280. package/dist/native/CommandRunner.js +81 -84
  281. package/dist/native/LldbCallTracer.js +365 -102
  282. package/dist/native/LldbRetainedObservations.js +178 -0
  283. package/dist/native/NativeCallObservation.js +62 -3
  284. package/dist/native/NativeMacOSProvider.js +98 -17
  285. package/dist/native/SignatureTargetBinding.js +155 -0
  286. package/dist/native/parsers/lipo.js +71 -1
  287. package/dist/native/pwndbg/PwndbgCoreContext.js +25 -5
  288. package/dist/native/pwntools/RecordedCrashStructureBindings.js +2 -0
  289. package/dist/process/DarwinProcessRunTokenReader.js +8 -9
  290. package/dist/process/ExecFileOutput.js +20 -0
  291. package/dist/process/OwnedCommand.js +7 -2
  292. package/dist/process/ProcessOwnership.js +203 -53
  293. package/dist/process/ProcessOwnershipObservation.js +49 -4
  294. package/dist/process/ProviderProcess.js +38 -7
  295. package/dist/process/capture/ProcessCaptureError.js +2 -2
  296. package/dist/process/capture/ProcessCaptureLifecycle.js +100 -34
  297. package/dist/server/EvidenceMcpServer.js +146 -0
  298. package/dist/server/createServer.js +15 -8
  299. package/dist/server/registerAndroidTools.js +2 -4
  300. package/dist/server/registerApplicationTools/helpers.js +2 -4
  301. package/dist/server/registerArtifactComparisonTool.js +11 -7
  302. package/dist/server/registerBinaryDiagnosticsTools.js +2 -4
  303. package/dist/server/registerBrowserScenarioTool.js +2 -4
  304. package/dist/server/registerBrowserTools.js +2 -4
  305. package/dist/server/registerBundleComparisonTool.js +2 -3
  306. package/dist/server/registerCloseLifecycleTool.js +8 -18
  307. package/dist/server/registerElectronTools.js +2 -4
  308. package/dist/server/registerEnhancedTools.js +18 -59
  309. package/dist/server/registerEvidenceTools.js +2 -4
  310. package/dist/server/registerEvmTools.js +2 -4
  311. package/dist/server/registerFirmwareTools.js +2 -4
  312. package/dist/server/registerFunctionComparisonTool.js +10 -6
  313. package/dist/server/registerInvestigationTools.js +7 -7
  314. package/dist/server/registerJavaScriptRecoveryTool.js +2 -4
  315. package/dist/server/registerJavaScriptRuntimeObservationTools.js +2 -4
  316. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +2 -4
  317. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +2 -4
  318. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +2 -4
  319. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +2 -4
  320. package/dist/server/registerOfficialTools.js +2 -4
  321. package/dist/server/registerProcessComparisonTool.js +71 -45
  322. package/dist/server/registerRecordedCrashTools.js +2 -4
  323. package/dist/server/registerSessionRecordTools.js +5 -6
  324. package/dist/server/registerSessionStatusTool.js +17 -3
  325. package/dist/server/registerSessionTools.js +11 -3
  326. package/dist/server/registerWebModuleTool.js +2 -4
  327. package/dist/server/registerWebNetworkCaptureTool.js +2 -4
  328. package/dist/server/registerWebRuntimeTools.js +3 -7
  329. package/dist/server/registerWebScriptTool.js +2 -4
  330. package/dist/server/registerWebSourceLocationTool.js +2 -4
  331. package/dist/server/runDerivedOperation.js +15 -3
  332. package/dist/server/sessionAvailabilityPolicy.js +0 -1
  333. package/dist/server/toolRegistrationOptions.js +2 -2
  334. package/dist/server/toolResult.js +62 -10
  335. package/dist/server/toolResultEncoding.js +33 -0
  336. package/install.sh +1 -1
  337. package/native/windows/build/manifest.json +1 -1
  338. package/package.json +8 -3
  339. package/scripts/electron-active-hook-boundaries.cjs +18 -20
  340. package/scripts/electron-active-hook.cjs +241 -37
  341. package/skills/reverse-engineer-anything/SKILL.md +11 -3
  342. package/skills/reverse-engineer-anything/references/javascript-applications.md +8 -1
  343. package/dist/ghidra/GhidraDefaults.js +0 -2
@@ -1,5 +1,8 @@
1
- import { HopperCancelledError, HopperProcessError, HopperProtocolError, HopperRemoteError, } from "../domain/hopperErrors.js";
1
+ import { hopperOperationStage } from "./HopperOperationStage.js";
2
+ import { HopperCancelledError, HopperProcessError, HopperProtocolError, HopperRemoteError, HopperTimeoutError, } from "../domain/hopperErrors.js";
2
3
  import { err } from "../domain/result.js";
4
+ /** Node timer ceiling; larger deadlines would be clamped instead of honored. */
5
+ const MAX_TIMEOUT_MS = 2_147_483_647;
3
6
  /** FIFO that keeps the wire serialized until Hopper actually replies. */
4
7
  export class HopperRequestQueue {
5
8
  send;
@@ -8,10 +11,18 @@ export class HopperRequestQueue {
8
11
  constructor(send) {
9
12
  this.send = send;
10
13
  }
11
- /** Queue one request until Hopper replies or the caller cancels it. */
14
+ /** Settle the caller on reply, cancellation, or deadline; retain active wire work. */
12
15
  run(id, method, params, options) {
13
16
  if (options.signal?.aborted === true)
14
17
  return Promise.resolve(err(new HopperCancelledError()));
18
+ const timeoutMs = options.timeoutMs;
19
+ if (timeoutMs !== undefined &&
20
+ (!Number.isInteger(timeoutMs) ||
21
+ timeoutMs < 0 ||
22
+ timeoutMs > MAX_TIMEOUT_MS))
23
+ return Promise.resolve(err(new HopperProtocolError(`Hopper timeoutMs must be an integer between 0 and ${String(MAX_TIMEOUT_MS)}`)));
24
+ if (timeoutMs === 0)
25
+ return Promise.resolve(err(new HopperTimeoutError(0, method, id, "not_started", hopperOperationStage(method))));
15
26
  return new Promise((resolve) => {
16
27
  let entry;
17
28
  const onAbort = options.signal === undefined ? undefined : () => this.#cancel(entry);
@@ -25,12 +36,18 @@ export class HopperRequestQueue {
25
36
  onAbort,
26
37
  callerState: "waiting",
27
38
  callerSettled: false,
39
+ transmitted: false,
28
40
  startedAt: undefined,
29
41
  heartbeat: undefined,
42
+ deadline: undefined,
43
+ deadlineAt: timeoutMs === undefined ? undefined : performance.now() + timeoutMs,
44
+ timeoutMs,
30
45
  };
31
46
  this.#queue.push(entry);
32
47
  if (onAbort !== undefined)
33
48
  options.signal?.addEventListener("abort", onAbort, { once: true });
49
+ if (timeoutMs !== undefined)
50
+ entry.deadline = setTimeout(() => this.#cancel(entry, this.#timeoutError(entry, timeoutMs)), timeoutMs);
34
51
  if (this.#active !== undefined || this.#queue.length > 1)
35
52
  this.#report(entry, `${method} queued behind ${String(this.#size() - 1)} Hopper request(s)`);
36
53
  this.#drain();
@@ -41,17 +58,10 @@ export class HopperRequestQueue {
41
58
  const entry = this.#active;
42
59
  if (entry === undefined || entry.id !== id)
43
60
  return false;
61
+ this.#expireIfElapsed(entry);
44
62
  this.#releaseWire(entry);
45
- if (!entry.callerSettled) {
46
- const contextual = !result.ok && result.error instanceof HopperRemoteError
47
- ? err(new HopperRemoteError(result.error.code, result.error.safeMessage, {
48
- diagnosticType: result.error.diagnosticType,
49
- operation: entry.method,
50
- requestId: entry.id,
51
- }))
52
- : result;
53
- this.#settleCaller(entry, contextual, "waiting");
54
- }
63
+ if (!entry.callerSettled)
64
+ this.#settleCaller(entry, result.ok ? result : err(withRequestContext(result.error, entry)), "waiting");
55
65
  this.#active = undefined;
56
66
  this.#drain();
57
67
  return true;
@@ -120,6 +130,10 @@ export class HopperRequestQueue {
120
130
  const entry = this.#queue.shift();
121
131
  if (entry === undefined)
122
132
  return;
133
+ if (this.#expireIfElapsed(entry)) {
134
+ this.#drain();
135
+ return;
136
+ }
123
137
  if (entry.signal?.aborted === true) {
124
138
  this.#settleCaller(entry, err(new HopperCancelledError()), "cancelled");
125
139
  this.#drain();
@@ -128,6 +142,17 @@ export class HopperRequestQueue {
128
142
  this.#active = entry;
129
143
  entry.startedAt = performance.now();
130
144
  this.#report(entry, `${entry.method} started on Hopper's serial bridge`);
145
+ this.#expireIfElapsed(entry);
146
+ // A progress observer may cancel or tear down synchronously before send.
147
+ // No native work exists yet, so it is safe to release this reserved slot.
148
+ if (this.#active !== entry || entry.callerSettled) {
149
+ if (this.#active === entry) {
150
+ this.#active = undefined;
151
+ this.#releaseWire(entry);
152
+ this.#drain();
153
+ }
154
+ return;
155
+ }
131
156
  entry.heartbeat = setInterval(() => {
132
157
  if (entry.callerSettled || entry.startedAt === undefined)
133
158
  return;
@@ -135,30 +160,44 @@ export class HopperRequestQueue {
135
160
  this.#report(entry, `${entry.method} is still running in Hopper (${String(elapsed)} ms elapsed)`);
136
161
  }, 1_000);
137
162
  try {
138
- this.send({ id: entry.id, method: entry.method, params: entry.params }, () => this.accept(entry.id, err(new HopperProcessError(null))));
163
+ entry.transmitted = true;
164
+ this.send({ id: entry.id, method: entry.method, params: entry.params }, () => this.accept(entry.id, err(new HopperProcessError(null, undefined, entry.method, entry.id, "unreachable", hopperOperationStage(entry.method)))));
139
165
  }
140
166
  catch (cause) {
141
167
  this.accept(entry.id, err(new HopperProtocolError("Hopper socket write failed", { cause })));
142
168
  }
143
169
  }
144
- #cancel(entry) {
170
+ #timeoutError(entry, timeoutMs) {
171
+ return new HopperTimeoutError(timeoutMs, entry.method, entry.id, entry.transmitted ? "busy" : "not_started", hopperOperationStage(entry.method));
172
+ }
173
+ #expireIfElapsed(entry) {
174
+ if (entry.deadlineAt !== undefined &&
175
+ entry.timeoutMs !== undefined &&
176
+ performance.now() >= entry.deadlineAt)
177
+ this.#settleCaller(entry, err(this.#timeoutError(entry, entry.timeoutMs)), "cancelled");
178
+ return entry.callerSettled;
179
+ }
180
+ #cancel(entry, error = new HopperCancelledError()) {
145
181
  if (entry.callerSettled)
146
182
  return;
147
183
  if (this.#active === entry) {
148
- this.#settleCaller(entry, err(new HopperCancelledError()), "cancelled");
184
+ this.#settleCaller(entry, err(error), "cancelled");
149
185
  return;
150
186
  }
151
187
  const index = this.#queue.indexOf(entry);
152
188
  if (index < 0)
153
189
  return;
154
190
  this.#queue.splice(index, 1);
155
- this.#settleCaller(entry, err(new HopperCancelledError()), "cancelled");
191
+ this.#settleCaller(entry, err(error), "cancelled");
156
192
  }
157
193
  #settleCaller(entry, result, callerState) {
158
194
  if (entry.callerSettled)
159
195
  return;
160
196
  entry.callerSettled = true;
161
197
  entry.callerState = callerState;
198
+ if (entry.deadline !== undefined)
199
+ clearTimeout(entry.deadline);
200
+ entry.deadline = undefined;
162
201
  if (this.#active === entry && entry.heartbeat !== undefined) {
163
202
  clearInterval(entry.heartbeat);
164
203
  entry.heartbeat = undefined;
@@ -195,6 +234,16 @@ export class HopperRequestQueue {
195
234
  }
196
235
  }
197
236
  }
198
- const withRequestContext = (error, entry) => error instanceof HopperProcessError
199
- ? new HopperProcessError(error.exitCode, error.diagnostic, entry.method, entry.id)
200
- : error;
237
+ const withRequestContext = (error, entry) => {
238
+ if (error instanceof HopperProcessError)
239
+ return new HopperProcessError(error.exitCode, error.diagnostic, error.operation ?? entry.method, error.requestId ?? entry.id, error.providerState, error.operation === undefined && error.stage === "connection"
240
+ ? hopperOperationStage(entry.method)
241
+ : error.stage);
242
+ if (error instanceof HopperRemoteError)
243
+ return new HopperRemoteError(error.code, error.safeMessage, {
244
+ diagnosticType: error.diagnosticType,
245
+ operation: error.operation ?? entry.method,
246
+ requestId: error.requestId ?? entry.id,
247
+ });
248
+ return error;
249
+ };
@@ -4,10 +4,11 @@ import { createConnection, createServer, } from "node:net";
4
4
  import { join, resolve } from "node:path";
5
5
  import { HopperStartError } from "../domain/hopperErrors.js";
6
6
  import { safeParseJson } from "../domain/safeJson.js";
7
- const leaseDirectory = join("/tmp", `rea-hopper-${process.getuid?.() ?? 0}`);
7
+ /** Shared private parent of per-target lease sockets; it outlives individual sessions. */
8
+ export const HOPPER_TARGET_LEASE_DIRECTORY = join("/tmp", `rea-hopper-${process.getuid?.() ?? 0}`);
8
9
  /** Keep two REA processes from opening duplicate Hopper documents for one target/profile. */
9
10
  export const acquireHopperTargetLease = async (input) => {
10
- const directory = input.directory ?? leaseDirectory;
11
+ const directory = input.directory ?? HOPPER_TARGET_LEASE_DIRECTORY;
11
12
  await ensureLeaseDirectory(directory);
12
13
  // The canonical path only derives the lease key; it never authorizes access.
13
14
  // Common expected `realpath` failures include a missing target or path
@@ -1,6 +1,12 @@
1
1
  import { pathToFileURL } from "node:url";
2
2
  import { browserOriginSchema, sanitizeBrowserUrl, } from "../domain/browserObservation.js";
3
+ import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
3
4
  import { authorizedElectronFile } from "../browser/ElectronFileScope.js";
5
+ /** Throw the operation's typed cancellation error whenever its signal is aborted. */
6
+ export const throwIfRuntimeObservationCancelled = (signal) => {
7
+ if (signal?.aborted === true)
8
+ throw new AnalysisCancelledError("observe_javascript_runtime");
9
+ };
4
10
  /**
5
11
  * Wire bucket for inspector script exclusions. The V8 observation schemas
6
12
  * fix the `unsupported_location` key, so every canonical denial reason maps
@@ -13,7 +19,8 @@ export const inspectorExclusionKey = (reason) => {
13
19
  return "unsupported_location";
14
20
  };
15
21
  /** Resolve a protocol location exposed by the explicitly selected Inspector endpoint. */
16
- export const authorizeRuntimeLocation = async (value) => {
22
+ export const authorizeRuntimeLocation = async (value, signal) => {
23
+ throwIfRuntimeObservationCancelled(signal);
17
24
  if (value.startsWith("node:") && value.length > 0)
18
25
  return {
19
26
  allowed: true,
@@ -21,6 +28,7 @@ export const authorizeRuntimeLocation = async (value) => {
21
28
  };
22
29
  if (value.startsWith("file:")) {
23
30
  const filePath = await authorizedElectronFile(value);
31
+ throwIfRuntimeObservationCancelled(signal);
24
32
  return filePath === undefined
25
33
  ? { allowed: false, reason: "not_approved" }
26
34
  : { allowed: true, location: { kind: "file", file_path: filePath } };
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { authorizeRuntimeLocation, inspectorExclusionKey, } from "./JavaScriptRuntimeScope.js";
2
+ import { authorizeRuntimeLocation, inspectorExclusionKey, throwIfRuntimeObservationCancelled, } from "./JavaScriptRuntimeScope.js";
3
3
  /** Empty durable-location exclusion counters. */
4
4
  export const createInspectorExclusionCounts = () => ({
5
5
  unsupported_location: 0,
@@ -15,7 +15,8 @@ export const describeInspectorTargetLimitations = () => [
15
15
  ];
16
16
  const LOCATION_AUTHORIZATION_WORKERS = 8;
17
17
  /** Canonically authorize, deduplicate, and sort one bounded raw capture. */
18
- export const finalizeInspectorCapture = async ({ input, runtime, target, state, authorizeLocation = authorizeRuntimeLocation, }) => {
18
+ export const finalizeInspectorCapture = async ({ input, runtime, target, state, authorizeLocation = authorizeRuntimeLocation, signal, }) => {
19
+ throwIfRuntimeObservationCancelled(signal);
19
20
  const exclusions = createInspectorExclusionCounts();
20
21
  const scripts = new Map();
21
22
  const draftsByUrl = new Map();
@@ -31,6 +32,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
31
32
  const worker = async () => {
32
33
  try {
33
34
  for (;;) {
35
+ throwIfRuntimeObservationCancelled(signal);
34
36
  if (stopScheduling)
35
37
  return;
36
38
  const index = nextGroup;
@@ -41,8 +43,9 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
41
43
  const [rawUrl, drafts] = group;
42
44
  authorizedGroups[index] = {
43
45
  drafts,
44
- decision: await authorizeLocation(rawUrl),
46
+ decision: await authorizeLocation(rawUrl, signal),
45
47
  };
48
+ throwIfRuntimeObservationCancelled(signal);
46
49
  }
47
50
  }
48
51
  catch (cause) {
@@ -52,6 +55,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
52
55
  };
53
56
  const workerResults = await Promise.allSettled(Array.from({ length: Math.min(LOCATION_AUTHORIZATION_WORKERS, groups.length) }, worker));
54
57
  const failedWorker = workerResults.find((result) => result.status === "rejected");
58
+ throwIfRuntimeObservationCancelled(signal);
55
59
  if (failedWorker?.status === "rejected")
56
60
  throw failedWorker.reason;
57
61
  for (const { drafts, decision } of authorizedGroups) {
@@ -81,6 +85,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
81
85
  : left.context_key > right.context_key
82
86
  ? 1
83
87
  : 0);
88
+ throwIfRuntimeObservationCancelled(signal);
84
89
  return {
85
90
  runtime,
86
91
  target: {
@@ -12,6 +12,11 @@ import { discoverV8Inspector, } from "./V8InspectorEndpoint.js";
12
12
  /** Public identity committed by passive V8 Inspector observations. */
13
13
  export { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
14
14
  import { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
15
+ /** Maximum decoded CDP message accepted while observing one Inspector target. */
16
+ export const INSPECTOR_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
17
+ /** Aggregate retained metadata budget, including an estimate of JS object overhead. */
18
+ export const INSPECTOR_MAX_RETAINED_METADATA_BYTES = 8 * 1024 * 1024;
19
+ const RETAINED_OBJECT_OVERHEAD_BYTES = 512;
15
20
  /** Attach-only provider; sends only Runtime.enable and Debugger.enable. */
16
21
  export class V8InspectorProvider {
17
22
  identity() {
@@ -61,7 +66,7 @@ export class V8InspectorProvider {
61
66
  const target = await authorizedTarget(discovery.targets, input, discovery.runtime.product);
62
67
  if (input.runtime_kind !== undefined)
63
68
  assertRuntimeKind(target, input.runtime_kind);
64
- connection = await CdpConnection.connect(target.webSocketUrl, "observe_javascript_runtime", options.signal);
69
+ connection = await CdpConnection.connect(target.webSocketUrl, "observe_javascript_runtime", options.signal, { maxPayloadBytes: INSPECTOR_MAX_PAYLOAD_BYTES });
65
70
  const state = emptyCaptureState();
66
71
  const removeListener = connection.onEvent((event) => ingestEvent(event, input, state));
67
72
  try {
@@ -77,6 +82,7 @@ export class V8InspectorProvider {
77
82
  runtime: discovery.runtime,
78
83
  target,
79
84
  state,
85
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
80
86
  });
81
87
  outcome = ok(javascriptRuntimeObservationSchema.parse(result));
82
88
  }
@@ -206,15 +212,24 @@ const ingestScript = (event, input, state) => {
206
212
  length: nonnegativeInteger(value?.length),
207
213
  isModule: value?.isModule === true,
208
214
  };
209
- const bytes = metadataBytes(draft);
210
- retainEvent(state, bytes);
215
+ const bytes = retainedMetadataBytes(draft);
216
+ if (!retainEvent(state, bytes))
217
+ return;
211
218
  state.scripts.push(draft);
212
219
  };
213
220
  const ingestContext = (event, input, state) => {
214
221
  if (event.method === "Runtime.executionContextsCleared") {
215
- for (const context of state.contexts.values())
216
- context.state = "cleared";
217
- retainEvent(state, 0);
222
+ let byteDelta = 0;
223
+ for (const context of state.contexts.values()) {
224
+ byteDelta +=
225
+ retainedMetadataBytes({ ...context, state: "cleared" }) -
226
+ retainedMetadataBytes(context);
227
+ }
228
+ if (replaceRetainedMetadata(state, byteDelta)) {
229
+ for (const context of state.contexts.values())
230
+ context.state = "cleared";
231
+ retainEvent(state, 0);
232
+ }
218
233
  return;
219
234
  }
220
235
  const parameters = recordValue(event.params);
@@ -238,18 +253,33 @@ const ingestContext = (event, input, state) => {
238
253
  : "destroyed",
239
254
  origin: origin === "" ? null : origin,
240
255
  };
241
- const bytes = metadataBytes(draft);
242
- retainEvent(state, bytes);
256
+ const previous = state.contexts.get(key);
257
+ const bytes = retainedMetadataBytes(draft) -
258
+ (previous === undefined ? 0 : retainedMetadataBytes(previous));
259
+ if (!retainEvent(state, bytes))
260
+ return;
243
261
  state.contexts.set(key, draft);
244
262
  };
245
263
  const retainEvent = (state, bytes) => {
246
- state.metadataBytes += bytes;
264
+ if (!replaceRetainedMetadata(state, bytes))
265
+ return false;
247
266
  state.eventsRetained += 1;
267
+ return true;
268
+ };
269
+ const replaceRetainedMetadata = (state, byteDelta) => {
270
+ if (state.metadataBytes + byteDelta > INSPECTOR_MAX_RETAINED_METADATA_BYTES) {
271
+ state.eventsDropped += 1;
272
+ state.truncated = true;
273
+ state.truncationReasons.add("retained_metadata_budget_exceeded");
274
+ return false;
275
+ }
276
+ state.metadataBytes += byteDelta;
277
+ return true;
248
278
  };
249
279
  const waitForCapture = async (connection, observationMs, signal) => {
250
280
  let removeDisconnect = () => undefined;
251
281
  const disconnected = new Promise((_resolve, reject) => {
252
- removeDisconnect = connection.onDisconnect(() => reject(new BrowserObservationError("observe_javascript_runtime", "disconnected")));
282
+ removeDisconnect = connection.onDisconnect((error) => reject(error));
253
283
  });
254
284
  try {
255
285
  await Promise.race([
@@ -261,7 +291,13 @@ const waitForCapture = async (connection, observationMs, signal) => {
261
291
  removeDisconnect();
262
292
  }
263
293
  };
264
- const metadataBytes = (value) => Buffer.byteLength(JSON.stringify(value));
294
+ const retainedMetadataBytes = (value) => {
295
+ const strings = "rawUrl" in value
296
+ ? [value.rawUrl, value.rawUrl, value.executionContextKey, value.cdpHash]
297
+ : [value.contextKey, value.origin];
298
+ return (RETAINED_OBJECT_OVERHEAD_BYTES +
299
+ strings.reduce((total, text) => total + (text?.length ?? 0) * 2, 0));
300
+ };
265
301
  const nonnegativeInteger = (value) => {
266
302
  const parsed = numberValue(value);
267
303
  return parsed === undefined ? 0 : Math.max(0, Math.trunc(parsed));
@@ -0,0 +1,47 @@
1
+ import { TraceMap, decodedMappings } from "@jridgewell/trace-mapping";
2
+ import { SourceMapFormatFailure, } from "./SourceMapFormat.js";
3
+ /** Determine whether a leaf-local mapping falls before its enclosing boundary. */
4
+ export const isBeforeSourceMapLeafStop = (leaf, line, column) => {
5
+ if (leaf.stop === null)
6
+ return true;
7
+ const generatedLine = leaf.offset.line + line;
8
+ const generatedColumn = column + (line === 0 ? leaf.offset.column : 0);
9
+ return (generatedLine < leaf.stop.line ||
10
+ (generatedLine === leaf.stop.line && generatedColumn < leaf.stop.column));
11
+ };
12
+ /** Resolve one declaration with the same pinned upstream implementation used by full maps. */
13
+ export const resolveSourceMapSource = (source, sourceRoot, mapUrl) => {
14
+ const map = new TraceMap({
15
+ version: 3,
16
+ names: [],
17
+ sources: [source ?? ""],
18
+ ...(sourceRoot == null ? {} : { sourceRoot }),
19
+ mappings: "",
20
+ }, mapUrl);
21
+ const resolved = map.resolvedSources[0];
22
+ if (resolved === undefined)
23
+ throw new SourceMapFormatFailure("format", "Source-map resolver did not return the requested source identity.");
24
+ return resolved;
25
+ };
26
+ /** Decode leaf mappings and validate producer indices before any consumer publishes them. */
27
+ export const decodeValidatedSourceMapLeaves = (leaves, mapUrl, check) => leaves.map((leaf) => {
28
+ check?.();
29
+ const map = new TraceMap(JSON.stringify(leaf.map), mapUrl);
30
+ const rows = decodedMappings(map);
31
+ for (const [line, row] of rows.entries()) {
32
+ if ((line & 255) === 0)
33
+ check?.();
34
+ for (const segment of row) {
35
+ const generatedLine = leaf.offset.line + line;
36
+ const generatedColumn = segment[0] + (line === 0 ? leaf.offset.column : 0);
37
+ if (!Number.isSafeInteger(generatedLine) ||
38
+ !Number.isSafeInteger(generatedColumn) ||
39
+ ![1, 4, 5].includes(segment.length) ||
40
+ segment.some((value) => !Number.isSafeInteger(value) || value < 0) ||
41
+ (segment.length !== 1 && segment[1] >= leaf.map.sources.length) ||
42
+ (segment.length === 5 && segment[4] >= leaf.map.names.length))
43
+ throw new SourceMapFormatFailure("format", "Decoded local or absolute position, source index or name index is invalid.");
44
+ }
45
+ }
46
+ return { leaf, rows, resolvedSources: map.resolvedSources };
47
+ });
@@ -12,10 +12,26 @@ import { PrivateRuntimeRoot, PrivateRuntimeRootUnavailableError, } from "../../p
12
12
  import { ProviderStartupDeadline } from "../../process/ProviderDeadline.js";
13
13
  import { cleanupOwnedProcessGroup } from "../../process/ProcessOwnership.js";
14
14
  import { ProviderProcessSupervisor, spawnOwnedProviderProcess, } from "../../process/ProviderProcess.js";
15
+ import { prepareProcessOwnershipInspection } from "../../process/ProcessOwnershipObservation.js";
15
16
  const OPERATION = "trace_web_source_location";
16
17
  const PROVIDER = "source-map-decoder";
17
18
  /** Run a trusted codec command and release its process/root before returning output. */
18
19
  export const runSourceMapCommand = async (request, path, options, dependencies) => {
20
+ try {
21
+ await (dependencies.prepareOwnership ?? prepareProcessOwnershipInspection)(options?.signal);
22
+ }
23
+ catch (cause) {
24
+ return err(options?.signal?.aborted === true
25
+ ? new AnalysisCancelledError(OPERATION)
26
+ : new ProviderAdapterError(PROVIDER, OPERATION, {
27
+ cause,
28
+ diagnostics: {
29
+ map_path: path,
30
+ error_message: message(cause),
31
+ phase: "process_ownership_preparation",
32
+ },
33
+ }));
34
+ }
19
35
  const deadline = new ProviderStartupDeadline(WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs, options?.signal);
20
36
  const command = new OwnedCodec(path, dependencies, deadline);
21
37
  let result;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { WEB_SOURCE_MAP_LIMITS } from "../../domain/webSourceLocation.js";
3
+ import { hasValidSourceMapContents } from "../../domain/sourceMapContents.js";
3
4
  const leafSchema = z.object({
4
5
  version: z.literal(3),
5
6
  file: z.string().nullable().optional(),
@@ -48,9 +49,21 @@ const addOffset = (base, offset) => {
48
49
  throw new SourceMapFormatFailure("limit", "Indexed offset exceeds the 262144 generated-row decoder budget.");
49
50
  return result;
50
51
  };
51
- const addSections = (node, pending) => {
52
+ const consumeRecords = (budget, count) => {
53
+ if (count > WEB_SOURCE_MAP_LIMITS.decodedRecords - budget.records)
54
+ throw new SourceMapFormatFailure("limit", `Source-map structure exceeds the ${WEB_SOURCE_MAP_LIMITS.decodedRecords}-record decoder budget.`);
55
+ budget.records += count;
56
+ };
57
+ const addSections = (node, pending, budget, options) => {
58
+ options.check?.();
59
+ const header = mapHeaderSchema.parse(node.value);
60
+ if (!Array.isArray(header.sections))
61
+ throw new SourceMapFormatFailure("format", "Indexed source-map sections must be an array.");
62
+ consumeRecords(budget, header.sections.length);
52
63
  const sections = indexedSchema.parse(node.value).sections;
53
64
  for (const [index, section] of sections.entries()) {
65
+ if ((index & 255) === 0)
66
+ options.check?.();
54
67
  const previous = sections[index - 1];
55
68
  if (previous !== undefined && compare(section.offset, previous.offset) <= 0)
56
69
  throw new SourceMapFormatFailure("format", "Indexed section offsets must be strictly increasing.");
@@ -81,7 +94,7 @@ const addSections = (node, pending) => {
81
94
  });
82
95
  }
83
96
  };
84
- const inspectMappingLayout = (mappings) => {
97
+ const inspectMappingLayout = (mappings, check) => {
85
98
  let lines = 1;
86
99
  let segments = 0;
87
100
  let components = 0;
@@ -94,7 +107,10 @@ const inspectMappingLayout = (mappings) => {
94
107
  if (inSegment && ![1, 4, 5].includes(components))
95
108
  throw new SourceMapFormatFailure("format", "Source-map segment requires one, four or five VLQ components.");
96
109
  };
110
+ let index = 0;
97
111
  for (const character of mappings) {
112
+ if ((index++ & 0x3fff) === 0)
113
+ check?.();
98
114
  if (character === "," || character === ";") {
99
115
  finish();
100
116
  inSegment = false;
@@ -123,6 +139,35 @@ const inspectMappingLayout = (mappings) => {
123
139
  finish();
124
140
  return { lines, segments };
125
141
  };
142
+ const inspectLeaf = (node, budget, options) => {
143
+ options.check?.();
144
+ consumeRecords(budget, 1);
145
+ if (typeof node.value !== "object" ||
146
+ node.value === null ||
147
+ Array.isArray(node.value))
148
+ return leafSchema.parse(node.value);
149
+ const rawMap = node.value;
150
+ const rawSources = rawMap.sources;
151
+ const rawNames = rawMap.names;
152
+ const rawContents = rawMap.sourcesContent;
153
+ consumeRecords(budget, Array.isArray(rawSources) ? rawSources.length : 0);
154
+ consumeRecords(budget, Array.isArray(rawNames) ? rawNames.length : 0);
155
+ consumeRecords(budget, Array.isArray(rawContents) ? rawContents.length : 0);
156
+ options.check?.();
157
+ if (options.profile === "browser-collection" && !Array.isArray(rawNames))
158
+ throw new SourceMapFormatFailure("format", "Source-map leaf requires a names array.");
159
+ if (options.profile === "browser-collection" &&
160
+ !hasValidSourceMapContents(Array.isArray(rawSources) ? rawSources.length : 0, rawContents))
161
+ throw new SourceMapFormatFailure("format", "Source-map sourcesContent must align with its source declarations.");
162
+ options.check?.();
163
+ const leaf = leafSchema.parse(node.value);
164
+ const layout = inspectMappingLayout(leaf.mappings, options.check);
165
+ consumeRecords(budget, layout.segments + layout.lines);
166
+ if (layout.segments > WEB_SOURCE_MAP_LIMITS.decodedSegments ||
167
+ node.offset.line + layout.lines > WEB_SOURCE_MAP_LIMITS.decodedRows)
168
+ throw new SourceMapFormatFailure("limit", "Source-map layout exceeds the 262144 segment/generated-row decoder budget.");
169
+ return leaf;
170
+ };
126
171
  /** Validate structure before allocation, retaining raw declarations and section boundaries. */
127
172
  export const inspectSourceMap = (text) => {
128
173
  const withoutBom = text.replace(/^\uFEFF/u, "");
@@ -130,53 +175,56 @@ export const inspectSourceMap = (text) => {
130
175
  ? withoutBom.slice(withoutBom.indexOf("\n") + 1)
131
176
  : withoutBom;
132
177
  const value = JSON.parse(jsonText);
178
+ const inspected = inspectSourceMapValue(value);
179
+ const normalized = inspected.format === "regular"
180
+ ? inspected.leaves[0]?.map
181
+ : {
182
+ version: 3,
183
+ sections: inspected.leaves.map((leaf) => ({
184
+ offset: leaf.offset,
185
+ map: leaf.map,
186
+ })),
187
+ };
188
+ return {
189
+ jsonText: JSON.stringify(normalized),
190
+ ...inspected,
191
+ };
192
+ };
193
+ /** Return validated leaves when a consumer needs decoded rows after preflight. */
194
+ export const inspectSourceMapValue = (value, options = {}) => {
195
+ options.check?.();
133
196
  const root = mapHeaderSchema.parse(value);
134
197
  const declarations = [];
135
198
  const leaves = [];
136
199
  const pending = [
137
200
  { value, path: [], offset: { line: 0, column: 0 }, stop: null },
138
201
  ];
139
- let segmentCount = 0;
202
+ const budget = options.budget ?? { records: 0 };
140
203
  while (pending.length > 0) {
204
+ options.check?.();
141
205
  const node = pending.pop();
142
206
  if (node === undefined)
143
207
  break;
144
208
  if (node.path.length > WEB_SOURCE_MAP_LIMITS.sectionDepth)
145
209
  throw new SourceMapFormatFailure("limit", "Indexed source-map nesting exceeds the 64-level decoder stack budget.");
146
210
  if (mapHeaderSchema.parse(node.value).sections !== undefined) {
147
- addSections(node, pending);
211
+ addSections(node, pending, budget, options);
148
212
  continue;
149
213
  }
150
- const leaf = leafSchema.parse(node.value);
151
- const layout = inspectMappingLayout(leaf.mappings);
152
- segmentCount += layout.segments;
153
- if (segmentCount > WEB_SOURCE_MAP_LIMITS.decodedSegments ||
154
- node.offset.line + layout.lines > WEB_SOURCE_MAP_LIMITS.decodedRows)
155
- throw new SourceMapFormatFailure("limit", "Source-map layout exceeds the 262144 segment/generated-row decoder budget.");
214
+ const leaf = inspectLeaf(node, budget, options);
156
215
  leaves.push({ map: leaf, offset: node.offset, stop: node.stop });
157
- for (const [sourceIndex, source] of leaf.sources.entries())
158
- declarations.push({
159
- sectionPath: node.path,
160
- sourceIndex,
161
- source,
162
- root: leaf.sourceRoot ?? null,
163
- content: leaf.sourcesContent?.[sourceIndex] ?? null,
164
- ignored: (leaf.ignoreList ?? leaf.x_google_ignoreList ?? []).includes(sourceIndex),
165
- });
216
+ if (options.profile !== "browser-collection")
217
+ for (const [sourceIndex, source] of leaf.sources.entries())
218
+ declarations.push({
219
+ sectionPath: node.path,
220
+ sourceIndex,
221
+ source,
222
+ root: leaf.sourceRoot ?? null,
223
+ content: leaf.sourcesContent?.[sourceIndex] ?? null,
224
+ ignored: (leaf.ignoreList ?? leaf.x_google_ignoreList ?? []).includes(sourceIndex),
225
+ });
166
226
  }
167
- // Absolute leaves adapt nested first-line column offsets before upstream AnyMap.
168
- // Raw map bytes and the original section/source identities are retained separately.
169
- const normalized = root.sections === undefined
170
- ? leaves[0]?.map
171
- : {
172
- version: 3,
173
- sections: leaves.map((leaf) => ({
174
- offset: leaf.offset,
175
- map: leaf.map,
176
- })),
177
- };
178
227
  return {
179
- jsonText: JSON.stringify(normalized),
180
228
  format: root.sections === undefined ? "regular" : "indexed",
181
229
  reportedFile: root.file ?? null,
182
230
  declarations,