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
@@ -6,6 +6,7 @@ import { parseArtifactInventoryEvidence } from "../artifactInventoryEvidence.js"
6
6
  import { evidenceSchema } from "../evidence.js";
7
7
  import { digestSchema } from "../digests.js";
8
8
  import { prefixedDigestSchema } from "../digests.js";
9
+ import { bridgeCandidateCoverageSchema, projectCartesianCandidates, } from "../bridgeCandidateProjection.js";
9
10
  const evidenceIdSchema = prefixedDigestSchema("ev");
10
11
  const pathSchema = z.string().min(1);
11
12
  const APPLE_ROOT_FORMATS = ["ipa", "directory", "zip", "dmg"];
@@ -52,6 +53,7 @@ export const appleApplicationProjectionResultSchema = z.strictObject({
52
53
  "unity-convention",
53
54
  ]),
54
55
  })),
56
+ bridge_candidate_coverage: bridgeCandidateCoverageSchema,
55
57
  coverage: z.strictObject({
56
58
  status: z.enum(["complete-within-inventory", "partial"]),
57
59
  inventory_complete: z.boolean(),
@@ -110,6 +112,7 @@ export const projectAppleApplication = (input) => {
110
112
  sidecars: entries.some(({ path }) => isSidecar(path)),
111
113
  symlinks: entries.some(({ kind }) => kind === "symlink"),
112
114
  unattributed,
115
+ bridgeCandidateCoverage: bridgeProjection.coverage,
113
116
  });
114
117
  const withoutId = {
115
118
  root_sha256: inventory.manifest.root_sha256,
@@ -127,8 +130,9 @@ export const projectAppleApplication = (input) => {
127
130
  .sort(compare),
128
131
  runtime_families: runtimeFamilies,
129
132
  bridge_candidates: bridgeProjection.candidates,
133
+ bridge_candidate_coverage: bridgeProjection.coverage,
130
134
  coverage: {
131
- status: complete
135
+ status: complete && bridgeProjection.coverage.status === "complete"
132
136
  ? "complete-within-inventory"
133
137
  : "partial",
134
138
  inventory_complete: inventory.complete,
@@ -137,9 +141,11 @@ export const projectAppleApplication = (input) => {
137
141
  };
138
142
  return appleApplicationProjectionResultSchema.parse({
139
143
  ...withoutId,
140
- projection_id: `aap_${digest(withoutId)}`,
144
+ projection_id: appleProjectionId(withoutId),
141
145
  });
142
146
  };
147
+ /** Identify the complete projection, including source Evidence provenance. */
148
+ export const appleProjectionId = (projection) => `aap_${digest(projection)}`;
143
149
  /**
144
150
  * IPA projection keeps every archive component, as before. Directory, ZIP,
145
151
  * and DMG inventories can hold installers and other siblings, so only
@@ -204,21 +210,37 @@ const identifyRuntimeFamilies = (all) => {
204
210
  families.add("unity");
205
211
  return [...families].sort(compare);
206
212
  };
207
- /** Pair scripts and native code only within the same application root. */
213
+ /** Pair scripts and native code within matching application-root groups. */
208
214
  const identifyBridgeCandidates = (scripts, native, roots) => {
209
215
  const owner = (path) => roots.find((root) => isWithin(path, root)) ?? null;
210
- return {
211
- candidates: scripts.flatMap((script) => {
212
- const root = owner(script.path);
213
- return native
214
- .filter(({ path }) => owner(path) === root)
215
- .map((item) => ({
216
- source_path: script.path,
217
- native_path: item.path,
218
- basis: bridgeBasis(item.path),
219
- }));
216
+ const scriptGroups = [];
217
+ for (const script of scripts) {
218
+ const root = owner(script.path);
219
+ const last = scriptGroups.at(-1);
220
+ if (last?.root === root)
221
+ last.left.push(script);
222
+ else
223
+ scriptGroups.push({ root, left: [script] });
224
+ }
225
+ const nativeByRoot = new Map();
226
+ for (const item of native) {
227
+ const root = owner(item.path);
228
+ const scoped = nativeByRoot.get(root) ?? [];
229
+ scoped.push(item);
230
+ nativeByRoot.set(root, scoped);
231
+ }
232
+ const groups = scriptGroups.map(({ root, left }) => ({
233
+ left,
234
+ right: nativeByRoot.get(root) ?? [],
235
+ }));
236
+ return projectCartesianCandidates({
237
+ groups,
238
+ createCandidate: (source_path, item) => ({
239
+ source_path,
240
+ native_path: item.path,
241
+ basis: bridgeBasis(item.path),
220
242
  }),
221
- };
243
+ });
222
244
  };
223
245
  const bridgeBasis = (path) => {
224
246
  const lower = path.toLowerCase();
@@ -264,6 +286,11 @@ const projectionLimitations = (facts) => [
264
286
  "AppleDouble sidecar entries (._* files and __MACOSX/) describe neighbouring files and are excluded from bundle roles.",
265
287
  ]
266
288
  : []),
289
+ ...(facts.bridgeCandidateCoverage.status === "partial"
290
+ ? [
291
+ `Bridge candidate pairs exceeded the projection safety budget; ${facts.bridgeCandidateCoverage.omitted_candidates} hypotheses are omitted. Component arrays still include every component from the supplied inventory pages.`,
292
+ ]
293
+ : []),
267
294
  "Bundle roles follow path conventions. Read each info_plist_path with inspect_plist for CFBundleExecutable, identifiers, and declared services.",
268
295
  "Bundle identifiers and signing claims require dedicated plist and CMS parsing; this projection reports only exact paths and hashes.",
269
296
  "Bridge candidates are path-based hypotheses, not observed runtime calls.",
@@ -0,0 +1,73 @@
1
+ /** Retain recognized image-selection settings separately from diagnostic observations. */
2
+ export const embeddedDyldOverrides = (environment, platforms) => {
3
+ const overrides = [];
4
+ for (const setting of environment) {
5
+ const delimiter = setting.indexOf("=");
6
+ if (delimiter <= 0)
7
+ continue;
8
+ const variable = setting.slice(0, delimiter);
9
+ const value = setting.slice(delimiter + 1);
10
+ const effect = overrideEffect(variable, value);
11
+ if (effect === undefined)
12
+ continue;
13
+ // ROOT_PATH is accepted only for simulator processes. Missing or unknown
14
+ // platform metadata cannot establish that dyld ignores it.
15
+ if (variable === "DYLD_ROOT_PATH" &&
16
+ platforms.length > 0 &&
17
+ platforms.every((platform) => platform >= 1 && platform <= 12) &&
18
+ !platforms.some((platform) => [7, 8, 9, 12].includes(platform)))
19
+ continue;
20
+ overrides.push({ variable, value, ...effect });
21
+ }
22
+ return overrides;
23
+ };
24
+ const overrideEffect = (variable, value) => {
25
+ switch (variable) {
26
+ // Empty directory components still produce /leaf candidates in dyld.
27
+ // In particular, an empty fallback value is not a no-op.
28
+ case "DYLD_LIBRARY_PATH":
29
+ return { phase: "override", scope: "library" };
30
+ case "DYLD_FRAMEWORK_PATH":
31
+ return { phase: "override", scope: "framework" };
32
+ case "DYLD_FALLBACK_LIBRARY_PATH":
33
+ return { phase: "fallback", scope: "library" };
34
+ case "DYLD_FALLBACK_FRAMEWORK_PATH":
35
+ return { phase: "fallback", scope: "framework" };
36
+ // Versioned paths map discovered images to their reported install names,
37
+ // so the directory's kind does not establish the replaced image's kind.
38
+ case "DYLD_VERSIONED_LIBRARY_PATH":
39
+ case "DYLD_VERSIONED_FRAMEWORK_PATH":
40
+ case "DYLD_IMAGE_SUFFIX":
41
+ // Versioned paths scan directories; suffixes create path variants.
42
+ // Empty directories cannot be scanned and empty suffixes repeat a path.
43
+ return /[^:]/u.test(value)
44
+ ? { phase: "override", scope: "all" }
45
+ : undefined;
46
+ case "DYLD_INSERT_LIBRARIES":
47
+ return value === "" ? undefined : { phase: "override", scope: "all" };
48
+ case "DYLD_ROOT_PATH":
49
+ case "DYLD_OVERLAY_PATH":
50
+ return value === "" ? undefined : { phase: "override", scope: "prefix" };
51
+ default:
52
+ return undefined;
53
+ }
54
+ };
55
+ const isFrameworkInstallName = (name, overrides) => {
56
+ const separator = name.lastIndexOf(".framework/");
57
+ if (separator < 0)
58
+ return false;
59
+ const framework = name.slice(0, separator).split("/").at(-1);
60
+ const leaf = name.slice(name.lastIndexOf("/") + 1);
61
+ return (leaf === framework ||
62
+ overrides.some(({ variable, value }) => variable === "DYLD_IMAGE_SUFFIX" && leaf === `${framework}${value}`));
63
+ };
64
+ /** Select only settings that can affect this dependency at its reached search phase. */
65
+ export const hasApplicableDyldOverrides = (overrides, installName, ordinarySearchFoundImage) => {
66
+ const kind = isFrameworkInstallName(installName, overrides)
67
+ ? "framework"
68
+ : "library";
69
+ return overrides.some(({ phase, scope }) => (phase === "override" || !ordinarySearchFoundImage) &&
70
+ (scope === "all" ||
71
+ scope === kind ||
72
+ (scope === "prefix" && !installName.startsWith("@"))));
73
+ };
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { compatibleSlice, directoryOf, expandPrefix, joinPath, resolveTreePath, } from "./dyldPaths.js";
3
3
  import { digestSchema } from "../digests.js";
4
+ import { hasApplicableDyldOverrides, embeddedDyldOverrides, } from "./dyldEnvironment.js";
4
5
  import { DYLIB_RESOLUTION_LIMITATIONS, deriveFindings, } from "./dylibResolutionFindings.js";
5
6
  /** Normalized path below the analyzed root: no `.`/`..` or empty segments. */
6
7
  const ROOT_RELATIVE_PATH = /^(?!\/)(?!(?:.*\/)?\.{1,2}(?:\/|$))(?!.*\/\/)(?!.*\/$)[^\\\0]+$/u;
@@ -31,7 +32,20 @@ const dependencySchema = z.strictObject({
31
32
  /** Observed dylib-loading facts of one Mach-O slice. */
32
33
  export const machoSliceSchema = z.strictObject({
33
34
  architecture: z.string().min(1),
35
+ /** Physical identity observed in the thin header and, for FAT, its table. */
36
+ slice_offset: z.number().int().nonnegative(),
37
+ slice_size: z.number().int().positive(),
38
+ cpu_type: z.number().int().nonnegative(),
39
+ cpu_subtype: z.number().int().nonnegative(),
40
+ fat_cpu_type: z.number().int().nonnegative().nullable(),
41
+ fat_cpu_subtype: z.number().int().nonnegative().nullable(),
42
+ fat_alignment_exponent: z.number().int().nonnegative().nullable(),
34
43
  file_type: z.enum(["execute", "dylib", "bundle", "other"]),
44
+ file_type_code: z.number().int().nonnegative(),
45
+ /** Raw LC_BUILD_VERSION platform number; null means no such command was observed. */
46
+ platform: z.number().int().nonnegative().nullable(),
47
+ /** Every distinct build platform declared by LC_BUILD_VERSION/version-min commands. */
48
+ platforms: z.array(z.number().int().nonnegative()),
35
49
  install_name: z.string().nullable(),
36
50
  dependencies: z.array(dependencySchema),
37
51
  rpaths: z.array(z.string()),
@@ -56,6 +70,8 @@ const candidateSchema = z.strictObject({
56
70
  "malformed",
57
71
  "unsupported",
58
72
  "architecture-missing",
73
+ "not-loadable",
74
+ "platform-mismatch",
59
75
  "outside-target",
60
76
  "escapes-target",
61
77
  "undetermined",
@@ -168,13 +184,65 @@ const evaluateCandidate = async (template, context) => {
168
184
  if (lookup.kind !== "file")
169
185
  return { ...base, outcome: "absent", resolved_path: null };
170
186
  const facts = await imageFacts(context, lookup.path);
171
- const outcome = facts.status !== "parsed"
172
- ? facts.status
173
- : compatibleSlice(facts.slices, context.architecture) === undefined
174
- ? "architecture-missing"
175
- : "resolved";
187
+ let outcome;
188
+ if (facts.status !== "parsed")
189
+ outcome = facts.status;
190
+ else {
191
+ const slice = compatibleSlice(facts.slices, context.architecture);
192
+ if (slice === undefined)
193
+ outcome = "architecture-missing";
194
+ // Dependency commands require a dylib; generic dyld image loadability
195
+ // also admits main executables and bundles for different operations.
196
+ else if (slice.file_type_code !== 6)
197
+ outcome = "not-loadable";
198
+ else {
199
+ const platformResult = platformLoadability(context.platforms, slice.platforms, slice.file_type_code, context.architecture);
200
+ outcome =
201
+ platformResult === "compatible"
202
+ ? "resolved"
203
+ : platformResult === "incompatible"
204
+ ? "platform-mismatch"
205
+ : "undetermined";
206
+ }
207
+ }
176
208
  return { ...base, outcome, resolved_path: lookup.path };
177
209
  };
210
+ /** Apply dyld's documented cross-platform load cases and retain uncertain host cases. */
211
+ const platformLoadability = (processPlatforms, imagePlatforms, fileType, architecture) => {
212
+ if (processPlatforms.length === 0 || imagePlatforms.length === 0)
213
+ return "unknown";
214
+ const outcomes = processPlatforms.map((processPlatform) => imageLoadabilityForPlatform(processPlatform, imagePlatforms, fileType, architecture));
215
+ if (outcomes.every((outcome) => outcome === "compatible"))
216
+ return "compatible";
217
+ if (outcomes.every((outcome) => outcome === "incompatible"))
218
+ return "incompatible";
219
+ return "unknown";
220
+ };
221
+ const imageLoadabilityForPlatform = (processPlatform, imagePlatforms, fileType, architecture) => {
222
+ if (imagePlatforms.includes(processPlatform))
223
+ return "compatible";
224
+ // These are explicit dyld loadableIntoProcess cross-platform cases.
225
+ if (processPlatform === 6 && imagePlatforms.includes(1))
226
+ return "compatible"; // macOS dylibs in Mac Catalyst processes
227
+ if (processPlatform === 2 && imagePlatforms.includes(6))
228
+ return "compatible"; // Catalyst dylibs in iOS processes
229
+ if (processPlatform === 2 && imagePlatforms.includes(11))
230
+ return "compatible"; // visionOS dylibs in iOS processes
231
+ if (processPlatform === 7 && imagePlatforms.includes(12))
232
+ return "compatible"; // visionOS simulator dylibs in iOS simulator processes
233
+ if (processPlatform === 1 && fileType === 2 && imagePlatforms.includes(6))
234
+ return "compatible"; // Catalyst main executables run on macOS
235
+ // dyld's remaining exceptions depend on host architecture or a special path.
236
+ if ((processPlatform === 2 &&
237
+ imagePlatforms.includes(1) &&
238
+ architecture.startsWith("arm64")) ||
239
+ ([7, 8, 9].includes(processPlatform) && imagePlatforms.includes(1)))
240
+ return "unknown";
241
+ if (isKnownPlatform(processPlatform) && imagePlatforms.every(isKnownPlatform))
242
+ return "incompatible";
243
+ return "unknown";
244
+ };
245
+ const isKnownPlatform = (platform) => platform >= 1 && platform <= 12;
178
246
  const imageFacts = async (context, path) => {
179
247
  const known = context.images.get(path);
180
248
  if (known !== undefined)
@@ -222,7 +290,19 @@ const resolveDependency = async (dependency, loader, context) => {
222
290
  if (candidate.outcome === "resolved")
223
291
  break;
224
292
  }
225
- const searched = resolution(candidates);
293
+ const modeled = resolution(candidates);
294
+ const searchUnknown = loaded === undefined &&
295
+ hasApplicableDyldOverrides(context.searchOverrides, dependency.install_name, modeled.image !== null);
296
+ if (searchUnknown)
297
+ context.environmentRoots.add(context.root);
298
+ // Fallbacks cannot preempt a modeled hit, and framework paths do not
299
+ // override ordinary dylibs. Reuse inherits uncertainty from the first load.
300
+ const searched = searchUnknown
301
+ ? {
302
+ ...modeled,
303
+ status: modeled.image === null ? "undetermined" : "conditional",
304
+ }
305
+ : modeled;
226
306
  // Reusing an image that itself loads only conditionally is conditional too.
227
307
  const resolved = loaded !== undefined &&
228
308
  context.loaded.get(loaded)?.conditional === true &&
@@ -306,6 +386,7 @@ export const traceDylibLoading = async (view, request) => {
306
386
  const roots = [];
307
387
  const edges = [];
308
388
  const withoutArchitecture = [];
389
+ const environmentRoots = new Set();
309
390
  for (const root of request.roots) {
310
391
  const facts = await view.image(root);
311
392
  images.set(root, facts);
@@ -316,12 +397,23 @@ export const traceDylibLoading = async (view, request) => {
316
397
  if (slices.length === 0)
317
398
  withoutArchitecture.push(root);
318
399
  for (const slice of slices) {
400
+ const executable = slice.file_type === "execute" ? root : null;
401
+ // Only the main executable supplies the process's embedded environment.
402
+ const searchOverrides = executable === null
403
+ ? []
404
+ : embeddedDyldOverrides(slice.dyld_environment, slice.platforms);
405
+ // Inserted libraries introduce unexamined images even without dependencies.
406
+ if (searchOverrides.some(({ variable, value }) => variable === "DYLD_INSERT_LIBRARIES" && value !== ""))
407
+ environmentRoots.add(root);
319
408
  roots.push({ image: root, architecture: slice.architecture });
320
409
  edges.push(...(await traceProcess({
321
410
  view,
322
411
  root,
323
412
  architecture: slice.architecture,
324
- executable: slice.file_type === "execute" ? root : null,
413
+ searchOverrides,
414
+ environmentRoots,
415
+ platforms: slice.platforms,
416
+ executable,
325
417
  loaded: new Map(),
326
418
  byInstallName: new Map(),
327
419
  images,
@@ -347,18 +439,37 @@ export const traceDylibLoading = async (view, request) => {
347
439
  edges,
348
440
  findings: deriveFindings(edges, roots, images),
349
441
  coverage: {
350
- status: unparsed.length === 0 && withoutArchitecture.length === 0
442
+ status: unparsed.length === 0 &&
443
+ withoutArchitecture.length === 0 &&
444
+ environmentRoots.size === 0
351
445
  ? "complete"
352
446
  : "partial",
353
447
  unparsed_images: unparsed,
354
448
  roots_without_architecture: withoutArchitecture,
355
449
  },
356
- limitations: DYLIB_RESOLUTION_LIMITATIONS,
450
+ limitations: [
451
+ ...DYLIB_RESOLUTION_LIMITATIONS,
452
+ ...(environmentRoots.size === 0
453
+ ? []
454
+ : [
455
+ `Roots ${[...environmentRoots].join(", ")} request applicable image-selection inputs through LC_DYLD_ENVIRONMENT that are not modeled; affected dependencies or inserted images remain uncertain.`,
456
+ ]),
457
+ ],
357
458
  };
358
459
  };
359
460
  const withoutDependencies = (slice) => ({
360
461
  architecture: slice.architecture,
462
+ slice_offset: slice.slice_offset,
463
+ slice_size: slice.slice_size,
464
+ cpu_type: slice.cpu_type,
465
+ cpu_subtype: slice.cpu_subtype,
466
+ fat_cpu_type: slice.fat_cpu_type,
467
+ fat_cpu_subtype: slice.fat_cpu_subtype,
468
+ fat_alignment_exponent: slice.fat_alignment_exponent,
361
469
  file_type: slice.file_type,
470
+ file_type_code: slice.file_type_code,
471
+ platform: slice.platform,
472
+ platforms: slice.platforms,
362
473
  install_name: slice.install_name,
363
474
  rpaths: slice.rpaths,
364
475
  dyld_environment: slice.dyld_environment,
@@ -14,22 +14,24 @@ export const deriveFindings = (edges, roots, images) => {
14
14
  edge_index: index,
15
15
  image: edge.loader,
16
16
  basis: "derived",
17
- explanation: `dyld searches ${earlier.join(", ")} before ${edge.resolution.image ?? edge.install_name}. A Mach-O placed at an earlier path would load first unless code-signing library validation rejects it; library validation is not evaluated here (see inspect_signature).`,
17
+ explanation: `The modeled dyld search checks ${earlier.join(", ")} before ${edge.resolution.image ?? edge.install_name}. A compatible Mach-O placed at an earlier modeled path could take precedence over that fallback, subject to unmodeled search inputs and code-signing library validation; library validation is not evaluated here (see inspect_signature).${edge.loader_conditional ? " This loader loads only conditionally; if it never loads, this search never occurs." : ""}${edge.resolution.status === "conditional" ? " Unmodeled environment overrides or earlier unknown candidates may take precedence; placing an image at this rpath does not establish which image dyld will load." : ""}`,
18
18
  });
19
19
  });
20
20
  for (const { image, architecture } of roots) {
21
21
  const facts = images.get(image);
22
- const environment = facts?.status === "parsed"
23
- ? (facts.slices.find((slice) => slice.architecture === architecture)
24
- ?.dyld_environment ?? [])
25
- : [];
22
+ const slice = facts?.status === "parsed"
23
+ ? facts.slices.find((candidate) => candidate.architecture === architecture)
24
+ : undefined;
25
+ const environment = slice?.dyld_environment ?? [];
26
26
  if (environment.length > 0)
27
27
  findings.push({
28
28
  kind: "dyld-environment-present",
29
29
  edge_index: null,
30
30
  image,
31
31
  basis: "derived",
32
- explanation: `${image} (${architecture}) sets dyld environment variables through LC_DYLD_ENVIRONMENT (${environment.join(", ")}); search paths they add are not modeled.`,
32
+ explanation: slice?.file_type === "execute"
33
+ ? `${image} (${architecture}) sets dyld environment variables through LC_DYLD_ENVIRONMENT (${environment.join(", ")}); any image-selection overrides they request are not modeled. Diagnostic settings alone do not alter resolution.`
34
+ : `${image} (${architecture}) contains LC_DYLD_ENVIRONMENT (${environment.join(", ")}). Dyld reads these commands only from the main executable; entries in this non-executable root are observations and do not alter resolution.`,
33
35
  });
34
36
  }
35
37
  return findings;
@@ -60,7 +62,7 @@ const unresolvedFinding = (edge, index) => {
60
62
  : {
61
63
  ...base,
62
64
  kind: "required-load-unresolved",
63
- explanation: `${missing}${when}dyld would fail to launch ${edge.root} (${edge.architecture}) unless the image is supplied elsewhere.`,
65
+ explanation: `${missing}${when}dyld would fail to load this required dependency for ${edge.root} (${edge.architecture}) unless the image is supplied elsewhere.`,
64
66
  };
65
67
  };
66
68
  export const DYLIB_RESOLUTION_LIMITATIONS = [
@@ -28,6 +28,7 @@ export const buildInterfaceBuilderAnalysis = (input) => {
28
28
  let omittedObjects = 0;
29
29
  let omittedConnections = 0;
30
30
  let omittedGraphNodes = 0;
31
+ let omittedHierarchyReferences = 0;
31
32
  for (const document of input.documents.slice(0, input.limits.max_documents)) {
32
33
  const parsed = parseInterfaceBuilderRecords(document.raw);
33
34
  const prefix = `ib:${document.relativePath}:`;
@@ -276,6 +277,8 @@ export const buildInterfaceBuilderAnalysis = (input) => {
276
277
  }
277
278
  }
278
279
  const objectsTruncated = parsed.omittedObjects > 0 || parsed.objects.length > documentObjectCount;
280
+ const parserOmittedHierarchyReferences = parsed.omittedHierarchyReferences;
281
+ omittedHierarchyReferences += parserOmittedHierarchyReferences;
279
282
  const parserOmittedConnections = parsed.omittedConnections;
280
283
  const connectionsTruncated = parserOmittedConnections > 0 ||
281
284
  parsed.connections.length > input.limits.max_connections;
@@ -291,7 +294,9 @@ export const buildInterfaceBuilderAnalysis = (input) => {
291
294
  document_kind: document.documentKind,
292
295
  object_count: parsed.objectCount,
293
296
  connection_count: parsed.connections.length + parserOmittedConnections,
294
- hierarchy_complete: parsed.hierarchy.length > 0 && !truncated,
297
+ hierarchy_complete: parsed.hierarchy.length > 0 &&
298
+ !truncated &&
299
+ parserOmittedHierarchyReferences === 0,
295
300
  });
296
301
  coverage.push({
297
302
  facet: `objects:${document.relativePath}`,
@@ -314,18 +319,22 @@ export const buildInterfaceBuilderAnalysis = (input) => {
314
319
  parserOmittedConnections,
315
320
  }, {
316
321
  facet: `hierarchy:${document.relativePath}`,
317
- status: parsed.hierarchy.length === 0
318
- ? "unsupported"
319
- : hierarchyTruncated || documentOmittedHierarchyEdges > 0
320
- ? "partial"
321
- : "complete",
322
- reason: parsed.hierarchy.length === 0
323
- ? "archive_hierarchy_not_decoded"
324
- : hierarchyTruncated || documentOmittedHierarchyEdges > 0
325
- ? "graph_limit_reached"
326
- : null,
322
+ status: parserOmittedHierarchyReferences > 0
323
+ ? "partial"
324
+ : parsed.hierarchy.length === 0
325
+ ? "unsupported"
326
+ : hierarchyTruncated || documentOmittedHierarchyEdges > 0
327
+ ? "partial"
328
+ : "complete",
329
+ reason: parserOmittedHierarchyReferences > 0
330
+ ? "archive_hierarchy_references_missing"
331
+ : parsed.hierarchy.length === 0
332
+ ? "archive_hierarchy_not_decoded"
333
+ : hierarchyTruncated || documentOmittedHierarchyEdges > 0
334
+ ? "graph_limit_reached"
335
+ : null,
327
336
  examined: hierarchyCount,
328
- omitted: documentOmittedHierarchyEdges,
337
+ omitted: documentOmittedHierarchyEdges + parserOmittedHierarchyReferences,
329
338
  }, {
330
339
  facet: `classes:${document.relativePath}`,
331
340
  status: Object.keys(parsed.classes).length > 0 ? "complete" : "unsupported",
@@ -367,7 +376,10 @@ export const buildInterfaceBuilderAnalysis = (input) => {
367
376
  nodes: [...nodes.values()],
368
377
  edges,
369
378
  coverage,
370
- truncated: truncated || omittedObjects > 0 || omittedConnections > 0,
379
+ truncated: truncated ||
380
+ omittedObjects > 0 ||
381
+ omittedConnections > 0 ||
382
+ omittedHierarchyReferences > 0,
371
383
  },
372
384
  limitations: [
373
385
  "The graph reflects recognized Interface Builder archive fields and connections; omitted private or unrecognized archive fields remain unknown.",
@@ -82,6 +82,7 @@ export const parseInterfaceBuilderRecords = (value) => {
82
82
  objectCount: objectEntries.length,
83
83
  omittedObjects: Math.max(0, objectEntries.length - 20_000),
84
84
  omittedConnections,
85
+ omittedHierarchyReferences: 0,
85
86
  };
86
87
  };
87
88
  /**
@@ -164,6 +165,7 @@ const keyedArchiveTable = (objectTable) => {
164
165
  return output;
165
166
  };
166
167
  const hierarchy = (start) => {
168
+ let omittedReferences = 0;
167
169
  const visited = new Set();
168
170
  const root = [];
169
171
  const pending = [
@@ -190,10 +192,17 @@ const keyedArchiveTable = (objectTable) => {
190
192
  const { value, output } = entry;
191
193
  const uid = archiveUid(value);
192
194
  if (uid !== undefined) {
195
+ // UID0 is archived nil, not a missing hierarchy object.
196
+ if (uid === 0)
197
+ continue;
198
+ const target = objectTable[uid];
199
+ if (target === undefined) {
200
+ omittedReferences += 1;
201
+ continue;
202
+ }
193
203
  if (visited.has(uid))
194
204
  continue;
195
205
  visited.add(uid);
196
- const target = objectTable[uid];
197
206
  if (Array.isArray(target)) {
198
207
  for (let index = target.length - 1; index >= 0; index -= 1) {
199
208
  const child = target[index];
@@ -229,7 +238,7 @@ const keyedArchiveTable = (objectTable) => {
229
238
  appendNode(record(value), undefined, output);
230
239
  }
231
240
  }
232
- return root;
241
+ return { items: root, omittedReferences };
233
242
  };
234
243
  const archivedArrayClass = (classReference) => {
235
244
  const uid = archiveUid(classReference);
@@ -317,11 +326,12 @@ const parseKeyedArchive = (root) => {
317
326
  return {
318
327
  objects,
319
328
  connections,
320
- hierarchy,
329
+ hierarchy: hierarchy.items,
321
330
  classes,
322
331
  objectCount,
323
332
  omittedObjects: Math.max(0, objectCount - 20_000),
324
333
  omittedConnections,
334
+ omittedHierarchyReferences: hierarchy.omittedReferences,
325
335
  };
326
336
  };
327
337
  /** Coerce unknown values to a string-keyed record; non-objects become `{}`. */
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { AnalysisInputError } from "../analysisErrorCore.js";
2
3
  import { jsonValueSchema } from "../jsonValue.js";
3
4
  /** Select one archive within the active bundle and optionally one named root. */
4
5
  export const keyedArchiveInputSchema = z.strictObject({
@@ -72,7 +73,14 @@ export const projectKeyedArchive = (value, selection) => {
72
73
  if (top === undefined || Object.keys(top).length === 0)
73
74
  throw new TypeError("Keyed archive has no $top roots");
74
75
  if (selection.root !== undefined && !Object.hasOwn(top, selection.root))
75
- throw new TypeError(`Archive root does not exist: ${selection.root}`);
76
+ throw new AnalysisInputError("inspect_keyed_archive", undefined, [
77
+ {
78
+ path: ["root"],
79
+ reason: "invalid_value",
80
+ message: `Archive root does not exist: ${selection.root}. Select one of the archive's $top roots or omit root.`,
81
+ expected: Object.keys(top),
82
+ },
83
+ ]);
76
84
  const roots = selection.root === undefined
77
85
  ? top
78
86
  : { [selection.root]: top[selection.root] ?? null };
@@ -0,0 +1,54 @@
1
+ /** Decode FAT table entries once so artifact and native readers share identity facts. */
2
+ export const readFatSliceDeclarations = (tableBytes, options) => {
3
+ const { count, wide, littleEndian } = options;
4
+ const entrySize = wide ? 32 : 20;
5
+ if (tableBytes.byteLength < count * entrySize)
6
+ return { status: "malformed", reason: "FAT table is truncated" };
7
+ const view = new DataView(tableBytes.buffer, tableBytes.byteOffset, tableBytes.byteLength);
8
+ const slices = [];
9
+ for (let index = 0; index < count; index++) {
10
+ const base = index * entrySize;
11
+ const offset = wide
12
+ ? safeNumber(view.getBigUint64(base + 8, littleEndian))
13
+ : view.getUint32(base + 8, littleEndian);
14
+ const size = wide
15
+ ? safeNumber(view.getBigUint64(base + 16, littleEndian))
16
+ : view.getUint32(base + 12, littleEndian);
17
+ if (offset === null || size === null)
18
+ return {
19
+ status: "malformed",
20
+ reason: "FAT offset or size exceeds the safe range",
21
+ };
22
+ slices.push({
23
+ index,
24
+ offset,
25
+ size,
26
+ alignmentExponent: view.getUint32(base + (wide ? 24 : 16), littleEndian),
27
+ cpuType: view.getUint32(base, littleEndian),
28
+ cpuSubtype: view.getUint32(base + 4, littleEndian),
29
+ });
30
+ }
31
+ return { status: "parsed", slices };
32
+ };
33
+ /** Validate byte-range and table/header identity facts shared by Mach-O readers. */
34
+ export const validateFatSlice = (declaration, containerSize, tableEnd, embedded) => {
35
+ const { index, offset, size, alignmentExponent, cpuType, cpuSubtype } = declaration;
36
+ if (!Number.isSafeInteger(offset) ||
37
+ !Number.isSafeInteger(size) ||
38
+ size <= 0 ||
39
+ offset < tableEnd)
40
+ return `FAT architecture ${index} has an invalid slice range`;
41
+ if (offset > containerSize || size > containerSize - offset)
42
+ return `FAT architecture ${index} slice range extends beyond the file`;
43
+ if (!Number.isInteger(alignmentExponent) ||
44
+ alignmentExponent < 0 ||
45
+ alignmentExponent > 52)
46
+ return `FAT architecture ${index} alignment exponent ${alignmentExponent} is unsupported`;
47
+ if (offset % 2 ** alignmentExponent !== 0)
48
+ return `FAT architecture ${index} offset is not aligned to 2^${alignmentExponent}`;
49
+ if (embedded !== undefined &&
50
+ (embedded.cpuType !== cpuType || embedded.cpuSubtype !== cpuSubtype))
51
+ return `FAT architecture ${index} CPU type/subtype disagrees with its Mach-O header`;
52
+ return null;
53
+ };
54
+ const safeNumber = (value) => value > BigInt(Number.MAX_SAFE_INTEGER) ? null : Number(value);