rea-agents 2.3.0 → 2.5.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 (209) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/ArtifactExtraction.js +5 -1
  7. package/dist/application/ArtifactGraphConstruction.js +3 -15
  8. package/dist/application/ArtifactInventory/classify.js +5 -1
  9. package/dist/application/ArtifactInventory/reader.js +2 -0
  10. package/dist/application/BinarySession.js +15 -4
  11. package/dist/application/BinarySessionStatus.js +32 -1
  12. package/dist/application/BrowserEvidence.js +14 -0
  13. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  14. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  15. package/dist/application/BrowserScenarioEvidence.js +40 -0
  16. package/dist/application/CallPathTracing.js +160 -0
  17. package/dist/application/CapabilityInventory.js +79 -15
  18. package/dist/application/CompositeProvider.js +10 -1
  19. package/dist/application/DirectAnalysis.js +7 -2
  20. package/dist/application/EnhancedLiteralTracing.js +118 -0
  21. package/dist/application/EnhancedTools.js +35 -121
  22. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  23. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  24. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  25. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  26. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  27. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  28. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  29. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  30. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  31. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  32. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  33. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  34. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  35. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  36. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  37. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  38. package/dist/application/ProcessCaptureEnvironment.js +26 -0
  39. package/dist/application/ProcessCaptureLifecycle.js +1 -0
  40. package/dist/application/ProcessCheckpoints.js +5 -0
  41. package/dist/application/ProcessHarness.js +45 -34
  42. package/dist/application/ProcessNormalization.js +6 -0
  43. package/dist/application/ProcessReactiveCoordinator.js +10 -3
  44. package/dist/application/ProcessReactiveEffects.js +15 -1
  45. package/dist/application/ProcessReactiveHarness.js +100 -0
  46. package/dist/application/ProcessReactiveObservations.js +101 -16
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/artifacts/ArtifactProvider.js +87 -22
  58. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  59. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  60. package/dist/browser/CdpBrowserProvider.js +2 -2
  61. package/dist/browser/CdpCaptureDocuments.js +57 -0
  62. package/dist/browser/CdpCaptureStorage.js +71 -29
  63. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  64. package/dist/browser/CdpEndpoint.js +8 -6
  65. package/dist/browser/CdpPageCapture.js +2 -0
  66. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  67. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  68. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  69. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  70. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  71. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  72. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  73. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  74. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  75. package/dist/browser/V8InspectorEndpoint.js +72 -0
  76. package/dist/browser/V8InspectorProvider.js +267 -0
  77. package/dist/catalogIdentity.js +8 -0
  78. package/dist/cli/artifactCommands.js +39 -0
  79. package/dist/cli/coreAnalysisCommands.js +37 -0
  80. package/dist/cli.js +4 -0
  81. package/dist/cliApplicationCommands.js +30 -2
  82. package/dist/cliBrowserAdvancedCommands.js +17 -5
  83. package/dist/cliBrowserCommands.js +1 -0
  84. package/dist/cliBrowserScenarioCommands.js +43 -0
  85. package/dist/cliCommandNames.js +8 -0
  86. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  87. package/dist/cliObservationOptions.js +4 -0
  88. package/dist/cliPolicyCommands.js +1 -0
  89. package/dist/config/environment.js +12 -0
  90. package/dist/config/parseConfig.js +88 -17
  91. package/dist/contracts/analysisSearchContract.js +14 -0
  92. package/dist/contracts/applicationToolContracts.js +86 -2
  93. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  94. package/dist/contracts/artifactToolContracts.js +22 -0
  95. package/dist/contracts/browserProviderToolContracts.js +7 -0
  96. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  97. package/dist/contracts/browserToolContracts.js +88 -98
  98. package/dist/contracts/enhancedInputs.js +25 -6
  99. package/dist/contracts/functionInstructionContract.js +8 -0
  100. package/dist/contracts/hopperMemoryContracts.js +22 -0
  101. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  102. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  103. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  104. package/dist/contracts/toolContractExamples.js +5 -0
  105. package/dist/contracts/toolContractTypes.js +1 -0
  106. package/dist/contracts/toolContracts.js +13 -21
  107. package/dist/contracts/toolEffects.js +21 -0
  108. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  109. package/dist/contracts/toolOutputSchemaPrimitives.js +40 -0
  110. package/dist/domain/analysisErrorProjection.js +8 -0
  111. package/dist/domain/analysisSnapshot.js +2 -0
  112. package/dist/domain/artifactGraph.js +2 -0
  113. package/dist/domain/artifactInspection.js +347 -0
  114. package/dist/domain/binaryTarget.js +3 -6
  115. package/dist/domain/browserCaptureComparison.js +82 -0
  116. package/dist/domain/browserObservation.js +8 -1
  117. package/dist/domain/browserObservationErrors.js +1 -0
  118. package/dist/domain/browserObservationSchemas.js +26 -0
  119. package/dist/domain/browserScenario.js +165 -0
  120. package/dist/domain/browserScenarioCapture.js +57 -0
  121. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  122. package/dist/domain/browserScenarioDiff.js +324 -0
  123. package/dist/domain/browserScenarioDiffValues.js +157 -0
  124. package/dist/domain/browserScenarioNormalization.js +45 -0
  125. package/dist/domain/browserScenarioValues.js +372 -0
  126. package/dist/domain/evidence.js +2 -0
  127. package/dist/domain/hopperValues.js +10 -0
  128. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  129. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  130. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  131. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  132. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  133. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  134. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  135. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  136. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  137. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  138. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  139. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  140. package/dist/domain/javascriptSemanticIr.js +21 -1
  141. package/dist/domain/javascriptSemanticObjects.js +115 -0
  142. package/dist/domain/javascriptSemanticPromises.js +290 -0
  143. package/dist/domain/javascriptSemanticResources.js +136 -0
  144. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  145. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  146. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  147. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  148. package/dist/domain/processCapture.js +71 -1
  149. package/dist/domain/processCaptureReactiveValidation.js +175 -0
  150. package/dist/domain/processCaptureValidation.js +2 -0
  151. package/dist/domain/processObservation.js +15 -6
  152. package/dist/domain/processReactiveRuntime.js +21 -10
  153. package/dist/domain/processReactiveScenario.js +1 -0
  154. package/dist/domain/processReactiveTransition.js +17 -10
  155. package/dist/domain/processScenario.js +21 -0
  156. package/dist/domain/providerCleanupError.js +11 -0
  157. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  158. package/dist/domain/reconstructionReadiness.js +121 -0
  159. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  160. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  161. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  162. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  163. package/dist/domain/sourceToBundleComparison.js +335 -0
  164. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  165. package/dist/domain/sourceToBundleSignals.js +202 -0
  166. package/dist/domain/webCaptureDiff.js +141 -27
  167. package/dist/domain/webScreenshot.js +1 -1
  168. package/dist/domain/zipPackageFormat.js +20 -0
  169. package/dist/generatedPackageMetadata.js +1 -1
  170. package/dist/ghidra/GhidraDefaults.js +1 -1
  171. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  172. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  173. package/dist/hopper/HopperCleanup.js +158 -0
  174. package/dist/hopper/HopperClient.js +69 -99
  175. package/dist/hopper/HopperProvider.js +20 -1
  176. package/dist/hopper/HopperRequestQueue.js +187 -0
  177. package/dist/hopper/HopperResponseStream.js +46 -0
  178. package/dist/hopper/protocol.js +1 -0
  179. package/dist/main/transport.js +24 -24
  180. package/dist/main.js +6 -0
  181. package/dist/mcpDoctor.js +25 -2
  182. package/dist/native/NativeMacOSProvider.js +1 -0
  183. package/dist/process/ProcessCommandIdentity.js +19 -0
  184. package/dist/process/ProcessOwnership.js +11 -8
  185. package/dist/server/DynamicToolAvailability.js +70 -0
  186. package/dist/server/createServer.js +46 -1
  187. package/dist/server/lifecycleProgress.js +10 -0
  188. package/dist/server/permissionFailure.js +12 -0
  189. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  190. package/dist/server/registerApplicationTools/helpers.js +11 -7
  191. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  192. package/dist/server/registerApplicationTools/readiness.js +55 -0
  193. package/dist/server/registerApplicationTools.js +6 -0
  194. package/dist/server/registerArtifactTools.js +9 -1
  195. package/dist/server/registerBrowserScenarioTool.js +25 -0
  196. package/dist/server/registerBrowserTools.js +2 -2
  197. package/dist/server/registerCloseLifecycleTool.js +57 -0
  198. package/dist/server/registerEnhancedTools.js +16 -5
  199. package/dist/server/registerEvidenceResources.js +4 -0
  200. package/dist/server/registerEvidenceTools.js +9 -2
  201. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  202. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  203. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  204. package/dist/server/registerSessionStatusTool.js +11 -8
  205. package/dist/server/registerSessionTools.js +11 -57
  206. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  207. package/package.json +12 -6
  208. package/skills/reverse-engineer-anything/SKILL.md +4 -3
  209. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +2 -2
