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.
- package/README.md +38 -31
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/Doctor.js +56 -6
- package/dist/application/ElectronBoundaryAnalysis.js +96 -0
- package/dist/application/ElectronBoundaryGraph.js +9 -0
- package/dist/application/ElectronBoundaryGraphContext.js +62 -0
- package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
- package/dist/application/ElectronBoundaryGraphNative.js +86 -0
- package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
- package/dist/application/EnhancedTools.js +38 -13
- package/dist/application/InvestigationProviders.js +30 -0
- package/dist/application/JavaScriptApplicationEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
- package/dist/application/JavaScriptApplicationService.js +74 -0
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
- package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
- package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
- package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
- package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
- package/dist/application/JavaScriptReplayPlanning.js +201 -0
- package/dist/application/JavaScriptReplayService.js +266 -0
- package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
- package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
- package/dist/application/ManagedApplicationGraphService.js +55 -0
- package/dist/application/ManagedMemberComparisonService.js +122 -0
- package/dist/application/ManagedNativeVerificationService.js +39 -0
- package/dist/application/ManagedReconstructionService.js +58 -0
- package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
- package/dist/application/PermissionAuthority.js +1 -1
- package/dist/application/ProcessSampling.js +15 -11
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/runtime.js +2 -0
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +5 -1
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +4 -1
- package/dist/cli.js +388 -0
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliCommandNames.js +13 -0
- package/dist/cliElectronCommands.js +103 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/config.js +83 -0
- package/dist/contracts/applicationToolContracts.js +91 -0
- package/dist/contracts/artifactToolContracts.js +5 -9
- package/dist/contracts/browserToolContracts.js +14 -62
- package/dist/contracts/electronToolContracts.js +45 -19
- package/dist/contracts/errorSchemas.js +1 -0
- package/dist/contracts/investigationExamples.js +0 -6
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
- package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
- package/dist/contracts/managedToolContracts.js +153 -0
- package/dist/contracts/managedWorkflowExamples.js +346 -0
- package/dist/contracts/managedWorkflowToolContracts.js +203 -0
- package/dist/contracts/nativeToolContracts.js +2 -6
- package/dist/contracts/promptContracts.js +18 -2
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +47 -45
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +46 -31
- package/dist/doctorRuntime.js +42 -1
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/browserObservation.js +3 -0
- package/dist/domain/bundleComparison.js +4 -4
- package/dist/domain/electronObservation.js +13 -0
- package/dist/domain/electronStaticAnalysis.js +9 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
- package/dist/domain/electronStaticAnalysisIpc.js +171 -0
- package/dist/domain/electronStaticAnalysisNative.js +164 -0
- package/dist/domain/electronStaticAnalysisTypes.js +1 -0
- package/dist/domain/electronStaticAnalysisValues.js +103 -0
- package/dist/domain/errors.js +67 -1
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
- package/dist/domain/javascriptApplicationGraph.js +2 -1
- package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
- package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
- package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
- package/dist/domain/javascriptApplicationVersionItems.js +221 -0
- package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
- package/dist/domain/javascriptFeatureSeed.js +140 -0
- package/dist/domain/javascriptFeatureTrace.js +232 -0
- package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
- package/dist/domain/javascriptFeatureTraversal.js +88 -0
- package/dist/domain/javascriptNativeHandoff.js +85 -0
- package/dist/domain/javascriptReplay.js +361 -0
- package/dist/domain/javascriptRuntimeLoadState.js +106 -0
- package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
- package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
- package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
- package/dist/domain/javascriptStaticAnalysis.js +31 -43
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
- package/dist/domain/javascriptStaticAnalysisState.js +6 -0
- package/dist/domain/managedApplicationGraph.js +556 -0
- package/dist/domain/managedArtifact.js +444 -0
- package/dist/domain/managedMemberComparison.js +565 -0
- package/dist/domain/managedNativeVerification.js +408 -0
- package/dist/domain/managedReconstruction.js +183 -0
- package/dist/domain/managedRuntimeCorrelation.js +175 -0
- package/dist/domain/reconstructionVerificationSchemas.js +1 -3
- package/dist/dotnet/ManagedArtifactInspector.js +318 -0
- package/dist/dotnet/ManagedMemberInspector.js +887 -0
- package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
- package/dist/dotnet/ManagedMetadataInventory.js +368 -0
- package/dist/dotnet/ManagedMetadataLayout.js +322 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
- package/dist/dotnet/ManagedPeReader.js +157 -0
- package/dist/dotnet/ManagedReaderFailure.js +10 -0
- package/dist/dotnet/ManagedStaticProvider.js +203 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraProvider.js +6 -0
- package/dist/main.js +14 -0
- package/dist/replay/JavaScriptReplayWorker.js +286 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
- package/dist/replay/LinuxRuntimeClosure.js +46 -0
- package/dist/replay/LinuxSeccompPolicy.js +40 -0
- package/dist/replay/ReplayWorkerProtocol.js +55 -0
- package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
- package/dist/server/createServer.js +52 -0
- package/dist/server/registerApplicationTools.js +115 -0
- package/dist/server/registerArtifactComparisonTool.js +16 -9
- package/dist/server/registerBrowserTools.js +33 -8
- package/dist/server/registerBundleComparisonTool.js +17 -20
- package/dist/server/registerElectronTools.js +45 -3
- package/dist/server/registerEnhancedTools.js +58 -13
- package/dist/server/registerEvidenceTools.js +5 -1
- package/dist/server/registerFunctionComparisonTool.js +14 -7
- package/dist/server/registerInvestigationTools.js +21 -17
- package/dist/server/registerManagedTools.js +6 -0
- package/dist/server/registerManagedWorkflowTools.js +277 -0
- package/dist/server/registerOfficialTools.js +8 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +51 -86
- package/dist/server/sessionEvidence.js +14 -19
- package/dist/server/toolInputValidation.js +77 -0
- package/dist/server/toolRegistrationOptions.js +79 -2
- package/dist/server/toolResult.js +25 -17
- package/package.json +12 -4
- package/scripts/rea.mjs +1 -1
- 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
|
-
|
|
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
|
|
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:
|
|
68
|
-
endpoints:
|
|
69
|
-
storage:
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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,
|