rea-agents 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/README.md +18 -20
  2. package/bridge/hopper_bridge.py +2 -0
  3. package/dist/android/JadxProvider.js +8 -2
  4. package/dist/android/JadxSession.js +51 -9
  5. package/dist/application/BinaryTargetResolver.js +63 -17
  6. package/dist/application/BrowserScenarioEvidence.js +2 -2
  7. package/dist/application/ClientConfigurationDocument.js +28 -0
  8. package/dist/application/ClientRegistrationStatus.js +19 -6
  9. package/dist/application/DirectAnalysis.js +12 -6
  10. package/dist/application/DirectAnalysisCleanup.js +0 -1
  11. package/dist/application/DirectAnalysisStatus.js +2 -5
  12. package/dist/application/Doctor.js +25 -34
  13. package/dist/application/DoctorDiagnostics.js +1 -1
  14. package/dist/application/EvidenceBundleFiles.js +1 -1
  15. package/dist/application/InvestigationProviders.js +4 -4
  16. package/dist/application/JsonFiles.js +15 -15
  17. package/dist/application/LinuxHopper.js +2 -1
  18. package/dist/application/ReconstructionObligationCandidates.js +9 -2
  19. package/dist/application/ReconstructionObligationLedgerProjection.js +3 -1
  20. package/dist/application/RegularFileRead.js +17 -0
  21. package/dist/application/Setup.js +18 -7
  22. package/dist/application/SetupClientConfiguration.js +18 -7
  23. package/dist/application/SetupHost.js +6 -4
  24. package/dist/application/SetupPlan.js +19 -15
  25. package/dist/application/SetupSkill.js +57 -8
  26. package/dist/application/SupportedClients.js +43 -0
  27. package/dist/application/Uninstall.js +18 -2
  28. package/dist/application/UpdateMaintenance.js +6 -2
  29. package/dist/application/Utf8JsonInput.js +64 -0
  30. package/dist/application/WebScriptExportService.js +13 -6
  31. package/dist/application/android/AndroidAnalysisService.js +1 -1
  32. package/dist/application/binary/AnalysisSnapshotCache.js +5 -4
  33. package/dist/application/binary/BinarySession.js +16 -0
  34. package/dist/application/binary/BinarySessionOpen.js +9 -1
  35. package/dist/application/javascript/ElectronActiveEvidence.js +2 -2
  36. package/dist/application/javascript/ElectronBoundaryAnalysis.js +2 -2
  37. package/dist/application/javascript/ElectronBoundaryGraphNative.js +5 -1
  38. package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -0
  39. package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +107 -46
  40. package/dist/application/javascript/JavaScriptApplicationService.js +1 -0
  41. package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +59 -45
  42. package/dist/application/javascript/JavaScriptArtifactAnalysis.js +0 -6
  43. package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +8 -18
  44. package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +34 -26
  45. package/dist/application/javascript/JavaScriptArtifactPathResolution.js +44 -12
  46. package/dist/application/javascript/JavaScriptArtifactReconstruction.js +6 -3
  47. package/dist/application/javascript/JavaScriptArtifactReconstructionInput.js +1 -0
  48. package/dist/application/javascript/JavaScriptJsonModules.js +2 -2
  49. package/dist/application/javascript/JavaScriptModuleRelationships.js +5 -17
  50. package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +11 -27
  51. package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +2 -2
  52. package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +8 -6
  53. package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +1 -1
  54. package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +7 -5
  55. package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +1 -1
  56. package/dist/application/javascript/JavaScriptSemanticGraphEvidence.js +1 -1
  57. package/dist/application/javascript/JavaScriptSemanticGraphFingerprintProjection.js +2 -2
  58. package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +2 -2
  59. package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +1 -1
  60. package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +4 -4
  61. package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +1 -1
  62. package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +10 -7
  63. package/dist/application/javascript/JavaScriptSemanticTraceService.js +15 -10
  64. package/dist/application/native/NativeCallRoutes.js +2 -4
  65. package/dist/artifacts/ArtifactProvider.js +14 -16
  66. package/dist/artifacts/ArtifactProviderMetadata.js +1 -4
  67. package/dist/artifacts/AsarArtifactReader.js +81 -17
  68. package/dist/artifacts/DirectoryArtifactReader.js +2 -2
  69. package/dist/artifacts/NativeDmgArtifactReader.js +152 -67
  70. package/dist/artifacts/NativeDmgOwnership.js +109 -0
  71. package/dist/artifacts/apple/KeyedArchiveReader.js +54 -46
  72. package/dist/artifacts/apple/MachoLoadCommandReader.js +36 -6
  73. package/dist/artifacts/extraction/ArtifactExtraction.js +29 -11
  74. package/dist/artifacts/inventory/ArtifactGraphConstruction.js +7 -8
  75. package/dist/artifacts/inventory/classify.js +33 -14
  76. package/dist/artifacts/inventory/hashStableRootArtifact.js +37 -0
  77. package/dist/artifacts/inventory/scanCanonical.js +28 -22
  78. package/dist/artifacts/inventory/scanReader.js +7 -11
  79. package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +65 -27
  80. package/dist/browser/CdpBrowserProvider.js +4 -3
  81. package/dist/browser/CdpCaptureStorageFingerprints.js +3 -3
  82. package/dist/browser/CdpElectronInspection.js +2 -2
  83. package/dist/browser/CdpElectronProvider.js +4 -3
  84. package/dist/browser/CdpElectronScripts.js +2 -2
  85. package/dist/browser/CdpWebMcpDiscovery.js +16 -13
  86. package/dist/browser/PlaywrightBrowserScenarioProvider.js +6 -6
  87. package/dist/browser/PlaywrightScenarioBrowser.js +145 -42
  88. package/dist/browser/PlaywrightScenarioSession.js +3 -11
  89. package/dist/browser/WebSourceMapFetcher.js +32 -6
  90. package/dist/browser/assets/ScenarioScriptCapture.js +8 -12
  91. package/dist/cli/coreAnalysisCommands.js +2 -2
  92. package/dist/cli/coreBinaryCommands.js +13 -9
  93. package/dist/cli/electronCommands.js +9 -10
  94. package/dist/cli/javascriptApplicationAnalysis.js +2 -12
  95. package/dist/cli/jsonOutput.js +3 -2
  96. package/dist/cli/streamedJsonOutput.js +83 -4
  97. package/dist/cli/utilityCommands.js +2 -4
  98. package/dist/cli.js +5 -2
  99. package/dist/cliJsonFile.js +179 -0
  100. package/dist/cliJsonInput.js +47 -34
  101. package/dist/cliObservationOptions.js +4 -0
  102. package/dist/composition/auxiliaryAnalysisProviders.js +5 -4
  103. package/dist/composition/browserScenario.js +2 -2
  104. package/dist/composition/directAnalysis.js +2 -3
  105. package/dist/contracts/artifactComparisonExample.js +5 -5
  106. package/dist/contracts/errorSchemas.js +3 -1
  107. package/dist/contracts/functionComparisonExample.js +8 -0
  108. package/dist/contracts/inputSchemaPresentation.js +94 -3
  109. package/dist/contracts/javascript/electronToolContracts.js +2 -1
  110. package/dist/contracts/javascript/javascriptApplicationWorkflowExamples.js +3 -2
  111. package/dist/contracts/javascript/javascriptExportShapeComparisonExample.js +6 -3
  112. package/dist/contracts/javascript/javascriptRuntimeReconciliationExample.js +8 -7
  113. package/dist/contracts/managed/managedToolContracts.js +3 -2
  114. package/dist/contracts/mobileApplicationGraphExamples.js +5 -5
  115. package/dist/contracts/native/nativeToolContracts.js +1 -1
  116. package/dist/contracts/sessionLifecycleInputs.js +4 -3
  117. package/dist/contracts/sessionStatusContract.js +3 -2
  118. package/dist/contracts/sessionToolContracts.js +3 -2
  119. package/dist/contracts/sessionToolSchemas.js +3 -2
  120. package/dist/contracts/toolOutputSchemaGroups.js +2 -2
  121. package/dist/contracts/toolOutputSchemaPrimitives.js +2 -5
  122. package/dist/domain/analysisErrorBase.js +5 -0
  123. package/dist/domain/analysisErrorPresentation.js +4 -2
  124. package/dist/domain/analysisSnapshot.js +3 -1
  125. package/dist/domain/analysisView/analysisView.js +1 -3
  126. package/dist/domain/android/androidPartialObservation.js +36 -0
  127. package/dist/domain/apple/dylibResolution.js +1 -1
  128. package/dist/domain/artifactComparison.js +4 -4
  129. package/dist/domain/artifactGraph.js +1 -1
  130. package/dist/domain/artifactIdentity.js +8 -8
  131. package/dist/domain/artifactInspection.js +8 -8
  132. package/dist/domain/artifactInventoryEvidence.js +5 -5
  133. package/dist/domain/browserCompleteness.js +3 -2
  134. package/dist/domain/browserScenarioCaptureValues.js +2 -2
  135. package/dist/domain/browserScenarioNormalization.js +3 -3
  136. package/dist/domain/browserScenarioValues.js +3 -2
  137. package/dist/domain/callPath.js +8 -10
  138. package/dist/domain/canonicalDigest.js +7 -1
  139. package/dist/domain/canonicalOrdering.js +1 -13
  140. package/dist/domain/changedBehavior.js +2 -3
  141. package/dist/domain/comparisonSemantics.js +0 -5
  142. package/dist/domain/digests.js +0 -3
  143. package/dist/domain/evidence.js +10 -5
  144. package/dist/domain/evidenceBundle.js +51 -33
  145. package/dist/domain/firmware/firmwareAnalysis.js +4 -3
  146. package/dist/domain/functionComparison.js +2 -1
  147. package/dist/domain/functionComparisonNormalization.js +4 -3
  148. package/dist/domain/functionComparisonResults.js +2 -2
  149. package/dist/domain/functionDossierEvidence.js +2 -1
  150. package/dist/domain/hopperValues.js +11 -15
  151. package/dist/domain/javascript/electronActiveObservation.js +4 -3
  152. package/dist/domain/javascript/electronStaticAnalysisBrowser.js +2 -2
  153. package/dist/domain/javascript/electronStaticAnalysisIpc.js +9 -1
  154. package/dist/domain/javascript/electronStaticAnalysisNative.js +5 -3
  155. package/dist/domain/javascript/electronStaticAnalysisValues.js +8 -4
  156. package/dist/domain/javascript/javascriptApplicationAnalysis.js +3 -1
  157. package/dist/domain/javascript/javascriptApplicationChangeGraph.js +12 -8
  158. package/dist/domain/javascript/javascriptApplicationEvidenceSchemas.js +4 -0
  159. package/dist/domain/javascript/javascriptApplicationGraph.js +15 -13
  160. package/dist/domain/javascript/javascriptApplicationVersionComparison.js +4 -4
  161. package/dist/domain/javascript/javascriptApplicationVersionComparisonSchemas.js +37 -22
  162. package/dist/domain/javascript/javascriptApplicationVersionItems.js +45 -34
  163. package/dist/domain/javascript/javascriptApplicationVersionKeys.js +7 -7
  164. package/dist/domain/javascript/javascriptAstFingerprint.js +2 -2
  165. package/dist/domain/javascript/javascriptAstValues.js +22 -0
  166. package/dist/domain/javascript/javascriptExportShapeComparison.js +3 -2
  167. package/dist/domain/javascript/javascriptExportShapeComparisonSchemas.js +31 -23
  168. package/dist/domain/javascript/javascriptExportShapeSelection.js +2 -2
  169. package/dist/domain/javascript/javascriptExportShapeVariants.js +3 -2
  170. package/dist/domain/javascript/javascriptFeatureSeed.js +5 -3
  171. package/dist/domain/javascript/javascriptFeatureTrace.js +7 -6
  172. package/dist/domain/javascript/javascriptFeatureTraceSchemas.js +17 -9
  173. package/dist/domain/javascript/javascriptFeatureTraversal.js +4 -4
  174. package/dist/domain/javascript/javascriptNativeHandoff.js +6 -6
  175. package/dist/domain/javascript/javascriptRuntimeObservation.js +8 -5
  176. package/dist/domain/javascript/javascriptRuntimeReconciliation.js +12 -6
  177. package/dist/domain/javascript/javascriptRuntimeReconciliationActive.js +9 -8
  178. package/dist/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.js +212 -0
  179. package/dist/domain/javascript/javascriptRuntimeReconciliationGraph.js +5 -4
  180. package/dist/domain/javascript/javascriptRuntimeReconciliationInputValidation.js +28 -0
  181. package/dist/domain/javascript/javascriptRuntimeReconciliationMatching.js +5 -5
  182. package/dist/domain/javascript/javascriptRuntimeReconciliationParsing.js +12 -223
  183. package/dist/domain/javascript/javascriptRuntimeReconciliationResult.js +2 -2
  184. package/dist/domain/javascript/javascriptRuntimeReconciliationRuntime.js +15 -4
  185. package/dist/domain/javascript/javascriptRuntimeReconciliationSchemas.js +2 -2
  186. package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +7 -6
  187. package/dist/domain/javascript/javascriptSemanticAnalysis.js +200 -8
  188. package/dist/domain/javascript/javascriptSemanticCallResolution.js +9 -7
  189. package/dist/domain/javascript/javascriptSemanticChildProcesses.js +2 -2
  190. package/dist/domain/javascript/javascriptSemanticCoverage.js +20 -0
  191. package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +7 -9
  192. package/dist/domain/javascript/javascriptSemanticDataEffects.js +4 -11
  193. package/dist/domain/javascript/javascriptSemanticFingerprints.js +2 -2
  194. package/dist/domain/javascript/javascriptSemanticGraph.js +142 -35
  195. package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +19 -1
  196. package/dist/domain/javascript/javascriptSemanticIr.js +0 -4
  197. package/dist/domain/javascript/javascriptSemanticIterationSources.js +106 -0
  198. package/dist/domain/javascript/javascriptSemanticMemberMutations.js +497 -113
  199. package/dist/domain/javascript/javascriptSemanticMutationInitializers.js +190 -0
  200. package/dist/domain/javascript/javascriptSemanticMutationValues.js +41 -27
  201. package/dist/domain/javascript/javascriptSemanticObjects.js +2 -6
  202. package/dist/domain/javascript/javascriptSemanticPrimitives.js +7 -2
  203. package/dist/domain/javascript/javascriptSemanticProjection.js +20 -14
  204. package/dist/domain/javascript/javascriptSemanticPromises.js +15 -21
  205. package/dist/domain/javascript/javascriptSemanticPropertyPaths.js +23 -0
  206. package/dist/domain/javascript/javascriptSemanticProvenance.js +3 -3
  207. package/dist/domain/javascript/javascriptSemanticQuery.js +8 -2
  208. package/dist/domain/javascript/javascriptSemanticQueryIdentity.js +3 -2
  209. package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -9
  210. package/dist/domain/javascript/javascriptSemanticRequireOrigin.js +16 -10
  211. package/dist/domain/javascript/javascriptSemanticReturns.js +23 -27
  212. package/dist/domain/javascript/javascriptSemanticTraversal.js +6 -1
  213. package/dist/domain/javascript/javascriptSemanticValues.js +135 -24
  214. package/dist/domain/javascript/javascriptSourceMapPaths.js +11 -1
  215. package/dist/domain/javascript/javascriptSourceParser.js +34 -9
  216. package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -6
  217. package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +26 -15
  218. package/dist/domain/javascript/sourceToBundleComparison.js +7 -5
  219. package/dist/domain/javascript/sourceToBundleSignals.js +7 -7
  220. package/dist/domain/jsonSerialization.js +109 -55
  221. package/dist/domain/jsonShape.js +46 -19
  222. package/dist/domain/localPath.js +10 -4
  223. package/dist/domain/managed/managedApplicationGraph.js +10 -6
  224. package/dist/domain/managed/managedApplicationGraphNodes.js +82 -57
  225. package/dist/domain/managed/managedMemberComparisonMatch.js +6 -7
  226. package/dist/domain/native/nativeCallObservation.js +2 -4
  227. package/dist/domain/native/nativeValueFlow.js +5 -11
  228. package/dist/domain/native/objcSwiftMetadata.js +15 -17
  229. package/dist/domain/process/processCapture.js +11 -10
  230. package/dist/domain/process/processCaptureCoverage.js +8 -5
  231. package/dist/domain/process/processCaptureExample.js +43 -0
  232. package/dist/domain/process/processCaptureValidation.js +4 -3
  233. package/dist/domain/process/processComparison.js +0 -23
  234. package/dist/domain/process/processScenario.js +3 -3
  235. package/dist/domain/process/processTraceEvaluation.js +4 -4
  236. package/dist/domain/process/processTraceSpecification.js +1 -3
  237. package/dist/domain/providerCleanupError.js +6 -2
  238. package/dist/domain/reconstructionCoverage.js +2 -1
  239. package/dist/domain/reconstructionUnknowns.js +4 -3
  240. package/dist/domain/reconstructionVerification.js +3 -2
  241. package/dist/domain/referenceSourceGraph.js +7 -7
  242. package/dist/domain/referenceSourceImportParsing.js +34 -45
  243. package/dist/domain/residualUnknown.js +3 -2
  244. package/dist/domain/staticRuntimeCorrelation.js +2 -1
  245. package/dist/domain/swiftSymbolClassification.js +6 -1
  246. package/dist/domain/symbolAnalysis.js +17 -2
  247. package/dist/domain/unicodeCodePointOrder.js +6 -0
  248. package/dist/domain/webBundleAnalyzerInspection.js +9 -1
  249. package/dist/domain/webCaptureDiff.js +71 -29
  250. package/dist/domain/webCaptureDiffSchemas.js +2 -8
  251. package/dist/domain/webInventory.js +3 -2
  252. package/dist/domain/webMcpDiscovery.js +4 -3
  253. package/dist/domain/webScriptExport.js +1 -1
  254. package/dist/dotnet/ManagedMemberInstructionDecoder.js +1 -2
  255. package/dist/dotnet/ManagedStaticProvider.js +10 -7
  256. package/dist/dotnet/ManagedStaticProviderMetadata.js +1 -4
  257. package/dist/filesystem/RegularFile.js +41 -0
  258. package/dist/generatedPackageMetadata.js +1 -1
  259. package/dist/ghidra/GhidraClient.js +14 -4
  260. package/dist/ghidra/GhidraInstallation.js +111 -10
  261. package/dist/ghidra/GhidraLauncher.js +43 -19
  262. package/dist/hopper/HopperCleanup.js +1 -1
  263. package/dist/ida/IdaOperationRunner.js +2 -0
  264. package/dist/ida/IdaSessionClient.js +4 -2
  265. package/dist/inspector/JavaScriptRuntimeScope.js +9 -5
  266. package/dist/inspector/V8InspectorCaptureProjection.js +46 -13
  267. package/dist/inspector/V8InspectorProvider.js +60 -8
  268. package/dist/main/shutdown.js +76 -32
  269. package/dist/main/transport.js +1 -1
  270. package/dist/native/AppleObjcDispatchFacets.js +3 -3
  271. package/dist/native/AppleObjcProperties.js +17 -7
  272. package/dist/native/LldbCallTracer.js +241 -75
  273. package/dist/native/NativeMacOSProvider.js +10 -5
  274. package/dist/native/NativeMacOSProviderMetadata.js +1 -2
  275. package/dist/process/ProcessOwnershipObservation.js +9 -13
  276. package/dist/process/capture/FilesystemSnapshot.js +88 -18
  277. package/dist/process/capture/ProcessCaptureLifecycle.js +13 -27
  278. package/dist/process/capture/ProcessFilesystemEffects.js +19 -4
  279. package/dist/process/capture/ProcessHarness.js +8 -21
  280. package/dist/server/analysisAdmission.js +8 -0
  281. package/dist/server/createServer.js +22 -23
  282. package/dist/server/registerApplicationTools/compareExportShapes.js +2 -1
  283. package/dist/server/registerApplicationTools/helpers.js +3 -2
  284. package/dist/server/registerArtifactTools.js +2 -2
  285. package/dist/server/registerCloseLifecycleTool.js +6 -4
  286. package/dist/server/registerEnhancedTools.js +78 -73
  287. package/dist/server/registerEvidenceTools.js +54 -46
  288. package/dist/server/registerManagedTools.js +7 -4
  289. package/dist/server/registerOfficialTools.js +31 -25
  290. package/dist/server/registerSessionTools.js +9 -3
  291. package/native/windows/build/manifest.json +1 -1
  292. package/package.json +6 -3
  293. package/skills/reverse-engineer-anything/SKILL.md +5 -5
  294. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +9 -0
  295. package/dist/artifacts/inventory/policy.js +0 -2
  296. package/dist/domain/managed/managedApplicationGraphNodeCoverage.js +0 -6
  297. package/dist/server/registerNativeTools.js +0 -6
