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
@@ -1,13 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import canonicalize from "canonicalize";
3
3
  import { z } from "zod";
4
+ import { analysisProfileSchema, analysisProfilesEqual, committedProviderSchema, } from "./analysisProfile.js";
4
5
  import { evidenceBundleForTarget, evidenceBundleSchema, parseEvidenceBundle, } from "./evidenceBundle.js";
5
- import { evidenceLocationSchema, providerSchema, } from "./evidence.js";
6
+ import { evidenceLocationSchema, } from "./evidence.js";
6
7
  import { jsonObjectSchema, jsonValueSchema, } from "./jsonValue.js";
7
8
  const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
8
9
  const architectureSchema = z.enum(["x86", "x86_64", "arm", "arm64"]);
9
10
  const formatSchema = z.enum([
10
- "hopper",
11
+ "analysis-database",
11
12
  "mach-o",
12
13
  "elf",
13
14
  "pe",
@@ -21,11 +22,13 @@ const formatSchema = z.enum([
21
22
  "javascript",
22
23
  "source-map",
23
24
  ]);
25
+ const kindSchema = z.enum(["executable", "database", "archive", "artifact"]);
24
26
  const subjectSchema = z.object({
25
27
  path: z.string().min(1),
26
28
  sha256: digestSchema,
27
29
  format: z.enum([
28
30
  ...formatSchema.options,
31
+ "hopper",
29
32
  "directory",
30
33
  "file",
31
34
  "unknown",
@@ -37,9 +40,24 @@ const subjectSchema = z.object({
37
40
  });
38
41
  const targetSchema = z.object({
39
42
  sha256: digestSchema,
43
+ kind: kindSchema,
40
44
  format: formatSchema,
41
45
  architecture: architectureSchema.nullable(),
42
- loader_args: z.array(z.string()),
46
+ });
47
+ const bindingSchema = z
48
+ .object({
49
+ provider: committedProviderSchema,
50
+ analysis_profile: analysisProfileSchema,
51
+ })
52
+ .superRefine((binding, context) => {
53
+ if (binding.provider.id !== binding.analysis_profile.provider.id ||
54
+ binding.provider.name !== binding.analysis_profile.provider.name ||
55
+ binding.provider.version !== binding.analysis_profile.provider.version)
56
+ context.addIssue({
57
+ code: "custom",
58
+ path: ["provider"],
59
+ message: "Snapshot binding provider does not match analysis profile",
60
+ });
43
61
  });
44
62
  const entrySchema = z.object({
45
63
  query_id: z.string().regex(/^query_[a-f0-9]{64}$/u),
@@ -48,16 +66,17 @@ const entrySchema = z.object({
48
66
  execution: z.object({
49
67
  result: jsonValueSchema,
50
68
  raw_result: jsonValueSchema.nullable(),
51
- provider: providerSchema,
69
+ provider: committedProviderSchema,
52
70
  limitations: z.array(z.string()),
53
71
  locations: z.array(evidenceLocationSchema),
54
72
  subject: subjectSchema.nullable(),
55
73
  }),
56
74
  });
57
- /** Provider-neutral, deterministic cache of successful immutable analysis calls. */
58
- const analysisSnapshotSchema = z.object({
59
- snapshot_version: z.literal(1),
75
+ /** Provider- and profile-exact cache of successful immutable analysis calls. */
76
+ export const analysisSnapshotSchema = z.object({
77
+ snapshot_version: z.literal(2),
60
78
  target: targetSchema,
79
+ binding: bindingSchema,
61
80
  entries: z.array(entrySchema).max(10_000),
62
81
  evidence_bundle: evidenceBundleSchema,
63
82
  });
@@ -71,19 +90,32 @@ const canonicalJson = (value) => {
71
90
  /** Project a binary into the path-free identity used for cache invalidation. */
72
91
  export const snapshotTarget = (target) => ({
73
92
  sha256: target.sha256,
93
+ kind: target.kind,
74
94
  format: target.format,
75
95
  architecture: target.architecture ?? null,
76
- loader_args: [...target.loaderArgs],
77
96
  });
78
- /** Compare an open binary with the immutable identity in a snapshot. */
97
+ /** Build the immutable provider/profile binding used by snapshot v2. */
98
+ export const snapshotBinding = (profile) => bindingSchema.parse({
99
+ provider: profile.provider,
100
+ analysis_profile: profile,
101
+ });
102
+ /** Compare an open binary with the immutable target identity in a snapshot. */
79
103
  export const snapshotMatchesTarget = (snapshot, target) => snapshot.sha256 === target.sha256 &&
104
+ snapshot.kind === target.kind &&
80
105
  snapshot.format === target.format &&
81
- snapshot.architecture === (target.architecture ?? null) &&
82
- JSON.stringify(snapshot.loader_args) === JSON.stringify(target.loaderArgs);
83
- /** Find exact persisted CLI evidence without starting an analysis provider. */
106
+ snapshot.architecture === (target.architecture ?? null);
107
+ /** Compare a snapshot binding with the selected concrete analysis profile. */
108
+ export const snapshotMatchesProfile = (binding, profile) => binding.provider.id === profile.provider.id &&
109
+ binding.provider.name === profile.provider.name &&
110
+ binding.provider.version === profile.provider.version &&
111
+ analysisProfilesEqual(binding.analysis_profile, profile);
112
+ /** Require an exact target and selected provider/profile cache partition. */
113
+ export const snapshotMatchesBinding = (snapshot, target, profile) => snapshotMatchesTarget(snapshot.target, target) &&
114
+ snapshotMatchesProfile(snapshot.binding, profile);
115
+ /** Find exact persisted CLI Evidence without starting an analysis provider. */
84
116
  export const snapshotEvidenceForQuery = (snapshot, query) => {
85
- const { target, operation, parameters, provider } = query;
86
- if (!snapshotMatchesTarget(snapshot.target, target))
117
+ const { target, bindingProfile, operation, parameters, provider, evidenceProfile, } = query;
118
+ if (!snapshotMatchesBinding(snapshot, target, bindingProfile))
87
119
  return undefined;
88
120
  const encodedParameters = canonicalJson(parameters);
89
121
  return snapshot.evidence_bundle.records.find((record) => record.subject?.digest.sha256 === target.sha256 &&
@@ -91,49 +123,56 @@ export const snapshotEvidenceForQuery = (snapshot, query) => {
91
123
  record.provider.id === provider.id &&
92
124
  record.provider.name === provider.name &&
93
125
  record.provider.version === provider.version &&
126
+ "analysis_profile" in record &&
127
+ analysisProfilesEqual(record.analysis_profile, evidenceProfile) &&
94
128
  canonicalJson(record.parameters) === encodedParameters);
95
129
  };
96
- /** Compute the stable lookup key for one provider-specific analysis query. */
97
- export const analysisQueryId = (target, operation, parameters, provider) => `query_${createHash("sha256")
98
- .update(canonicalJson({
99
- target,
100
- operation,
101
- parameters,
102
- provider: {
103
- id: provider.id,
104
- name: provider.name,
105
- version: provider.version,
106
- },
107
- }))
130
+ /** Compute the stable lookup key for one provider/profile-specific query. */
131
+ export const analysisQueryId = (target, binding, operation, parameters) => `query_${createHash("sha256")
132
+ .update(canonicalJson({ target, binding, operation, parameters }))
108
133
  .digest("hex")}`;
109
134
  /** Create one serializable snapshot entry from a successful provider call. */
110
- export const createAnalysisSnapshotEntry = (target, operation, parameters, execution) => ({
111
- query_id: analysisQueryId(target, operation, parameters, execution.provider),
112
- operation,
113
- parameters: jsonObjectSchema.parse(parameters),
114
- execution: {
115
- result: execution.result,
116
- raw_result: execution.rawResult,
117
- provider: execution.provider,
118
- limitations: [...execution.limitations],
119
- locations: [...execution.locations],
120
- subject: execution.subject === null
121
- ? null
122
- : {
123
- ...execution.subject,
124
- architecture: execution.subject.architecture ?? null,
125
- },
126
- },
127
- });
128
- /** Parse a snapshot and reject altered query IDs or non-canonical entry order. */
135
+ export const createAnalysisSnapshotEntry = (input) => {
136
+ const { target, binding, operation, parameters, execution } = input;
137
+ if (execution.analysisProfile === undefined ||
138
+ !snapshotMatchesProfile(binding, execution.analysisProfile) ||
139
+ execution.provider.id !== binding.provider.id ||
140
+ execution.provider.name !== binding.provider.name ||
141
+ execution.provider.version !== binding.provider.version)
142
+ throw new TypeError("Snapshot execution does not match its provider/profile binding");
143
+ return {
144
+ query_id: analysisQueryId(target, binding, operation, parameters),
145
+ operation,
146
+ parameters: jsonObjectSchema.parse(parameters),
147
+ execution: {
148
+ result: execution.result,
149
+ raw_result: execution.rawResult,
150
+ provider: binding.provider,
151
+ limitations: [...execution.limitations],
152
+ locations: [...execution.locations],
153
+ subject: execution.subject === null
154
+ ? null
155
+ : {
156
+ ...execution.subject,
157
+ architecture: execution.subject.architecture ?? null,
158
+ },
159
+ },
160
+ };
161
+ };
162
+ /** Parse v2 and reject legacy snapshots, altered query IDs, or non-canonical order. */
129
163
  export const parseAnalysisSnapshot = (input) => {
164
+ rejectLegacySnapshot(input);
130
165
  const parsed = analysisSnapshotSchema.parse(input);
131
166
  parseEvidenceBundle(parsed.evidence_bundle);
132
167
  if (JSON.stringify(evidenceBundleForTarget(parsed.evidence_bundle, parsed.target.sha256)) !== JSON.stringify(parsed.evidence_bundle))
133
168
  throw new TypeError("Analysis snapshot evidence contains records for another target");
134
169
  const ids = new Set();
135
170
  for (const entry of parsed.entries) {
136
- const expected = analysisQueryId(parsed.target, entry.operation, entry.parameters, entry.execution.provider);
171
+ if (entry.execution.provider.id !== parsed.binding.provider.id ||
172
+ entry.execution.provider.name !== parsed.binding.provider.name ||
173
+ entry.execution.provider.version !== parsed.binding.provider.version)
174
+ throw new TypeError("Analysis snapshot entry provider does not match");
175
+ const expected = analysisQueryId(parsed.target, parsed.binding, entry.operation, entry.parameters);
137
176
  if (entry.query_id !== expected)
138
177
  throw new TypeError("Analysis snapshot query identifier does not match");
139
178
  if (ids.has(entry.query_id))
@@ -147,3 +186,10 @@ export const parseAnalysisSnapshot = (input) => {
147
186
  };
148
187
  /** Serialize a validated snapshot with byte-stable canonical entry ordering. */
149
188
  export const serializeAnalysisSnapshot = (snapshot) => `${canonicalJson(parseAnalysisSnapshot(snapshot))}\n`;
189
+ const rejectLegacySnapshot = (input) => {
190
+ if (typeof input === "object" &&
191
+ input !== null &&
192
+ "snapshot_version" in input &&
193
+ input.snapshot_version === 1)
194
+ throw new TypeError("Analysis snapshot v1 is incompatible with profile-exact replay; recapture it as snapshot v2. Its Evidence bundle may be imported separately.");
195
+ };
@@ -1,7 +1,6 @@
1
1
  import canonicalize from "canonicalize";
2
2
  import { z } from "zod";
3
3
  import {} from "./artifactGraph.js";
4
- import { evidenceSchema } from "./evidence.js";
5
4
  import { parseArtifactInventoryEvidence, } from "./artifactInventoryEvidence.js";
6
5
  const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
7
6
  const comparisonStatusSchema = z.enum([
@@ -32,9 +31,9 @@ const comparisonDimensionSchema = z.enum([
32
31
  "integrity",
33
32
  ]);
34
33
  /** Strict Evidence-backed input for deterministic artifact comparison. */
35
- export const artifactComparisonInputSchema = z.object({
36
- left: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
37
- right: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
34
+ export const artifactComparisonInputSchema = z.strictObject({
35
+ left_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
36
+ right_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
38
37
  offset: z.number().int().min(0).default(0),
39
38
  limit: z.number().int().min(1).max(500).default(100),
40
39
  unknown_registry_approved: z
@@ -42,6 +42,7 @@ const artifactFormatSchema = z.enum([
42
42
  "elf",
43
43
  "pe",
44
44
  "hopper",
45
+ "analysis-database",
45
46
  "ipa",
46
47
  "apk",
47
48
  "plist",
@@ -9,7 +9,7 @@ import { BinaryTargetError } from "./errors.js";
9
9
  import { err, ok } from "./result.js";
10
10
  const execFileAsync = promisify(execFile);
11
11
  /**
12
- * Resolve and classify a readable local target before Hopper is launched.
12
+ * Resolve and classify a readable local target before a provider is selected.
13
13
  * FAT Mach-O inputs select only a host-compatible architecture so setup remains
14
14
  * non-interactive; unsupported or ambiguous inputs are returned as typed errors.
15
15
  */
@@ -31,8 +31,7 @@ export const parseBinaryTarget = async (input, cwd = process.cwd(), hostArchitec
31
31
  sourcePath: canonical,
32
32
  sha256: await sha256File(path),
33
33
  kind: "database",
34
- format: "hopper",
35
- loaderArgs: [],
34
+ format: "analysis-database",
36
35
  });
37
36
  const handle = await open(path, "r");
38
37
  let detected;
@@ -45,7 +44,6 @@ export const parseBinaryTarget = async (input, cwd = process.cwd(), hostArchitec
45
44
  sha256: await sha256File(path),
46
45
  kind: isArchiveFormat(artifactFormat) ? "archive" : "artifact",
47
46
  format: artifactFormat,
48
- loaderArgs: [],
49
47
  });
50
48
  detected = await readExecutableMetadata(handle, hostArchitecture);
51
49
  }
@@ -210,9 +208,8 @@ const readExecutableMetadata = async (handle, hostArchitecture) => {
210
208
  return parseExecutableHeader(bytes, hostArchitecture);
211
209
  };
212
210
  /**
213
- * Parse supported executable headers without I/O and derive explicit Hopper
214
- * loader arguments. The caller must supply the host architecture used for FAT
215
- * Mach-O slice selection.
211
+ * Parse supported executable headers without I/O. The caller supplies the host
212
+ * architecture used for deterministic FAT Mach-O slice selection.
216
213
  */
217
214
  export const parseExecutableHeader = (bytes, hostArchitecture) => {
218
215
  if (bytes.length >= 4 &&
@@ -240,7 +237,6 @@ const parseThinMachO = (bytes, magic) => {
240
237
  format: "mach-o",
241
238
  architecture,
242
239
  availableArchitectures: [architecture],
243
- loaderArgs: ["-l", "Mach-O", hopperArchitectureFlag(architecture)],
244
240
  });
245
241
  };
246
242
  const parseFatMachO = (bytes, magic, host) => {
@@ -274,13 +270,6 @@ const parseFatMachO = (bytes, magic, host) => {
274
270
  format: "mach-o",
275
271
  architecture: preferred,
276
272
  availableArchitectures: architectures,
277
- loaderArgs: [
278
- "-l",
279
- "FAT",
280
- hopperArchitectureFlag(preferred),
281
- "-l",
282
- "Mach-O",
283
- ],
284
273
  });
285
274
  };
286
275
  const parseElf = (bytes) => {
@@ -299,7 +288,6 @@ const parseElf = (bytes) => {
299
288
  format: "elf",
300
289
  architecture,
301
290
  availableArchitectures: [architecture],
302
- loaderArgs: ["-l", "ELF", hopperArchitectureFlag(architecture)],
303
291
  });
304
292
  };
305
293
  const parsePe = (bytes) => {
@@ -320,7 +308,6 @@ const parsePeRecord = (record) => {
320
308
  format: "pe",
321
309
  architecture,
322
310
  availableArchitectures: [architecture],
323
- loaderArgs: ["-l", "WinPE", hopperArchitectureFlag(architecture)],
324
311
  });
325
312
  };
326
313
  const machArchitecture = (cpu) => {
@@ -362,15 +349,3 @@ const peArchitecture = (machine) => {
362
349
  }
363
350
  return undefined;
364
351
  };
365
- const hopperArchitectureFlag = (architecture) => {
366
- switch (architecture) {
367
- case "x86":
368
- return "--intel-32";
369
- case "x86_64":
370
- return "--intel-64";
371
- case "arm":
372
- return "--armv7";
373
- case "arm64":
374
- return "--aarch64";
375
- }
376
- };
@@ -382,6 +382,7 @@ const browserResourceReconciliationSchema = z.discriminatedUnion("status", [
382
382
  ]);
383
383
  const browserScriptSchema = z.object({
384
384
  script_key: z.string().regex(/^scr_[a-f0-9]{64}(?:_[0-9]+)?$/u),
385
+ frame_id: z.string().nullable().default(null),
385
386
  url: z.string(),
386
387
  origin: z.string().nullable(),
387
388
  cdp_hash: z.string(),
@@ -464,6 +465,8 @@ const browserWorkerSchema = z.object({
464
465
  url: z.string(),
465
466
  origin: z.string().nullable(),
466
467
  attached: z.boolean(),
468
+ opener_target_id: z.string().nullable().default(null),
469
+ parent_frame_id: z.string().nullable().default(null),
467
470
  });
468
471
  const browserCspSourceSchema = z.object({
469
472
  kind: z.enum([
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { z } from "zod";
3
- import { evidenceBundleSchema, parseEvidenceBundle, serializeEvidenceBundle, } from "./evidenceBundle.js";
3
+ import { parseEvidenceBundle, serializeEvidenceBundle, } from "./evidenceBundle.js";
4
4
  const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
5
5
  const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
6
6
  const classificationSchema = z.enum([
@@ -12,9 +12,9 @@ const classificationSchema = z.enum([
12
12
  "history_diverged",
13
13
  ]);
14
14
  /** Strict bounded input for canonical Evidence bundle comparison. */
15
- export const bundleComparisonInputSchema = z.object({
16
- left: evidenceBundleSchema,
17
- right: evidenceBundleSchema,
15
+ export const bundleComparisonInputSchema = z.strictObject({
16
+ left_bundle_path: z.string().min(1).max(4_096),
17
+ right_bundle_path: z.string().min(1).max(4_096),
18
18
  record_pairs: z
19
19
  .array(z.object({
20
20
  left_evidence_id: evidenceIdSchema,
@@ -38,6 +38,7 @@ export const inspectElectronPageInputSchema = z
38
38
  max_dom_nodes: z.number().int().min(1).max(10_000).default(2_000),
39
39
  max_scripts: z.number().int().min(1).max(2_000).default(500),
40
40
  max_resources: z.number().int().min(1).max(10_000).default(2_000),
41
+ max_workers: z.number().int().min(1).max(5_000).default(500),
41
42
  max_script_source_bytes: z
42
43
  .number()
43
44
  .int()
@@ -56,6 +57,7 @@ export const inspectElectronPageInputSchema = z
56
57
  max_dom_nodes: 2_000,
57
58
  max_scripts: 500,
58
59
  max_resources: 2_000,
60
+ max_workers: 500,
59
61
  max_script_source_bytes: 1_024 * 1_024,
60
62
  max_total_script_source_bytes: 4 * 1_024 * 1_024,
61
63
  }),
@@ -143,6 +145,7 @@ export const electronPageInspectionSchema = z.object({
143
145
  total: z.number().int().min(0),
144
146
  items: z.array(z.object({
145
147
  script_key: z.string().regex(/^electron_script_[a-f0-9]{64}$/u),
148
+ frame_id: z.string().nullable().default(null),
146
149
  file_path: z.string(),
147
150
  cdp_hash: z.string(),
148
151
  length: z.number().int().min(0),
@@ -158,5 +161,15 @@ export const electronPageInspectionSchema = z.object({
158
161
  mime_type: z.string(),
159
162
  content_size: z.number().min(0).nullable(),
160
163
  })),
164
+ workers: z
165
+ .array(z.object({
166
+ target_id: z.string().min(1).max(256),
167
+ type: z.string().min(1).max(100),
168
+ file_path: z.string(),
169
+ attached: z.boolean(),
170
+ opener_target_id: z.string().min(1).max(256).nullable(),
171
+ parent_frame_id: z.string().min(1).max(256).nullable(),
172
+ }))
173
+ .default([]),
161
174
  limitations: z.array(z.string()),
162
175
  });
@@ -0,0 +1,9 @@
1
+ import { inspectElectronBrowserNode } from "./electronStaticAnalysisBrowser.js";
2
+ import { inspectElectronIpcNode } from "./electronStaticAnalysisIpc.js";
3
+ import { inspectElectronNativeNode } from "./electronStaticAnalysisNative.js";
4
+ /** Collect Electron-specific syntax during the shared inert AST traversal. */
5
+ export const inspectElectronStaticNode = (node, context) => {
6
+ inspectElectronBrowserNode(node, context);
7
+ inspectElectronIpcNode(node, context);
8
+ inspectElectronNativeNode(node, context);
9
+ };
@@ -0,0 +1,194 @@
1
+ import * as t from "@babel/types";
2
+ import { boundedExpression, collectContextBridgeMembers, electronStaticValue, objectProperty, } from "./electronStaticAnalysisValues.js";
3
+ import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
4
+ import { argumentNode, calleeName, compareCodePoints, propertyName, range, staticPath, } from "./javascriptStaticAnalysisHelpers.js";
5
+ const MAX_WEB_PREFERENCES = 64;
6
+ /** Inspect BrowserWindow, contextBridge, and utility-process syntax. */
7
+ export const inspectElectronBrowserNode = (node, context) => {
8
+ if (t.isNewExpression(node))
9
+ inspectBrowserWindow(node, context);
10
+ if (!t.isCallExpression(node))
11
+ return;
12
+ inspectContextBridge(node, context);
13
+ inspectUtilityProcess(node, context);
14
+ };
15
+ const inspectBrowserWindow = (node, context) => {
16
+ const name = calleeName(node.callee);
17
+ if (name !== "BrowserWindow" && !name.endsWith(".BrowserWindow"))
18
+ return;
19
+ const options = argumentNode(node.arguments[0]);
20
+ const collected = collectWindowOptions(context.source, options);
21
+ accountForStructure(context, collected.unknown, collected.omitted);
22
+ const finding = {
23
+ options_status: options === undefined
24
+ ? "missing"
25
+ : t.isObjectExpression(options)
26
+ ? "object-literal"
27
+ : "dynamic",
28
+ web_preferences_status: collected.status,
29
+ web_preferences: collected.preferences,
30
+ omitted_web_preferences: collected.omitted,
31
+ preload_path: collected.preloadPath,
32
+ module_key: null,
33
+ location: range(node),
34
+ };
35
+ addLocatedFinding(context, {
36
+ collection: context.accumulator.browserWindows,
37
+ key: `electron-window\0${name}`,
38
+ node,
39
+ value: finding,
40
+ });
41
+ };
42
+ const collectWindowOptions = (source, options) => {
43
+ if (!t.isObjectExpression(options))
44
+ return {
45
+ status: options === undefined ? "missing" : "dynamic",
46
+ preferences: [],
47
+ preloadPath: null,
48
+ unknown: options === undefined ? 0 : 1,
49
+ omitted: 0,
50
+ };
51
+ const property = objectProperty(options, "webPreferences");
52
+ if (property === undefined)
53
+ return {
54
+ status: "missing",
55
+ preferences: [],
56
+ preloadPath: null,
57
+ unknown: 0,
58
+ omitted: 0,
59
+ };
60
+ if (!t.isObjectExpression(property.value))
61
+ return {
62
+ status: "dynamic",
63
+ preferences: [],
64
+ preloadPath: null,
65
+ unknown: 1,
66
+ omitted: 0,
67
+ };
68
+ return collectWebPreferences(source, property.value);
69
+ };
70
+ const collectWebPreferences = (source, object) => {
71
+ const preferences = [];
72
+ let unknown = 0;
73
+ let preloadPath = null;
74
+ for (const property of object.properties) {
75
+ if (t.isSpreadElement(property)) {
76
+ unknown += 1;
77
+ preferences.push({
78
+ name: `[spread@${String(property.start ?? -1)}]`,
79
+ value: electronStaticValue(source, property.argument),
80
+ });
81
+ continue;
82
+ }
83
+ const name = propertyName(property.key);
84
+ if (name === "" || property.computed) {
85
+ unknown += 1;
86
+ preferences.push({
87
+ name: `[dynamic@${String(property.start ?? -1)}]`,
88
+ value: electronStaticValue(source, property),
89
+ });
90
+ continue;
91
+ }
92
+ if (t.isObjectMethod(property)) {
93
+ unknown += 1;
94
+ preferences.push({
95
+ name,
96
+ value: electronStaticValue(source, property),
97
+ });
98
+ continue;
99
+ }
100
+ const path = name === "preload" ? staticPath(property.value) : undefined;
101
+ if (path !== undefined)
102
+ preloadPath = path;
103
+ const value = path === undefined
104
+ ? electronStaticValue(source, property.value)
105
+ : { status: "literal", value: path, expression: null };
106
+ if (value.status === "dynamic")
107
+ unknown += 1;
108
+ preferences.push({ name, value });
109
+ }
110
+ preferences.sort((left, right) => compareCodePoints(left.name, right.name));
111
+ return {
112
+ status: "object-literal",
113
+ preferences: preferences.slice(0, MAX_WEB_PREFERENCES),
114
+ preloadPath,
115
+ unknown,
116
+ omitted: Math.max(0, preferences.length - MAX_WEB_PREFERENCES),
117
+ };
118
+ };
119
+ const inspectContextBridge = (node, context) => {
120
+ const name = calleeName(node.callee);
121
+ const main = name === "contextBridge.exposeInMainWorld" ||
122
+ name.endsWith(".contextBridge.exposeInMainWorld");
123
+ const isolated = name === "contextBridge.exposeInIsolatedWorld" ||
124
+ name.endsWith(".contextBridge.exposeInIsolatedWorld");
125
+ if (!main && !isolated)
126
+ return;
127
+ const keyNode = argumentNode(node.arguments[isolated ? 1 : 0]);
128
+ const apiNode = argumentNode(node.arguments[isolated ? 2 : 1]);
129
+ const key = electronStaticValue(context.source, keyNode);
130
+ const api = collectContextBridgeMembers(apiNode);
131
+ const worldId = isolated
132
+ ? electronStaticValue(context.source, argumentNode(node.arguments[0]))
133
+ : null;
134
+ const unknown = (key.status === "dynamic" ? 1 : 0) +
135
+ (worldId?.status === "dynamic" ? 1 : 0) +
136
+ api.unknown;
137
+ accountForStructure(context, unknown, api.omitted);
138
+ const finding = {
139
+ world: isolated ? "isolated" : "main",
140
+ world_id: worldId,
141
+ api_key: key.status === "literal" && typeof key.value === "string"
142
+ ? key.value
143
+ : null,
144
+ api_key_expression: key.status === "dynamic" ? key.expression : null,
145
+ api_status: api.status,
146
+ members: api.members,
147
+ unknown_members: api.unknown,
148
+ omitted_members: api.omitted,
149
+ module_key: null,
150
+ location: range(node),
151
+ };
152
+ addLocatedFinding(context, {
153
+ collection: context.accumulator.contextBridgeApis,
154
+ key: `context-bridge\0${isolated ? "isolated" : "main"}\0${finding.api_key ?? finding.api_key_expression ?? "[missing]"}`,
155
+ node,
156
+ value: finding,
157
+ });
158
+ };
159
+ const inspectUtilityProcess = (node, context) => {
160
+ const name = calleeName(node.callee);
161
+ if (name !== "utilityProcess.fork" && !name.endsWith(".utilityProcess.fork"))
162
+ return;
163
+ const moduleNode = argumentNode(node.arguments[0]);
164
+ const modulePath = moduleNode === undefined ? undefined : staticPath(moduleNode);
165
+ const options = argumentNode(node.arguments[2]);
166
+ const serviceName = t.isObjectExpression(options) &&
167
+ objectProperty(options, "serviceName") !== undefined
168
+ ? staticPath(objectProperty(options, "serviceName")?.value ?? options)
169
+ : undefined;
170
+ if (modulePath === undefined)
171
+ context.accumulator.unknownFindings += 1;
172
+ const finding = {
173
+ module_path: modulePath ?? null,
174
+ module_expression: modulePath === undefined
175
+ ? boundedExpression(context.source, moduleNode)
176
+ : null,
177
+ service_name: serviceName ?? null,
178
+ module_key: null,
179
+ location: range(node),
180
+ };
181
+ addLocatedFinding(context, {
182
+ collection: context.accumulator.utilityProcesses,
183
+ key: `electron-utility\0${modulePath ?? finding.module_expression ?? "[missing]"}`,
184
+ node,
185
+ value: finding,
186
+ });
187
+ };
188
+ const accountForStructure = (context, unknown, omitted) => {
189
+ context.accumulator.unknownFindings += unknown;
190
+ if (omitted === 0)
191
+ return;
192
+ context.accumulator.structuralTruncation = true;
193
+ context.accumulator.droppedFindings += omitted;
194
+ };