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,11 +1,10 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { createAnalysisExecution, } from "../application/AnalysisProvider.js";
3
- import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisTimeoutError, } from "../domain/analysisErrorCore.js";
3
+ import { AnalysisCancelledError, AnalysisArtifactChangedError, AnalysisAccessDeniedError, AnalysisCapabilityUnavailableError, AnalysisInputError, AnalysisResourceConstraintError, AnalysisTimeoutError, } from "../domain/analysisErrorCore.js";
4
4
  import {} from "../domain/analysisErrorBase.js";
5
5
  import { ProviderAdapterError } from "../domain/providerAdapterError.js";
6
6
  import { err, ok } from "../domain/result.js";
7
7
  import { GhidraClient } from "./GhidraClient.js";
8
- import { GHIDRA_STARTUP_TIMEOUT_MS } from "./GhidraDefaults.js";
9
8
  import { isGhidraFunctionOperation, parseGhidraFunctionInput, parseGhidraFunctionResult, } from "./GhidraFunctionValues.js";
10
9
  import { isGhidraInventoryOperation, parseGhidraInventoryInput, parseGhidraInventoryResult, } from "./GhidraInventoryValues.js";
11
10
  import { ghidraInstallationDiagnostics, } from "./GhidraInstallation.js";
@@ -55,12 +54,11 @@ export const createGhidraProviderClient = (input) => {
55
54
  ]
56
55
  : [];