@@ -14,10 +14,10 @@ import {} from "../domain/analysisErrorBase.js";
14
14
  import { interfaceBuilderLimitsSchema } from "../domain/apple/interfaceBuilderGraph.js";
15
15
  import { err, ok } from "../domain/result.js";
16
16
  import { ArtifactReaderFailure } from "./ArtifactReader.js";
17
- import { ARTIFACT_PROVIDER_IDENTITY as IDENTITY, artifactCapabilities, } from "./ArtifactProviderMetadata.js";
17
+ import { artifactCapabilities } from "./ArtifactProviderMetadata.js";
18
+ import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
18
19
  import { createEvidence } from "../domain/evidence.js";
19
20
  import { createArtifactInspection } from "../domain/artifactInspection.js";
20
- import { resolveArtifactIntegrityPolicy } from "./inventory/policy.js";
21
21
  /** Read-only inventory and exclusively owned extraction provider. */
22
22
  export class ArtifactProvider {
23
23
  #capabilities;
@@ -29,7 +29,7 @@ export class ArtifactProvider {
29
29
  this.#platform = platform;
30
30
  }
31
31
  identity() {
32
- return IDENTITY;
32
+ return ARTIFACT_GRAPH_PROVIDER;
33
33
  }
34
34
  capabilities() {
35
35
  return this.#capabilities;
@@ -49,9 +49,9 @@ class ArtifactClient {
49
49
  }
50
50
  async execute(operation, parameters, options) {
51
51
  if (operation === "health")
52
- return ok(createAnalysisExecution(null, IDENTITY));
52
+ return ok(createAnalysisExecution(null, ARTIFACT_GRAPH_PROVIDER));
53
53
  if (!isArtifactOperation(operation))
54
- return err(new AnalysisCapabilityUnavailableError(IDENTITY.id, operation, "Operation is not implemented by artifact graph provider."));
54
+ return err(new AnalysisCapabilityUnavailableError(ARTIFACT_GRAPH_PROVIDER.id, operation, "Operation is not implemented by artifact graph provider."));
55
55
  try {
56
56
  if (operation === "inspect_artifact") {
57
57
  const inspected = await this.inspectArtifact(parameters, options);
@@ -69,7 +69,7 @@ class ArtifactClient {
69
69
  limits,
70
70
  ...(options?.signal === undefined ? {} : { signal: options.signal }),
71
71
  });
72
- return ok(createAnalysisExecution(result, IDENTITY, {
72
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
73
73
  limitations: result.limitations,
74
74
  locations: result.documents.map(({ relative_path: path }) => ({
75
75
  kind: "artifact-path",
@@ -108,7 +108,7 @@ class ArtifactClient {
108
108
  });
109
109
  if (standalone && result.archive_sha256 !== this.target.sha256)
110
110
  throw new ArtifactReaderFailure("integrity", `Active archive digest changed: expected ${this.target.sha256}, observed ${result.archive_sha256}`);
111
- return ok(createAnalysisExecution(result, IDENTITY, {
111
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
112
112
  limitations: result.limitations,
113
113
  locations: [{ kind: "artifact-path", path: result.archive_path }],
114
114
  }));
@@ -126,7 +126,7 @@ class ArtifactClient {
126
126
  outputRoot: parsed.output_root,
127
127
  environment: this.environment,
128
128
  }, options?.signal);
129
- return ok(createAnalysisExecution(result, IDENTITY, {
129
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
130
130
  rawResult: null,
131
131
  limitations: result.limitations,
132
132
  subject: subjectFor(this.target.sourcePath ?? this.target.path, result.manifest),
@@ -138,7 +138,7 @@ class ArtifactClient {
138
138
  }
139
139
  const parsed = artifactInventoryInputSchema.parse(parameters);
140
140
  const result = await this.inventory(parsed, options);
141
- return ok(createAnalysisExecution(result, IDENTITY, {
141
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
142
142
  rawResult: null,
143
143
  limitations: result.limitations,
144
144
  subject: subjectFor(this.target.sourcePath ?? this.target.path, result.manifest),
@@ -172,7 +172,7 @@ class ArtifactClient {
172
172
  page: parameters,
173
173
  ...(options?.signal === undefined ? {} : { signal: options.signal }),
174
174
  });
175
- return ok(createAnalysisExecution(result, IDENTITY, {
175
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
176
176
  limitations: result.limitations,
177
177
  locations: result.catalogs.map(({ path }) => ({
178
178
  kind: "artifact-path",
@@ -196,7 +196,7 @@ class ArtifactClient {
196
196
  parameters,
197
197
  ...(options?.signal === undefined ? {} : { signal: options.signal }),
198
198
  });
199
- return ok(createAnalysisExecution(result, IDENTITY, {
199
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
200
200
  limitations: result.limitations,
201
201
  locations: result.images.map(({ path }) => ({
202
202
  kind: "artifact-path",
@@ -221,7 +221,7 @@ class ArtifactClient {
221
221
  kind: "artifact-path",
222
222
  path,
223
223
  }));
224
- const inventoryEvidence = createEvidence(subject, IDENTITY, {
224
+ const inventoryEvidence = createEvidence(subject, ARTIFACT_GRAPH_PROVIDER, {
225
225
  operation: "inventory_artifact",
226
226
  parameters: inventoryParameters,
227
227
  result: inventory,
@@ -236,7 +236,7 @@ class ArtifactClient {
236
236
  total: 1,
237
237
  message: "inventory substep completed",
238
238
  });
239
- return ok(createAnalysisExecution(result, IDENTITY, {
239
+ return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
240
240
  rawResult: null,
241
241
  limitations: result.limitations,
242
242
  subject,
@@ -247,9 +247,7 @@ class ArtifactClient {
247
247
  return inventoryArtifact(this.target.sourcePath ?? this.target.path, {
248
248
  environment: this.environment,
249
249
  ...(options?.signal === undefined ? {} : { signal: options.signal }),
250
- integrity: resolveArtifactIntegrityPolicy({
251
- mode: parsed.integrity_policy,
252
- }),
250
+ integrity: { mode: parsed.integrity_policy },
253
251
  });
254
252
  }
255
253
  }
@@ -1,12 +1,9 @@
1
1
  import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
2
2
  import { ARTIFACT_ANALYSIS_OPERATIONS } from "../contracts/artifactToolContracts.js";
3
- /** Identity of artifact inventory and owned extraction observations. */
4
- export const ARTIFACT_PROVIDER_IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
5
- const IDENTITY = ARTIFACT_PROVIDER_IDENTITY;
6
3
  /** Declare artifact coverage without constructing readers or acquiring resources. */
7
4
  export const artifactCapabilities = (platform = process.platform) => Object.freeze(ARTIFACT_ANALYSIS_OPERATIONS.map((operation) => {
8
5
  const common = {
9
- provider: IDENTITY,
6
+ provider: ARTIFACT_GRAPH_PROVIDER,
10
7
  operation,
11
8
  effects: Object.freeze({
12
9
  mutatesArtifact: false,
@@ -1,7 +1,10 @@
1
- import { constants, createReadStream } from "node:fs";
2
- import { lstat, open, realpath } from "node:fs/promises";
1
+ import { constants, createReadStream, createWriteStream, } from "node:fs";
2
+ import { lstat, mkdtemp, open, realpath, rm, } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { createHash } from "node:crypto";
3
5
  import { isAbsolute, join, relative, sep } from "node:path";
4
- import { Readable } from "node:stream";
6
+ import { Readable, Transform } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
5
8
  import { getRawHeader, listPackage, statFile, uncache } from "@electron/asar";
6
9
  import { ArtifactReaderFailure, } from "./ArtifactReader.js";
7
10
  import { closeAsarHandle, readValidatedAsarEntry } from "./AsarEntryStream.js";
@@ -15,24 +18,28 @@ import { closeAsarHandle, readValidatedAsarEntry } from "./AsarEntryStream.js";
15
18
  export class AsarArtifactReader {
16
19
  path;
17
20
  openUnpackedFile;
21
+ removeSnapshot;
18
22
  format = "asar";
19
23
  #archiveSize;
20
24
  #headerSize;
21
25
  #entries = new Map();
22
- constructor(path, openUnpackedFile = open) {
26
+ #snapshot;
27
+ #snapshotRoot;
28
+ constructor(path, openUnpackedFile = open, removeSnapshot = (path) => rm(path, { recursive: true, force: true })) {
23
29
  this.path = path;
24
30
  this.openUnpackedFile = openUnpackedFile;
31
+ this.removeSnapshot = removeSnapshot;
25
32
  }
26
33
  async *entries(signal) {
27
34
  let paths;
28
35
  try {
29
- // ASAR caches headers by path even after the archive has been replaced.
36
+ await this.prepareContainer(undefined, signal);
37
+ const snapshotPath = this.#snapshotPath();
30
38
  this.#resetArchiveState();
31
39
  this.#entries.clear();
32
- uncache(this.path);
33
- paths = listPackage(this.path, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"));
34
- this.#headerSize = getRawHeader(this.path).headerSize;
35
- const archiveMetadata = await lstat(this.path);
40
+ paths = listPackage(snapshotPath, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"));
41
+ this.#headerSize = getRawHeader(snapshotPath).headerSize;
42
+ const archiveMetadata = await lstat(snapshotPath);
36
43
  if (!archiveMetadata.isFile() || archiveMetadata.isSymbolicLink())
37
44
  throw new ArtifactReaderFailure("format", "ASAR container is not a regular file");
38
45
  this.#archiveSize = archiveMetadata.size;
@@ -54,7 +61,7 @@ export class AsarArtifactReader {
54
61
  : listed;
55
62
  let metadata;
56
63
  try {
57
- metadata = statFile(this.path, providerPath, false);
64
+ metadata = statFile(this.#snapshotPath(), providerPath, false);
58
65
  }
59
66
  catch (cause) {
60
67
  throw asarFailure(this.path, `stat ${path}`, cause);
@@ -138,7 +145,7 @@ export class AsarArtifactReader {
138
145
  throw new ArtifactReaderFailure("format", `ASAR entry range is outside its container: ${producedEntry.path}`);
139
146
  let handle;
140
147
  try {
141
- const openedHandle = await open(this.path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
148
+ const openedHandle = await open(this.#snapshotPath(), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
142
149
  handle = openedHandle;
143
150
  const observed = await openedHandle.stat();
144
151
  abortIfNeeded(signal);
@@ -161,16 +168,73 @@ export class AsarArtifactReader {
161
168
  throw asarFailure(this.path, `read ${producedEntry.path}`, cause);
162
169
  }
163
170
  }
164
- /** Open raw container bytes to verify the inventory identity before analysis. */
165
- openContainer(signal) {
171
+ /** Capture once per reader lifetime; bind all interpretation to these exact bytes. */
172
+ async prepareContainer(expectedSha256, signal) {
166
173
  abortIfNeeded(signal);
167
- return createReadStream(this.path, signal === undefined ? {} : { signal });
174
+ if (this.#snapshot === undefined && this.#snapshotRoot !== undefined)
175
+ await this.close();
176
+ if (this.#snapshot === undefined) {
177
+ try {
178
+ this.#snapshotRoot = await mkdtemp(join(tmpdir(), "rea-asar-snapshot-"));
179
+ const snapshotPath = join(this.#snapshotRoot, "container.asar");
180
+ const hash = createHash("sha256");
181
+ const handle = await open(this.path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
182
+ const source = handle.createReadStream();
183
+ const digesting = new Transform({
184
+ transform(chunk, _encoding, callback) {
185
+ hash.update(chunk);
186
+ callback(null, chunk);
187
+ },
188
+ });
189
+ await pipeline(source, digesting, createWriteStream(snapshotPath, { flags: "wx", mode: 0o600 }), signal === undefined ? {} : { signal });
190
+ this.#snapshot = { path: snapshotPath, sha256: hash.digest("hex") };
191
+ }
192
+ catch (cause) {
193
+ const primary = signal?.aborted === true
194
+ ? new ArtifactReaderFailure("cancelled", "ASAR operation cancelled", { cause })
195
+ : asarFailure(this.path, "snapshot", cause);
196
+ try {
197
+ await this.close();
198
+ }
199
+ catch (cleanupCause) {
200
+ throw ArtifactReaderFailure.withCleanup(primary, ArtifactReaderFailure.cleanupObservation(cleanupCause, this.path));
201
+ }
202
+ throw primary;
203
+ }
204
+ }
205
+ if (expectedSha256 !== undefined &&
206
+ this.#snapshot.sha256 !== expectedSha256)
207
+ throw new ArtifactReaderFailure("integrity", `ASAR container changed before interpretation: ${this.path}`);
208
+ }
209
+ /** Read the same captured container that supplies headers and packed members. */
210
+ async openContainer(signal) {
211
+ await this.prepareContainer(undefined, signal);
212
+ return createReadStream(this.#snapshotPath(), signal === undefined ? {} : { signal });
168
213
  }
169
- close() {
170
- uncache(this.path);
214
+ async close() {
215
+ const root = this.#snapshotRoot;
216
+ if (root !== undefined) {
217
+ try {
218
+ await this.removeSnapshot(root);
219
+ }
220
+ catch (cause) {
221
+ throw ArtifactReaderFailure.withCleanup(cause, {
222
+ reason: `Could not remove ASAR snapshot: ${root}`,
223
+ resources: [root],
224
+ });
225
+ }
226
+ this.#snapshotRoot = undefined;
227
+ }
228
+ if (this.#snapshot !== undefined)
229
+ uncache(this.#snapshot.path);
230
+ this.#snapshot = undefined;
171
231
  this.#resetArchiveState();
172
232
  this.#entries.clear();
173
- return Promise.resolve();
233
+ }
234
+ #snapshotPath() {
235
+ if (this.#snapshot === undefined)
236
+ throw new ArtifactReaderFailure("integrity", "ASAR snapshot is not available");
237
+ return this.#snapshot.path;
174
238
  }
175
239
  provenance() {
176
240
  return [];
@@ -1,7 +1,7 @@
1
1
  import { constants } from "node:fs";
2
2
  import { lstat, open, opendir, realpath } from "node:fs/promises";
3
3
  import { isAbsolute, join, relative, sep } from "node:path";
4
- import { compareCodePoints } from "../domain/canonicalOrdering.js";
4
+ import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
5
5
  import { ArtifactReaderFailure, } from "./ArtifactReader.js";
6
6
  /** Deterministic, non-symlink-following directory reader. */
7
7
  export class DirectoryArtifactReader {
@@ -39,7 +39,7 @@ export class DirectoryArtifactReader {
39
39
  await handle.close().catch(() => undefined);
40
40
  }
41
41
  await withDirectoryIoContext("inspect directory", directory, () => assertContainedDirectory(root, directory));
42
- children.sort(compareCodePoints);
42
+ children.sort(compareUnicodeCodePoints);
43
43
  const directories = [];
44
44
  for (const name of children) {
45
45
  abortIfNeeded(signal);
@@ -1,53 +1,48 @@
1
1
  import { mkdtemp, realpath, rm } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { basename, join } from "node:path";
4
- import { parse } from "plist";
5
- import { z } from "zod";
6
4
  import { ArtifactReaderFailure, } from "./ArtifactReader.js";
7
5
  import { DirectoryArtifactReader } from "./DirectoryArtifactReader.js";
8
- import { execFileOutput } from "../process/ExecFileOutput.js";
9
- const DETACH_TIMEOUT_MS = 120_000;
10
- /**
11
- * Waits before rechecking devices whose detach failed. A related device's
12
- * later detach can eject one, `hdiutil info` can still list an ejected device
13
- * briefly, and a volume can report busy just after it was read.
14
- */
6
+ import { execFileOutput, execFileOutputFailure, } from "../process/ExecFileOutput.js";
7
+ import { parseNativeDmgAttachOutput, parseNativeDmgInfo, resolveNativeDmgOwnership, } from "./NativeDmgOwnership.js";
8
+ const HDIUTIL_COMMAND_TIMEOUT_MS = 120_000;
9
+ // APFS ejects and busy reporting can lag a detach attempt.
15
10
  const DETACH_SETTLE_DELAYS_MS = [250, 500, 1000];
16
11
  const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
17
- const attachOutputSchema = z.object({
18
- "system-entities": z.array(z.object({
19
- "dev-entry": z.string().startsWith("/dev/"),
20
- "mount-point": z.string().optional(),
21
- })),
22
- });
23
- const infoOutputSchema = z.object({
24
- images: z.array(z.object({
25
- "system-entities": z.array(z.object({ "dev-entry": z.string() })),
26
- })),
27
- });
12
+ class NativeDmgCommandFailure extends ArtifactReaderFailure {
13
+ stdout;
14
+ processNotStarted;
15
+ constructor(reason, message, stdout, options) {
16
+ super(reason, message, options?.cause === undefined ? undefined : { cause: options.cause });
17
+ this.stdout = stdout;
18
+ this.processNotStarted = options?.processNotStarted ?? false;
19
+ }
20
+ }
28
21
  const createSystemHost = (environment) => ({
29
22
  delay,
30
23
  async run(arguments_, signal, options) {
31
24
  try {
32
25
  const { stdout, stderr } = await execFileOutput("/usr/bin/hdiutil", [...arguments_], {
33
26
  env: environment,
27
+ stopSignal: "SIGTERM",
34
28
  ...(options === undefined ? {} : { timeout: options.timeoutMs }),
35
29
  ...(signal === undefined ? {} : { signal }),
36
30
  });
37
31
  return { stdout, stderr, exitCode: 0 };
38
32
  }
39
33
  catch (cause) {
34
+ const output = execFileOutputFailure(cause);
40
35
  if (cause instanceof Error && cause.name === "AbortError")
41
- throw new ArtifactReaderFailure("cancelled", "DMG operation cancelled");
42
- const exitCode = processExitCode(cause);
36
+ throw new NativeDmgCommandFailure("cancelled", "DMG operation cancelled", output?.stdout ?? "", { cause });
37
+ const exitCode = typeof output?.code === "number" ? output.code : processExitCode(cause);
43
38
  if (exitCode !== undefined)
44
39
  return {
45
- stdout: processOutput(cause, "stdout"),
46
- stderr: processOutput(cause, "stderr"),
40
+ stdout: output?.stdout ?? processOutput(cause, "stdout"),
41
+ stderr: output?.stderr ?? processOutput(cause, "stderr"),
47
42
  exitCode,
48
43
  cause,
49
44
  };
50
- throw new ArtifactReaderFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, { cause });
45
+ throw new NativeDmgCommandFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, output?.stdout ?? "", { cause, processNotStarted: processNotStarted(cause) });
51
46
  }
52
47
  },
53
48
  });
@@ -59,7 +54,13 @@ export class NativeDmgArtifactReader {
59
54
  #provenance = [];
60
55
  #directory;
61
56
  #devices = [];
57
+ #inventoryConfirmedDevices = new Set();
62
58
  #mountRoot;
59
+ #baselineDevices = new Set();
60
+ #attachOutput;
61
+ #attachOutputReturned = false;
62
+ #attachMayHaveMounted = false;
63
+ #ownershipUncertainty;
63
64
  constructor(path, host) {
64
65
  this.path = path;
65
66
  this.host = host;
@@ -88,7 +89,7 @@ export class NativeDmgArtifactReader {
88
89
  return structuredClone(this.#provenance);
89
90
  }
90
91
  async close() {
91
- let detachFailure;
92
+ let detachFailure = await this.#reconcileAttachState();
92
93
  let remainingDevices = [];
93
94
  for (const device of [...this.#devices].reverse()) {
94
95
  const failure = await this.#detach(device);
@@ -97,10 +98,7 @@ export class NativeDmgArtifactReader {
97
98
  detachFailure ??= failure;
98
99
  }
99
100
  }
100
- // Detaching a synthesized APFS container also ejects the image that backs
101
- // it, so a device whose own detach failed may be gone by the end of the
102
- // pass, and hdiutil can list an ejected device or report busy briefly.
103
- // Only a device that stays attached through every recheck is a failure.
101
+ // APFS container detach may briefly leave its backing image listed.
104
102
  for (const delayMs of DETACH_SETTLE_DELAYS_MS) {
105
103
  if (remainingDevices.length === 0)
106
104
  break;
@@ -120,13 +118,19 @@ export class NativeDmgArtifactReader {
120
118
  if (remainingDevices.length === 0)
121
119
  detachFailure = undefined;
122
120
  this.#devices = remainingDevices.reverse();
123
- if (this.#devices.length === 0 && this.#mountRoot !== undefined)
121
+ if (this.#devices.length === 0 &&
122
+ this.#mountRoot !== undefined &&
123
+ !this.#attachMayHaveMounted)
124
124
  await rm(this.#mountRoot, { recursive: true, force: true }).then(() => {
125
125
  this.#mountRoot = undefined;
126
126
  this.#directory = undefined;
127
127
  }, (cause) => {
128
128
  detachFailure ??= cause;
129
129
  });
130
+ if (this.#attachMayHaveMounted) {
131
+ detachFailure ??= new Error(this.#ownershipUncertainty ??
132
+ "hdiutil attachment ownership remains unknown");
133
+ }
130
134
  if (detachFailure !== undefined)
131
135
  throw new ArtifactReaderFailure("unavailable", `DMG detach or mount-root cleanup failed for ${JSON.stringify(this.path)}: ${failureMessage(detachFailure)}`, {
132
136
  cause: detachFailure,
@@ -134,6 +138,11 @@ export class NativeDmgArtifactReader {
134
138
  reason: failureMessage(detachFailure),
135
139
  resources: [
136
140
  ...this.#devices.map((device) => `DMG device ${device}`),
141
+ ...(this.#attachMayHaveMounted
142
+ ? [
143
+ `DMG attachment ownership unknown at mount root ${this.#mountRoot ?? this.path}`,
144
+ ]
145
+ : []),
137
146
  ...(this.#mountRoot === undefined
138
147
  ? []
139
148
  : [`DMG mount root ${this.#mountRoot}`]),
@@ -145,7 +154,7 @@ export class NativeDmgArtifactReader {
145
154
  async #detach(device) {
146
155
  try {
147
156
  await runChecked(this.host, ["detach", device], undefined, {
148
- timeoutMs: DETACH_TIMEOUT_MS,
157
+ timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS,
149
158
  });
150
159
  this.#provenance.push(command(["detach", device], ["mount"]));
151
160
  return undefined;
@@ -154,18 +163,28 @@ export class NativeDmgArtifactReader {
154
163
  return (await this.#isAttached(device)) ? cause : undefined;
155
164
  }
156
165
  }
157
- /**
158
- * Report whether hdiutil still lists a device; unknown state counts as
159
- * attached. Like detach, this cleanup query runs after the inventory
160
- * snapshot has captured provenance, so it is not recorded there.
161
- */
166
+ async #reconcileAttachState() {
167
+ if (!this.#attachMayHaveMounted)
168
+ return undefined;
169
+ try {
170
+ await this.#resolveAttachOwnership();
171
+ return undefined;
172
+ }
173
+ catch (cause) {
174
+ this.#ownershipUncertainty = failureMessage(cause);
175
+ return cause;
176
+ }
177
+ }
178
+ /** Treat unknown hdiutil state as attached during cleanup. */
162
179
  async #isAttached(device) {
163
180
  try {
164
- const info = await runChecked(this.host, ["info", "-plist"], undefined, {
165
- timeoutMs: DETACH_TIMEOUT_MS,
166
- });
167
- const parsed = infoOutputSchema.parse(parse(info.stdout));
168
- return parsed.images.some((image) => image["system-entities"].some((entity) => entity["dev-entry"] === device));
181
+ const parsed = await this.#readInfo();
182
+ const isListed = parsed.images.some((image) => image["system-entities"].some((entity) => entity["dev-entry"] === device));
183
+ if (isListed) {
184
+ this.#inventoryConfirmedDevices.add(device);
185
+ return true;
186
+ }
187
+ return !this.#inventoryConfirmedDevices.has(device);
169
188
  }
170
189
  catch {
171
190
  return true;
@@ -174,44 +193,62 @@ export class NativeDmgArtifactReader {
174
193
  async attach(signal) {
175
194
  await runChecked(this.host, ["verify", this.path], signal);
176
195
  this.#provenance.push(command(["verify", this.path], ["read"]));
177
- this.#mountRoot = await realpath(await mkdtemp(join(tmpdir(), "rea-dmg-")));
196
+ const baseline = await this.#readInfo();
197
+ this.#baselineDevices = new Set(baseline.images.flatMap((image) => image["system-entities"].map(({ "dev-entry": device }) => device)));
198
+ const mountRoot = await realpath(await mkdtemp(join(tmpdir(), "rea-dmg-")));
199
+ this.#mountRoot = mountRoot;
178
200
  try {
201
+ this.#attachMayHaveMounted = true;
179
202
  const attached = await runChecked(this.host, [
180
203
  "attach",
181
204
  "-readonly",
182
205
  "-nobrowse",
183
206
  "-plist",
184
207
  "-mountroot",
185
- this.#mountRoot,
208
+ mountRoot,
186
209
  this.path,
187
- ], signal);
188
- const parsed = attachOutputSchema.parse(parse(attached.stdout));
189
- const devices = [
190
- ...new Set(parsed["system-entities"].map((entity) => entity["dev-entry"])),
191
- ];
192
- // Detaching an observed whole disk also detaches its partitions. Do not
193
- // subsequently detach the now-unavailable child devices.
194
- this.#devices = devices.filter((device) => !devices.some((parent) => /^\/dev\/disk\d+$/u.test(parent) &&
195
- device.startsWith(`${parent}s`) &&
196
- /^\d+$/u.test(device.slice(parent.length + 1))));
197
- if (this.#devices.length === 0)
210
+ ], signal, { timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS });
211
+ this.#attachOutputReturned = true;
212
+ let parsed;
213
+ try {
214
+ parsed = parseNativeDmgAttachOutput(attached.stdout);
215
+ }
216
+ catch (cause) {
217
+ throw new ArtifactReaderFailure("format", "hdiutil returned a malformed attachment plist", { cause });
218
+ }
219
+ if (parsed["system-entities"].length === 0)
198
220
  throw new ArtifactReaderFailure("format", "hdiutil returned no attached devices");
199
- for (const entity of parsed["system-entities"])
200
- if (entity["mount-point"] !== undefined &&
201
- !entity["mount-point"].startsWith(`${this.#mountRoot}/`))
202
- throw new ArtifactReaderFailure("path", "hdiutil mounted outside the owned root");
221
+ this.#attachOutput = parsed;
222
+ const ownership = await this.#resolveAttachOwnership();
223
+ if (ownership !== "owned")
224
+ throw new ArtifactReaderFailure(ownership === "unknown" ? "unavailable" : "path", ownership === "unknown"
225
+ ? `DMG attachment ownership is uncertain: ${this.#ownershipUncertainty ?? "no unique mount under the owned root"}`
226
+ : "hdiutil did not mount a new image beneath the owned root");
203
227
  this.#provenance.push(command([
204
228
  "attach",
205
229
  "-readonly",
206
230
  "-nobrowse",
207
231
  "-plist",
208
232
  "-mountroot",
209
- this.#mountRoot,
233
+ mountRoot,
210
234
  this.path,
211
235
  ], ["read", "mount"]));
212
- this.#directory = new DirectoryArtifactReader(this.#mountRoot);
236
+ this.#directory = new DirectoryArtifactReader(mountRoot);
213
237
  }
214
238
  catch (cause) {
239
+ if (cause instanceof NativeDmgCommandFailure && cause.stdout !== "") {
240
+ try {
241
+ this.#attachOutput = parseNativeDmgAttachOutput(cause.stdout);
242
+ this.#attachOutputReturned = true;
243
+ }
244
+ catch {
245
+ // The original command failure remains primary; fresh info still recovers mounts.
246
+ }
247
+ }
248
+ if (cause instanceof NativeDmgCommandFailure && cause.processNotStarted) {
249
+ this.#attachMayHaveMounted = false;
250
+ this.#ownershipUncertainty = undefined;
251
+ }
215
252
  let cleanupFailure;
216
253
  try {
217
254
  await this.close();
@@ -224,8 +261,57 @@ export class NativeDmgArtifactReader {
224
261
  throw cause;
225
262
  }
226
263
  }
264
+ async #readInfo() {
265
+ const info = await runChecked(this.host, ["info", "-plist"], undefined, {
266
+ timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS,
267
+ });
268
+ return parseNativeDmgInfo(info.stdout);
269
+ }
270
+ async #resolveAttachOwnership() {
271
+ const mountRoot = this.#mountRoot;
272
+ if (mountRoot === undefined)
273
+ throw new Error("DMG mount root is unavailable for ownership discovery");
274
+ const ownership = resolveNativeDmgOwnership({
275
+ info: await this.#readInfo(),
276
+ ...(this.#attachOutput === undefined
277
+ ? {}
278
+ : { attachOutput: this.#attachOutput }),
279
+ attachOutputReturned: this.#attachOutputReturned,
280
+ mountRoot,
281
+ baselineDevices: this.#baselineDevices,
282
+ });
283
+ if (ownership.status === "owned") {
284
+ this.#devices = [...ownership.devices];
285
+ if (ownership.source === "info")
286
+ for (const device of ownership.devices)
287
+ this.#inventoryConfirmedDevices.add(device);
288
+ this.#attachMayHaveMounted = false;
289
+ this.#ownershipUncertainty = undefined;
290
+ return "owned";
291
+ }
292
+ if (ownership.status === "unknown") {
293
+ this.#ownershipUncertainty = ownership.reason;
294
+ return "unknown";
295
+ }
296
+ this.#attachMayHaveMounted = false;
297
+ this.#ownershipUncertainty = undefined;
298
+ this.#devices = [];
299
+ return "none";
300
+ }
227
301
  }
228
302
  const failureMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
303
+ const processNotStarted = (cause) => {
304
+ if (!(cause instanceof Error))
305
+ return false;
306
+ const syscall = Reflect.get(cause, "syscall");
307
+ const code = Reflect.get(cause, "code");
308
+ const errno = Reflect.get(cause, "errno");
309
+ return (typeof syscall === "string" &&
310
+ syscall.startsWith("spawn ") &&
311
+ (typeof code === "string" ||
312
+ typeof code === "number" ||
313
+ typeof errno === "number"));
314
+ };
229
315
  const runChecked = async (host, arguments_, signal, options) => {
230
316
  let result;
231
317
  try {
@@ -235,10 +321,9 @@ const runChecked = async (host, arguments_, signal, options) => {
235
321
  if (cause instanceof ArtifactReaderFailure)
236
322
  throw cause;
237
323
  if (cause instanceof Error && cause.name === "AbortError")
238
- throw new ArtifactReaderFailure("cancelled", "DMG operation cancelled", {
239
- cause,
240
- });
241
- throw new ArtifactReaderFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, { cause });
324
+ throw new NativeDmgCommandFailure("cancelled", "DMG operation cancelled", execFileOutputFailure(cause)?.stdout ?? "", { cause });
325
+ const output = execFileOutputFailure(cause);
326
+ throw new NativeDmgCommandFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, output?.stdout ?? "", { cause, processNotStarted: processNotStarted(cause) });
242
327
  }
243
328
  if (result.exitCode !== 0) {
244
329
  const failure = {
@@ -253,9 +338,9 @@ const runChecked = async (host, arguments_, signal, options) => {
253
338
  const reason = commandFailureReason(failure, arguments_[0]);
254
339
  const details = describeCommandFailure(failure, arguments_);
255
340
  const unknownVerifyFailure = arguments_[0] === "verify" && reason === "unavailable";
256
- throw new ArtifactReaderFailure(reason, unknownVerifyFailure
341
+ throw new NativeDmgCommandFailure(reason, unknownVerifyFailure
257
342
  ? `hdiutil verify failed; captured diagnostics do not establish the failure cause: ${details}`
258
- : `hdiutil ${arguments_[0] ?? "operation"} failed: ${details}`, result.cause === undefined ? undefined : { cause: result.cause });
343
+ : `hdiutil ${arguments_[0] ?? "operation"} failed: ${details}`, result.stdout, { cause: result.cause ?? failure });
259
344
  }
260
345
  return { stdout: result.stdout, exitCode: 0 };
261
346
  };