rea-agents 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -66
- package/bridge/ghidra/ReaGhidraBridge.java +2075 -0
- package/bridge/hopper_bridge.py +13 -3
- package/dist/application/AbortablePromise.js +25 -0
- package/dist/application/AnalysisProvider.js +3 -0
- package/dist/application/AnalysisProviderEvaluation.js +60 -0
- package/dist/application/AnalysisProviderRegistry.js +228 -0
- package/dist/application/AnalysisSnapshotCache.js +41 -21
- package/dist/application/AnalysisSnapshotFiles.js +5 -6
- package/dist/application/BinarySession.js +218 -101
- package/dist/application/CapabilityInventory.js +18 -0
- package/dist/application/CompositeProvider.js +20 -8
- package/dist/application/CrossVersionInvestigation.js +10 -6
- package/dist/application/DirectAnalysis.js +49 -35
- package/dist/application/Doctor.js +110 -8
- 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 +39 -14
- package/dist/application/InvestigationProviders.js +41 -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 +305 -0
- package/dist/application/JavaScriptArtifactAnalysisTypes.js +1 -0
- package/dist/application/JavaScriptArtifactFiles.js +231 -0
- package/dist/application/JavaScriptArtifactGraphAccumulator.js +54 -0
- package/dist/application/JavaScriptArtifactGraphBuilder.js +144 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +205 -0
- package/dist/application/JavaScriptArtifactGraphDocuments.js +108 -0
- package/dist/application/JavaScriptArtifactGraphEvidence.js +99 -0
- package/dist/application/JavaScriptArtifactGraphFindings.js +271 -0
- package/dist/application/JavaScriptArtifactGraphStructure.js +409 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +77 -0
- package/dist/application/JavaScriptArtifactReconstructionInput.js +18 -0
- 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/MacHopper.js +20 -4
- 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/ProcessCaptureLifecycle.js +1 -1
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessSampling.js +16 -12
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/SessionProviderRouter.js +219 -0
- package/dist/application/Setup.js +84 -60
- package/dist/application/SetupClients.js +1 -1
- package/dist/application/SetupPlan.js +9 -2
- package/dist/application/SupportedClients.js +40 -16
- package/dist/application/Upgrade.js +1 -1
- package/dist/application/runtime.js +11 -6
- package/dist/artifacts/AsarArtifactReader.js +23 -3
- package/dist/browser/CdpBrowserProvider.js +4 -2
- package/dist/browser/CdpCaptureEvents.js +49 -0
- package/dist/browser/CdpElectronInspection.js +106 -162
- package/dist/browser/CdpElectronProvider.js +9 -3
- package/dist/browser/CdpElectronScriptEvents.js +69 -0
- package/dist/browser/CdpElectronScripts.js +94 -0
- package/dist/browser/CdpElectronWorkers.js +56 -0
- package/dist/browser/CdpEndpoint.js +14 -1
- package/dist/browser/CdpPageCapture.js +8 -4
- package/dist/catalogIdentity.js +6 -42
- package/dist/cli.js +446 -34
- package/dist/cliApplicationCommands.js +62 -0
- package/dist/cliBrowserAdvancedCommands.js +5 -4
- package/dist/cliBrowserCommands.js +5 -4
- package/dist/cliCommandNames.js +56 -0
- package/dist/cliElectronCommands.js +106 -3
- package/dist/cliEvidenceCommands.js +4 -3
- package/dist/cliInvestigationCommands.js +2 -1
- package/dist/cliJsonInput.js +54 -0
- package/dist/cliPolicyCommands.js +2 -1
- package/dist/cliProcessCommands.js +3 -2
- package/dist/config.js +101 -2
- 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/enhancedInputs.js +2 -2
- 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/providerSelection.js +28 -0
- package/dist/contracts/sessionLifecycleInputs.js +2 -0
- package/dist/contracts/toolContractExamples.js +10 -25
- package/dist/contracts/toolContracts.js +69 -65
- package/dist/contracts/toolEffects.js +160 -0
- package/dist/contracts/toolOutputSchemas.js +152 -42
- package/dist/doctorRuntime.js +47 -0
- package/dist/domain/analysisProfile.js +78 -0
- package/dist/domain/analysisSnapshot.js +92 -46
- package/dist/domain/artifactComparison.js +3 -4
- package/dist/domain/artifactGraph.js +1 -0
- package/dist/domain/binaryTarget.js +4 -29
- 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 +148 -7
- package/dist/domain/evidence.js +44 -5
- package/dist/domain/evidenceBundle.js +9 -7
- package/dist/domain/functionComparison.js +9 -3
- package/dist/domain/functionComparisonNormalization.js +37 -0
- package/dist/domain/functionComparisonSchemas.js +3 -8
- package/dist/domain/functionDossierEvidence.js +4 -1
- package/dist/domain/hopperValues.js +66 -9
- package/dist/domain/inputIssueProjection.js +52 -0
- package/dist/domain/investigationWorkspace.js +2 -1
- package/dist/domain/javascriptApplicationAnalysis.js +155 -0
- package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
- package/dist/domain/javascriptApplicationEvidenceSchemas.js +379 -0
- package/dist/domain/javascriptApplicationGraph.js +282 -0
- package/dist/domain/javascriptApplicationGraphSchemas.js +139 -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/javascriptAstFingerprint.js +143 -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 +346 -0
- package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +274 -0
- package/dist/domain/javascriptStaticAnalysisState.js +23 -0
- package/dist/domain/javascriptStaticAnalysisTypes.js +1 -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/GhidraAnalysisProfile.js +37 -0
- package/dist/ghidra/GhidraClient.js +400 -0
- package/dist/ghidra/GhidraClientTypes.js +1 -0
- package/dist/ghidra/GhidraDefaults.js +20 -0
- package/dist/ghidra/GhidraDiagnostics.js +43 -0
- package/dist/ghidra/GhidraDoctor.js +59 -0
- package/dist/ghidra/GhidraFunctionValues.js +264 -0
- package/dist/ghidra/GhidraInstallation.js +239 -0
- package/dist/ghidra/GhidraInventoryValues.js +217 -0
- package/dist/ghidra/GhidraLauncher.js +139 -0
- package/dist/ghidra/GhidraProvider.js +366 -0
- package/dist/ghidra/GhidraRequestQueue.js +98 -0
- package/dist/ghidra/GhidraResponseBuffer.js +31 -0
- package/dist/ghidra/GhidraResponseRouter.js +24 -0
- package/dist/ghidra/GhidraSessionError.js +39 -0
- package/dist/ghidra/GhidraSessionValues.js +62 -0
- package/dist/ghidra/GhidraSocketConnection.js +46 -0
- package/dist/ghidra/protocol.js +69 -0
- package/dist/hopper/BridgeLauncher.js +19 -54
- package/dist/hopper/HopperAnalysisProfile.js +106 -0
- package/dist/hopper/HopperClient.js +154 -162
- package/dist/hopper/HopperProvider.js +99 -7
- package/dist/hopper/HopperSessionValues.js +27 -0
- package/dist/hopper/HopperSocketConnection.js +31 -0
- package/dist/main.js +14 -0
- package/dist/native/NativeMacOSProvider.js +3 -1
- package/dist/process/PendingOperations.js +59 -0
- package/dist/process/PrivateRuntimeRoot.js +33 -0
- package/dist/process/ProviderDeadline.js +95 -0
- package/dist/process/ProviderProcess.js +275 -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 +53 -0
- package/dist/server/promptCompletion.js +9 -3
- 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 +67 -19
- 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 +11 -8
- package/dist/server/registerProcessComparisonTool.js +25 -47
- package/dist/server/registerSessionStatusTool.js +7 -7
- package/dist/server/registerSessionTools.js +57 -87
- 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 +17 -7
- package/scripts/rea.mjs +36 -2
- package/skills/rea-analysis/SKILL.md +59 -3
- /package/dist/{application → process}/ProcessOwnership.js +0 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import * as t from "@babel/types";
|
|
3
|
+
import { collectJavaScriptExports, fingerprintJavaScriptAst, } from "./javascriptAstFingerprint.js";
|
|
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
|
+
import { createJavaScriptAnalysisAccumulator, } from "./javascriptStaticAnalysisState.js";
|
|
6
|
+
import { addBoundedFinding, addLocatedFinding, finalizeLocatedFindings, moduleAtOffset, } from "./javascriptStaticAnalysisFindings.js";
|
|
7
|
+
import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
|
|
8
|
+
/** Parse one bounded JavaScript artifact and recover static structure only. */
|
|
9
|
+
export const analyzeJavaScriptStaticSource = (source, limits) => {
|
|
10
|
+
let file;
|
|
11
|
+
try {
|
|
12
|
+
file = parse(source, {
|
|
13
|
+
sourceType: "unambiguous",
|
|
14
|
+
errorRecovery: true,
|
|
15
|
+
plugins: ["jsx", "typescript"],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return failedJavaScriptStaticAnalysis();
|
|
20
|
+
}
|
|
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) => {
|
|
27
|
+
if (limits.now() > limits.deadline)
|
|
28
|
+
accumulator.truncated = true;
|
|
29
|
+
if (!accumulator.truncated)
|
|
30
|
+
t.traverseFast(file, (node) => {
|
|
31
|
+
accumulator.visitedNodes += 1;
|
|
32
|
+
if (accumulator.visitedNodes > limits.maxAstNodes ||
|
|
33
|
+
(accumulator.visitedNodes % 1_024 === 0 &&
|
|
34
|
+
limits.now() > limits.deadline)) {
|
|
35
|
+
accumulator.truncated = true;
|
|
36
|
+
return t.traverseFast.stop;
|
|
37
|
+
}
|
|
38
|
+
inspectNode(source, node, accumulator, limits);
|
|
39
|
+
return undefined;
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const finalizeStaticAnalysis = (source, file, accumulator, limits) => {
|
|
43
|
+
const parserErrors = file.errors.length;
|
|
44
|
+
const limitations = [
|
|
45
|
+
...(parserErrors === 0
|
|
46
|
+
? []
|
|
47
|
+
: [
|
|
48
|
+
"The parser recovered from syntax errors; affected facts are partial.",
|
|
49
|
+
]),
|
|
50
|
+
...(accumulator.truncated
|
|
51
|
+
? ["AST analysis stopped at the configured node or time bound."]
|
|
52
|
+
: []),
|
|
53
|
+
...(accumulator.droppedFindings === 0
|
|
54
|
+
? []
|
|
55
|
+
: ["Static findings were truncated at the configured finding bound."]),
|
|
56
|
+
...(accumulator.structuralTruncation
|
|
57
|
+
? ["Bundler metadata reached a per-record structural bound."]
|
|
58
|
+
: []),
|
|
59
|
+
...(accumulator.unknownFindings === 0
|
|
60
|
+
? []
|
|
61
|
+
: [
|
|
62
|
+
"One or more static keys, expressions, or Electron boundary values were dynamic and remain unknown.",
|
|
63
|
+
]),
|
|
64
|
+
"JavaScript syntax was parsed as data and was never evaluated.",
|
|
65
|
+
];
|
|
66
|
+
return {
|
|
67
|
+
parse_status: accumulator.truncated || accumulator.droppedFindings > 0
|
|
68
|
+
? "truncated"
|
|
69
|
+
: parserErrors > 0 || accumulator.unknownFindings > 0
|
|
70
|
+
? "partial"
|
|
71
|
+
: "complete",
|
|
72
|
+
parse_error_count: parserErrors,
|
|
73
|
+
visited_ast_nodes: Math.min(accumulator.visitedNodes, limits.maxAstNodes),
|
|
74
|
+
dropped_findings: accumulator.droppedFindings,
|
|
75
|
+
references: finalizeLocatedFindings(accumulator.references, accumulator.modules),
|
|
76
|
+
endpoints: finalizeLocatedFindings(accumulator.endpoints, accumulator.modules),
|
|
77
|
+
storage: finalizeLocatedFindings(accumulator.storage, accumulator.modules),
|
|
78
|
+
bundler_registrations: sortedUnique(accumulator.registrations, registrationKey),
|
|
79
|
+
role_paths: finalizeLocatedFindings(accumulator.roles, accumulator.modules),
|
|
80
|
+
source_map_urls: accumulator.sourceMaps,
|
|
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
|
+
},
|
|
90
|
+
limitations,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const inspectNode = (source, node, accumulator, limits) => {
|
|
94
|
+
const findings = {
|
|
95
|
+
source,
|
|
96
|
+
accumulator,
|
|
97
|
+
maximum: limits.maxFindings,
|
|
98
|
+
};
|
|
99
|
+
inspectElectronStaticNode(node, findings);
|
|
100
|
+
if (t.isCallExpression(node)) {
|
|
101
|
+
inspectBundlerRegistration(source, node, accumulator, limits);
|
|
102
|
+
inspectCall(node, findings);
|
|
103
|
+
}
|
|
104
|
+
else if (t.isNewExpression(node))
|
|
105
|
+
inspectCall(node, findings);
|
|
106
|
+
if ((t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) &&
|
|
107
|
+
node.source !== undefined)
|
|
108
|
+
addReference(findings, {
|
|
109
|
+
node,
|
|
110
|
+
kind: "static-import",
|
|
111
|
+
specifier: node.source.value,
|
|
112
|
+
});
|
|
113
|
+
else if (t.isExportNamedDeclaration(node) && node.source != null)
|
|
114
|
+
addReference(findings, {
|
|
115
|
+
node,
|
|
116
|
+
kind: "static-import",
|
|
117
|
+
specifier: node.source.value,
|
|
118
|
+
});
|
|
119
|
+
else if (t.isImportExpression(node))
|
|
120
|
+
addReference(findings, {
|
|
121
|
+
node,
|
|
122
|
+
kind: "dynamic-import",
|
|
123
|
+
specifier: stringValue(node.source),
|
|
124
|
+
});
|
|
125
|
+
if (t.isObjectProperty(node)) {
|
|
126
|
+
inspectRouteProperty(node, findings);
|
|
127
|
+
inspectRoleProperty(node, findings);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const inspectCall = (node, context) => {
|
|
131
|
+
const { accumulator } = context;
|
|
132
|
+
const name = calleeName(node.callee);
|
|
133
|
+
const first = stringValue(node.arguments[0]);
|
|
134
|
+
const module = moduleAtOffset(node.start, accumulator.modules);
|
|
135
|
+
const moduleRequire = module?.requireName !== null && name === module?.requireName;
|
|
136
|
+
if (name === "require" ||
|
|
137
|
+
name.endsWith(".require") ||
|
|
138
|
+
name.includes("__webpack_require__") ||
|
|
139
|
+
moduleRequire)
|
|
140
|
+
addReference(context, {
|
|
141
|
+
node,
|
|
142
|
+
kind: "require",
|
|
143
|
+
specifier: argumentValue(node.arguments[0]),
|
|
144
|
+
});
|
|
145
|
+
if (name.endsWith(".e") && name.includes("require"))
|
|
146
|
+
addReference(context, {
|
|
147
|
+
node,
|
|
148
|
+
kind: "dynamic-import",
|
|
149
|
+
specifier: prefixedArgument("chunk:", node.arguments[0]),
|
|
150
|
+
});
|
|
151
|
+
if (name === "Worker" || name.endsWith(".Worker"))
|
|
152
|
+
addReference(context, { node, kind: "worker", specifier: first });
|
|
153
|
+
if (name.endsWith("serviceWorker.register"))
|
|
154
|
+
addReference(context, { node, kind: "service-worker", specifier: first });
|
|
155
|
+
inspectEndpointCall(node, name, first, context);
|
|
156
|
+
inspectStorageCall(node, name, first, context);
|
|
157
|
+
if (name.endsWith("loadFile") && first !== undefined)
|
|
158
|
+
addLocatedFinding(context, {
|
|
159
|
+
collection: accumulator.roles,
|
|
160
|
+
key: `role\0renderer\0${first}`,
|
|
161
|
+
node,
|
|
162
|
+
value: {
|
|
163
|
+
role: "renderer",
|
|
164
|
+
path: first,
|
|
165
|
+
mechanism: `call:${name}`,
|
|
166
|
+
module_key: null,
|
|
167
|
+
location: range(node),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const inspectBundlerRegistration = (source, call, accumulator, limits) => {
|
|
172
|
+
const runtime = chunkRuntime(call);
|
|
173
|
+
const entry = call.arguments[0];
|
|
174
|
+
if (runtime === undefined || !t.isArrayExpression(entry))
|
|
175
|
+
return;
|
|
176
|
+
const chunkIds = entry.elements[0];
|
|
177
|
+
const table = entry.elements[1];
|
|
178
|
+
if (!t.isArrayExpression(chunkIds) || !t.isObjectExpression(table))
|
|
179
|
+
return;
|
|
180
|
+
const modules = [];
|
|
181
|
+
for (const property of table.properties) {
|
|
182
|
+
const factory = moduleFactory(property);
|
|
183
|
+
if (factory === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
if (accumulator.moduleCount >= limits.maxModules) {
|
|
186
|
+
accumulator.droppedFindings += 1;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
accumulator.moduleCount += 1;
|
|
190
|
+
const key = modulePropertyName(property);
|
|
191
|
+
if (key.startsWith("[computed@") || key.startsWith("[unknown@"))
|
|
192
|
+
accumulator.unknownFindings += 1;
|
|
193
|
+
const exactSource = sourceSlice(source, factory);
|
|
194
|
+
const fingerprint = fingerprintJavaScriptAst(factory, Math.min(limits.maxAstNodes, 100_000));
|
|
195
|
+
const exportsValue = collectJavaScriptExports(factory, 256);
|
|
196
|
+
if (fingerprint.truncated || exportsValue.truncated) {
|
|
197
|
+
accumulator.droppedFindings += 1;
|
|
198
|
+
accumulator.structuralTruncation = true;
|
|
199
|
+
}
|
|
200
|
+
modules.push({
|
|
201
|
+
module_key: key,
|
|
202
|
+
source_sha256: sha256Text(exactSource),
|
|
203
|
+
structural_fingerprint_sha256: fingerprint.truncated
|
|
204
|
+
? null
|
|
205
|
+
: fingerprint.sha256,
|
|
206
|
+
structural_fingerprint_algorithm: fingerprint.truncated
|
|
207
|
+
? null
|
|
208
|
+
: "babel-ast-v1",
|
|
209
|
+
structural_fingerprint_status: fingerprint.truncated
|
|
210
|
+
? "truncated"
|
|
211
|
+
: "complete",
|
|
212
|
+
exports: exportsValue.values,
|
|
213
|
+
exports_truncated: exportsValue.truncated,
|
|
214
|
+
location: range(factory),
|
|
215
|
+
});
|
|
216
|
+
if (typeof factory.start === "number" && typeof factory.end === "number")
|
|
217
|
+
accumulator.modules.push({
|
|
218
|
+
start: factory.start,
|
|
219
|
+
end: factory.end,
|
|
220
|
+
key,
|
|
221
|
+
requireName: factoryRequireName(factory),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const chunkKeys = staticArrayValues(chunkIds, Math.min(256, limits.maxFindings));
|
|
225
|
+
if (chunkKeys.omitted > 0) {
|
|
226
|
+
accumulator.droppedFindings += chunkKeys.omitted;
|
|
227
|
+
accumulator.structuralTruncation = true;
|
|
228
|
+
}
|
|
229
|
+
accumulator.unknownFindings += chunkKeys.unknown;
|
|
230
|
+
const registration = {
|
|
231
|
+
bundler: runtime.toLowerCase().includes("rspack")
|
|
232
|
+
? "rspack"
|
|
233
|
+
: "webpack",
|
|
234
|
+
runtime,
|
|
235
|
+
chunk_keys: chunkKeys.values,
|
|
236
|
+
omitted_chunk_keys: chunkKeys.omitted,
|
|
237
|
+
unknown_chunk_keys: chunkKeys.unknown,
|
|
238
|
+
modules: modules.sort((left, right) => compareCodePoints(left.module_key, right.module_key)),
|
|
239
|
+
location: range(call),
|
|
240
|
+
};
|
|
241
|
+
addBoundedFinding(accumulator, `registration\0${registrationKey(registration)}`, limits.maxFindings, () => accumulator.registrations.push(registration));
|
|
242
|
+
};
|
|
243
|
+
const inspectEndpointCall = (node, name, first, context) => {
|
|
244
|
+
const route = STATIC_ROUTE_CALL_NAMES.some((candidate) => name === candidate || name.endsWith(`.${candidate}`));
|
|
245
|
+
if (route && first !== undefined)
|
|
246
|
+
addEndpoint(context, {
|
|
247
|
+
node,
|
|
248
|
+
kind: "route",
|
|
249
|
+
value: first,
|
|
250
|
+
mechanism: `call:${name}`,
|
|
251
|
+
});
|
|
252
|
+
const endpoint = endpointArgument(name, node.arguments);
|
|
253
|
+
if (endpoint !== undefined)
|
|
254
|
+
addEndpoint(context, {
|
|
255
|
+
node,
|
|
256
|
+
kind: "network",
|
|
257
|
+
value: endpoint,
|
|
258
|
+
mechanism: `call:${name}`,
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
const inspectRouteProperty = (node, context) => {
|
|
262
|
+
const key = propertyName(node.key);
|
|
263
|
+
if ((key === "path" || key === "route") && t.isStringLiteral(node.value))
|
|
264
|
+
addEndpoint(context, {
|
|
265
|
+
node,
|
|
266
|
+
kind: "route",
|
|
267
|
+
value: node.value.value,
|
|
268
|
+
mechanism: `property:${key}`,
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const inspectRoleProperty = (node, context) => {
|
|
272
|
+
if (propertyName(node.key) !== "preload")
|
|
273
|
+
return;
|
|
274
|
+
const path = staticPath(node.value);
|
|
275
|
+
if (path === undefined)
|
|
276
|
+
return;
|
|
277
|
+
addLocatedFinding(context, {
|
|
278
|
+
collection: context.accumulator.roles,
|
|
279
|
+
key: `role\0preload\0${path}`,
|
|
280
|
+
node,
|
|
281
|
+
value: {
|
|
282
|
+
role: "preload",
|
|
283
|
+
path,
|
|
284
|
+
mechanism: "property:preload",
|
|
285
|
+
module_key: null,
|
|
286
|
+
location: range(node),
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
const inspectStorageCall = (node, name, first, context) => {
|
|
291
|
+
const storage = storageKind(name);
|
|
292
|
+
if (storage === undefined)
|
|
293
|
+
return;
|
|
294
|
+
addLocatedFinding(context, {
|
|
295
|
+
collection: context.accumulator.storage,
|
|
296
|
+
key: `storage\0${storage}\0${first ?? ""}`,
|
|
297
|
+
node,
|
|
298
|
+
value: {
|
|
299
|
+
kind: storage,
|
|
300
|
+
name: first ?? null,
|
|
301
|
+
mechanism: `call:${name}`,
|
|
302
|
+
module_key: null,
|
|
303
|
+
location: range(node),
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const addReference = (context, input) => {
|
|
308
|
+
const boundedSpecifier = input.specifier?.slice(0, 4_096) ?? null;
|
|
309
|
+
const expression = boundedSpecifier === null
|
|
310
|
+
? sourceSlice(context.source, input.node).slice(0, 4_096)
|
|
311
|
+
: null;
|
|
312
|
+
addLocatedFinding(context, {
|
|
313
|
+
collection: context.accumulator.references,
|
|
314
|
+
key: `reference\0${input.kind}\0${boundedSpecifier ?? expression}`,
|
|
315
|
+
node: input.node,
|
|
316
|
+
value: {
|
|
317
|
+
kind: input.kind,
|
|
318
|
+
specifier: boundedSpecifier,
|
|
319
|
+
expression,
|
|
320
|
+
module_key: null,
|
|
321
|
+
location: range(input.node),
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const addEndpoint = (context, input) => addLocatedFinding(context, {
|
|
326
|
+
collection: context.accumulator.endpoints,
|
|
327
|
+
key: `endpoint\0${input.kind}\0${input.value}`,
|
|
328
|
+
node: input.node,
|
|
329
|
+
value: {
|
|
330
|
+
kind: input.kind,
|
|
331
|
+
value: sanitizeCandidate(input.value),
|
|
332
|
+
mechanism: input.mechanism,
|
|
333
|
+
module_key: null,
|
|
334
|
+
location: range(input.node),
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
const addSourceMapDirectives = (source, accumulator, maximum) => {
|
|
338
|
+
for (const match of source.matchAll(/\/\/[#@]\s*sourceMappingURL\s*=\s*([^\s]+)/gu)) {
|
|
339
|
+
const declared = match[1]?.slice(0, 4_096);
|
|
340
|
+
if (declared === undefined)
|
|
341
|
+
continue;
|
|
342
|
+
const start = match.index;
|
|
343
|
+
const location = rangeForOffsets(source, start, start + match[0].length);
|
|
344
|
+
addBoundedFinding(accumulator, `source-map\0${declared}`, maximum, () => accumulator.sourceMaps.push({ declared_url: declared, location }));
|
|
345
|
+
}
|
|
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;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { posix } from "node:path";
|
|
3
|
+
import * as t from "@babel/types";
|
|
4
|
+
import { sanitizeBrowserUrl } from "./browserObservation.js";
|
|
5
|
+
/** Explicit result for source text that Babel cannot parse. */
|
|
6
|
+
export const failedJavaScriptStaticAnalysis = () => ({
|
|
7
|
+
parse_status: "failed",
|
|
8
|
+
parse_error_count: 1,
|
|
9
|
+
visited_ast_nodes: 0,
|
|
10
|
+
dropped_findings: 0,
|
|
11
|
+
references: [],
|
|
12
|
+
endpoints: [],
|
|
13
|
+
storage: [],
|
|
14
|
+
bundler_registrations: [],
|
|
15
|
+
role_paths: [],
|
|
16
|
+
source_map_urls: [],
|
|
17
|
+
vendors: [],
|
|
18
|
+
electron: {
|
|
19
|
+
browser_windows: [],
|
|
20
|
+
context_bridge_apis: [],
|
|
21
|
+
ipc: [],
|
|
22
|
+
sender_validations: [],
|
|
23
|
+
utility_processes: [],
|
|
24
|
+
native_addon_bindings: [],
|
|
25
|
+
},
|
|
26
|
+
limitations: [
|
|
27
|
+
"JavaScript source could not be parsed; absence of findings is not evidence of absence.",
|
|
28
|
+
"JavaScript syntax was parsed as data and was never evaluated.",
|
|
29
|
+
],
|
|
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;
|
|
33
|
+
/** Recognize the bounded runtime name for a Webpack/Rspack push call. */
|
|
34
|
+
export const chunkRuntime = (call) => {
|
|
35
|
+
if (!t.isMemberExpression(call.callee) &&
|
|
36
|
+
!t.isOptionalMemberExpression(call.callee))
|
|
37
|
+
return undefined;
|
|
38
|
+
if (propertyName(call.callee.property) !== "push")
|
|
39
|
+
return undefined;
|
|
40
|
+
return findChunkRuntime(call.callee.object, 0);
|
|
41
|
+
};
|
|
42
|
+
const findChunkRuntime = (node, depth) => {
|
|
43
|
+
if (depth >= 128)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (t.isIdentifier(node) && /(?:webpack|rspack)Chunk/iu.test(node.name))
|
|
46
|
+
return node.name.slice(0, 4_096);
|
|
47
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
48
|
+
const property = propertyName(node.property);
|
|
49
|
+
if (/(?:webpack|rspack)Chunk/iu.test(property))
|
|
50
|
+
return property.slice(0, 4_096);
|
|
51
|
+
return t.isNode(node.object)
|
|
52
|
+
? findChunkRuntime(node.object, depth + 1)
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
if (t.isAssignmentExpression(node))
|
|
56
|
+
return (findChunkRuntime(node.left, depth + 1) ??
|
|
57
|
+
findChunkRuntime(node.right, depth + 1));
|
|
58
|
+
if (t.isLogicalExpression(node) || t.isBinaryExpression(node))
|
|
59
|
+
return (findChunkRuntime(node.left, depth + 1) ??
|
|
60
|
+
findChunkRuntime(node.right, depth + 1));
|
|
61
|
+
if (t.isParenthesizedExpression(node) || t.isTSAsExpression(node))
|
|
62
|
+
return findChunkRuntime(node.expression, depth + 1);
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|
|
65
|
+
/** Return a literal function factory from one module-table property. */
|
|
66
|
+
export const moduleFactory = (property) => {
|
|
67
|
+
if (t.isObjectMethod(property))
|
|
68
|
+
return property;
|
|
69
|
+
if (t.isObjectProperty(property) &&
|
|
70
|
+
(t.isFunctionExpression(property.value) ||
|
|
71
|
+
t.isArrowFunctionExpression(property.value)))
|
|
72
|
+
return property.value;
|
|
73
|
+
return undefined;
|
|
74
|
+
};
|
|
75
|
+
/** Derive a bounded literal or explicitly computed module key. */
|
|
76
|
+
export const modulePropertyName = (property) => t.isObjectMethod(property) || t.isObjectProperty(property)
|
|
77
|
+
? property.computed &&
|
|
78
|
+
!t.isStringLiteral(property.key) &&
|
|
79
|
+
!t.isNumericLiteral(property.key)
|
|
80
|
+
? `[computed@${String(property.start ?? -1)}]`
|
|
81
|
+
: propertyName(property.key).slice(0, 4_096) ||
|
|
82
|
+
`[unknown@${String(property.start ?? -1)}]`
|
|
83
|
+
: `[unknown@${String(property.start ?? -1)}]`;
|
|
84
|
+
/** Read the factory-local bundler require parameter when declared. */
|
|
85
|
+
export const factoryRequireName = (factory) => {
|
|
86
|
+
const parameter = factory.params[2];
|
|
87
|
+
return t.isIdentifier(parameter) ? parameter.name.slice(0, 4_096) : null;
|
|
88
|
+
};
|
|
89
|
+
/** Collect bounded unique literal values and explicit omissions from an array. */
|
|
90
|
+
export const staticArrayValues = (array, maximum) => {
|
|
91
|
+
const staticValues = array.elements.flatMap((element) => {
|
|
92
|
+
const value = argumentValue(element);
|
|
93
|
+
return value === undefined ? [] : [value.slice(0, 4_096)];
|
|
94
|
+
});
|
|
95
|
+
const values = [...new Set(staticValues)].sort(compareCodePoints);
|
|
96
|
+
return {
|
|
97
|
+
values: values.slice(0, maximum),
|
|
98
|
+
omitted: Math.max(0, values.length - maximum),
|
|
99
|
+
unknown: array.elements.length - staticValues.length,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
/** Resolve a bounded path composed only from inert literal syntax. */
|
|
103
|
+
export const staticPath = (node) => staticPathAt(node, 0);
|
|
104
|
+
const staticPathAt = (node, depth) => {
|
|
105
|
+
if (depth >= 128)
|
|
106
|
+
return undefined;
|
|
107
|
+
if (t.isStringLiteral(node))
|
|
108
|
+
return node.value.slice(0, 4_096);
|
|
109
|
+
if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
|
|
110
|
+
const value = node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
111
|
+
return value?.slice(0, 4_096);
|
|
112
|
+
}
|
|
113
|
+
if (t.isBinaryExpression(node, { operator: "+" })) {
|
|
114
|
+
const left = staticPathAt(node.left, depth + 1);
|
|
115
|
+
const right = staticPathAt(node.right, depth + 1);
|
|
116
|
+
return left === undefined || right === undefined
|
|
117
|
+
? undefined
|
|
118
|
+
: `${left}${right}`.slice(0, 4_096);
|
|
119
|
+
}
|
|
120
|
+
if (t.isCallExpression(node) || t.isNewExpression(node))
|
|
121
|
+
return staticCallPath(node, depth);
|
|
122
|
+
return undefined;
|
|
123
|
+
};
|
|
124
|
+
const staticCallPath = (node, depth) => {
|
|
125
|
+
const name = calleeName(node.callee);
|
|
126
|
+
if (name === "URL" || name.endsWith(".URL"))
|
|
127
|
+
return stringValue(node.arguments[0]);
|
|
128
|
+
if (!name.endsWith(".join") && !name.endsWith(".resolve") && name !== "join")
|
|
129
|
+
return undefined;
|
|
130
|
+
const parts = [];
|
|
131
|
+
for (const argument of node.arguments) {
|
|
132
|
+
if (t.isIdentifier(argument, { name: "__dirname" }))
|
|
133
|
+
continue;
|
|
134
|
+
const value = t.isNode(argument)
|
|
135
|
+
? staticPathAt(argument, depth + 1)
|
|
136
|
+
: undefined;
|
|
137
|
+
if (value === undefined)
|
|
138
|
+
return undefined;
|
|
139
|
+
parts.push(value);
|
|
140
|
+
}
|
|
141
|
+
return parts.length === 0 ? undefined : posix.join(...parts).slice(0, 4_096);
|
|
142
|
+
};
|
|
143
|
+
/** Select the literal URL argument for recognized network callees. */
|
|
144
|
+
export const endpointArgument = (name, args) => {
|
|
145
|
+
if (name === "fetch" || name.endsWith(".fetch") || name === "WebSocket")
|
|
146
|
+
return stringValue(args[0]);
|
|
147
|
+
if (name.endsWith(".open") && stringValue(args[1]) !== undefined)
|
|
148
|
+
return stringValue(args[1]);
|
|
149
|
+
if (["get", "post", "put", "patch", "delete", "request"].some((method) => name === method || name.endsWith(`.${method}`)))
|
|
150
|
+
return stringValue(args[0]);
|
|
151
|
+
if (name.endsWith("loadURL"))
|
|
152
|
+
return stringValue(args[0]);
|
|
153
|
+
return undefined;
|
|
154
|
+
};
|
|
155
|
+
/** Classify a recognized storage API call name. */
|
|
156
|
+
export const storageKind = (name) => {
|
|
157
|
+
if (name.includes("localStorage."))
|
|
158
|
+
return "local-storage";
|
|
159
|
+
if (name.includes("sessionStorage."))
|
|
160
|
+
return "session-storage";
|
|
161
|
+
if (name.endsWith("indexedDB.open"))
|
|
162
|
+
return "indexed-db";
|
|
163
|
+
if (name.endsWith("caches.open"))
|
|
164
|
+
return "cache-storage";
|
|
165
|
+
if (name === "Database" || name.endsWith(".Database"))
|
|
166
|
+
return "sqlite";
|
|
167
|
+
return undefined;
|
|
168
|
+
};
|
|
169
|
+
/** Produce a dotted bounded callee name from member syntax. */
|
|
170
|
+
export const calleeName = (node) => calleeNameAt(node, 0);
|
|
171
|
+
const calleeNameAt = (node, depth) => {
|
|
172
|
+
if (depth >= 128)
|
|
173
|
+
return "[deep]";
|
|
174
|
+
if (t.isIdentifier(node))
|
|
175
|
+
return node.name.slice(0, 4_096);
|
|
176
|
+
if (t.isImport(node))
|
|
177
|
+
return "import";
|
|
178
|
+
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
179
|
+
const object = t.isNode(node.object)
|
|
180
|
+
? calleeNameAt(node.object, depth + 1)
|
|
181
|
+
: "";
|
|
182
|
+
const property = propertyName(node.property);
|
|
183
|
+
return (object === "" ? property : `${object}.${property}`).slice(0, 4_096);
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
};
|
|
187
|
+
/** Read a literal property name without evaluating computed syntax. */
|
|
188
|
+
export const propertyName = (node) => {
|
|
189
|
+
if (t.isIdentifier(node))
|
|
190
|
+
return node.name.slice(0, 4_096);
|
|
191
|
+
if (t.isStringLiteral(node) || t.isNumericLiteral(node))
|
|
192
|
+
return String(node.value).slice(0, 4_096);
|
|
193
|
+
return "";
|
|
194
|
+
};
|
|
195
|
+
/** Return a string literal value without evaluating an expression. */
|
|
196
|
+
export const stringValue = (node) => t.isStringLiteral(node) ? node.value.slice(0, 4_096) : undefined;
|
|
197
|
+
/** Return a string or numeric argument literal. */
|
|
198
|
+
export const argumentValue = (node) => {
|
|
199
|
+
if (t.isStringLiteral(node) || t.isNumericLiteral(node))
|
|
200
|
+
return String(node.value).slice(0, 4_096);
|
|
201
|
+
return undefined;
|
|
202
|
+
};
|
|
203
|
+
/** Prefix a static argument value when one is available. */
|
|
204
|
+
export const prefixedArgument = (prefix, node) => {
|
|
205
|
+
const value = argumentValue(node);
|
|
206
|
+
return value === undefined ? undefined : `${prefix}${value}`;
|
|
207
|
+
};
|
|
208
|
+
/** Retain the exact bounded source span represented by an AST node. */
|
|
209
|
+
export const sourceSlice = (source, node) => typeof node.start !== "number" || typeof node.end !== "number"
|
|
210
|
+
? ""
|
|
211
|
+
: source.slice(node.start, node.end);
|
|
212
|
+
/** Convert Babel locations into the stable graph source-range shape. */
|
|
213
|
+
export const range = (node) => ({
|
|
214
|
+
start: {
|
|
215
|
+
line: node.loc?.start.line ?? 1,
|
|
216
|
+
column: node.loc?.start.column ?? 0,
|
|
217
|
+
},
|
|
218
|
+
end: {
|
|
219
|
+
line: node.loc?.end.line ?? node.loc?.start.line ?? 1,
|
|
220
|
+
column: node.loc?.end.column ?? node.loc?.start.column ?? 0,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
/** Convert exact text offsets into a source range. */
|
|
224
|
+
export const rangeForOffsets = (source, start, end) => ({
|
|
225
|
+
start: pointForOffset(source, start),
|
|
226
|
+
end: pointForOffset(source, end),
|
|
227
|
+
});
|
|
228
|
+
const pointForOffset = (source, offset) => {
|
|
229
|
+
const before = source.slice(0, offset);
|
|
230
|
+
const lines = before.split("\n");
|
|
231
|
+
return { line: lines.length, column: lines.at(-1)?.length ?? 0 };
|
|
232
|
+
};
|
|
233
|
+
/** Observe known bundler/framework marker strings without claiming dependency use. */
|
|
234
|
+
export const detectVendors = (source) => vendorPatterns.flatMap(({ name, patterns }) => patterns.some((pattern) => source.includes(pattern)) ? [name] : []);
|
|
235
|
+
/** Remove credentials, fragments, and query values from endpoint candidates. */
|
|
236
|
+
export const sanitizeCandidate = (value) => {
|
|
237
|
+
const bounded = value.slice(0, 4_096);
|
|
238
|
+
try {
|
|
239
|
+
const parsed = new URL(bounded, "https://rea.invalid");
|
|
240
|
+
const sanitized = sanitizeBrowserUrl(parsed.href).url;
|
|
241
|
+
return parsed.origin === "https://rea.invalid"
|
|
242
|
+
? sanitized.replace("https://rea.invalid", "")
|
|
243
|
+
: sanitized;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return bounded.split("#", 1)[0]?.split("?", 1)[0] ?? "";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
/** Commit the static semantic content used to deduplicate registrations. */
|
|
250
|
+
export const registrationKey = (registration) => `${registration.runtime}\0${registration.chunk_keys.join("\0")}\0${String(registration.omitted_chunk_keys)}\0${String(registration.unknown_chunk_keys)}\0${registration.modules.map(({ module_key: key, source_sha256: digest }) => `${key}:${digest}`).join("\0")}`;
|
|
251
|
+
/** Sort values by a deterministic unique semantic key. */
|
|
252
|
+
export const sortedUnique = (values, key) => [...new Map(values.map((value) => [key(value), value])).values()].sort((left, right) => compareCodePoints(key(left), key(right)));
|
|
253
|
+
/** Hash exact UTF-8 source text. */
|
|
254
|
+
export const sha256Text = (value) => createHash("sha256").update(value).digest("hex");
|
|
255
|
+
/** Compare strings by Unicode code point for canonical ordering. */
|
|
256
|
+
export const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
257
|
+
/** Recognized route-construction call suffixes. */
|
|
258
|
+
export const STATIC_ROUTE_CALL_NAMES = [
|
|
259
|
+
"route",
|
|
260
|
+
"addRoute",
|
|
261
|
+
"useRoutes",
|
|
262
|
+
"createBrowserRouter",
|
|
263
|
+
"createHashRouter",
|
|
264
|
+
];
|
|
265
|
+
const vendorPatterns = [
|
|
266
|
+
{ name: "webpack", patterns: ["__webpack_require__", "webpackChunk"] },
|
|
267
|
+
{ name: "Rspack", patterns: ["__webpack_require__.f", "rspackChunk"] },
|
|
268
|
+
{ name: "Vite", patterns: ["__vite__", "import.meta.hot"] },
|
|
269
|
+
{ name: "React", patterns: ["React.createElement", "react/jsx-runtime"] },
|
|
270
|
+
{ name: "Vue", patterns: ["__VUE__", "createApp("] },
|
|
271
|
+
{ name: "Next.js", patterns: ["__NEXT_DATA__", "/_next/"] },
|
|
272
|
+
{ name: "Angular", patterns: ["ɵɵdefineComponent"] },
|
|
273
|
+
{ name: "Svelte", patterns: ["svelte/internal"] },
|
|
274
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Create isolated mutable state for one bounded source analysis. */
|
|
2
|
+
export const createJavaScriptAnalysisAccumulator = () => ({
|
|
3
|
+
references: [],
|
|
4
|
+
endpoints: [],
|
|
5
|
+
storage: [],
|
|
6
|
+
roles: [],
|
|
7
|
+
sourceMaps: [],
|
|
8
|
+
registrations: [],
|
|
9
|
+
browserWindows: [],
|
|
10
|
+
contextBridgeApis: [],
|
|
11
|
+
ipc: [],
|
|
12
|
+
senderValidations: [],
|
|
13
|
+
utilityProcesses: [],
|
|
14
|
+
nativeAddonBindings: [],
|
|
15
|
+
modules: [],
|
|
16
|
+
seen: new Set(),
|
|
17
|
+
visitedNodes: 0,
|
|
18
|
+
droppedFindings: 0,
|
|
19
|
+
moduleCount: 0,
|
|
20
|
+
unknownFindings: 0,
|
|
21
|
+
structuralTruncation: false,
|
|
22
|
+
truncated: false,
|
|
23
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|