rea-agents 1.6.0 → 2.0.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 (238) hide show
  1. package/README.md +157 -66
  2. package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
  3. package/bridge/hopper_bridge.py +13 -3
  4. package/dist/application/AbortablePromise.js +25 -0
  5. package/dist/application/AnalysisProvider.js +3 -0
  6. package/dist/application/AnalysisProviderEvaluation.js +60 -0
  7. package/dist/application/AnalysisProviderRegistry.js +228 -0
  8. package/dist/application/AnalysisSnapshotCache.js +41 -21
  9. package/dist/application/AnalysisSnapshotFiles.js +5 -6
  10. package/dist/application/BinarySession.js +218 -101
  11. package/dist/application/CapabilityInventory.js +18 -0
  12. package/dist/application/CompositeProvider.js +20 -8
  13. package/dist/application/CrossVersionInvestigation.js +10 -6
  14. package/dist/application/DirectAnalysis.js +49 -35
  15. package/dist/application/Doctor.js +110 -8
  16. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  17. package/dist/application/ElectronBoundaryGraph.js +9 -0
  18. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  19. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  20. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  21. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  22. package/dist/application/EnhancedTools.js +39 -14
  23. package/dist/application/InvestigationProviders.js +41 -0
  24. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  25. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  26. package/dist/application/JavaScriptApplicationService.js +74 -0
  27. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  28. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  29. package/dist/application/JavaScriptArtifactAnalysis.js +305 -0
  30. package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
  31. package/dist/application/JavaScriptArtifactFiles.js +231 -0
  32. package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
  33. package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
  34. package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
  35. package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
  36. package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
  37. package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
  38. package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
  39. package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
  40. package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
  41. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  42. package/dist/application/JavaScriptReplayService.js +266 -0
  43. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  44. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  45. package/dist/application/MacHopper.js +20 -4
  46. package/dist/application/ManagedApplicationGraphService.js +55 -0
  47. package/dist/application/ManagedMemberComparisonService.js +122 -0
  48. package/dist/application/ManagedNativeVerificationService.js +39 -0
  49. package/dist/application/ManagedReconstructionService.js +58 -0
  50. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  51. package/dist/application/PermissionAuthority.js +1 -1
  52. package/dist/application/ProcessCaptureLifecycle.js +1 -1
  53. package/dist/application/ProcessHarness.js +1 -1
  54. package/dist/application/ProcessSampling.js +16 -12
  55. package/dist/application/ProjectPermissionStore.js +1 -0
  56. package/dist/application/SessionProviderRouter.js +219 -0
  57. package/dist/application/Setup.js +84 -60
  58. package/dist/application/SetupClients.js +1 -1
  59. package/dist/application/SetupPlan.js +9 -2
  60. package/dist/application/SupportedClients.js +40 -16
  61. package/dist/application/Upgrade.js +1 -1
  62. package/dist/application/runtime.js +11 -6
  63. package/dist/artifacts/AsarArtifactReader.js +23 -3
  64. package/dist/browser/CdpBrowserProvider.js +4 -2
  65. package/dist/browser/CdpCaptureEvents.js +49 -0
  66. package/dist/browser/CdpElectronInspection.js +106 -162
  67. package/dist/browser/CdpElectronProvider.js +9 -3
  68. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  69. package/dist/browser/CdpElectronScripts.js +94 -0
  70. package/dist/browser/CdpElectronWorkers.js +56 -0
  71. package/dist/browser/CdpEndpoint.js +14 -1
  72. package/dist/browser/CdpPageCapture.js +8 -4
  73. package/dist/catalogIdentity.js +6 -42
  74. package/dist/cli.js +446 -34
  75. package/dist/cliApplicationCommands.js +62 -0
  76. package/dist/cliBrowserAdvancedCommands.js +5 -4
  77. package/dist/cliBrowserCommands.js +5 -4
  78. package/dist/cliCommandNames.js +56 -0
  79. package/dist/cliElectronCommands.js +106 -3
  80. package/dist/cliEvidenceCommands.js +4 -3
  81. package/dist/cliInvestigationCommands.js +2 -1
  82. package/dist/cliJsonInput.js +54 -0
  83. package/dist/cliPolicyCommands.js +2 -1
  84. package/dist/cliProcessCommands.js +3 -2
  85. package/dist/config.js +101 -2
  86. package/dist/contracts/applicationToolContracts.js +91 -0
  87. package/dist/contracts/artifactToolContracts.js +5 -9
  88. package/dist/contracts/browserToolContracts.js +14 -62
  89. package/dist/contracts/electronToolContracts.js +45 -19
  90. package/dist/contracts/enhancedInputs.js +2 -2
  91. package/dist/contracts/errorSchemas.js +1 -0
  92. package/dist/contracts/investigationExamples.js +0 -6
  93. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  94. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  95. package/dist/contracts/managedToolContracts.js +153 -0
  96. package/dist/contracts/managedWorkflowExamples.js +346 -0
  97. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  98. package/dist/contracts/nativeToolContracts.js +2 -6
  99. package/dist/contracts/promptContracts.js +18 -2
  100. package/dist/contracts/providerSelection.js +28 -0
  101. package/dist/contracts/sessionLifecycleInputs.js +2 -0
  102. package/dist/contracts/toolContractExamples.js +10 -25
  103. package/dist/contracts/toolContracts.js +69 -65
  104. package/dist/contracts/toolEffects.js +160 -0
  105. package/dist/contracts/toolOutputSchemas.js +152 -42
  106. package/dist/doctorRuntime.js +47 -0
  107. package/dist/domain/analysisProfile.js +78 -0
  108. package/dist/domain/analysisSnapshot.js +92 -46
  109. package/dist/domain/artifactComparison.js +3 -4
  110. package/dist/domain/artifactGraph.js +1 -0
  111. package/dist/domain/binaryTarget.js +4 -29
  112. package/dist/domain/browserObservation.js +3 -0
  113. package/dist/domain/bundleComparison.js +4 -4
  114. package/dist/domain/electronObservation.js +13 -0
  115. package/dist/domain/electronStaticAnalysis.js +9 -0
  116. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  117. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  118. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  119. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  120. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  121. package/dist/domain/errors.js +148 -7
  122. package/dist/domain/evidence.js +44 -5
  123. package/dist/domain/evidenceBundle.js +9 -7
  124. package/dist/domain/functionComparison.js +9 -3
  125. package/dist/domain/functionComparisonNormalization.js +37 -0
  126. package/dist/domain/functionComparisonSchemas.js +3 -8
  127. package/dist/domain/functionDossierEvidence.js +4 -1
  128. package/dist/domain/hopperValues.js +66 -9
  129. package/dist/domain/inputIssueProjection.js +52 -0
  130. package/dist/domain/investigationWorkspace.js +2 -1
  131. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  132. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  133. package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
  134. package/dist/domain/javascriptApplicationGraph.js +282 -0
  135. package/dist/domain/javascriptApplicationGraphSchemas.js +139 -0
  136. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  137. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  138. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  139. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  140. package/dist/domain/javascriptAstFingerprint.js +143 -0
  141. package/dist/domain/javascriptFeatureSeed.js +140 -0
  142. package/dist/domain/javascriptFeatureTrace.js +232 -0
  143. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  144. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  145. package/dist/domain/javascriptNativeHandoff.js +85 -0
  146. package/dist/domain/javascriptReplay.js +361 -0
  147. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  148. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  149. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  150. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  151. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  152. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  153. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  154. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  155. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +346 -0
  157. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  158. package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
  159. package/dist/domain/javascriptStaticAnalysisState.js +23 -0
  160. package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
  161. package/dist/domain/managedApplicationGraph.js +556 -0
  162. package/dist/domain/managedArtifact.js +444 -0
  163. package/dist/domain/managedMemberComparison.js +565 -0
  164. package/dist/domain/managedNativeVerification.js +408 -0
  165. package/dist/domain/managedReconstruction.js +183 -0
  166. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  167. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  168. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  169. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  170. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  171. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  172. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  173. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  174. package/dist/dotnet/ManagedPeReader.js +157 -0
  175. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  176. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  177. package/dist/generatedPackageMetadata.js +2 -2
  178. package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
  179. package/dist/ghidra/GhidraClient.js +400 -0
  180. package/dist/ghidra/GhidraClientTypes.js +1 -0
  181. package/dist/ghidra/GhidraDefaults.js +20 -0
  182. package/dist/ghidra/GhidraDiagnostics.js +43 -0
  183. package/dist/ghidra/GhidraDoctor.js +59 -0
  184. package/dist/ghidra/GhidraFunctionValues.js +264 -0
  185. package/dist/ghidra/GhidraInstallation.js +239 -0
  186. package/dist/ghidra/GhidraInventoryValues.js +217 -0
  187. package/dist/ghidra/GhidraLauncher.js +139 -0
  188. package/dist/ghidra/GhidraProvider.js +366 -0
  189. package/dist/ghidra/GhidraRequestQueue.js +98 -0
  190. package/dist/ghidra/GhidraResponseBuffer.js +31 -0
  191. package/dist/ghidra/GhidraResponseRouter.js +24 -0
  192. package/dist/ghidra/GhidraSessionError.js +39 -0
  193. package/dist/ghidra/GhidraSessionValues.js +62 -0
  194. package/dist/ghidra/GhidraSocketConnection.js +46 -0
  195. package/dist/ghidra/protocol.js +69 -0
  196. package/dist/hopper/BridgeLauncher.js +19 -54
  197. package/dist/hopper/HopperAnalysisProfile.js +106 -0
  198. package/dist/hopper/HopperClient.js +154 -162
  199. package/dist/hopper/HopperProvider.js +99 -7
  200. package/dist/hopper/HopperSessionValues.js +27 -0
  201. package/dist/hopper/HopperSocketConnection.js +31 -0
  202. package/dist/main.js +14 -0
  203. package/dist/native/NativeMacOSProvider.js +3 -1
  204. package/dist/process/PendingOperations.js +59 -0
  205. package/dist/process/PrivateRuntimeRoot.js +33 -0
  206. package/dist/process/ProviderDeadline.js +95 -0
  207. package/dist/process/ProviderProcess.js +275 -0
  208. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  209. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  210. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  211. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  212. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  213. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  214. package/dist/server/createServer.js +53 -0
  215. package/dist/server/promptCompletion.js +9 -3
  216. package/dist/server/registerApplicationTools.js +115 -0
  217. package/dist/server/registerArtifactComparisonTool.js +16 -9
  218. package/dist/server/registerBrowserTools.js +33 -8
  219. package/dist/server/registerBundleComparisonTool.js +17 -20
  220. package/dist/server/registerElectronTools.js +45 -3
  221. package/dist/server/registerEnhancedTools.js +67 -19
  222. package/dist/server/registerEvidenceTools.js +5 -1
  223. package/dist/server/registerFunctionComparisonTool.js +14 -7
  224. package/dist/server/registerInvestigationTools.js +21 -17
  225. package/dist/server/registerManagedTools.js +6 -0
  226. package/dist/server/registerManagedWorkflowTools.js +277 -0
  227. package/dist/server/registerOfficialTools.js +11 -8
  228. package/dist/server/registerProcessComparisonTool.js +25 -47
  229. package/dist/server/registerSessionStatusTool.js +7 -7
  230. package/dist/server/registerSessionTools.js +57 -87
  231. package/dist/server/sessionEvidence.js +14 -19
  232. package/dist/server/toolInputValidation.js +77 -0
  233. package/dist/server/toolRegistrationOptions.js +79 -2
  234. package/dist/server/toolResult.js +25 -17
  235. package/package.json +17 -7
  236. package/scripts/rea.mjs +36 -2
  237. package/skills/rea-analysis/SKILL.md +59 -3
  238. /package/dist/{application → process}/ProcessOwnership.js +0 -0