@@ -0,0 +1,237 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
4
+ import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
5
+ import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
6
+ const CHILD_PROCESS_METHODS = ["spawn", "exec", "execFile", "fork"];
7
+ const CHILD_LISTENER_METHODS = ["on", "once", "addListener"];
8
+ /** Recover explicit asynchronous node:child_process ownership candidates. */
9
+ export const collectJavaScriptSemanticChildProcesses = (program, state, callables) => {
10
+ const candidates = collectSpawns(program, state, callables);
11
+ const childProcessSpawns = candidates.map((candidate) => immutableSpawn(candidate));
12
+ return {
13
+ childProcessSpawns,
14
+ childProcessInteractions: collectInteractions(program, state, callables, candidates),
15
+ };
16
+ };
17
+ const collectSpawns = (program, state, callables) => {
18
+ const output = [];
19
+ const admittedCallables = new Set(callables.map(({ callableId }) => callableId));
20
+ const callableStack = [];
21
+ traverseJavaScriptAst(program, {
22
+ enter: (node, parent) => {
23
+ const callableId = semanticCallableIdForNode(node);
24
+ if (callableId !== null && admittedCallables.has(callableId))
25
+ callableStack.push(callableId);
26
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
27
+ return;
28
+ const method = childProcessMethod(node.callee, state);
29
+ if (method === null)
30
+ return;
31
+ if (operationLimitReached(output.length, state))
32
+ return;
33
+ output.push({
34
+ node,
35
+ method,
36
+ ownerCallableId: callableStack.at(-1) ?? null,
37
+ resultBindingId: assignedBindingId(parent, node, state),
38
+ });
39
+ },
40
+ exit: (node) => popCallable(node, callableStack),
41
+ });
42
+ return output;
43
+ };
44
+ const immutableSpawn = (candidate) => {
45
+ const options = spawnOptions(candidate);
46
+ const command = literalString(candidate.node.arguments[0]);
47
+ return {
48
+ processId: spawnId(candidate),
49
+ method: candidate.method,
50
+ location: range(candidate.node),
51
+ ownerCallableId: candidate.ownerCallableId,
52
+ resultBindingId: candidate.resultBindingId,
53
+ command,
54
+ argvCount: spawnArgvCount(candidate),
55
+ environmentSupplied: objectHasProperty(options, "env"),
56
+ stdioMode: stdioMode(options),
57
+ resolution: command === null ? "partial" : "complete",
58
+ };
59
+ };
60
+ const collectInteractions = (program, state, callables, spawns) => {
61
+ const output = [];
62
+ const callableIds = new Set(callables.map(({ callableId }) => callableId));
63
+ const callableStack = [];
64
+ const spawnByNode = new WeakMap();
65
+ const spawnsByBinding = new Map();
66
+ for (const spawn of spawns) {
67
+ spawnByNode.set(spawn.node, spawn);
68
+ if (spawn.resultBindingId === null)
69
+ continue;
70
+ const existing = spawnsByBinding.get(spawn.resultBindingId) ?? [];
71
+ existing.push(spawn);
72
+ spawnsByBinding.set(spawn.resultBindingId, existing);
73
+ }
74
+ const context = {
75
+ state,
76
+ spawnByNode,
77
+ spawnsByBinding,
78
+ };
79
+ traverseJavaScriptAst(program, {
80
+ enter: (node) => {
81
+ const callableId = semanticCallableIdForNode(node);
82
+ if (callableId !== null && callableIds.has(callableId))
83
+ callableStack.push(callableId);
84
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
85
+ return;
86
+ const interaction = childInteraction(node, callableStack.at(-1) ?? null, context);
87
+ if (interaction === null)
88
+ return;
89
+ if (operationLimitReached(spawns.length + output.length, state))
90
+ return;
91
+ output.push(interaction);
92
+ },
93
+ exit: (node) => popCallable(node, callableStack),
94
+ });
95
+ return output;
96
+ };
97
+ const childInteraction = (node, ownerCallableId, context) => {
98
+ const { state, spawnByNode, spawnsByBinding } = context;
99
+ const member = memberCallee(node);
100
+ if (member === null)
101
+ return null;
102
+ const method = semanticStaticPropertyName(member.property, member.computed);
103
+ const binding = objectBinding(member.object, state);
104
+ const linked = resolveChildCandidates(member.object, binding, spawnByNode, spawnsByBinding);
105
+ if (linked.length === 0)
106
+ return null;
107
+ const classification = classifyInteraction(method, node.arguments[0]);
108
+ if (classification === null)
109
+ return null;
110
+ const { kind, interactionMethod, eventName } = classification;
111
+ const listener = node.arguments[1];
112
+ return {
113
+ interactionId: `child:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
114
+ kind,
115
+ method: interactionMethod,
116
+ location: range(node),
117
+ ownerCallableId,
118
+ processBindingId: binding?.bindingId ?? null,
119
+ linkedProcessIds: linked
120
+ .map((candidate) => spawnId(candidate))
121
+ .sort(compareCodePoints),
122
+ eventName: kind === "listener" ? eventName : null,
123
+ signalName: kind === "signal"
124
+ ? (literalString(node.arguments[0]) ?? "SIGTERM")
125
+ : null,
126
+ listenerLocation: kind === "listener" && t.isNode(listener) ? range(listener) : null,
127
+ resolution: linked.length === 1 ? "complete" : "partial",
128
+ };
129
+ };
130
+ const classifyInteraction = (method, argument) => {
131
+ if (method === "kill")
132
+ return { kind: "signal", interactionMethod: "kill", eventName: null };
133
+ const listenerMethod = CHILD_LISTENER_METHODS.find((candidate) => candidate === method);
134
+ const eventName = literalString(argument);
135
+ return listenerMethod !== undefined &&
136
+ (eventName === "exit" || eventName === "error")
137
+ ? { kind: "listener", interactionMethod: listenerMethod, eventName }
138
+ : null;
139
+ };
140
+ const childProcessMethod = (callee, state) => {
141
+ if (t.isIdentifier(callee)) {
142
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
143
+ return binding === undefined ? null : methodFromBinding(binding);
144
+ }
145
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
146
+ return null;
147
+ const method = admittedChildMethod(semanticStaticPropertyName(callee.property, callee.computed));
148
+ if (method === null || !t.isIdentifier(callee.object))
149
+ return null;
150
+ const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
151
+ return childProcessNamespace(binding) ? method : null;
152
+ };
153
+ const methodFromBinding = (binding) => {
154
+ for (const { specifier, importedPath } of binding.directOrigins) {
155
+ if (!childProcessModule(specifier))
156
+ continue;
157
+ const method = admittedChildMethod(importedPath.at(-1) ?? "");
158
+ if (method !== null)
159
+ return method;
160
+ }
161
+ return null;
162
+ };
163
+ const admittedChildMethod = (value) => CHILD_PROCESS_METHODS.find((candidate) => candidate === value) ?? null;
164
+ const childProcessNamespace = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => childProcessModule(specifier) && importedPath.length === 0) ?? false;
165
+ const childProcessModule = (specifier) => specifier === "child_process" || specifier === "node:child_process";
166
+ const resolveChildCandidates = (node, binding, spawnByNode, spawnsByBinding) => {
167
+ const direct = spawnByNode.get(node);
168
+ if (direct !== undefined)
169
+ return [direct];
170
+ return binding === undefined
171
+ ? []
172
+ : (spawnsByBinding.get(binding.bindingId) ?? []);
173
+ };
174
+ const spawnOptions = (candidate) => {
175
+ const index = candidate.method === "exec" ? 1 : 2;
176
+ const value = candidate.node.arguments[index];
177
+ return t.isObjectExpression(value) ? value : null;
178
+ };
179
+ const spawnArgvCount = (candidate) => {
180
+ if (candidate.method === "exec")
181
+ return null;
182
+ const args = candidate.node.arguments[1];
183
+ return t.isArrayExpression(args) ? args.elements.length : null;
184
+ };
185
+ const objectHasProperty = (object, name) => objectProperty(object, name) !== null;
186
+ const stdioMode = (options) => {
187
+ const property = objectProperty(options, "stdio");
188
+ if (property === null || !t.isObjectProperty(property))
189
+ return "default";
190
+ if (t.isStringLiteral(property.value))
191
+ return property.value.value;
192
+ if (t.isArrayExpression(property.value))
193
+ return "array";
194
+ return "dynamic";
195
+ };
196
+ const objectProperty = (object, name) => object?.properties.find((property) => (t.isObjectMethod(property) || t.isObjectProperty(property)) &&
197
+ propertyName(property.key) === name) ?? null;
198
+ const objectBinding = (node, state) => t.isIdentifier(node)
199
+ ? resolveSemanticBindingState(state, node, node.name)
200
+ : undefined;
201
+ const assignedBindingId = (parent, node, state) => {
202
+ const identifier = t.isVariableDeclarator(parent) &&
203
+ parent.init === node &&
204
+ t.isIdentifier(parent.id)
205
+ ? parent.id
206
+ : t.isAssignmentExpression(parent) &&
207
+ parent.right === node &&
208
+ t.isIdentifier(parent.left)
209
+ ? parent.left
210
+ : null;
211
+ return identifier === null
212
+ ? null
213
+ : (resolveSemanticBindingState(state, identifier, identifier.name)
214
+ ?.bindingId ?? null);
215
+ };
216
+ const literalString = (node) => {
217
+ if (t.isStringLiteral(node))
218
+ return node.value;
219
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0)
220
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
221
+ return null;
222
+ };
223
+ const operationLimitReached = (retained, state) => {
224
+ if (retained < state.limits.maxChildProcessOperations)
225
+ return false;
226
+ reachSemanticLimit(state, "maxChildProcessOperations");
227
+ return true;
228
+ };
229
+ const popCallable = (node, stack) => {
230
+ const callableId = semanticCallableIdForNode(node);
231
+ if (callableId !== null && stack.at(-1) === callableId)
232
+ stack.pop();
233
+ };
234
+ const spawnId = (candidate) => `child:spawn:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
235
+ const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
236
+ ? node.callee
237
+ : null;
@@ -0,0 +1,125 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, semanticResolutionBlocked, } from "./javascriptSemanticState.js";
4
+ import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
5
+ /** Resolve a direct or namespace-imported built-in method. */
6
+ export const builtinDataEffectMethod = (callee, state, specifiers) => {
7
+ if (t.isIdentifier(callee)) {
8
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
9
+ return moduleMethod(binding, specifiers);
10
+ }
11
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
12
+ return null;
13
+ if (!t.isIdentifier(callee.object))
14
+ return null;
15
+ const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
16
+ const namespace = binding?.directOrigins.some(({ specifier, importedPath }) => specifiers.includes(specifier) && importedPath.length === 0);
17
+ return namespace
18
+ ? semanticStaticPropertyName(callee.property, callee.computed)
19
+ : null;
20
+ };
21
+ const moduleMethod = (binding, specifiers) => {
22
+ for (const { specifier, importedPath } of binding?.directOrigins ?? []) {
23
+ if (!specifiers.includes(specifier))
24
+ continue;
25
+ return importedPath.at(-1) ?? null;
26
+ }
27
+ return null;
28
+ };
29
+ /** Recognize global or node:process aliases without accepting shadowing. */
30
+ export const isSemanticProcessObject = (node, state) => {
31
+ if (!t.isIdentifier(node))
32
+ return false;
33
+ const binding = resolveSemanticBindingState(state, node, node.name);
34
+ if (binding === undefined)
35
+ return (node.name === "process" &&
36
+ !semanticResolutionBlocked(state, node, node.name));
37
+ return binding.directOrigins.some(({ specifier }) => specifier === "process" || specifier === "node:process");
38
+ };
39
+ /** Resolve the nearest direct variable/assignment owner before a callable. */
40
+ export const outerDataEffectBinding = (node, context) => {
41
+ for (let index = context.ancestors.length - 1; index >= 0; index -= 1) {
42
+ const ancestor = context.ancestors[index];
43
+ if (ancestor === undefined)
44
+ continue;
45
+ if (t.isVariableDeclarator(ancestor) &&
46
+ t.isNode(ancestor.init) &&
47
+ containsSemanticNode(ancestor.init, node) &&
48
+ t.isIdentifier(ancestor.id))
49
+ return (resolveSemanticBindingState(context.state, ancestor.id, ancestor.id.name)?.bindingId ?? null);
50
+ if (t.isAssignmentExpression(ancestor) &&
51
+ containsSemanticNode(ancestor.right, node) &&
52
+ t.isIdentifier(ancestor.left))
53
+ return (resolveSemanticBindingState(context.state, ancestor.left, ancestor.left.name)?.bindingId ?? null);
54
+ if (semanticCallableIdForNode(ancestor) !== null)
55
+ return null;
56
+ }
57
+ return null;
58
+ };
59
+ /** Traverse with exact callable and ancestor stacks. */
60
+ export const traverseDataEffects = (program, context, visit) => {
61
+ context.callableStack.length = 0;
62
+ context.ancestors.length = 0;
63
+ traverseJavaScriptAst(program, {
64
+ enter: (node, parent) => {
65
+ const callableId = semanticCallableIdForNode(node);
66
+ if (callableId !== null && context.callableIds.has(callableId))
67
+ context.callableStack.push(callableId);
68
+ visit(node, parent);
69
+ context.ancestors.push(node);
70
+ },
71
+ exit: (node) => {
72
+ context.ancestors.pop();
73
+ const callableId = semanticCallableIdForNode(node);
74
+ if (callableId !== null && context.callableStack.at(-1) === callableId)
75
+ context.callableStack.pop();
76
+ },
77
+ });
78
+ };
79
+ /** Resolve one direct identifier argument to a lexical binding. */
80
+ export const dataEffectArgumentBindingId = (node, state) => t.isIdentifier(node)
81
+ ? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
82
+ : null;
83
+ /** Read one JSON primitive default without evaluating code. */
84
+ export const dataEffectPrimitive = (node) => {
85
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node))
86
+ return node.value;
87
+ if (t.isBooleanLiteral(node))
88
+ return node.value;
89
+ return null;
90
+ };
91
+ /** Read one static string or expression-free template. */
92
+ export const dataEffectLiteralString = (node) => {
93
+ if (t.isStringLiteral(node))
94
+ return node.value;
95
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0)
96
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
97
+ return null;
98
+ };
99
+ /** Read a member callee from call or construction syntax. */
100
+ export const dataEffectMemberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
101
+ ? node.callee
102
+ : null;
103
+ /** Read a parent member's object when present. */
104
+ export const dataEffectMemberObject = (node) => node !== null &&
105
+ (t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
106
+ ? node.object
107
+ : null;
108
+ /** Compare Babel source offsets for exact containment. */
109
+ export const containsSemanticNode = (outer, inner) => outer.start !== null &&
110
+ outer.start !== undefined &&
111
+ outer.end !== null &&
112
+ outer.end !== undefined &&
113
+ inner.start !== null &&
114
+ inner.start !== undefined &&
115
+ inner.end !== null &&
116
+ inner.end !== undefined &&
117
+ outer.start <= inner.start &&
118
+ outer.end >= inner.end;
119
+ /** Record one bounded data-effect frontier. */
120
+ export const dataEffectLimitReached = (retained, limit, name, state) => {
121
+ if (retained < limit)
122
+ return false;
123
+ reachSemanticLimit(state, name);
124
+ return true;
125
+ };
@@ -0,0 +1,284 @@
1
+ import * as t from "@babel/types";
2
+ import { semanticStaticPropertyName } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, semanticResolutionBlocked, } from "./javascriptSemanticState.js";
4
+ import { builtinDataEffectMethod as builtinMethod, containsSemanticNode as containsNode, dataEffectArgumentBindingId as argumentBindingId, dataEffectLimitReached as limitReached, dataEffectLiteralString as literalString, dataEffectMemberCallee as memberCallee, dataEffectMemberObject as memberObject, dataEffectPrimitive as primitiveValue, isSemanticProcessObject as isProcessObject, outerDataEffectBinding as outerAssignedBinding, traverseDataEffects as traverseWithContext, } from "./javascriptSemanticDataEffectHelpers.js";
5
+ import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
6
+ /** Recover configuration, request, and boundary candidates from inert syntax. */
7
+ export const collectJavaScriptSemanticDataEffects = (program, state, callables) => {
8
+ const context = {
9
+ state,
10
+ callableIds: new Set(callables.map(({ callableId }) => callableId)),
11
+ callableStack: [],
12
+ ancestors: [],
13
+ };
14
+ const configuration = collectConfigurations(program, context);
15
+ const requests = collectRequests(program, context);
16
+ return {
17
+ configurationOperations: [
18
+ ...configuration.map(({ operation }) => operation),
19
+ ...collectDefaults(program, context, configuration),
20
+ ],
21
+ requestOperations: [
22
+ ...requests.map(({ operation }) => operation),
23
+ ...collectResponseConsumers(program, context, requests),
24
+ ],
25
+ boundaryOperations: collectBoundaries(program, context),
26
+ };
27
+ };
28
+ const collectConfigurations = (program, context) => {
29
+ const output = [];
30
+ traverseWithContext(program, context, (node, parent) => {
31
+ const details = configurationDetails(node, parent, context);
32
+ if (details === null)
33
+ return;
34
+ if (limitReached(output.length, context.state.limits.maxConfigurationOperations, "maxConfigurationOperations", context.state))
35
+ return;
36
+ const operation = {
37
+ configId: `config:${details.kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
38
+ kind: details.kind,
39
+ location: range(node),
40
+ ownerCallableId: context.callableStack.at(-1) ?? null,
41
+ resultBindingId: outerAssignedBinding(node, context),
42
+ key: details.key,
43
+ sourceConfigId: null,
44
+ value: null,
45
+ resolution: details.resolution,
46
+ };
47
+ output.push({ node, operation });
48
+ });
49
+ return output;
50
+ };
51
+ const configurationDetails = (node, parent, context) => {
52
+ if ((t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) &&
53
+ !(memberObject(parent) === node)) {
54
+ const processMember = processConfigurationMember(node, context.state);
55
+ if (processMember !== null)
56
+ return processMember;
57
+ }
58
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
59
+ return null;
60
+ const fileMethod = builtinMethod(node.callee, context.state, [
61
+ "fs",
62
+ "node:fs",
63
+ ]);
64
+ return fileMethod === "readFile" || fileMethod === "readFileSync"
65
+ ? {
66
+ kind: "file",
67
+ key: literalString(node.arguments[0]),
68
+ resolution: literalString(node.arguments[0]) === null ? "partial" : "complete",
69
+ }
70
+ : null;
71
+ };
72
+ const processConfigurationMember = (node, state) => {
73
+ const object = node.object;
74
+ if (!t.isMemberExpression(object) && !t.isOptionalMemberExpression(object))
75
+ return null;
76
+ if (!isProcessObject(object.object, state))
77
+ return null;
78
+ const family = semanticStaticPropertyName(object.property, object.computed);
79
+ if (family !== "env" && family !== "argv")
80
+ return null;
81
+ const key = semanticStaticPropertyName(node.property, node.computed) || null;
82
+ return {
83
+ kind: family === "env" ? "environment" : "argv",
84
+ key,
85
+ resolution: key === null ? "partial" : "complete",
86
+ };
87
+ };
88
+ const collectDefaults = (program, context, configurations) => {
89
+ const output = [];
90
+ traverseWithContext(program, context, (node) => {
91
+ if (!t.isLogicalExpression(node) ||
92
+ (node.operator !== "??" && node.operator !== "||"))
93
+ return;
94
+ const sources = configurations.filter(({ node: source }) => containsNode(node.left, source));
95
+ if (sources.length !== 1 || sources[0] === undefined)
96
+ return;
97
+ if (limitReached(configurations.length + output.length, context.state.limits.maxConfigurationOperations, "maxConfigurationOperations", context.state))
98
+ return;
99
+ output.push({
100
+ configId: `config:default:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
101
+ kind: "default",
102
+ location: range(node.right),
103
+ ownerCallableId: context.callableStack.at(-1) ?? null,
104
+ resultBindingId: outerAssignedBinding(node, context),
105
+ key: sources[0].operation.key,
106
+ sourceConfigId: sources[0].operation.configId,
107
+ value: primitiveValue(node.right),
108
+ resolution: primitiveValue(node.right) === null ? "partial" : "complete",
109
+ });
110
+ });
111
+ return output;
112
+ };
113
+ const collectRequests = (program, context) => {
114
+ const output = [];
115
+ traverseWithContext(program, context, (node) => {
116
+ if (!t.isCallExpression(node) &&
117
+ !t.isOptionalCallExpression(node) &&
118
+ !t.isNewExpression(node))
119
+ return;
120
+ const method = requestMethod(node, context.state);
121
+ if (method === null)
122
+ return;
123
+ if (limitReached(output.length, context.state.limits.maxRequestOperations, "maxRequestOperations", context.state))
124
+ return;
125
+ const endpoint = requestEndpoint(node);
126
+ output.push({
127
+ node,
128
+ operation: {
129
+ requestId: `request:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
130
+ kind: "request",
131
+ method,
132
+ location: range(node),
133
+ ownerCallableId: context.callableStack.at(-1) ?? null,
134
+ resultBindingId: outerAssignedBinding(node, context),
135
+ linkedRequestIds: [],
136
+ endpoint,
137
+ fields: requestFields(node, method, context.state),
138
+ resolution: endpoint === null ? "partial" : "complete",
139
+ },
140
+ });
141
+ });
142
+ return output;
143
+ };
144
+ const collectResponseConsumers = (program, context, requests) => {
145
+ const requestsByBinding = new Map();
146
+ for (const request of requests) {
147
+ const bindingId = request.operation.resultBindingId;
148
+ if (bindingId === null)
149
+ continue;
150
+ const existing = requestsByBinding.get(bindingId) ?? [];
151
+ existing.push(request);
152
+ requestsByBinding.set(bindingId, existing);
153
+ }
154
+ const output = [];
155
+ traverseWithContext(program, context, (node) => {
156
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
157
+ return;
158
+ const member = memberCallee(node);
159
+ if (member === null || !t.isIdentifier(member.object))
160
+ return;
161
+ const method = semanticStaticPropertyName(member.property, member.computed);
162
+ if (method !== "json" && method !== "text" && method !== "arrayBuffer")
163
+ return;
164
+ const binding = resolveSemanticBindingState(context.state, member.object, member.object.name);
165
+ const linked = binding === undefined
166
+ ? []
167
+ : (requestsByBinding.get(binding.bindingId) ?? []);
168
+ if (linked.length === 0)
169
+ return;
170
+ if (limitReached(requests.length + output.length, context.state.limits.maxRequestOperations, "maxRequestOperations", context.state))
171
+ return;
172
+ output.push({
173
+ requestId: `response:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
174
+ kind: "response-consumer",
175
+ method,
176
+ location: range(node),
177
+ ownerCallableId: context.callableStack.at(-1) ?? null,
178
+ resultBindingId: outerAssignedBinding(node, context),
179
+ linkedRequestIds: linked
180
+ .map(({ operation }) => operation.requestId)
181
+ .sort(compareCodePoints),
182
+ endpoint: null,
183
+ fields: [],
184
+ resolution: linked.length === 1 ? "complete" : "partial",
185
+ });
186
+ });
187
+ return output;
188
+ };
189
+ const collectBoundaries = (program, context) => {
190
+ const output = [];
191
+ traverseWithContext(program, context, (node) => {
192
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
193
+ return;
194
+ const details = boundaryDetails(node, context.state);
195
+ if (details === null)
196
+ return;
197
+ if (limitReached(output.length, context.state.limits.maxBoundaryOperations, "maxBoundaryOperations", context.state))
198
+ return;
199
+ output.push({
200
+ boundaryId: `boundary:${details.kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
201
+ kind: details.kind,
202
+ method: details.method,
203
+ location: range(node),
204
+ ownerCallableId: context.callableStack.at(-1) ?? null,
205
+ sourceBindingId: argumentBindingId(node.arguments[0], context.state),
206
+ resultBindingId: outerAssignedBinding(node, context),
207
+ resolution: details.resolution,
208
+ });
209
+ });
210
+ return output;
211
+ };
212
+ const boundaryDetails = (node, state) => {
213
+ if (t.isIdentifier(node.callee)) {
214
+ const name = node.callee.name;
215
+ const unbound = resolveSemanticBindingState(state, node.callee, name) === undefined &&
216
+ !semanticResolutionBlocked(state, node.callee, name);
217
+ if (unbound &&
218
+ ["String", "Number", "Boolean", "parseInt", "parseFloat"].includes(name))
219
+ return { kind: "coerce", method: name, resolution: "complete" };
220
+ }
221
+ const member = memberCallee(node);
222
+ if (member === null)
223
+ return null;
224
+ const method = semanticStaticPropertyName(member.property, member.computed);
225
+ if (method === "parse" &&
226
+ t.isIdentifier(member.object, { name: "JSON" }) &&
227
+ resolveSemanticBindingState(state, member.object, "JSON") === undefined &&
228
+ !semanticResolutionBlocked(state, member.object, "JSON"))
229
+ return { kind: "parse", method: "JSON.parse", resolution: "complete" };
230
+ if (method === "parse" || method === "safeParse")
231
+ return { kind: "parse", method, resolution: "partial" };
232
+ if (method === "validate" || method === "assert" || method === "is")
233
+ return { kind: "validate", method, resolution: "partial" };
234
+ return null;
235
+ };
236
+ const requestMethod = (node, state) => {
237
+ if (t.isIdentifier(node.callee)) {
238
+ const name = node.callee.name;
239
+ const unbound = resolveSemanticBindingState(state, node.callee, name) === undefined &&
240
+ !semanticResolutionBlocked(state, node.callee, name);
241
+ if (!t.isNewExpression(node) && unbound && name === "fetch")
242
+ return "fetch";
243
+ if (t.isNewExpression(node) && unbound && name === "WebSocket")
244
+ return "WebSocket";
245
+ }
246
+ const member = memberCallee(node);
247
+ if (member === null || !t.isIdentifier(member.object))
248
+ return null;
249
+ const method = semanticStaticPropertyName(member.property, member.computed);
250
+ if (method !== "request" && method !== "get")
251
+ return null;
252
+ const binding = resolveSemanticBindingState(state, member.object, member.object.name);
253
+ return binding?.directOrigins.some(({ specifier, importedPath }) => ["http", "https", "node:http", "node:https"].includes(specifier) &&
254
+ importedPath.length === 0)
255
+ ? method
256
+ : null;
257
+ };
258
+ const requestFields = (node, method, state) => {
259
+ const candidate = method === "fetch" || method === "WebSocket"
260
+ ? node.arguments[1]
261
+ : t.isObjectExpression(node.arguments[0])
262
+ ? node.arguments[0]
263
+ : node.arguments[1];
264
+ if (!t.isObjectExpression(candidate))
265
+ return [];
266
+ return candidate.properties.flatMap((property) => {
267
+ if (!t.isObjectProperty(property))
268
+ return [];
269
+ const name = propertyName(property.key);
270
+ if (name.length === 0)
271
+ return [];
272
+ return [
273
+ {
274
+ name,
275
+ sourceBindingId: t.isIdentifier(property.value)
276
+ ? (resolveSemanticBindingState(state, property.value, property.value.name)?.bindingId ?? null)
277
+ : null,
278
+ },
279
+ ];
280
+ });
281
+ };
282
+ const requestEndpoint = (node) => t.isObjectExpression(node.arguments[0])
283
+ ? null
284
+ : literalString(node.arguments[0]);