rea-agents 1.7.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 (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -0,0 +1,191 @@
1
+ import { isAbsolute, posix, relative } from "node:path";
2
+ import { compareCodePoints, } from "./javascriptApplicationGraph.js";
3
+ /** Index statically reconstructed entities that can receive runtime matches. */
4
+ export const collectStaticRuntimeCandidates = (layers) => layers
5
+ .flatMap((layer) => candidatesForLayer(layer))
6
+ .sort((left, right) => compareCodePoints(left.layer.layerId, right.layer.layerId) ||
7
+ compareCodePoints(left.node.node_id, right.node.node_id));
8
+ /** Translate one already-authorized runtime location into artifact paths. */
9
+ export const mappedRuntimePaths = (layer, entity) => {
10
+ const paths = [];
11
+ if (entity.location.kind === "file" && layer.result.format === "directory") {
12
+ const path = pathBelowRoot(layer.result.input_path, entity.location.value);
13
+ if (path !== null)
14
+ paths.push({ path, basis: "artifact-path" });
15
+ }
16
+ for (const mapping of layer.runtimeMappings) {
17
+ const suffix = mapping.kind === "file-root" && entity.location.kind === "file"
18
+ ? pathBelowRoot(mapping.root, entity.location.value)
19
+ : mapping.kind === "url-prefix" && entity.location.kind === "url"
20
+ ? pathBelowUrlPrefix(mapping.prefix, entity.location.value)
21
+ : null;
22
+ if (suffix === null)
23
+ continue;
24
+ const path = safeArtifactPath(mapping.artifact_prefix, suffix);
25
+ if (path === null)
26
+ continue;
27
+ paths.push({
28
+ path,
29
+ basis: mapping.kind === "file-root"
30
+ ? "operator-file-mapping"
31
+ : "operator-url-mapping",
32
+ });
33
+ }
34
+ return uniqueMappedPaths(paths);
35
+ };
36
+ /** Precompute the artifact prefixes exercised by a bounded runtime set. */
37
+ export const createStaticRuntimeScope = (layer, entities) => ({
38
+ artifactPrefixes: activeArtifactPrefixes(layer, entities),
39
+ pathsByNodeId: indexStaticPaths(layer.graph),
40
+ });
41
+ /** Whether one static node falls under a mapping exercised by this runtime set. */
42
+ export const staticNodeWithinRuntimeScope = (node, scope) => (scope.pathsByNodeId.get(node.node_id) ?? []).some((path) => scope.artifactPrefixes.some((prefix) => pathWithinPrefix(path, prefix)));
43
+ const candidatesForLayer = (layer) => {
44
+ const graph = layer.graph;
45
+ const pathsByNodeId = indexStaticPaths(graph);
46
+ return graph.nodes.flatMap((node) => {
47
+ const category = candidateCategory(node);
48
+ if (category === null)
49
+ return [];
50
+ return [
51
+ {
52
+ layer,
53
+ node,
54
+ category,
55
+ paths: pathsByNodeId.get(node.node_id) ?? [],
56
+ digests: candidateDigests(node),
57
+ },
58
+ ];
59
+ });
60
+ };
61
+ const candidateCategory = (node) => {
62
+ if (node.kind === "electron-renderer")
63
+ return "renderer";
64
+ if (node.kind === "javascript-asset" || node.kind === "javascript-module")
65
+ return "javascript";
66
+ if (node.kind === "worker")
67
+ return "worker";
68
+ if (node.kind === "service-worker")
69
+ return "service-worker";
70
+ return null;
71
+ };
72
+ const indexStaticPaths = (graph) => {
73
+ const nodes = new Map(graph.nodes.map((candidate) => [candidate.node_id, candidate]));
74
+ const relatedBySource = new Map();
75
+ for (const edge of graph.edges) {
76
+ if (edge.relation !== "maps_to")
77
+ continue;
78
+ const target = nodes.get(edge.target_node_id);
79
+ if (target === undefined)
80
+ continue;
81
+ const related = relatedBySource.get(edge.source_node_id);
82
+ if (related === undefined)
83
+ relatedBySource.set(edge.source_node_id, [target]);
84
+ else
85
+ related.push(target);
86
+ }
87
+ return new Map(graph.nodes.map((node) => [
88
+ node.node_id,
89
+ uniqueSorted([
90
+ ...nodePaths(node),
91
+ ...(relatedBySource.get(node.node_id) ?? []).flatMap(nodePaths),
92
+ ]),
93
+ ]));
94
+ };
95
+ const nodePaths = (node) => {
96
+ const paths = [];
97
+ if (node.identity.strategy === "canonical-path")
98
+ paths.push(node.identity.path);
99
+ for (const observation of node.observations) {
100
+ for (const property of ["path", "resolved_path", "declared_path"])
101
+ if (typeof observation.properties[property] === "string")
102
+ paths.push(observation.properties[property]);
103
+ if (observation.evidence.location.available &&
104
+ observation.evidence.location.value.kind === "artifact-path")
105
+ paths.push(observation.evidence.location.value.path);
106
+ }
107
+ return paths
108
+ .map(normalizeArtifactPath)
109
+ .filter((path) => path !== null && path !== "artifact-root");
110
+ };
111
+ const candidateDigests = (node) => {
112
+ const values = [];
113
+ if (node.kind === "javascript-asset" &&
114
+ node.identity.strategy === "content-digest")
115
+ values.push({
116
+ sha256: node.identity.sha256,
117
+ basis: "content-sha256",
118
+ });
119
+ if (node.kind === "javascript-module")
120
+ for (const observation of node.observations) {
121
+ const digest = observation.properties.source_sha256;
122
+ if (typeof digest === "string" && /^[a-f0-9]{64}$/u.test(digest))
123
+ values.push({ sha256: digest, basis: "module-source-sha256" });
124
+ }
125
+ return [
126
+ ...new Map(values.map((value) => [`${value.basis}:${value.sha256}`, value])).values(),
127
+ ];
128
+ };
129
+ const pathBelowRoot = (root, value) => {
130
+ const remainder = relative(root, value);
131
+ if (remainder === "" ||
132
+ remainder.startsWith("..") ||
133
+ isAbsolute(remainder) ||
134
+ remainder.includes("\\"))
135
+ return null;
136
+ return normalizeArtifactPath(remainder);
137
+ };
138
+ const pathBelowUrlPrefix = (prefix, value) => {
139
+ let base;
140
+ let candidate;
141
+ try {
142
+ base = new URL(prefix);
143
+ candidate = new URL(value);
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ if (base.origin !== candidate.origin ||
149
+ !candidate.pathname.startsWith(base.pathname))
150
+ return null;
151
+ try {
152
+ return normalizeArtifactPath(decodeURIComponent(candidate.pathname.slice(base.pathname.length)));
153
+ }
154
+ catch {
155
+ return null;
156
+ }
157
+ };
158
+ const activeArtifactPrefixes = (layer, entities) => {
159
+ const prefixes = new Set();
160
+ if (layer.result.format === "directory" &&
161
+ entities.some(({ location }) => location.kind === "file" &&
162
+ pathBelowRoot(layer.result.input_path, location.value) !== null))
163
+ prefixes.add("");
164
+ for (const mapping of layer.runtimeMappings)
165
+ if (entities.some(({ location }) => mapping.kind === "file-root" && location.kind === "file"
166
+ ? pathBelowRoot(mapping.root, location.value) !== null
167
+ : mapping.kind === "url-prefix" && location.kind === "url"
168
+ ? pathBelowUrlPrefix(mapping.prefix, location.value) !== null
169
+ : false))
170
+ prefixes.add(mapping.artifact_prefix);
171
+ return [...prefixes].sort(compareCodePoints);
172
+ };
173
+ const pathWithinPrefix = (path, prefix) => prefix === "" || path === prefix || path.startsWith(`${prefix}/`);
174
+ const safeArtifactPath = (prefix, suffix) => normalizeArtifactPath(prefix === "" ? suffix : posix.join(prefix, suffix));
175
+ const normalizeArtifactPath = (value) => {
176
+ const normalized = posix
177
+ .normalize(value.replaceAll("\\", "/"))
178
+ .replace(/^\.\//u, "");
179
+ return normalized !== "" &&
180
+ normalized !== "." &&
181
+ normalized !== ".." &&
182
+ !normalized.startsWith("/") &&
183
+ !normalized.startsWith("../")
184
+ ? normalized
185
+ : null;
186
+ };
187
+ const uniqueMappedPaths = (values) => [
188
+ ...new Map(values.map((value) => [`${value.basis}:${value.path}`, value])).values(),
189
+ ].sort((left, right) => compareCodePoints(left.path, right.path) ||
190
+ compareCodePoints(left.basis, right.basis));
191
+ const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
@@ -3,6 +3,8 @@ import * as t from "@babel/types";
3
3
  import { collectJavaScriptExports, fingerprintJavaScriptAst, } from "./javascriptAstFingerprint.js";
4
4
  import { STATIC_ROUTE_CALL_NAMES, argumentValue, calleeName, chunkRuntime, compareCodePoints, detectVendors, endpointArgument, failedJavaScriptStaticAnalysis, factoryRequireName, moduleFactory, modulePropertyName, prefixedArgument, propertyName, range, rangeForOffsets, registrationKey, sanitizeCandidate, sha256Text, sortedUnique, sourceSlice, staticArrayValues, staticPath, storageKind, stringValue, } from "./javascriptStaticAnalysisHelpers.js";
5
5
  import { createJavaScriptAnalysisAccumulator, } from "./javascriptStaticAnalysisState.js";
6
+ import { addBoundedFinding, addLocatedFinding, finalizeLocatedFindings, moduleAtOffset, } from "./javascriptStaticAnalysisFindings.js";
7
+ import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
6
8
  /** Parse one bounded JavaScript artifact and recover static structure only. */
7
9
  export const analyzeJavaScriptStaticSource = (source, limits) => {
8
10
  let file;
@@ -17,6 +19,11 @@ export const analyzeJavaScriptStaticSource = (source, limits) => {
17
19
  return failedJavaScriptStaticAnalysis();
18
20
  }
19
21
  const accumulator = createJavaScriptAnalysisAccumulator();
22
+ traverseStaticSource(source, file, accumulator, limits);
23
+ addSourceMapDirectives(source, accumulator, limits.maxFindings);
24
+ return finalizeStaticAnalysis(source, file, accumulator, limits);
25
+ };
26
+ const traverseStaticSource = (source, file, accumulator, limits) => {
20
27
  if (limits.now() > limits.deadline)
21
28
  accumulator.truncated = true;
22
29
  if (!accumulator.truncated)
@@ -31,7 +38,8 @@ export const analyzeJavaScriptStaticSource = (source, limits) => {
31
38
  inspectNode(source, node, accumulator, limits);
32
39
  return undefined;
33
40
  });
34
- addSourceMapDirectives(source, accumulator, limits.maxFindings);
41
+ };
42
+ const finalizeStaticAnalysis = (source, file, accumulator, limits) => {
35
43
  const parserErrors = file.errors.length;
36
44
  const limitations = [
37
45
  ...(parserErrors === 0
@@ -51,7 +59,7 @@ export const analyzeJavaScriptStaticSource = (source, limits) => {
51
59
  ...(accumulator.unknownFindings === 0
52
60
  ? []
53
61
  : [
54
- "One or more bundler registration keys were dynamic and remain unknown.",
62
+ "One or more static keys, expressions, or Electron boundary values were dynamic and remain unknown.",
55
63
  ]),
56
64
  "JavaScript syntax was parsed as data and was never evaluated.",
57
65
  ];
@@ -64,13 +72,21 @@ export const analyzeJavaScriptStaticSource = (source, limits) => {
64
72
  parse_error_count: parserErrors,
65
73
  visited_ast_nodes: Math.min(accumulator.visitedNodes, limits.maxAstNodes),
66
74
  dropped_findings: accumulator.droppedFindings,
67
- references: finalizeLocated(accumulator.references, accumulator.modules),
68
- endpoints: finalizeLocated(accumulator.endpoints, accumulator.modules),
69
- storage: finalizeLocated(accumulator.storage, accumulator.modules),
75
+ references: finalizeLocatedFindings(accumulator.references, accumulator.modules),
76
+ endpoints: finalizeLocatedFindings(accumulator.endpoints, accumulator.modules),
77
+ storage: finalizeLocatedFindings(accumulator.storage, accumulator.modules),
70
78
  bundler_registrations: sortedUnique(accumulator.registrations, registrationKey),
71
- role_paths: finalizeLocated(accumulator.roles, accumulator.modules),
79
+ role_paths: finalizeLocatedFindings(accumulator.roles, accumulator.modules),
72
80
  source_map_urls: accumulator.sourceMaps,
73
81
  vendors: detectVendors(source),
82
+ electron: {
83
+ browser_windows: finalizeLocatedFindings(accumulator.browserWindows, accumulator.modules),
84
+ context_bridge_apis: finalizeLocatedFindings(accumulator.contextBridgeApis, accumulator.modules),
85
+ ipc: finalizeLocatedFindings(accumulator.ipc, accumulator.modules),
86
+ sender_validations: finalizeLocatedFindings(accumulator.senderValidations, accumulator.modules),
87
+ utility_processes: finalizeLocatedFindings(accumulator.utilityProcesses, accumulator.modules),
88
+ native_addon_bindings: finalizeLocatedFindings(accumulator.nativeAddonBindings, accumulator.modules),
89
+ },
74
90
  limitations,
75
91
  };
76
92
  };
@@ -80,6 +96,7 @@ const inspectNode = (source, node, accumulator, limits) => {
80
96
  accumulator,
81
97
  maximum: limits.maxFindings,
82
98
  };
99
+ inspectElectronStaticNode(node, findings);
83
100
  if (t.isCallExpression(node)) {
84
101
  inspectBundlerRegistration(source, node, accumulator, limits);
85
102
  inspectCall(node, findings);
@@ -114,7 +131,7 @@ const inspectCall = (node, context) => {
114
131
  const { accumulator } = context;
115
132
  const name = calleeName(node.callee);
116
133
  const first = stringValue(node.arguments[0]);
117
- const module = moduleAt(node.start, accumulator.modules);
134
+ const module = moduleAtOffset(node.start, accumulator.modules);
118
135
  const moduleRequire = module?.requireName !== null && name === module?.requireName;
119
136
  if (name === "require" ||
120
137
  name.endsWith(".require") ||
@@ -138,7 +155,7 @@ const inspectCall = (node, context) => {
138
155
  inspectEndpointCall(node, name, first, context);
139
156
  inspectStorageCall(node, name, first, context);
140
157
  if (name.endsWith("loadFile") && first !== undefined)
141
- addLocated(context, {
158
+ addLocatedFinding(context, {
142
159
  collection: accumulator.roles,
143
160
  key: `role\0renderer\0${first}`,
144
161
  node,
@@ -221,7 +238,7 @@ const inspectBundlerRegistration = (source, call, accumulator, limits) => {
221
238
  modules: modules.sort((left, right) => compareCodePoints(left.module_key, right.module_key)),
222
239
  location: range(call),
223
240
  };
224
- addBounded(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
241
+ addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
225
242
  };
226
243
  const inspectEndpointCall = (node, name, first, context) => {
227
244
  const route = STATIC_ROUTE_CALL_NAMES.some((candidate) => name === candidate || name.endsWith(`.${candidate}`));
@@ -257,7 +274,7 @@ const inspectRoleProperty = (node, context) => {
257
274
  const path = staticPath(node.value);
258
275
  if (path === undefined)
259
276
  return;
260
- addLocated(context, {
277
+ addLocatedFinding(context, {
261
278
  collection: context.accumulator.roles,
262
279
  key: `role\0preload\0${path}`,
263
280
  node,
@@ -274,7 +291,7 @@ const inspectStorageCall = (node, name, first, context) => {
274
291
  const storage = storageKind(name);
275
292
  if (storage === undefined)
276
293
  return;
277
- addLocated(context, {
294
+ addLocatedFinding(context, {
278
295
  collection: context.accumulator.storage,
279
296
  key: `storage\0${storage}\0${first ?? ""}`,
280
297
  node,
@@ -292,7 +309,7 @@ const addReference = (context, input) => {
292
309
  const expression = boundedSpecifier === null
293
310
  ? sourceSlice(context.source, input.node).slice(0, 4_096)
294
311
  : null;
295
- addLocated(context, {
312
+ addLocatedFinding(context, {
296
313
  collection: context.accumulator.references,
297
314
  key: `reference\0${input.kind}\0${boundedSpecifier ?? expression}`,
298
315
  node: input.node,
@@ -305,7 +322,7 @@ const addReference = (context, input) => {
305
322
  },
306
323
  });
307
324
  };
308
- const addEndpoint = (context, input) => addLocated(context, {
325
+ const addEndpoint = (context, input) => addLocatedFinding(context, {
309
326
  collection: context.accumulator.endpoints,
310
327
  key: `endpoint\0${input.kind}\0${input.value}`,
311
328
  node: input.node,
@@ -317,35 +334,6 @@ const addEndpoint = (context, input) => addLocated(context, {
317
334
  location: range(input.node),
318
335
  },
319
336
  });
320
- const addLocated = (context, input) => addBounded(context.accumulator, `${input.key}\0${String(input.node.start)}`, context.maximum, () => input.collection.push({
321
- offset: input.node.start ?? -1,
322
- value: input.value,
323
- }));
324
- const addBounded = (accumulator, key, maximum, add) => {
325
- if (accumulator.seen.has(key))
326
- return;
327
- const count = accumulator.references.length +
328
- accumulator.endpoints.length +
329
- accumulator.storage.length +
330
- accumulator.roles.length +
331
- accumulator.sourceMaps.length +
332
- accumulator.registrations.length;
333
- if (count >= maximum) {
334
- accumulator.droppedFindings += 1;
335
- return;
336
- }
337
- accumulator.seen.add(key);
338
- add();
339
- };
340
- const finalizeLocated = (values, modules) => values.map(({ offset, value }) => ({
341
- ...value,
342
- module_key: moduleAt(offset, modules)?.key ?? null,
343
- }));
344
- const moduleAt = (offset, modules) => {
345
- if (offset === null || offset === undefined)
346
- return undefined;
347
- return modules.find(({ start, end }) => offset >= start && offset <= end);
348
- };
349
337
  const addSourceMapDirectives = (source, accumulator, maximum) => {
350
338
  for (const match of source.matchAll(/\/\/[#@]\s*sourceMappingURL\s*=\s*([^\s]+)/gu)) {
351
339
  const declared = match[1]?.slice(0, 4_096);
@@ -353,6 +341,6 @@ const addSourceMapDirectives = (source, accumulator, maximum) => {
353
341
  continue;
354
342
  const start = match.index;
355
343
  const location = rangeForOffsets(source, start, start + match[0].length);
356
- addBounded(accumulator, `source-map\0${declared}`, maximum, () => accumulator.sourceMaps.push({ declared_url: declared, location }));
344
+ addBoundedFinding(accumulator, `source-map\0${declared}`, maximum, () => accumulator.sourceMaps.push({ declared_url: declared, location }));
357
345
  }
358
346
  };
@@ -0,0 +1,39 @@
1
+ /** Add one module-attributable finding under the shared global finding bound. */
2
+ export const addLocatedFinding = (context, input) => addBoundedFinding(context.accumulator, `${input.key}\0${String(input.node.start)}`, context.maximum, () => input.collection.push({
3
+ offset: input.node.start ?? -1,
4
+ value: input.value,
5
+ }));
6
+ /** Reserve one deterministic finding key before mutating an accumulator. */
7
+ export const addBoundedFinding = (accumulator, key, maximum, add) => {
8
+ if (accumulator.seen.has(key))
9
+ return;
10
+ if (accumulatedFindingCount(accumulator) >= maximum) {
11
+ accumulator.droppedFindings += 1;
12
+ return;
13
+ }
14
+ accumulator.seen.add(key);
15
+ add();
16
+ };
17
+ /** Attach recovered bundle-module ownership to findings after traversal. */
18
+ export const finalizeLocatedFindings = (values, modules) => values.map(({ offset, value }) => ({
19
+ ...value,
20
+ module_key: moduleAtOffset(offset, modules)?.key ?? null,
21
+ }));
22
+ /** Locate the recovered bundle factory containing one exact source offset. */
23
+ export const moduleAtOffset = (offset, modules) => {
24
+ if (offset === null || offset === undefined)
25
+ return undefined;
26
+ return modules.find(({ start, end }) => offset >= start && offset <= end);
27
+ };
28
+ const accumulatedFindingCount = (accumulator) => accumulator.references.length +
29
+ accumulator.endpoints.length +
30
+ accumulator.storage.length +
31
+ accumulator.roles.length +
32
+ accumulator.sourceMaps.length +
33
+ accumulator.registrations.length +
34
+ accumulator.browserWindows.length +
35
+ accumulator.contextBridgeApis.length +
36
+ accumulator.ipc.length +
37
+ accumulator.senderValidations.length +
38
+ accumulator.utilityProcesses.length +
39
+ accumulator.nativeAddonBindings.length;
@@ -15,11 +15,21 @@ export const failedJavaScriptStaticAnalysis = () => ({
15
15
  role_paths: [],
16
16
  source_map_urls: [],
17
17
  vendors: [],
18
+ electron: {
19
+ browser_windows: [],
20
+ context_bridge_apis: [],
21
+ ipc: [],
22
+ sender_validations: [],
23
+ utility_processes: [],
24
+ native_addon_bindings: [],
25
+ },
18
26
  limitations: [
19
27
  "JavaScript source could not be parsed; absence of findings is not evidence of absence.",
20
28
  "JavaScript syntax was parsed as data and was never evaluated.",
21
29
  ],
22
30
  });
31
+ /** Normalize one Babel call/new argument into the inert node shared by inspectors. */
32
+ export const argumentNode = (argument) => argument !== null && t.isNode(argument) ? argument : undefined;
23
33
  /** Recognize the bounded runtime name for a Webpack/Rspack push call. */
24
34
  export const chunkRuntime = (call) => {
25
35
  if (!t.isMemberExpression(call.callee) &&
@@ -6,6 +6,12 @@ export const createJavaScriptAnalysisAccumulator = () => ({
6
6
  roles: [],
7
7
  sourceMaps: [],
8
8
  registrations: [],
9
+ browserWindows: [],
10
+ contextBridgeApis: [],
11
+ ipc: [],
12
+ senderValidations: [],
13
+ utilityProcesses: [],
14
+ nativeAddonBindings: [],
9
15
  modules: [],
10
16
  seen: new Set(),
11
17
  visitedNodes: 0,