rea-agents 1.5.0 → 1.7.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 (172) hide show
  1. package/README.md +144 -58
  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 +229 -102
  11. package/dist/application/BrowserEvidence.js +87 -15
  12. package/dist/application/BrowserObservationService.js +65 -1
  13. package/dist/application/CapabilityInventory.js +7 -0
  14. package/dist/application/ClientConfigPath.js +33 -0
  15. package/dist/application/CompositeProvider.js +20 -8
  16. package/dist/application/CrossVersionInventory.js +16 -3
  17. package/dist/application/CrossVersionInvestigation.js +17 -62
  18. package/dist/application/CrossVersionInvestigationEvidence.js +64 -0
  19. package/dist/application/CrossVersionInvestigationReplay.js +170 -0
  20. package/dist/application/DirectAnalysis.js +49 -35
  21. package/dist/application/Doctor.js +70 -13
  22. package/dist/application/ElectronEvidence.js +35 -0
  23. package/dist/application/ElectronObservationPort.js +1 -0
  24. package/dist/application/ElectronObservationService.js +46 -0
  25. package/dist/application/EnhancedTools.js +1 -1
  26. package/dist/application/EvidenceLedger.js +5 -2
  27. package/dist/application/InvestigationProviders.js +11 -0
  28. package/dist/application/JavaScriptArtifactAnalysis.js +299 -0
  29. package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
  30. package/dist/application/JavaScriptArtifactFiles.js +231 -0
  31. package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
  32. package/dist/application/JavaScriptArtifactGraphBuilder.js +101 -0
  33. package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
  34. package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
  35. package/dist/application/JavaScriptArtifactGraphEvidence.js +89 -0
  36. package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
  37. package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
  38. package/dist/application/JavaScriptArtifactReconstruction.js +74 -0
  39. package/dist/application/JavaScriptArtifactReconstructionInput.js +99 -0
  40. package/dist/application/MacHopper.js +20 -4
  41. package/dist/application/PermissionAuthority.js +30 -0
  42. package/dist/application/ProcessCaptureLifecycle.js +2 -6
  43. package/dist/application/ProcessHarness.js +13 -7
  44. package/dist/application/ProcessSampling.js +59 -8
  45. package/dist/application/ProjectPermissionStore.js +1 -0
  46. package/dist/application/ReferenceSourceImport.js +13 -16
  47. package/dist/application/SessionProviderRouter.js +219 -0
  48. package/dist/application/Setup.js +153 -70
  49. package/dist/application/SetupClients.js +8 -5
  50. package/dist/application/SetupInstallFailure.js +24 -0
  51. package/dist/application/SetupPlan.js +19 -8
  52. package/dist/application/SetupSkill.js +20 -3
  53. package/dist/application/SupportedClients.js +40 -16
  54. package/dist/application/Uninstall.js +20 -7
  55. package/dist/application/Upgrade.js +15 -4
  56. package/dist/application/runtime.js +9 -6
  57. package/dist/artifacts/AsarArtifactReader.js +23 -3
  58. package/dist/browser/CdpBrowserProvider.js +158 -53
  59. package/dist/browser/CdpCaptureCompleteness.js +90 -0
  60. package/dist/browser/CdpCaptureDocuments.js +180 -21
  61. package/dist/browser/CdpCaptureEvents.js +342 -43
  62. package/dist/browser/CdpCaptureValues.js +5 -7
  63. package/dist/browser/CdpConnection.js +47 -24
  64. package/dist/browser/CdpElectronInspection.js +330 -0
  65. package/dist/browser/CdpElectronProvider.js +136 -0
  66. package/dist/browser/CdpEndpoint.js +77 -5
  67. package/dist/browser/CdpObservationSession.js +301 -0
  68. package/dist/browser/CdpPageCapture.js +159 -56
  69. package/dist/browser/CdpSafeMetadata.js +203 -0
  70. package/dist/browser/CdpScreenshot.js +101 -0
  71. package/dist/browser/CdpTargetSession.js +47 -0
  72. package/dist/browser/CdpWebMcpDiscovery.js +242 -0
  73. package/dist/browser/ElectronFileScope.js +50 -0
  74. package/dist/browser/PngVisualDiff.js +177 -0
  75. package/dist/browser/SensitiveTextCapture.js +25 -0
  76. package/dist/browser/WebSourceMapFetcher.js +357 -0
  77. package/dist/catalogIdentity.js +2 -33
  78. package/dist/cli.js +62 -34
  79. package/dist/cliBrowserAdvancedCommands.js +170 -0
  80. package/dist/cliBrowserCommands.js +180 -3
  81. package/dist/cliCommandNames.js +43 -0
  82. package/dist/cliElectronCommands.js +115 -0
  83. package/dist/cliEvidenceCommands.js +4 -3
  84. package/dist/cliInvestigationCommands.js +17 -9
  85. package/dist/cliPolicyCommands.js +49 -3
  86. package/dist/cliProcessCommands.js +3 -2
  87. package/dist/config.js +58 -7
  88. package/dist/contracts/browserToolContracts.js +304 -3
  89. package/dist/contracts/electronToolContracts.js +73 -0
  90. package/dist/contracts/enhancedInputs.js +2 -2
  91. package/dist/contracts/providerSelection.js +28 -0
  92. package/dist/contracts/sessionLifecycleInputs.js +2 -0
  93. package/dist/contracts/toolContracts.js +24 -20
  94. package/dist/contracts/toolOutputSchemas.js +117 -22
  95. package/dist/doctorRuntime.js +6 -0
  96. package/dist/domain/analysisProfile.js +78 -0
  97. package/dist/domain/analysisSnapshot.js +92 -46
  98. package/dist/domain/artifactGraph.js +1 -0
  99. package/dist/domain/binaryTarget.js +4 -29
  100. package/dist/domain/browserCompleteness.js +101 -0
  101. package/dist/domain/browserObservation.js +240 -14
  102. package/dist/domain/browserSession.js +63 -0
  103. package/dist/domain/electronObservation.js +162 -0
  104. package/dist/domain/errors.js +81 -6
  105. package/dist/domain/evidence.js +44 -5
  106. package/dist/domain/evidenceBundle.js +9 -7
  107. package/dist/domain/functionComparison.js +9 -3
  108. package/dist/domain/functionComparisonNormalization.js +37 -0
  109. package/dist/domain/functionDossierEvidence.js +4 -1
  110. package/dist/domain/hopperValues.js +66 -9
  111. package/dist/domain/investigationWorkspace.js +6 -1
  112. package/dist/domain/javascriptApplicationEvidenceSchemas.js +361 -0
  113. package/dist/domain/javascriptApplicationGraph.js +281 -0
  114. package/dist/domain/javascriptApplicationGraphSchemas.js +132 -0
  115. package/dist/domain/javascriptAstFingerprint.js +143 -0
  116. package/dist/domain/javascriptStaticAnalysis.js +358 -0
  117. package/dist/domain/javascriptStaticAnalysisHelpers.js +264 -0
  118. package/dist/domain/javascriptStaticAnalysisState.js +17 -0
  119. package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
  120. package/dist/domain/jsonShape.js +125 -0
  121. package/dist/domain/referenceSourceGraph.js +3 -1
  122. package/dist/domain/webBundleAnalysis.js +195 -0
  123. package/dist/domain/webBundleAnalyzer.js +423 -0
  124. package/dist/domain/webCaptureDiff.js +184 -0
  125. package/dist/domain/webContentArtifact.js +47 -0
  126. package/dist/domain/webInventory.js +61 -0
  127. package/dist/domain/webMcpDiscovery.js +68 -0
  128. package/dist/domain/webScreenshot.js +114 -0
  129. package/dist/generatedPackageMetadata.js +2 -2
  130. package/dist/ghidra/GhidraAnalysisProfile.js +37 -0
  131. package/dist/ghidra/GhidraClient.js +400 -0
  132. package/dist/ghidra/GhidraClientTypes.js +1 -0
  133. package/dist/ghidra/GhidraDefaults.js +20 -0
  134. package/dist/ghidra/GhidraDiagnostics.js +43 -0
  135. package/dist/ghidra/GhidraDoctor.js +59 -0
  136. package/dist/ghidra/GhidraFunctionValues.js +264 -0
  137. package/dist/ghidra/GhidraInstallation.js +239 -0
  138. package/dist/ghidra/GhidraInventoryValues.js +217 -0
  139. package/dist/ghidra/GhidraLauncher.js +139 -0
  140. package/dist/ghidra/GhidraProvider.js +360 -0
  141. package/dist/ghidra/GhidraRequestQueue.js +98 -0
  142. package/dist/ghidra/GhidraResponseBuffer.js +31 -0
  143. package/dist/ghidra/GhidraResponseRouter.js +24 -0
  144. package/dist/ghidra/GhidraSessionError.js +39 -0
  145. package/dist/ghidra/GhidraSessionValues.js +62 -0
  146. package/dist/ghidra/GhidraSocketConnection.js +46 -0
  147. package/dist/ghidra/protocol.js +69 -0
  148. package/dist/hopper/BridgeLauncher.js +21 -57
  149. package/dist/hopper/HopperAnalysisProfile.js +106 -0
  150. package/dist/hopper/HopperClient.js +197 -165
  151. package/dist/hopper/HopperProvider.js +105 -8
  152. package/dist/hopper/HopperSessionValues.js +27 -0
  153. package/dist/hopper/HopperSocketConnection.js +31 -0
  154. package/dist/main.js +36 -31
  155. package/dist/native/NativeMacOSProvider.js +6 -3
  156. package/dist/native/NativeMachoInspection.js +21 -6
  157. package/dist/process/PendingOperations.js +59 -0
  158. package/dist/process/PrivateRuntimeRoot.js +33 -0
  159. package/dist/{application → process}/ProcessOwnership.js +40 -20
  160. package/dist/process/ProviderDeadline.js +95 -0
  161. package/dist/process/ProviderProcess.js +275 -0
  162. package/dist/server/createServer.js +8 -0
  163. package/dist/server/promptCompletion.js +9 -3
  164. package/dist/server/registerBrowserTools.js +61 -2
  165. package/dist/server/registerElectronTools.js +36 -0
  166. package/dist/server/registerEnhancedTools.js +9 -6
  167. package/dist/server/registerInvestigationTools.js +11 -12
  168. package/dist/server/registerOfficialTools.js +3 -0
  169. package/dist/server/registerSessionTools.js +7 -1
  170. package/package.json +12 -6
  171. package/scripts/rea.mjs +37 -3
  172. package/skills/rea-analysis/SKILL.md +19 -9