57
56
  const client = clientFactory({
57
+ startupTimeoutMs: config.ghidraStartupTimeoutMs,
58
58
  platform: installation.platform,
59
59
  launcher: new GhidraHeadlessLauncher({
60
60
  analyzeHeadlessPath: prerequisites.value.analyzeHeadlessPath,
61
- ...(config.ghidraJavaHome === undefined
62
- ? {}
63
- : { javaHome: config.ghidraJavaHome }),
61
+ javaHome: prerequisites.value.javaHome,
64
62
  bridgeScriptPath: fileURLToPath(new URL("../../bridge/ghidra/ReaGhidraBridge.java", import.meta.url)),
65
63
  ...(target.format === "dos-mz" ? { dosMz: true } : {}),
66
64
  ...(target.format === "dos-com" ? { dosCom: true } : {}),
@@ -110,7 +108,7 @@ export const createGhidraProviderClient = (input) => {
110
108
  if (operation === "health") {
111
109
  const started = await client.start(options?.signal);
112
110
  if (!started.ok)
113
- return err(projectSessionError(operation, started.error));
111
+ return err(projectSessionError(operation, started.error, config.ghidraStartupTimeoutMs));
114
112
  const failed = await checkExtensions(operation, started.value);
115
113
  if (failed !== undefined)
116
114
  return err(failed);
@@ -127,14 +125,14 @@ export const createGhidraProviderClient = (input) => {
127
125
  if (extensions.length > 0) {
128
126
  const started = await client.start(options?.signal);
129
127
  if (!started.ok)
130
- return err(projectSessionError(operation, started.error));
128
+ return err(projectSessionError(operation, started.error, config.ghidraStartupTimeoutMs));
131
129
  const failed = await checkExtensions(operation, started.value);
132
130
  if (failed !== undefined)
133
131
  return err(failed);
134
132
  }
135
133
  const called = await client.callTool(operation, input.value, options?.signal === undefined ? {} : { signal: options.signal });
136
134
  if (!called.ok)
137
- return err(projectSessionError(operation, called.error));
135
+ return err(projectSessionError(operation, called.error, config.ghidraStartupTimeoutMs));
138
136
  const result = isGhidraFunctionOperation(operation)
139
137
  ? parseGhidraFunctionResult(operation, called.value)
140
138
  : parseGhidraInventoryResult(operation, called.value);
@@ -142,7 +140,7 @@ export const createGhidraProviderClient = (input) => {
142
140
  return result;
143
141
  let normalized = result.value;
144
142
  if (operation === "inspect_native_load_image") {
145
- const attested = await attestGhidraNativeLoadImage(target, operation, result.value, client, (failure) => projectSessionError(operation, failure));
143
+ const attested = await attestGhidraNativeLoadImage(target, operation, result.value, client, (failure) => projectSessionError(operation, failure, config.ghidraStartupTimeoutMs));
146
144
  if (!attested.ok)
147
145
  return attested;
148
146
  normalized = attested.value;
@@ -176,6 +174,7 @@ const ghidraClientPrerequisites = (target, profile, installation) => {
176
174
  return err(new ProviderAdapterError("ghidra", "health"));
177
175
  return ok({
178
176
  analyzeHeadlessPath: installation.analyzeHeadlessPath,
177
+ javaHome: installation.javaHome,
179
178
  providerVersion: installation.providerVersion,
180
179
  profile,
181
180
  });
@@ -184,7 +183,11 @@ const unavailableClient = (failure) => ({
184
183
  execute: () => Promise.resolve(err(failure)),
185
184
  close: () => Promise.resolve(),
186
185
  });
187
- const projectSessionError = (operation, failure) => {
186
+ const projectSessionError = (operation, failure, startupTimeoutMs) => {
187
+ if (failure.cause instanceof AnalysisAccessDeniedError)
188
+ return new AnalysisAccessDeniedError(operation, failure.cause.path, failure.cause.systemCode, { cause: failure });
189
+ if (failure.cause instanceof AnalysisArtifactChangedError)
190
+ return new AnalysisArtifactChangedError(operation, failure.cause.path, failure.cause.reason, { cause: failure });
188
191
  if (operation === "annotate_native_function" &&
189
192
  failure.kind === "remote" &&
190
193
  failure.remoteCode === "invalid_function_name")
@@ -194,12 +197,20 @@ const projectSessionError = (operation, failure) => {
194
197
  if (failure.kind === "cancelled")
195
198
  return new AnalysisCancelledError(operation);
196
199
  if (failure.kind === "timeout" || failure.kind === "analysis_timeout")
197
- return new AnalysisTimeoutError(operation, failure.timeoutMs ?? GHIDRA_STARTUP_TIMEOUT_MS);
200
+ return new AnalysisTimeoutError(operation, failure.timeoutMs ?? startupTimeoutMs);
198
201
  if (failure.kind === "remote" && failure.remoteCode === "decompile_cancelled")
199
202
  return new AnalysisCancelledError(operation);
203
+ if (failure.kind === "remote" &&
204
+ failure.remoteCode === "regex_stack_exhausted")
205
+ return new AnalysisResourceConstraintError(operation, "memory", failure.message, null, {
206
+ cause: failure,
207
+ remediationAction: "Retry this search in literal mode or simplify the regex. The active analysis session and annotations remain available.",
208
+ });
200
209
  if (failure.kind === "remote" &&
201
210
  ["invalid_request", "not_found", "ambiguous"].includes(failure.remoteCode ?? ""))
202
- return new AnalysisInputError(operation, { cause: failure });
211
+ return new AnalysisInputError(operation, { cause: failure }, [
212
+ { path: [], reason: "invalid_value", message: failure.message },
213
+ ]);
203
214
  if (failure.kind === "remote" && failure.remoteCode === "method_unavailable")
204
215
  return new AnalysisCapabilityUnavailableError("ghidra", operation, failure.message);
205
216
  return new ProviderAdapterError("ghidra", operation, {
@@ -14,13 +14,17 @@ export class GhidraSessionError extends Error {
14
14
  }
15
15
  }
16
16
  /** Build a session failure while preserving bounded remote failure metadata. */
17
- const createGhidraSessionError = (failure) => new GhidraSessionError(failure.kind, failure.message, failure.remoteCode === undefined
18
- ? failure.diagnostics
19
- : {
20
- ...failure.diagnostics,
21
- remote_code: failure.redact(failure.remoteCode),
22
- remote_message: failure.message,
23
- }, {
17
+ const createGhidraSessionError = (failure) => new GhidraSessionError(failure.kind, failure.message, {
18
+ ...failure.diagnostics,
19
+ failure_kind: failure.kind,
20
+ failure_message: failure.message,
21
+ ...(failure.remoteCode === undefined
22
+ ? {}
23
+ : {
24
+ remote_code: failure.redact(failure.remoteCode),
25
+ remote_message: failure.message,
26
+ }),
27
+ }, {
24
28
  ...(failure.cause === undefined ? {} : { cause: failure.cause }),
25
29
  ...(failure.timeoutMs === undefined
26
30
  ? {}
@@ -1,7 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
- import { constants, createReadStream } from "node:fs";
3
- import { chmod, copyFile, rm } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { open, rm } from "node:fs/promises";
4
4
  import { extname, join } from "node:path";
5
+ import { Transform } from "node:stream";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { AnalysisArtifactChangedError, AnalysisAccessDeniedError, } from "../domain/analysisErrorCore.js";
5
8
  import { windowsPrivateRuntime } from "../windows/WindowsPrivateRuntime.js";
6
9
  /** Copy one target and require its bytes to match the parsed target identity. */
7
10
  export const createGhidraTargetSnapshot = async (sourcePath, runtimeRoot, expectedSha256, options = {}) => {
@@ -12,29 +15,61 @@ export const createGhidraTargetSnapshot = async (sourcePath, runtimeRoot, expect
12
15
  if ((options.platform ?? process.platform) === "win32") {
13
16
  const observed = await windowsPrivateRuntime(runtimeRoot).snapshot(sourcePath, `target-${expectedSha256.slice(0, 12)}${suffix}`, options.signal);
14
17
  if (observed.sha256 !== expectedSha256)
15
- throw new Error(`Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observed.sha256}`);
18
+ throw new AnalysisArtifactChangedError("open_binary", sourcePath, `Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observed.sha256}`);
16
19
  return {
17
20
  path: snapshotPath,
18
21
  sha256: observed.sha256,
19
22
  admission: { source: observed.source, snapshot: observed.snapshot },
20
23
  };
21
24
  }
25
+ let source;
26
+ let destination;
22
27
  try {
23
- await copyFile(sourcePath, snapshotPath, constants.COPYFILE_EXCL);
24
- await chmod(snapshotPath, 0o600);
28
+ source = await openSnapshotSource(sourcePath);
29
+ if (!(await source.stat()).isFile())
30
+ throw new AnalysisArtifactChangedError("open_binary", sourcePath, "The selected Ghidra source is no longer a regular file");
31
+ destination = await open(snapshotPath, "wx", 0o600);
32
+ await destination.chmod(0o600);
25
33
  const hash = createHash("sha256");
26
- for await (const chunk of createReadStream(snapshotPath))
27
- hash.update(chunk);
34
+ const digest = new Transform({
35
+ transform(chunk, _encoding, done) {
36
+ if (!Buffer.isBuffer(chunk)) {
37
+ done(new TypeError("Ghidra snapshot stream must contain bytes"));
38
+ return;
39
+ }
40
+ hash.update(chunk);
41
+ done(null, chunk);
42
+ },
43
+ });
44
+ await pipeline(source.createReadStream(), digest, destination.createWriteStream(), options.signal === undefined ? {} : { signal: options.signal });
28
45
  const observedSha256 = hash.digest("hex");
29
46
  if (observedSha256 !== expectedSha256)
30
- throw new Error(`Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observedSha256}`);
47
+ throw new AnalysisArtifactChangedError("open_binary", sourcePath, `Ghidra target snapshot digest mismatch: expected ${expectedSha256}, observed ${observedSha256}`);
31
48
  return { path: snapshotPath, sha256: observedSha256 };
32
49
  }
33
50
  catch (cause) {
34
- await rm(snapshotPath, { force: true }).catch((cause) => {
35
- // best-effort cleanup: snapshot removal must not mask the copy failure.
36
- void cause;
37
- });
51
+ if (destination !== undefined)
52
+ await rm(snapshotPath, { force: true }).catch((cause) => {
53
+ // best-effort cleanup: snapshot removal must not mask the copy failure.
54
+ void cause;
55
+ });
56
+ throw cause;
57
+ }
58
+ finally {
59
+ await Promise.all([source?.close(), destination?.close()]);
60
+ }
61
+ };
62
+ const openSnapshotSource = async (path) => {
63
+ try {
64
+ // A path replaced by a FIFO must not block startup before its type is checked.
65
+ return await open(path, constants.O_RDONLY | constants.O_NONBLOCK);
66
+ }
67
+ catch (cause) {
68
+ const code = cause instanceof Error && "code" in cause ? cause.code : undefined;
69
+ if (code === "EACCES" || code === "EPERM")
70
+ throw new AnalysisAccessDeniedError("open_binary", path, code, { cause });
71
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP")
72
+ throw new AnalysisArtifactChangedError("open_binary", path, `The selected Ghidra source could not be opened (${code})`, { cause });
38
73
  throw cause;
39
74
  }
40
75
  };
@@ -1,7 +1,27 @@
1
1
  import { access, open } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { PrivateRuntimeRoot } from "../process/PrivateRuntimeRoot.js";
2
4
  import { readWindowsPrivateRuntimeFile } from "../windows/WindowsPrivateRuntime.js";
3
5
  import { err, ok } from "../domain/result.js";
4
6
  import { GhidraSessionError } from "./GhidraSessionError.js";
7
+ /** Allocate an endpoint that fits the host's Unix sockaddr without moving the project. */
8
+ export const createGhidraEndpoint = async (runtimeRoot, transport, platform) => {
9
+ const path = join(runtimeRoot, transport === "unix-socket" ? "bridge.sock" : "bridge-endpoint.json");
10
+ // sockaddr_un includes a terminating NUL: macOS has 104 bytes, Linux 108.
11
+ const maximumPathBytes = platform === "darwin" ? 103 : 107;
12
+ if (transport !== "unix-socket" ||
13
+ Buffer.byteLength(path, "utf8") <= maximumPathBytes)
14
+ return { endpoint: { transport, path } };
15
+ const socketRoot = await PrivateRuntimeRoot.create({
16
+ parent: "/tmp",
17
+ prefix: "rea-ghidra-socket-",
18
+ platform,
19
+ });
20
+ return {
21
+ endpoint: { transport, path: join(socketRoot.path, "s") },
22
+ socketRoot,
23
+ };
24
+ };
5
25
  /** Observe a ready endpoint, returning null while its bridge is not listening. */
6
26
  export const observeGhidraEndpoint = async (endpoint) => {
7
27
  if (endpoint.transport === "unix-socket") {
@@ -10,6 +10,7 @@ import { spawnOwnedProviderProcess, } from "../process/ProviderProcess.js";
10
10
  import { waitForAbortableDelay } from "../process/ProviderDeadline.js";
11
11
  import { selectLinuxPrivateDisplayStrategy, } from "./LinuxPrivateDisplayProbe.js";
12
12
  import writeFileAtomic from "write-file-atomic";
13
+ import { prepareHopperMachOImage, } from "./HopperMachOImage.js";
13
14
  import { acquireHopperTargetLease } from "./HopperTargetLease.js";
14
15
  const execFileAsync = promisify(execFile);
15
16
  const HOPPER_BACKGROUND_STARTUP_MS = 5_000;
@@ -41,14 +42,22 @@ export class HopperApplicationLauncher {
41
42
  const lease = leaseResult.value;
42
43
  let leaseTransferred = false;
43
44
  try {
45
+ let loadedPath = this.options.targetPath;
46
+ if (this.options.preparedImage !== undefined) {
47
+ const prepared = await prepareHopperMachOImage(this.options.targetPath, this.options.preparedImage.sourceSha256, this.options.preparedImage.image, join(session.directory, "image.macho"), options.signal);
48
+ if (!prepared.ok)
49
+ return prepared;
50
+ loadedPath = prepared.value;
51
+ }
44
52
  const ownsProcessLifetime = usesLinuxDemo(this.options);
45
- const bootstrap = await this.#writeBootstrap(session, ownsProcessLifetime);
53
+ const bootstrap = await this.#writeBootstrap(session, ownsProcessLifetime, loadedPath);
46
54
  if (!bootstrap.ok)
47
55
  return bootstrap;
48
56
  const launched = await this.#launchPreparedTarget({
49
57
  session,
50
58
  signal: options.signal,
51
59
  bootstrapPath: bootstrap.value,
60
+ loadedPath,
52
61
  ownsProcessLifetime,
53
62
  lease,
54
63
  });
@@ -69,7 +78,7 @@ export class HopperApplicationLauncher {
69
78
  "-Y",
70
79
  bootstrapPath,
71
80
  action,
72
- this.options.targetPath,
81
+ input.loadedPath,
73
82
  ];
74
83
  try {
75
84
  if (signal?.aborted === true)
@@ -108,15 +117,26 @@ export class HopperApplicationLauncher {
108
117
  await cleanupOwnedProcessGroup(started.ownership);
109
118
  return err(new HopperStartError({ cause }));
110
119
  }
111
- return ok({
120
+ const ownedLauncher = {
121
+ ...(this.options.preparedImage === undefined
122
+ ? {}
123
+ : { preparedImagePath: input.loadedPath }),
112
124
  process: started.process,
113
125
  ownsProcessLifetime: true,
114
126
  ownership: started.ownership,
115
- shutdownMode: ownsProcessLifetime
116
- ? "process-cleanup"
117
- : "bridge-request",
118
127
  cleanup,
119
- });
128
+ };
129
+ return ownsProcessLifetime
130
+ ? ok({
131
+ ...ownedLauncher,
132
+ providerLifetime: "launcher-process",
133
+ shutdownMode: "process-cleanup",
134
+ })
135
+ : ok({
136
+ ...ownedLauncher,
137
+ providerLifetime: "external-application",
138
+ shutdownMode: "bridge-request",
139
+ });
120
140
  }
121
141
  catch (cause) {
122
142
  return err(hopperLaunchFailure(cause, signal));
@@ -144,10 +164,10 @@ export class HopperApplicationLauncher {
144
164
  return err(new HopperStartError({ cause }));
145
165
  }
146
166
  }
147
- async #writeBootstrap(session, ownsProcessLifetime) {
167
+ async #writeBootstrap(session, ownsProcessLifetime, loadedPath) {
148
168
  const bootstrapPath = `${session.directory}/bootstrap.py`;
149
169
  try {
150
- await writeFile(bootstrapPath, bridgeBootstrapSource(session, this.options, ownsProcessLifetime), { encoding: "utf8", mode: 0o600 });
170
+ await writeFile(bootstrapPath, bridgeBootstrapSource(session, this.options, ownsProcessLifetime, loadedPath), { encoding: "utf8", mode: 0o600 });
151
171
  await chmod(bootstrapPath, 0o600);
152
172
  return ok(bootstrapPath);
153
173
  }
@@ -173,11 +193,11 @@ export class HopperApplicationLauncher {
173
193
  };
174
194
  }
175
195
  }
176
- const bridgeBootstrapSource = (session, options, ownsProcessLifetime) => [
196
+ const bridgeBootstrapSource = (session, options, ownsProcessLifetime, loadedPath) => [
177
197
  `REA_SOCKET = ${JSON.stringify(session.socketPath)}`,
178
198
  `REA_TOKEN = ${JSON.stringify(session.token)}`,
179
199
  `REA_RUN_ID = ${JSON.stringify(session.runId)}`,
180
- `REA_TARGET_PATH = ${JSON.stringify(options.targetPath)}`,
200
+ `REA_TARGET_PATH = ${JSON.stringify(loadedPath)}`,
181
201
  `REA_OWNS_PROCESS_LIFETIME = ${ownsProcessLifetime ? "True" : "False"}`,
182
202
  `exec(compile(open(${JSON.stringify(options.bridgeScriptPath)}, 'rb').read(), ${JSON.stringify(options.bridgeScriptPath)}, 'exec'))`,
183
203
  "",
@@ -1,15 +1,37 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { createReadStream } from "node:fs";
3
3
  import { createAnalysisProfile } from "../domain/analysisProfile.js";
4
- import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
4
+ import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, } from "../domain/analysisErrorCore.js";
5
+ import { BinaryTargetError } from "../domain/configurationErrors.js";
5
6
  import { ProviderAdapterError } from "../domain/providerAdapterError.js";
6
7
  import {} from "../domain/analysisErrorBase.js";
7
8
  import { err, ok } from "../domain/result.js";
9
+ import { resolveHopperMachOImage, } from "./HopperMachOImage.js";
8
10
  /** Resolve Hopper open semantics without placing its CLI flags in BinaryTarget. */
9
11
  export const resolveHopperAnalysisProfile = async (target, options) => {
10
12
  if (target.kind !== "executable" && target.kind !== "database")
11
13
  return ok({ profile: null, compatibility: {} });
12
- const derived = hopperLoaderArgsForTarget(target);
14
+ let machoContainer;
15
+ if (target.kind === "executable" && target.format === "mach-o") {
16
+ const header = await readMachoMagic(target.path, options.signal);
17
+ if (!header.ok)
18
+ return header;
19
+ machoContainer = [0xcafebabf, 0xbfbafeca].includes(header.value)
20
+ ? "fat64"
21
+ : [0xcafebabe, 0xbebafeca].includes(header.value)
22
+ ? "fat32"
23
+ : "thin";
24
+ }
25
+ let preparedImage;
26
+ if (target.kind === "executable" &&
27
+ machoContainer === "fat64" &&
28
+ options.loaderArgsOverride.length === 0) {
29
+ const resolved = await resolveHopperMachOImage(target, options.signal);
30
+ if (!resolved.ok)
31
+ return resolved;
32
+ preparedImage = resolved.value;
33
+ }
34
+ const derived = hopperLoaderArgsForTarget(target, preparedImage === undefined ? machoContainer : "thin");
13
35
  if (!derived.ok)
14
36
  return derived;
15
37
  const loaderArgs = options.loaderArgsOverride.length === 0
@@ -34,6 +56,12 @@ export const resolveHopperAnalysisProfile = async (target, options) => {
34
56
  available_architectures: [
35
57
  ...(target.availableArchitectures ?? []),
36
58
  ].sort(),
59
+ ...(machoContainer === undefined
60
+ ? {}
61
+ : { macho_container: machoContainer }),
62
+ ...(preparedImage === undefined
63
+ ? {}
64
+ : { prepared_image: { ...preparedImage } }),
37
65
  loader: {
38
66
  source: options.loaderArgsOverride.length === 0
39
67
  ? "derived"
@@ -48,7 +76,7 @@ export const resolveHopperAnalysisProfile = async (target, options) => {
48
76
  });
49
77
  };
50
78
  /** Derive Hopper's complete non-interactive CLI loader selection. */
51
- export const hopperLoaderArgsForTarget = (target) => {
79
+ export const hopperLoaderArgsForTarget = (target, machoContainer) => {
52
80
  if (target.kind === "database")
53
81
  return ok([]);
54
82
  if (target.kind !== "executable")
@@ -57,7 +85,9 @@ export const hopperLoaderArgsForTarget = (target) => {
57
85
  const flag = hopperArchitectureFlag(architecture);
58
86
  switch (target.format) {
59
87
  case "mach-o":
60
- return ok((target.availableArchitectures?.length ?? 0) > 1
88
+ if (machoContainer === undefined)
89
+ return err(new AnalysisCapabilityUnavailableError("hopper", "resolve_analysis_profile", "Mach-O container kind is unknown; resolve the Hopper analysis profile before starting its client."));
90
+ return ok(machoContainer === "fat32" || machoContainer === "fat64"
61
91
  ? ["-l", "FAT", flag, "-l", "Mach-O"]
62
92
  : ["-l", "Mach-O", flag]);
63
93
  case "elf":
@@ -81,6 +111,34 @@ const hopperArchitectureFlag = (architecture) => {
81
111
  return "--aarch64";
82
112
  }
83
113
  };
114
+ const readMachoMagic = async (path, signal) => {
115
+ if (signalIsAborted(signal))
116
+ return err(new AnalysisCancelledError("open_binary"));
117
+ const stream = createReadStream(path, { start: 0, end: 3 });
118
+ const onAbort = () => {
119
+ stream.destroy();
120
+ };
121
+ signal?.addEventListener("abort", onAbort, { once: true });
122
+ try {
123
+ const chunks = [];
124
+ for await (const chunk of stream)
125
+ chunks.push(Buffer.from(chunk));
126
+ if (signalIsAborted(signal))
127
+ return err(new AnalysisCancelledError("open_binary"));
128
+ const bytes = Buffer.concat(chunks);
129
+ return bytes.length === 4
130
+ ? ok(bytes.readUInt32BE(0))
131
+ : err(new BinaryTargetError(path, "Mach-O header became truncated before Hopper loader selection"));
132
+ }
133
+ catch (cause) {
134
+ return signalIsAborted(signal)
135
+ ? err(new AnalysisCancelledError("open_binary"))
136
+ : err(new BinaryTargetError(path, `Cannot read the Mach-O container header: ${cause instanceof Error ? cause.message : String(cause)}`, { cause }));
137
+ }
138
+ finally {
139
+ signal?.removeEventListener("abort", onAbort);
140
+ }
141
+ };
84
142
  const sha256File = async (path, signal) => {
85
143
  if (signalIsAborted(signal))
86
144
  return err(new AnalysisCancelledError("open_binary"));
@@ -11,7 +11,7 @@ export const cleanupHopperSession = async (input) => {
11
11
  cleanupResult: undefined,
12
12
  shutdownConfirmed: false,
13
13
  };
14
- await report(input.progress, 0, input.retainDocument
14
+ await report(input.progress, 0, input.retainDocument && input.launch?.preparedImagePath === undefined
15
15
  ? "detaching REA from the Hopper document"
16
16
  : "requesting Hopper document shutdown");
17
17
  await requestShutdown(input, state);
@@ -20,7 +20,18 @@ export const cleanupHopperSession = async (input) => {
20
20
  await stopProcess(input, state);
21
21
  recordUnconfirmedDocument(input, state);
22
22
  await report(input.progress, 0.75, "removing Hopper private runtime files");
23
- await closeRuntimeRoot(input.runtimeRoot, state);
23
+ if (input.launch?.preparedImagePath !== undefined &&
24
+ !state.shutdownConfirmed) {
25
+ const resource = input.runtimeRoot?.path ?? input.launch.preparedImagePath;
26
+ state.resources.add(resource);
27
+ state.issues.push({
28
+ resource,
29
+ reason: "Prepared image retained because native document closure was not confirmed",
30
+ });
31
+ }
32
+ else {
33
+ await closeRuntimeRoot(input.runtimeRoot, state);
34
+ }
24
35
  return cleanupOutcome(input, state);
25
36
  };
26
37
  const requestShutdown = async (input, state) => {
@@ -34,7 +45,8 @@ const requestShutdown = async (input, state) => {
34
45
  }, "Hopper shutdown skipped because a bridge operation remained active");
35
46
  return;
36
47
  }
37
- const method = input.retainDocument || input.launch?.shutdownMode === "process-cleanup"
48
+ const method = (input.retainDocument && input.launch?.preparedImagePath === undefined) ||
49
+ input.launch?.shutdownMode === "process-cleanup"
38
50
  ? "shutdown"
39
51
  : "shutdown_document";
40
52
  const shutdown = await input