@@ -0,0 +1,43 @@
1
+ /** Preserve actionable runtime coordinates after cleanup and redact bearer data. */
2
+ export const createGhidraDiagnostics = (options) => {
3
+ if (options.runtimeRoot === undefined &&
4
+ options.launch === undefined &&
5
+ Object.keys(options.previous).length > 0)
6
+ return options.previous;
7
+ const redacted = (value) => options.token === undefined
8
+ ? value
9
+ : value.replaceAll(options.token, "[REDACTED]");
10
+ return {
11
+ target_path: options.targetPath,
12
+ provider_version: options.providerVersion,
13
+ profile_digest: options.profileDigest,
14
+ ...(options.runtimeRoot === undefined
15
+ ? {}
16
+ : {
17
+ runtime_root: options.runtimeRoot,
18
+ socket_path: `${options.runtimeRoot}/bridge.sock`,
19
+ }),
20
+ ...(options.launch === undefined
21
+ ? {}
22
+ : {
23
+ project_root: options.launch.projectRoot,
24
+ ghidra_log_path: options.launch.ghidraLogPath,
25
+ script_log_path: options.launch.scriptLogPath,
26
+ process_id: options.launch.process.pid ?? null,
27
+ }),
28
+ ...(options.snapshot === undefined
29
+ ? {}
30
+ : {
31
+ exit_code: options.snapshot.exitCode ?? null,
32
+ exit_signal: options.snapshot.signal ?? null,
33
+ stdout: boundedStream(options.snapshot.stdout, redacted),
34
+ stderr: boundedStream(options.snapshot.stderr, redacted),
35
+ }),
36
+ };
37
+ };
38
+ const boundedStream = (stream, redact) => ({
39
+ text: redact(stream.text),
40
+ bytes: stream.bytes,
41
+ retained_bytes: stream.retainedBytes,
42
+ truncated: stream.truncated,
43
+ });
@@ -0,0 +1,59 @@
1
+ import { inspectGhidraInstallation, } from "./GhidraInstallation.js";
2
+ /** Project adapter-owned installation facts into the generic doctor contract. */
3
+ export const projectGhidraDoctorInspection = (inspection) => ({
4
+ id: "ghidra",
5
+ configured: inspection.installDir !== null,
6
+ available: inspection.available,
7
+ providerVersion: inspection.providerVersion,
8
+ registrationEnvironment: inspection.available && inspection.installDir !== null
9
+ ? {
10
+ GHIDRA_INSTALL_DIR: inspection.installDir,
11
+ ...(inspection.javaHome === null
12
+ ? {}
13
+ : { JAVA_HOME: inspection.javaHome }),
14
+ }
15
+ : {},
16
+ checks: selectedChecks(inspection).map((check) => projectCheck(check, inspection.installDir !== null)),
17
+ });
18
+ /** Inspect the process-configured BYO Ghidra installation for `rea doctor`. */
19
+ export const inspectSystemGhidraProvider = () => Promise.resolve(projectGhidraDoctorInspection(inspectGhidraInstallation({
20
+ ...(process.env.GHIDRA_INSTALL_DIR === undefined
21
+ ? {}
22
+ : { installDir: process.env.GHIDRA_INSTALL_DIR }),
23
+ ...(process.env.JAVA_HOME === undefined
24
+ ? {}
25
+ : { javaHome: process.env.JAVA_HOME }),
26
+ })));
27
+ const selectedChecks = (inspection) => {
28
+ const byName = new Map(inspection.checks.map((candidate) => [candidate.name, candidate]));
29
+ const configuration = byName.get("configuration");
30
+ if (configuration === undefined)
31
+ return [];
32
+ if (!configuration.ok)
33
+ return [configuration];
34
+ const installation = byName.get("installation");
35
+ return [
36
+ configuration,
37
+ byName.get("platform"),
38
+ byName.get("architecture"),
39
+ installation,
40
+ ...(installation?.ok === true
41
+ ? [byName.get("version"), byName.get("headless")]
42
+ : []),
43
+ byName.get("java"),
44
+ ].filter((candidate) => Boolean(candidate));
45
+ };
46
+ const projectCheck = (check, configured) => ({
47
+ name: check.name,
48
+ ok: check.ok,
49
+ code: check.code,
50
+ detail: check.detail,
51
+ remediation: check.remediation,
52
+ classification: check.name === "platform" || check.name === "architecture"
53
+ ? "unsupported_host"
54
+ : check.name === "java" && check.code === "runtime_missing"
55
+ ? "missing_dependency"
56
+ : configured
57
+ ? "config_drift"
58
+ : "missing_analysis_engine",
59
+ });
@@ -0,0 +1,264 @@
1
+ import { z } from "zod";
2
+ import { AnalysisInputError, AnalysisOutputError } from "../domain/errors.js";
3
+ import { functionDossierSchema, } from "../domain/hopperValues.js";
4
+ import { jsonObjectSchema, jsonValueSchema, } from "../domain/jsonValue.js";
5
+ import { err, ok } from "../domain/result.js";
6
+ import { ghidraBoundedIdentifierSchema, ghidraCanonicalAddressSchema, } from "./GhidraInventoryValues.js";
7
+ /** Read-only function operations admitted by the Ghidra adapter. */
8
+ export const GHIDRA_FUNCTION_OPERATIONS = [
9
+ "analyze_function",
10
+ "procedure_assembly",
11
+ "procedure_callees",
12
+ "procedure_callers",
13
+ "procedure_info",
14
+ "procedure_pseudo_code",
15
+ "procedure_references",
16
+ "xrefs",
17
+ ];
18
+ const operationSet = new Set(GHIDRA_FUNCTION_OPERATIONS);
19
+ /** Narrow an application operation to the Ghidra function-analysis surface. */
20
+ export const isGhidraFunctionOperation = (operation) => operationSet.has(operation);
21
+ const document = ghidraBoundedIdentifierSchema.nullable().default(null);
22
+ const procedure = ghidraBoundedIdentifierSchema;
23
+ const directProcedure = { document, procedure };
24
+ const collectionOffsets = z
25
+ .object({
26
+ comments: z.number().int().min(0).default(0),
27
+ callers: z.number().int().min(0).default(0),
28
+ callees: z.number().int().min(0).default(0),
29
+ incoming_references: z.number().int().min(0).default(0),
30
+ outgoing_references: z.number().int().min(0).default(0),
31
+ referenced_strings: z.number().int().min(0).default(0),
32
+ referenced_names: z.number().int().min(0).default(0),
33
+ basic_blocks: z.number().int().min(0).default(0),
34
+ })
35
+ .strict()
36
+ .default({
37
+ comments: 0,
38
+ callers: 0,
39
+ callees: 0,
40
+ incoming_references: 0,
41
+ outgoing_references: 0,
42
+ referenced_strings: 0,
43
+ referenced_names: 0,
44
+ basic_blocks: 0,
45
+ });
46
+ const inputSchemas = {
47
+ analyze_function: z
48
+ .object({
49
+ procedure,
50
+ include_assembly: z.boolean().default(false),
51
+ limit: z.number().int().min(1).max(500).default(100),
52
+ max_pseudocode_chars: z
53
+ .number()
54
+ .int()
55
+ .min(1)
56
+ .max(100_000)
57
+ .default(20_000),
58
+ max_instructions: z.number().int().min(1).max(5_000).default(500),
59
+ pseudocode_offset: z.number().int().min(0).default(0),
60
+ assembly_offset: z.number().int().min(0).default(0),
61
+ collection_offset: collectionOffsets,
62
+ })
63
+ .strict(),
64
+ procedure_assembly: z.object(directProcedure).strict(),
65
+ procedure_callees: z.object(directProcedure).strict(),
66
+ procedure_callers: z.object(directProcedure).strict(),
67
+ procedure_info: z.object(directProcedure).strict(),
68
+ procedure_pseudo_code: z.object(directProcedure).strict(),
69
+ procedure_references: z
70
+ .object({
71
+ ...directProcedure,
72
+ direction: z.enum(["incoming", "outgoing"]).default("outgoing"),
73
+ offset: z.number().int().min(0).default(0),
74
+ limit: z.number().int().min(1).max(500).default(100),
75
+ max_instructions: z.number().int().min(1).max(5_000).default(500),
76
+ })
77
+ .strict(),
78
+ xrefs: z.object({ document, address: ghidraCanonicalAddressSchema }).strict(),
79
+ };
80
+ /** Validate and default one function request before it crosses the socket. */
81
+ export const parseGhidraFunctionInput = (operation, value) => {
82
+ const parsed = inputSchemas[operation].safeParse(value);
83
+ return parsed.success
84
+ ? ok(jsonObjectSchema.parse(parsed.data))
85
+ : err(new AnalysisInputError(operation, { cause: parsed.error }));
86
+ };
87
+ const classification = z
88
+ .object({
89
+ external: z.boolean(),
90
+ thunk: z.boolean(),
91
+ thunk_target: ghidraCanonicalAddressSchema.nullable(),
92
+ provenance: z.literal("ghidra-function-manager"),
93
+ })
94
+ .strict();
95
+ const procedureIdentity = z
96
+ .object({
97
+ address: ghidraCanonicalAddressSchema,
98
+ name: z.string().min(1),
99
+ classification,
100
+ })
101
+ .strict();
102
+ const localVariable = z
103
+ .object({
104
+ description: z.string(),
105
+ provenance: z.literal("ghidra-function-database"),
106
+ })
107
+ .strict();
108
+ const referenceKind = z
109
+ .object({
110
+ available: z.literal(true),
111
+ provenance: z.literal("ghidra-reference-manager"),
112
+ type: z.string().min(1),
113
+ flow: z.boolean(),
114
+ call: z.boolean(),
115
+ jump: z.boolean(),
116
+ data: z.boolean(),
117
+ read: z.boolean(),
118
+ write: z.boolean(),
119
+ indirect: z.boolean(),
120
+ computed: z.boolean(),
121
+ conditional: z.boolean(),
122
+ terminal: z.boolean(),
123
+ primary: z.boolean(),
124
+ operand_index: z.number().int(),
125
+ external: z.boolean(),
126
+ })
127
+ .strict();
128
+ const referenceEdge = z
129
+ .object({
130
+ source_address: ghidraCanonicalAddressSchema,
131
+ target_address: ghidraCanonicalAddressSchema,
132
+ source_procedure: procedureIdentity.nullable(),
133
+ target_procedure: procedureIdentity.nullable(),
134
+ kind: referenceKind,
135
+ })
136
+ .strict();
137
+ const boundedReferences = z
138
+ .object({
139
+ items: z.array(referenceEdge),
140
+ total: z.number().int().min(0).nullable(),
141
+ returned: z.number().int().min(0),
142
+ truncated: z.boolean(),
143
+ next_offset: z.number().int().min(0).nullable(),
144
+ })
145
+ .strict()
146
+ .superRefine((value, context) => {
147
+ if (value.returned !== value.items.length)
148
+ context.addIssue({
149
+ code: "custom",
150
+ path: ["returned"],
151
+ message: "returned must equal the number of reference items",
152
+ });
153
+ if (value.total !== null && value.total < value.returned)
154
+ context.addIssue({
155
+ code: "custom",
156
+ path: ["total"],
157
+ message: "total cannot be smaller than returned",
158
+ });
159
+ if (value.next_offset !== null && !value.truncated)
160
+ context.addIssue({
161
+ code: "custom",
162
+ path: ["next_offset"],
163
+ message: "a continuation requires truncated output",
164
+ });
165
+ });
166
+ const procedureReferences = z
167
+ .object({
168
+ procedure: procedureIdentity,
169
+ direction: z.enum(["incoming", "outgoing"]),
170
+ references: boundedReferences,
171
+ instructions_scanned: z.number().int().min(0),
172
+ instruction_scan_truncated: z.boolean(),
173
+ })
174
+ .strict();
175
+ const procedureInfo = z
176
+ .object({
177
+ name: z.string().min(1),
178
+ entrypoint: ghidraCanonicalAddressSchema,
179
+ basicblock_count: z.number().int().min(0),
180
+ length: z.number().int().min(0),
181
+ signature: z.string().nullable(),
182
+ locals: z.array(localVariable),
183
+ classification,
184
+ })
185
+ .strict();
186
+ const ghidraFunctionDossier = functionDossierSchema.superRefine((value, context) => {
187
+ const addresses = [
188
+ value.procedure.address,
189
+ ...value.comments.items.map(({ address }) => address),
190
+ ...value.callers.items.map(({ address }) => address),
191
+ ...value.callees.items.map(({ address }) => address),
192
+ ...value.incoming_references.items.flatMap(referenceAddresses),
193
+ ...value.outgoing_references.items.flatMap(referenceAddresses),
194
+ ...value.referenced_strings.items.flatMap(({ address, source_address }) => [address, source_address]),
195
+ ...value.referenced_names.items.flatMap(({ address, source_address }) => [
196
+ address,
197
+ source_address,
198
+ ]),
199
+ ...value.basic_blocks.items.flatMap(({ start, end, successors }) => [
200
+ start,
201
+ end,
202
+ ...successors,
203
+ ]),
204
+ ];
205
+ if (addresses.some((address) => !ghidraCanonicalAddressSchema.safeParse(address).success))
206
+ context.addIssue({
207
+ code: "custom",
208
+ message: "Ghidra dossier contains a non-canonical address",
209
+ });
210
+ const identities = [
211
+ value.procedure,
212
+ ...value.callers.items,
213
+ ...value.callees.items,
214
+ ...value.incoming_references.items.flatMap(referenceProcedures),
215
+ ...value.outgoing_references.items.flatMap(referenceProcedures),
216
+ ];
217
+ if (identities.some(({ classification: facts }) => !classification.safeParse(facts).success))
218
+ context.addIssue({
219
+ code: "custom",
220
+ message: "Ghidra dossier omitted function classification",
221
+ });
222
+ if (value.procedure.locals.some((variable) => !localVariable.safeParse(variable).success) ||
223
+ [
224
+ ...value.incoming_references.items,
225
+ ...value.outgoing_references.items,
226
+ ].some(({ kind }) => !referenceKind.safeParse(kind).success))
227
+ context.addIssue({
228
+ code: "custom",
229
+ message: "Ghidra dossier contains invalid provenance",
230
+ });
231
+ if (!value.limitations.some((item) => /indirect|computed/u.test(item)) ||
232
+ !value.limitations.some((item) => /provider|Ghidra|Hopper/u.test(item)))
233
+ context.addIssue({
234
+ code: "custom",
235
+ message: "Ghidra dossier omitted required uncertainty limitations",
236
+ });
237
+ });
238
+ const resultSchemas = {
239
+ analyze_function: ghidraFunctionDossier,
240
+ procedure_assembly: z.string(),
241
+ procedure_callees: z.array(ghidraCanonicalAddressSchema),
242
+ procedure_callers: z.array(ghidraCanonicalAddressSchema),
243
+ procedure_info: procedureInfo,
244
+ procedure_pseudo_code: z.string().nullable(),
245
+ procedure_references: procedureReferences,
246
+ xrefs: z.array(ghidraCanonicalAddressSchema),
247
+ };
248
+ /** Require exact, bounded Java-bridge function output before creating Evidence. */
249
+ export const parseGhidraFunctionResult = (operation, value) => {
250
+ const parsed = resultSchemas[operation].safeParse(value);
251
+ return parsed.success
252
+ ? ok(jsonValueSchema.parse(parsed.data))
253
+ : err(new AnalysisOutputError(operation, "Ghidra bridge output did not match the function-analysis contract", { cause: parsed.error }));
254
+ };
255
+ const referenceAddresses = (edge) => [
256
+ edge.source_address,
257
+ edge.target_address,
258
+ ...(edge.source_procedure === null ? [] : [edge.source_procedure.address]),
259
+ ...(edge.target_procedure === null ? [] : [edge.target_procedure.address]),
260
+ ];
261
+ const referenceProcedures = (edge) => [
262
+ ...(edge.source_procedure === null ? [] : [edge.source_procedure]),
263
+ ...(edge.target_procedure === null ? [] : [edge.target_procedure]),
264
+ ];
@@ -0,0 +1,239 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { constants } from "node:fs";
3
+ import { accessSync, readFileSync } from "node:fs";
4
+ import { delimiter, join } from "node:path";
5
+ /** Exact Ghidra build whose Java bridge contract this REA release supports. */
6
+ export const SUPPORTED_GHIDRA_VERSION = "12.1.2";
7
+ /** Exact JDK major documented by the supported Ghidra release. */
8
+ export const SUPPORTED_GHIDRA_JAVA_MAJOR = 21;
9
+ /** Inspect an installation without importing a target or modifying Ghidra. */
10
+ export const inspectGhidraInstallation = (options, host = systemGhidraInstallationHost()) => {
11
+ const platform = options.platform ?? process.platform;
12
+ const architecture = options.architecture ?? process.arch;
13
+ const coordinates = installationCoordinates(options, host);
14
+ const checks = installationChecks({
15
+ coordinates,
16
+ platform,
17
+ architecture,
18
+ javaHome: options.javaHome,
19
+ host,
20
+ });
21
+ const failed = checks.find(({ ok }) => !ok);
22
+ return {
23
+ available: failed === undefined,
24
+ platform,
25
+ architecture,
26
+ installDir: coordinates.installDir,
27
+ analyzeHeadlessPath: coordinates.analyzeHeadlessPath,
28
+ applicationPropertiesPath: coordinates.applicationPropertiesPath,
29
+ providerVersion: coordinates.providerVersion,
30
+ javaCommand: coordinates.javaCommand,
31
+ javaHome: coordinates.java?.home ?? options.javaHome ?? null,
32
+ javaVersion: coordinates.java?.version ?? null,
33
+ checks,
34
+ rejectionCode: failed?.code ?? null,
35
+ reason: failed?.remediation ?? null,
36
+ };
37
+ };
38
+ const installationCoordinates = (options, host) => {
39
+ const installDir = options.installDir ?? null;
40
+ const applicationPropertiesPath = installDir === null
41
+ ? null
42
+ : join(installDir, "Ghidra", "application.properties");
43
+ const analyzeHeadlessPath = installDir === null ? null : join(installDir, "support", "analyzeHeadless");
44
+ const properties = applicationPropertiesPath === null
45
+ ? undefined
46
+ : host.readText(applicationPropertiesPath);
47
+ const javaCommand = options.javaHome === undefined
48
+ ? "java"
49
+ : join(options.javaHome, "bin", "java");
50
+ return {
51
+ installDir,
52
+ applicationPropertiesPath,
53
+ analyzeHeadlessPath,
54
+ propertiesAvailable: properties !== undefined,
55
+ providerVersion: properties === undefined
56
+ ? null
57
+ : propertyValue(properties, "application.version"),
58
+ javaCommand,
59
+ java: host.probeJava(javaCommand, ghidraJavaEnvironment(options.javaHome)),
60
+ };
61
+ };
62
+ const installationChecks = ({ coordinates, platform, architecture, javaHome, host, }) => [
63
+ installationCheck({
64
+ name: "configuration",
65
+ ok: coordinates.installDir !== null,
66
+ code: "not_configured",
67
+ detail: coordinates.installDir ?? "GHIDRA_INSTALL_DIR is not set",
68
+ remediation: "Set GHIDRA_INSTALL_DIR to an extracted Ghidra 12.1.2 release directory.",
69
+ }),
70
+ installationCheck({
71
+ name: "platform",
72
+ ok: platform === "linux",
73
+ code: "unsupported_host",
74
+ detail: platform,
75
+ remediation: "REA's Ghidra v1 adapter currently supports Linux only.",
76
+ }),
77
+ installationCheck({
78
+ name: "architecture",
79
+ ok: architecture === "x64",
80
+ code: "unsupported_host",
81
+ detail: architecture,
82
+ remediation: "Use the official Linux x86-64 Ghidra distribution on an x64 host.",
83
+ }),
84
+ installationCheck({
85
+ name: "installation",
86
+ ok: coordinates.propertiesAvailable,
87
+ code: "executable_missing",
88
+ detail: coordinates.applicationPropertiesPath ??
89
+ "Ghidra application properties unavailable",
90
+ remediation: "Point GHIDRA_INSTALL_DIR at the root of an extracted official Ghidra release.",
91
+ }),
92
+ installationCheck({
93
+ name: "version",
94
+ ok: coordinates.providerVersion === SUPPORTED_GHIDRA_VERSION,
95
+ code: coordinates.providerVersion === null
96
+ ? "version_unresolved"
97
+ : "unsupported_version",
98
+ detail: coordinates.providerVersion ?? "unknown",
99
+ remediation: `Install Ghidra ${SUPPORTED_GHIDRA_VERSION}, or update REA for a different Ghidra build.`,
100
+ }),
101
+ installationCheck({
102
+ name: "headless",
103
+ ok: coordinates.analyzeHeadlessPath !== null &&
104
+ host.executable(coordinates.analyzeHeadlessPath),
105
+ code: "executable_missing",
106
+ detail: coordinates.analyzeHeadlessPath ?? "analyzeHeadless unavailable",
107
+ remediation: "Restore the executable support/analyzeHeadless script from the official Ghidra release.",
108
+ }),
109
+ javaCheck(coordinates.java, coordinates.javaCommand, javaHome),
110
+ ];
111
+ /** Project an installation probe into caller-visible, secret-free diagnostics. */
112
+ export const ghidraInstallationDiagnostics = (inspection) => ({
113
+ install_dir: inspection.installDir,
114
+ analyze_headless_path: inspection.analyzeHeadlessPath,
115
+ application_properties_path: inspection.applicationPropertiesPath,
116
+ provider_version: inspection.providerVersion,
117
+ java_command: inspection.javaCommand,
118
+ java_home: inspection.javaHome,
119
+ java_version: inspection.javaVersion,
120
+ platform: inspection.platform,
121
+ architecture: inspection.architecture,
122
+ checks: inspection.checks.map((check) => ({
123
+ name: check.name,
124
+ ok: check.ok,
125
+ code: check.code,
126
+ detail: check.detail,
127
+ })),
128
+ });
129
+ /** Environment that makes an explicitly selected JDK win over PATH discovery. */
130
+ export const ghidraJavaEnvironment = (javaHome, environment = process.env) => {
131
+ const boundedEnvironment = {
132
+ ...environment,
133
+ _JAVA_OPTIONS: "",
134
+ JAVA_TOOL_OPTIONS: "",
135
+ JDK_JAVA_OPTIONS: "",
136
+ GHIDRA_JAVA_OPTIONS: "",
137
+ };
138
+ return javaHome === undefined
139
+ ? boundedEnvironment
140
+ : {
141
+ ...boundedEnvironment,
142
+ JAVA_HOME: javaHome,
143
+ PATH: `${join(javaHome, "bin")}${delimiter}${environment.PATH ?? ""}`,
144
+ };
145
+ };
146
+ const installationCheck = (options) => ({
147
+ name: options.name,
148
+ ok: options.ok,
149
+ code: options.ok ? null : options.code,
150
+ detail: options.detail,
151
+ remediation: options.ok ? null : options.remediation,
152
+ });
153
+ const javaCheck = (observation, command, configuredHome) => {
154
+ const supported = observation !== undefined &&
155
+ observation.major === SUPPORTED_GHIDRA_JAVA_MAJOR &&
156
+ observation.bits === 64 &&
157
+ observation.jdk;
158
+ const detail = observation === undefined
159
+ ? command
160
+ : `${observation.version}; ${String(observation.bits)}-bit; ${observation.jdk ? "JDK" : "runtime only"}; ${observation.home}`;
161
+ return installationCheck({
162
+ name: "java",
163
+ ok: supported,
164
+ code: observation === undefined ? "runtime_missing" : "unsupported_version",
165
+ detail,
166
+ remediation: configuredHome === undefined
167
+ ? "Install a 64-bit JDK 21 or set JAVA_HOME before starting REA."
168
+ : "Update JAVA_HOME to the root of a 64-bit JDK 21 installation.",
169
+ });
170
+ };
171
+ const propertyValue = (content, name) => {
172
+ for (const line of content.split(/\r?\n/u)) {
173
+ const separator = line.indexOf("=");
174
+ if (separator < 0 || line.slice(0, separator).trim() !== name)
175
+ continue;
176
+ const value = line.slice(separator + 1).trim();
177
+ return value.length === 0 ? null : value;
178
+ }
179
+ return null;
180
+ };
181
+ const systemGhidraInstallationHost = () => ({
182
+ readText(path) {
183
+ try {
184
+ return readFileSync(path, "utf8");
185
+ }
186
+ catch {
187
+ return undefined;
188
+ }
189
+ },
190
+ executable(path) {
191
+ try {
192
+ accessSync(path, constants.X_OK);
193
+ return true;
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ },
199
+ probeJava(command, environment) {
200
+ const observed = spawnSync(command, ["-XshowSettings:properties", "-version"], {
201
+ encoding: "utf8",
202
+ env: environment,
203
+ timeout: 5_000,
204
+ maxBuffer: 128 * 1024,
205
+ windowsHide: true,
206
+ });
207
+ if (observed.error !== undefined || observed.status !== 0)
208
+ return undefined;
209
+ const output = `${observed.stdout ?? ""}\n${observed.stderr ?? ""}`;
210
+ const version = javaProperty(output, "java.version");
211
+ const home = javaProperty(output, "java.home");
212
+ const bits = Number.parseInt(javaProperty(output, "sun.arch.data.model") ?? "0", 10);
213
+ if (version === null || home === null)
214
+ return undefined;
215
+ return {
216
+ version,
217
+ major: javaMajor(version),
218
+ home,
219
+ bits,
220
+ jdk: executablePath(join(home, "bin", "javac")),
221
+ };
222
+ },
223
+ });
224
+ const executablePath = (path) => {
225
+ try {
226
+ accessSync(path, constants.X_OK);
227
+ return true;
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ };
233
+ const javaProperty = (output, name) => new RegExp(`^\\s*${name.replaceAll(".", "\\.")}\\s*=\\s*(.+?)\\s*$`, "mu").exec(output)?.[1] ?? null;
234
+ const javaMajor = (version) => {
235
+ const first = Number.parseInt(version.split(".")[0] ?? "0", 10);
236
+ if (first !== 1)
237
+ return first;
238
+ return Number.parseInt(version.split(".")[1] ?? "0", 10);
239
+ };