@@ -0,0 +1,205 @@
1
+ import { posix } from "node:path";
2
+ import { JavaScriptArtifactGraphAccumulator } from "./JavaScriptArtifactGraphAccumulator.js";
3
+ import { artifactObservationEvidence, astObservationEvidence, completeReconstructionCoverage, partialReconstructionCoverage, staticInferenceEvidence, unavailableAstEvidence, } from "./JavaScriptArtifactGraphEvidence.js";
4
+ /** Add a direct artifact-containment edge. */
5
+ export const addArtifactContainsEdge = (context, input) => {
6
+ context.accumulator.addEdge({
7
+ source_node_id: input.source.node_id,
8
+ target_node_id: input.target.node_id,
9
+ relation: "contains",
10
+ properties: { path: input.file.path },
11
+ evidence: artifactObservationEvidence({
12
+ sha256: input.operation === "inventory-entry"
13
+ ? input.file.container_sha256
14
+ : input.file.sha256,
15
+ path: input.file.path,
16
+ operation: input.operation,
17
+ coverage: completeReconstructionCoverage(),
18
+ }),
19
+ });
20
+ };
21
+ /** Add an exact AST containment edge. */
22
+ export const addAstContainsEdge = (context, input) => {
23
+ context.accumulator.addEdge({
24
+ source_node_id: input.source.node_id,
25
+ target_node_id: input.target.node_id,
26
+ relation: "contains",
27
+ properties: input.properties,
28
+ evidence: astObservationEvidence({
29
+ sha256: input.file.sha256,
30
+ path: input.file.path,
31
+ range: input.range,
32
+ operation: "map-bundler-structure",
33
+ coverage: input.coverage,
34
+ }),
35
+ });
36
+ };
37
+ /** Add an explicitly inferred static relationship edge. */
38
+ export const addStaticInferenceEdge = (context, input) => {
39
+ context.accumulator.addEdge({
40
+ source_node_id: input.source.node_id,
41
+ target_node_id: input.target.node_id,
42
+ relation: input.relation,
43
+ properties: input.properties,
44
+ evidence: staticInferenceEvidence({
45
+ sha256: input.file.sha256,
46
+ path: input.file.path,
47
+ range: input.range,
48
+ operation: "map-static-relationship",
49
+ coverage: input.coverage,
50
+ }),
51
+ });
52
+ };
53
+ /** Preserve a static parse or approval gap as an explicit unknown scope. */
54
+ export const addUnavailableStaticParseScope = (context, input) => {
55
+ const node = context.accumulator.addNode({
56
+ kind: "unknown",
57
+ identity: artifactLocalIdentity(input.file.sha256, "static-parse-scope", `${input.operation}:${input.file.path}`),
58
+ observations: [
59
+ {
60
+ label: `${input.file.path} parse scope`,
61
+ properties: {
62
+ path: input.file.path,
63
+ operation: input.operation,
64
+ limitation: input.limitation,
65
+ },
66
+ evidence: unavailableAstEvidence({
67
+ sha256: input.file.sha256,
68
+ path: input.file.path,
69
+ operation: input.operation,
70
+ coverage: partialReconstructionCoverage([], null, false),
71
+ limitation: input.limitation,
72
+ }),
73
+ },
74
+ ],
75
+ });
76
+ context.accumulator.addEdge({
77
+ source_node_id: input.asset.node_id,
78
+ target_node_id: node.node_id,
79
+ relation: "contains",
80
+ properties: { scope: "unavailable-static-analysis" },
81
+ evidence: artifactObservationEvidence({
82
+ sha256: input.file.sha256,
83
+ path: input.file.path,
84
+ operation: "associate-unavailable-parse-scope",
85
+ coverage: completeReconstructionCoverage(),
86
+ }),
87
+ });
88
+ };
89
+ /** Coverage for one bounded JavaScript AST analysis. */
90
+ export const javascriptAnalysisCoverage = (analysis, input) => analysis.parse_status === "complete"
91
+ ? completeReconstructionCoverage(javaScriptAnalysisLimits(input))
92
+ : {
93
+ status: "partial",
94
+ truncated: analysis.parse_status === "truncated",
95
+ omitted_count: null,
96
+ limits: javaScriptAnalysisLimits(input),
97
+ };
98
+ /** Stable identity for a fact scoped to one exact artifact. */
99
+ export const artifactLocalIdentity = (sha256, namespace, key) => ({
100
+ strategy: "artifact-local-key",
101
+ stability: "artifact-version",
102
+ artifact_sha256: sha256,
103
+ namespace: namespace.slice(0, 512),
104
+ key: key.slice(0, 4_096),
105
+ });
106
+ /** Create or merge an Electron role inferred from static syntax or metadata. */
107
+ export const createElectronRoleNode = (context, input) => context.accumulator.addNode({
108
+ kind: input.kind,
109
+ identity: artifactLocalIdentity(input.anchor.sha256, "electron-role", `${input.kind}:${input.resolvedPath ?? input.declaredPath}`),
110
+ observations: [
111
+ {
112
+ label: input.resolvedPath ?? input.declaredPath,
113
+ properties: {
114
+ declared_path: input.declaredPath,
115
+ resolved_path: input.resolvedPath,
116
+ mechanism: input.mechanism,
117
+ },
118
+ evidence: staticInferenceEvidence({
119
+ sha256: input.anchor.sha256,
120
+ path: input.anchor.path,
121
+ operation: "discover-electron-entrypoint",
122
+ coverage: input.coverage ?? completeReconstructionCoverage(),
123
+ ...(input.range === undefined ? {} : { range: input.range }),
124
+ }),
125
+ },
126
+ ],
127
+ });
128
+ /** Resolve an Electron role to an inventoried asset when available. */
129
+ export const linkElectronRoleToAsset = (context, input) => {
130
+ if (input.path === null)
131
+ return;
132
+ const asset = context.assetNodes.get(input.path) ?? context.fileNodes.get(input.path);
133
+ if (asset === undefined)
134
+ return;
135
+ context.accumulator.addEdge({
136
+ source_node_id: input.role.node_id,
137
+ target_node_id: asset.node_id,
138
+ relation: "maps_to",
139
+ properties: { resolved_path: input.path },
140
+ evidence: staticInferenceEvidence({
141
+ sha256: input.anchor.sha256,
142
+ path: input.anchor.path,
143
+ operation: "resolve-electron-entrypoint",
144
+ coverage: input.coverage ?? completeReconstructionCoverage(),
145
+ ...(input.range === undefined ? {} : { range: input.range }),
146
+ }),
147
+ });
148
+ };
149
+ /** Deterministic key for one recovered module inside a bundle asset. */
150
+ export const moduleLookupKey = (path, moduleKey) => `${path}\0${moduleKey}`;
151
+ /** Resolve a relative static specifier without escaping the artifact root. */
152
+ export const resolveArtifactPath = (specifier, sourcePath, files) => {
153
+ const clean = specifier.split("#", 1)[0]?.split("?", 1)[0] ?? "";
154
+ if (clean === "" || /^[A-Za-z][A-Za-z+.-]*:/u.test(clean))
155
+ return null;
156
+ if (!clean.startsWith(".") && !clean.startsWith("/"))
157
+ return null;
158
+ const candidate = posix.normalize(clean.startsWith("/")
159
+ ? clean.slice(1)
160
+ : posix.join(posix.dirname(sourcePath), clean));
161
+ if (candidate === ".." || candidate.startsWith("../"))
162
+ return null;
163
+ for (const path of pathCandidates(candidate))
164
+ if (files.has(path))
165
+ return path;
166
+ return null;
167
+ };
168
+ /** Locate the recovered module that owns a static finding. */
169
+ export const sourceNodeFor = (context, path, moduleKey) => moduleKey === null
170
+ ? undefined
171
+ : context.moduleNodes.get(moduleLookupKey(path, moduleKey));
172
+ const pathCandidates = (path) => [
173
+ path,
174
+ `${path}.js`,
175
+ `${path}.mjs`,
176
+ `${path}.cjs`,
177
+ `${path}.ts`,
178
+ `${path}.tsx`,
179
+ `${path}.node`,
180
+ posix.join(path, "index.js"),
181
+ posix.join(path, "index.ts"),
182
+ ];
183
+ /** Named AST budgets committed to graph evidence coverage. */
184
+ export const javaScriptAnalysisLimits = (input) => [
185
+ {
186
+ name: "max-ast-nodes",
187
+ value: input.limits.max_ast_nodes,
188
+ unit: "items",
189
+ },
190
+ {
191
+ name: "max-findings",
192
+ value: input.limits.max_findings,
193
+ unit: "items",
194
+ },
195
+ {
196
+ name: "max-modules",
197
+ value: input.limits.max_modules,
198
+ unit: "items",
199
+ },
200
+ {
201
+ name: "max-parse-milliseconds",
202
+ value: input.limits.max_parse_milliseconds,
203
+ unit: "milliseconds",
204
+ },
205
+ ];
@@ -0,0 +1,108 @@
1
+ import { addArtifactContainsEdge, addStaticInferenceEdge, createElectronRoleNode, resolveArtifactPath, } from "./JavaScriptArtifactGraphContext.js";
2
+ import { artifactObservationEvidence, completeReconstructionCoverage, partialReconstructionCoverage, } from "./JavaScriptArtifactGraphEvidence.js";
3
+ /** Project HTML renderer entrypoints and their local script assets. */
4
+ export const addJavaScriptHtmlRoles = (context) => {
5
+ const byHtml = new Map();
6
+ for (const script of context.analysis.html_scripts) {
7
+ const existing = byHtml.get(script.html_path);
8
+ byHtml.set(script.html_path, existing === undefined ? [script] : [...existing, script]);
9
+ }
10
+ for (const [htmlPath, scripts] of byHtml) {
11
+ const html = context.filesByPath.get(htmlPath);
12
+ if (html === undefined)
13
+ continue;
14
+ const role = createElectronRoleNode(context, {
15
+ kind: "electron-renderer",
16
+ anchor: html,
17
+ declaredPath: html.path,
18
+ resolvedPath: html.path,
19
+ mechanism: "html-script-entrypoint",
20
+ });
21
+ addArtifactContainsEdge(context, {
22
+ source: context.root,
23
+ target: role,
24
+ file: html,
25
+ operation: "discover-html-renderer",
26
+ });
27
+ for (const script of scripts) {
28
+ const resolvedPath = resolveArtifactPath(script.script_path, html.path, context.filesByPath);
29
+ if (resolvedPath === null)
30
+ continue;
31
+ const resolved = context.fileNodes.get(resolvedPath);
32
+ if (resolved === undefined)
33
+ continue;
34
+ addStaticInferenceEdge(context, {
35
+ source: role,
36
+ target: resolved,
37
+ file: html,
38
+ range: script.location,
39
+ coverage: completeReconstructionCoverage(),
40
+ relation: "loads",
41
+ properties: {
42
+ script_path: script.script_path,
43
+ resolved_path: resolvedPath,
44
+ },
45
+ });
46
+ }
47
+ }
48
+ };
49
+ /** Project original source names and optional content digests from approved maps. */
50
+ export const addJavaScriptSourceMapOriginals = (context) => {
51
+ for (const sourceMap of context.analysis.source_maps) {
52
+ const mapFile = context.filesByPath.get(sourceMap.path);
53
+ const mapNode = context.fileNodes.get(sourceMap.path);
54
+ if (mapFile === undefined || mapNode === undefined)
55
+ continue;
56
+ const coverage = sourceMap.status === "truncated"
57
+ ? partialReconstructionCoverage([
58
+ {
59
+ name: "max-source-map-sources",
60
+ value: context.input.limits.max_source_map_sources,
61
+ unit: "items",
62
+ },
63
+ ], sourceMap.omitted_sources, true)
64
+ : completeReconstructionCoverage();
65
+ for (const original of sourceMap.sources) {
66
+ const node = context.accumulator.addNode({
67
+ kind: "source-module",
68
+ identity: {
69
+ strategy: "source-map-original",
70
+ stability: "source-map-exact",
71
+ source_map_sha256: mapFile.sha256,
72
+ original_source: original.source,
73
+ source_sha256: original.content_sha256,
74
+ },
75
+ observations: [
76
+ {
77
+ label: original.source,
78
+ properties: {
79
+ source: original.source,
80
+ content_available: original.content !== null,
81
+ source_sha256: original.content_sha256,
82
+ },
83
+ evidence: artifactObservationEvidence({
84
+ sha256: mapFile.sha256,
85
+ path: mapFile.path,
86
+ operation: "parse-local-source-map",
87
+ coverage,
88
+ limitations: sourceMap.limitation === null ? [] : [sourceMap.limitation],
89
+ }),
90
+ },
91
+ ],
92
+ });
93
+ context.accumulator.addEdge({
94
+ source_node_id: mapNode.node_id,
95
+ target_node_id: node.node_id,
96
+ relation: "contains",
97
+ properties: { original_source: original.source },
98
+ evidence: artifactObservationEvidence({
99
+ sha256: mapFile.sha256,
100
+ path: mapFile.path,
101
+ operation: "parse-local-source-map",
102
+ coverage,
103
+ limitations: sourceMap.limitation === null ? [] : [sourceMap.limitation],
104
+ }),
105
+ });
106
+ }
107
+ }
108
+ };
@@ -0,0 +1,89 @@
1
+ const EXTRACTOR = {
2
+ name: "rea-javascript-artifact-reconstruction",
3
+ version: "1",
4
+ executable_sha256: null,
5
+ };
6
+ /** Exact artifact-byte evidence for one safely inventoried local file. */
7
+ export const artifactObservationEvidence = (input) => ({
8
+ authority: "artifact-bytes",
9
+ state: "observed",
10
+ confidence: "exact",
11
+ artifact: artifactReference(input.sha256),
12
+ location: {
13
+ available: true,
14
+ value: { kind: "artifact-path", path: input.path },
15
+ },
16
+ extractor: { ...EXTRACTOR, operation: input.operation },
17
+ coverage: input.coverage,
18
+ limitations: [...(input.limitations ?? [])],
19
+ evidence_ids: [],
20
+ });
21
+ /** Exact AST syntax evidence with an actionable source range. */
22
+ export const astObservationEvidence = (input) => ({
23
+ authority: "ast-static-analysis",
24
+ state: "observed",
25
+ confidence: "exact",
26
+ artifact: artifactReference(input.sha256),
27
+ location: {
28
+ available: true,
29
+ value: { kind: "source-range", source: input.path, ...input.range },
30
+ },
31
+ extractor: { ...EXTRACTOR, operation: input.operation },
32
+ coverage: input.coverage,
33
+ limitations: [...(input.limitations ?? [])],
34
+ evidence_ids: [],
35
+ });
36
+ /** Explicit static relationship inference; never promoted to observation. */
37
+ export const staticInferenceEvidence = (input) => ({
38
+ authority: "static-relationship-inference",
39
+ state: "inferred",
40
+ confidence: input.confidence ?? "high",
41
+ artifact: artifactReference(input.sha256),
42
+ location: input.range === undefined
43
+ ? { available: true, value: { kind: "artifact-path", path: input.path } }
44
+ : {
45
+ available: true,
46
+ value: { kind: "source-range", source: input.path, ...input.range },
47
+ },
48
+ extractor: { ...EXTRACTOR, operation: input.operation },
49
+ coverage: input.coverage,
50
+ limitations: [
51
+ "Static syntax does not prove that this relationship executes at runtime.",
52
+ ...(input.limitations ?? []),
53
+ ],
54
+ evidence_ids: [],
55
+ });
56
+ /** Unavailable AST fact that preserves a parse, approval, or limit gap. */
57
+ export const unavailableAstEvidence = (input) => ({
58
+ authority: "ast-static-analysis",
59
+ state: "unavailable",
60
+ confidence: "unknown",
61
+ artifact: artifactReference(input.sha256),
62
+ location: {
63
+ available: true,
64
+ value: { kind: "artifact-path", path: input.path },
65
+ },
66
+ extractor: { ...EXTRACTOR, operation: input.operation },
67
+ coverage: input.coverage,
68
+ limitations: [input.limitation, ...(input.limitations ?? [])],
69
+ evidence_ids: [],
70
+ });
71
+ /** Complete local projection coverage under named hard limits. */
72
+ export const completeReconstructionCoverage = (limits = []) => ({
73
+ status: "complete",
74
+ truncated: false,
75
+ omitted_count: 0,
76
+ limits: [...limits],
77
+ });
78
+ /** Partial reconstruction coverage with either exact truncation or a policy gap. */
79
+ export const partialReconstructionCoverage = (limits, omitted, truncated) => ({
80
+ status: "partial",
81
+ truncated,
82
+ omitted_count: omitted,
83
+ limits: [...limits],
84
+ });
85
+ const artifactReference = (sha256) => ({
86
+ available: true,
87
+ artifact_id: `art_${sha256}`,
88
+ sha256,
89
+ });
@@ -0,0 +1,271 @@
1
+ import { addStaticInferenceEdge, artifactLocalIdentity, createElectronRoleNode, javascriptAnalysisCoverage, linkElectronRoleToAsset, moduleLookupKey, resolveArtifactPath, sourceNodeFor, } from "./JavaScriptArtifactGraphContext.js";
2
+ import { astObservationEvidence, staticInferenceEvidence, } from "./JavaScriptArtifactGraphEvidence.js";
3
+ /** Project imports, workers, roles, endpoints, storage, and source-map links. */
4
+ export const addJavaScriptStaticFindings = (context) => {
5
+ for (const analyzed of context.analysis.files) {
6
+ const { file, javascript } = analyzed;
7
+ const asset = context.assetNodes.get(file.path);
8
+ if (javascript === null || asset === undefined)
9
+ continue;
10
+ const coverage = javascriptAnalysisCoverage(javascript, context.input);
11
+ for (const value of javascript.references)
12
+ addReference(context, { file, asset, value, coverage });
13
+ for (const value of javascript.endpoints)
14
+ addEndpoint(context, { file, asset, value, coverage });
15
+ for (const value of javascript.storage)
16
+ addStorage(context, { file, asset, value, coverage });
17
+ for (const value of javascript.role_paths)
18
+ addDiscoveredRole(context, { file, asset, value, coverage });
19
+ for (const value of javascript.source_map_urls)
20
+ addSourceMapEdge(context, { file, asset, value, coverage });
21
+ }
22
+ };
23
+ const addReference = (context, input) => {
24
+ const source = sourceNodeFor(context, input.file.path, input.value.module_key) ??
25
+ input.asset;
26
+ const resolved = input.value.specifier === null
27
+ ? null
28
+ : resolveReference(context, input.file.path, input.value.specifier);
29
+ if (input.value.kind === "worker" || input.value.kind === "service-worker") {
30
+ const worker = context.accumulator.addNode({
31
+ kind: input.value.kind === "worker" ? "worker" : "service-worker",
32
+ identity: artifactLocalIdentity(input.file.sha256, input.value.kind, input.value.specifier ?? input.value.expression ?? "[dynamic]"),
33
+ observations: [
34
+ {
35
+ label: input.value.specifier,
36
+ properties: {
37
+ declared_specifier: input.value.specifier,
38
+ expression: input.value.expression,
39
+ resolved_path: resolved?.path ?? null,
40
+ },
41
+ evidence: staticInferenceEvidence({
42
+ sha256: input.file.sha256,
43
+ path: input.file.path,
44
+ range: input.value.location,
45
+ operation: "discover-worker",
46
+ coverage: input.coverage,
47
+ }),
48
+ },
49
+ ],
50
+ });
51
+ addStaticInferenceEdge(context, {
52
+ source,
53
+ target: worker,
54
+ file: input.file,
55
+ range: input.value.location,
56
+ coverage: input.coverage,
57
+ relation: "loads",
58
+ properties: { kind: input.value.kind },
59
+ });
60
+ if (resolved !== null)
61
+ addStaticInferenceEdge(context, {
62
+ source: worker,
63
+ target: resolved.node,
64
+ file: input.file,
65
+ range: input.value.location,
66
+ coverage: input.coverage,
67
+ relation: "maps_to",
68
+ properties: { resolved_path: resolved.path },
69
+ });
70
+ return;
71
+ }
72
+ const target = resolved?.node ??
73
+ unresolvedReferenceNode(context, input.file, input.value, input.coverage);
74
+ addStaticInferenceEdge(context, {
75
+ source,
76
+ target,
77
+ file: input.file,
78
+ range: input.value.location,
79
+ coverage: input.coverage,
80
+ relation: resolved?.file.kind === "native-addon" ? "loads" : "imports",
81
+ properties: {
82
+ kind: input.value.kind,
83
+ specifier: input.value.specifier,
84
+ expression: input.value.expression,
85
+ resolved_path: resolved?.path ?? null,
86
+ },
87
+ });
88
+ };
89
+ const addEndpoint = (context, input) => {
90
+ const source = sourceNodeFor(context, input.file.path, input.value.module_key) ??
91
+ input.asset;
92
+ const node = context.accumulator.addNode({
93
+ kind: "endpoint",
94
+ identity: artifactLocalIdentity(input.file.sha256, input.value.kind, input.value.value),
95
+ observations: [
96
+ {
97
+ label: input.value.value,
98
+ properties: {
99
+ endpoint_kind: input.value.kind,
100
+ value: input.value.value,
101
+ mechanism: input.value.mechanism,
102
+ },
103
+ evidence: astObservationEvidence({
104
+ sha256: input.file.sha256,
105
+ path: input.file.path,
106
+ range: input.value.location,
107
+ operation: "discover-static-endpoint",
108
+ coverage: input.coverage,
109
+ }),
110
+ },
111
+ ],
112
+ });
113
+ addStaticInferenceEdge(context, {
114
+ source,
115
+ target: node,
116
+ file: input.file,
117
+ range: input.value.location,
118
+ coverage: input.coverage,
119
+ relation: input.value.kind === "route" ? "exposes" : "calls",
120
+ properties: { mechanism: input.value.mechanism },
121
+ });
122
+ };
123
+ const addStorage = (context, input) => {
124
+ const source = sourceNodeFor(context, input.file.path, input.value.module_key) ??
125
+ input.asset;
126
+ const node = context.accumulator.addNode({
127
+ kind: "storage",
128
+ identity: artifactLocalIdentity(input.file.sha256, input.value.kind, input.value.name ?? input.value.mechanism),
129
+ observations: [
130
+ {
131
+ label: input.value.name ?? input.value.kind,
132
+ properties: {
133
+ storage_kind: input.value.kind,
134
+ name: input.value.name,
135
+ mechanism: input.value.mechanism,
136
+ },
137
+ evidence: astObservationEvidence({
138
+ sha256: input.file.sha256,
139
+ path: input.file.path,
140
+ range: input.value.location,
141
+ operation: "discover-static-storage",
142
+ coverage: input.coverage,
143
+ }),
144
+ },
145
+ ],
146
+ });
147
+ addStaticInferenceEdge(context, {
148
+ source,
149
+ target: node,
150
+ file: input.file,
151
+ range: input.value.location,
152
+ coverage: input.coverage,
153
+ relation: "persists_to",
154
+ properties: { mechanism: input.value.mechanism },
155
+ });
156
+ };
157
+ const addDiscoveredRole = (context, input) => {
158
+ const resolvedPath = resolveArtifactPath(input.value.path, input.file.path, context.filesByPath);
159
+ const role = createElectronRoleNode(context, {
160
+ kind: input.value.role === "preload" ? "electron-preload" : "electron-renderer",
161
+ anchor: input.file,
162
+ declaredPath: input.value.path,
163
+ resolvedPath,
164
+ mechanism: input.value.mechanism,
165
+ range: input.value.location,
166
+ coverage: input.coverage,
167
+ });
168
+ const source = sourceNodeFor(context, input.file.path, input.value.module_key) ??
169
+ input.asset;
170
+ addStaticInferenceEdge(context, {
171
+ source,
172
+ target: role,
173
+ file: input.file,
174
+ range: input.value.location,
175
+ coverage: input.coverage,
176
+ relation: "loads",
177
+ properties: { mechanism: input.value.mechanism },
178
+ });
179
+ linkElectronRoleToAsset(context, {
180
+ role,
181
+ anchor: input.file,
182
+ path: resolvedPath,
183
+ range: input.value.location,
184
+ coverage: input.coverage,
185
+ });
186
+ };
187
+ const addSourceMapEdge = (context, input) => {
188
+ const resolvedPath = resolveArtifactPath(input.value.declared_url, input.file.path, context.filesByPath);
189
+ const resolvedFile = resolvedPath === null ? undefined : context.filesByPath.get(resolvedPath);
190
+ const resolvedNode = resolvedPath === null ? undefined : context.fileNodes.get(resolvedPath);
191
+ const target = resolvedFile?.kind === "source-map" && resolvedNode !== undefined
192
+ ? resolvedNode
193
+ : context.accumulator.addNode({
194
+ kind: "source-map",
195
+ identity: artifactLocalIdentity(input.file.sha256, "unresolved-source-map", input.value.declared_url),
196
+ observations: [
197
+ {
198
+ label: input.value.declared_url,
199
+ properties: {
200
+ declared_url: input.value.declared_url,
201
+ resolved_path: resolvedPath,
202
+ available: false,
203
+ },
204
+ evidence: staticInferenceEvidence({
205
+ sha256: input.file.sha256,
206
+ path: input.file.path,
207
+ range: input.value.location,
208
+ operation: "discover-source-map",
209
+ coverage: input.coverage,
210
+ confidence: "medium",
211
+ limitations: [
212
+ "The declared source map was not present in the inventoried artifact.",
213
+ ],
214
+ }),
215
+ },
216
+ ],
217
+ });
218
+ addStaticInferenceEdge(context, {
219
+ source: input.asset,
220
+ target,
221
+ file: input.file,
222
+ range: input.value.location,
223
+ coverage: input.coverage,
224
+ relation: "maps_to",
225
+ properties: {
226
+ declared_url: input.value.declared_url,
227
+ resolved_path: resolvedPath,
228
+ },
229
+ });
230
+ };
231
+ const resolveReference = (context, sourcePath, specifier) => {
232
+ const module = context.moduleNodes.get(moduleLookupKey(sourcePath, specifier));
233
+ if (module !== undefined) {
234
+ const file = context.filesByPath.get(sourcePath);
235
+ return file === undefined
236
+ ? null
237
+ : { path: `${sourcePath}#module:${specifier}`, file, node: module };
238
+ }
239
+ const path = resolveArtifactPath(specifier, sourcePath, context.filesByPath);
240
+ if (path === null)
241
+ return null;
242
+ const file = context.filesByPath.get(path);
243
+ const node = context.fileNodes.get(path);
244
+ return file === undefined || node === undefined ? null : { path, file, node };
245
+ };
246
+ const unresolvedReferenceNode = (context, file, reference, coverage) => context.accumulator.addNode({
247
+ kind: "javascript-module",
248
+ identity: artifactLocalIdentity(file.sha256, "unresolved-reference", `${reference.kind}:${reference.specifier ?? reference.expression ?? "[dynamic]"}`),
249
+ observations: [
250
+ {
251
+ label: reference.specifier,
252
+ properties: {
253
+ reference_kind: reference.kind,
254
+ specifier: reference.specifier,
255
+ expression: reference.expression,
256
+ resolution: "unresolved",
257
+ },
258
+ evidence: staticInferenceEvidence({
259
+ sha256: file.sha256,
260
+ path: file.path,
261
+ range: reference.location,
262
+ operation: "resolve-static-reference",
263
+ coverage,
264
+ confidence: "low",
265
+ limitations: [
266
+ "The static reference could not be resolved within the inventoried artifact.",
267
+ ],
268
+ }),
269
+ },
270
+ ],
271
+ });