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,231 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { AsarArtifactReader } from "../artifacts/AsarArtifactReader.js";
|
|
4
|
+
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../artifacts/ArtifactPaths.js";
|
|
5
|
+
import { ArtifactReaderFailure, } from "../artifacts/ArtifactReader.js";
|
|
6
|
+
import { streamChunkToBuffer } from "../artifacts/StreamBytes.js";
|
|
7
|
+
/** Read only selected textual entries through an already-inventoried reader. */
|
|
8
|
+
export const readJavaScriptArtifactFiles = async (reader, snapshot, input, signal) => {
|
|
9
|
+
const inventory = expectedInventory(snapshot);
|
|
10
|
+
const expected = inventory.files;
|
|
11
|
+
const selections = selectTextFiles(expected, input);
|
|
12
|
+
const context = {
|
|
13
|
+
input,
|
|
14
|
+
expected,
|
|
15
|
+
expectedContainers: inventory.containers,
|
|
16
|
+
selections,
|
|
17
|
+
registry: new ArtifactPathRegistry(),
|
|
18
|
+
files: [],
|
|
19
|
+
containers: [],
|
|
20
|
+
signal,
|
|
21
|
+
textBytes: 0,
|
|
22
|
+
invalidUtf8: 0,
|
|
23
|
+
};
|
|
24
|
+
await visitReader(reader, "", snapshot.manifest.root_sha256, context);
|
|
25
|
+
const files = context.files.sort((left, right) => compareCodePoints(left.path, right.path));
|
|
26
|
+
assertExpectedFilesWereVisited(expected, files);
|
|
27
|
+
const selected = [...selections.values()].filter(({ selected: isSelected }) => isSelected).length;
|
|
28
|
+
return {
|
|
29
|
+
files,
|
|
30
|
+
containers: context.containers.sort((left, right) => compareCodePoints(left.path, right.path)),
|
|
31
|
+
text_files_selected: selected,
|
|
32
|
+
text_bytes_read: context.textBytes,
|
|
33
|
+
omitted_text_files: [...selections.values()].filter(({ selected: isSelected }) => !isSelected).length,
|
|
34
|
+
limit_omitted_text_files: [...selections.values()].filter(({ reason }) => reason === "file-limit" || reason === "byte-limit").length,
|
|
35
|
+
policy_filtered_text_files: [...selections.values()].filter(({ reason }) => reason === "not-approved").length,
|
|
36
|
+
invalid_utf8_files: context.invalidUtf8,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const visitReader = async (reader, prefix, containerSha256, context) => {
|
|
40
|
+
for await (const entry of reader.entries(context.signal)) {
|
|
41
|
+
abortIfNeeded(context.signal);
|
|
42
|
+
const path = normalizeArtifactPath(prefix === "" ? entry.path : `${prefix}/${entry.path}`, {
|
|
43
|
+
maxDepth: context.input.limits.max_depth,
|
|
44
|
+
maxPathBytes: context.input.limits.max_path_bytes,
|
|
45
|
+
});
|
|
46
|
+
const nestedAsar = isFilesystemAsar(entry, path);
|
|
47
|
+
context.registry.add(path, nestedAsar ? "directory" : entry.kind);
|
|
48
|
+
if (nestedAsar) {
|
|
49
|
+
await visitNestedAsar(entry, path, context);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const expected = context.expected.get(path);
|
|
53
|
+
if (expected === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
const selection = context.selections.get(path);
|
|
56
|
+
const text = await readTextIfSelected(context, {
|
|
57
|
+
reader,
|
|
58
|
+
entry,
|
|
59
|
+
expected,
|
|
60
|
+
selection,
|
|
61
|
+
});
|
|
62
|
+
context.files.push({
|
|
63
|
+
path,
|
|
64
|
+
container_sha256: containerSha256,
|
|
65
|
+
sha256: expected.sha256,
|
|
66
|
+
bytes: expected.bytes,
|
|
67
|
+
inventory_artifact_id: expected.inventoryArtifactId,
|
|
68
|
+
kind: expected.kind,
|
|
69
|
+
unpacked: entry.unpacked,
|
|
70
|
+
text,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const visitNestedAsar = async (entry, path, context) => {
|
|
75
|
+
const inventory = expectedContainer(path, context);
|
|
76
|
+
context.containers.push(inventory);
|
|
77
|
+
const nested = new AsarArtifactReader(entry.adapterKey);
|
|
78
|
+
try {
|
|
79
|
+
await visitReader(nested, path, inventory.sha256, context);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await nested.close();
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const expectedContainer = (path, context) => {
|
|
86
|
+
const container = context.expectedContainers.get(path);
|
|
87
|
+
if (container === undefined)
|
|
88
|
+
throw new ArtifactReaderFailure("integrity", `Nested ASAR disappeared from inventory: ${path}`);
|
|
89
|
+
return container;
|
|
90
|
+
};
|
|
91
|
+
const readTextIfSelected = async (context, input) => {
|
|
92
|
+
if (input.expected.kind === "native-addon")
|
|
93
|
+
return { included: false, reason: "not-applicable" };
|
|
94
|
+
if (input.selection?.selected !== true)
|
|
95
|
+
return {
|
|
96
|
+
included: false,
|
|
97
|
+
reason: input.selection?.reason ?? "file-limit",
|
|
98
|
+
};
|
|
99
|
+
const bytes = await readBounded(await input.reader.open(input.entry, context.signal), context.input.limits.max_text_file_bytes, context.signal);
|
|
100
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
101
|
+
if (digest !== input.expected.sha256 || bytes.length !== input.expected.bytes)
|
|
102
|
+
throw new ArtifactReaderFailure("integrity", `Artifact entry changed after inventory: ${input.expected.path}`);
|
|
103
|
+
context.textBytes += bytes.length;
|
|
104
|
+
try {
|
|
105
|
+
return {
|
|
106
|
+
included: true,
|
|
107
|
+
value: new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
context.invalidUtf8 += 1;
|
|
112
|
+
return { included: false, reason: "invalid-utf8" };
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const expectedInventory = (snapshot) => {
|
|
116
|
+
const nodes = new Map(snapshot.nodes.map((node) => [node.artifact_id, node]));
|
|
117
|
+
const files = new Map();
|
|
118
|
+
const containers = new Map();
|
|
119
|
+
for (const occurrence of snapshot.occurrences) {
|
|
120
|
+
if (occurrence.artifact_id === null || occurrence.logical_path === ".")
|
|
121
|
+
continue;
|
|
122
|
+
const node = nodes.get(occurrence.artifact_id);
|
|
123
|
+
if (node === undefined)
|
|
124
|
+
throw new ArtifactReaderFailure("integrity", `Artifact inventory node is missing: ${occurrence.logical_path}`);
|
|
125
|
+
if (occurrence.logical_path.toLowerCase().endsWith(".asar"))
|
|
126
|
+
containers.set(occurrence.logical_path, {
|
|
127
|
+
path: occurrence.logical_path,
|
|
128
|
+
sha256: node.sha256,
|
|
129
|
+
bytes: node.size,
|
|
130
|
+
inventory_artifact_id: node.artifact_id,
|
|
131
|
+
});
|
|
132
|
+
const kind = relevantKind(occurrence.logical_path);
|
|
133
|
+
if (kind === undefined)
|
|
134
|
+
continue;
|
|
135
|
+
files.set(occurrence.logical_path, {
|
|
136
|
+
path: occurrence.logical_path,
|
|
137
|
+
sha256: node.sha256,
|
|
138
|
+
bytes: node.size,
|
|
139
|
+
inventoryArtifactId: node.artifact_id,
|
|
140
|
+
kind,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return { files, containers };
|
|
144
|
+
};
|
|
145
|
+
const selectTextFiles = (files, input) => {
|
|
146
|
+
const selected = new Map();
|
|
147
|
+
let fileCount = 0;
|
|
148
|
+
let bytes = 0;
|
|
149
|
+
const ordered = [...files.values()].sort((left, right) => {
|
|
150
|
+
const priority = filePriority(left.kind) - filePriority(right.kind);
|
|
151
|
+
return priority === 0 ? compareCodePoints(left.path, right.path) : priority;
|
|
152
|
+
});
|
|
153
|
+
for (const file of ordered) {
|
|
154
|
+
if (file.kind === "native-addon")
|
|
155
|
+
continue;
|
|
156
|
+
if (file.kind === "source-map" && !input.source_map_read_approved) {
|
|
157
|
+
selected.set(file.path, { selected: false, reason: "not-approved" });
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (fileCount >= input.limits.max_text_files ||
|
|
161
|
+
file.bytes > input.limits.max_text_file_bytes) {
|
|
162
|
+
selected.set(file.path, { selected: false, reason: "file-limit" });
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (bytes + file.bytes > input.limits.max_total_text_bytes) {
|
|
166
|
+
selected.set(file.path, { selected: false, reason: "byte-limit" });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
selected.set(file.path, { selected: true, reason: null });
|
|
170
|
+
fileCount += 1;
|
|
171
|
+
bytes += file.bytes;
|
|
172
|
+
}
|
|
173
|
+
return selected;
|
|
174
|
+
};
|
|
175
|
+
const readBounded = async (stream, maximum, signal) => {
|
|
176
|
+
const chunks = [];
|
|
177
|
+
let bytes = 0;
|
|
178
|
+
for await (const raw of stream) {
|
|
179
|
+
abortIfNeeded(signal);
|
|
180
|
+
const chunk = streamChunkToBuffer(raw);
|
|
181
|
+
bytes += chunk.length;
|
|
182
|
+
if (bytes > maximum) {
|
|
183
|
+
stream.destroy();
|
|
184
|
+
throw new ArtifactReaderFailure("limit", "Selected JavaScript artifact text exceeded its byte limit");
|
|
185
|
+
}
|
|
186
|
+
chunks.push(chunk);
|
|
187
|
+
}
|
|
188
|
+
return Buffer.concat(chunks, bytes);
|
|
189
|
+
};
|
|
190
|
+
const assertExpectedFilesWereVisited = (expected, actual) => {
|
|
191
|
+
const observed = new Set(actual.map(({ path }) => path));
|
|
192
|
+
for (const path of expected.keys())
|
|
193
|
+
if (!observed.has(path))
|
|
194
|
+
throw new ArtifactReaderFailure("integrity", `Relevant artifact entry disappeared after inventory: ${path}`);
|
|
195
|
+
};
|
|
196
|
+
const relevantKind = (path) => {
|
|
197
|
+
const lower = path.toLowerCase();
|
|
198
|
+
if (lower === "package.json" || lower.endsWith("/package.json"))
|
|
199
|
+
return "package-json";
|
|
200
|
+
if (/\.(?:cjs|mjs|js|jsx|ts|tsx)$/u.test(lower))
|
|
201
|
+
return "javascript";
|
|
202
|
+
if (/\.html?$/u.test(lower))
|
|
203
|
+
return "html";
|
|
204
|
+
if (lower.endsWith(".map"))
|
|
205
|
+
return "source-map";
|
|
206
|
+
if (lower.endsWith(".node"))
|
|
207
|
+
return "native-addon";
|
|
208
|
+
return undefined;
|
|
209
|
+
};
|
|
210
|
+
const filePriority = (kind) => {
|
|
211
|
+
switch (kind) {
|
|
212
|
+
case "package-json":
|
|
213
|
+
return 0;
|
|
214
|
+
case "html":
|
|
215
|
+
return 1;
|
|
216
|
+
case "javascript":
|
|
217
|
+
return 2;
|
|
218
|
+
case "source-map":
|
|
219
|
+
return 3;
|
|
220
|
+
case "native-addon":
|
|
221
|
+
return 4;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const isFilesystemAsar = (entry, path) => entry.kind === "file" &&
|
|
225
|
+
path.toLowerCase().endsWith(".asar") &&
|
|
226
|
+
isAbsolute(entry.adapterKey);
|
|
227
|
+
const abortIfNeeded = (signal) => {
|
|
228
|
+
if (signal?.aborted === true)
|
|
229
|
+
throw new ArtifactReaderFailure("cancelled", "JavaScript artifact reconstruction cancelled");
|
|
230
|
+
};
|
|
231
|
+
const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createJavaScriptApplicationEdge, createJavaScriptApplicationNode, } from "../domain/javascriptApplicationGraph.js";
|
|
2
|
+
/** Deduplicate graph entities while retaining distinct evidence observations. */
|
|
3
|
+
export class JavaScriptArtifactGraphAccumulator {
|
|
4
|
+
#nodes = new Map();
|
|
5
|
+
#edges = new Map();
|
|
6
|
+
#omittedObservations = 0;
|
|
7
|
+
/** Create or merge one canonical node. */
|
|
8
|
+
addNode(input) {
|
|
9
|
+
const created = createJavaScriptApplicationNode(input);
|
|
10
|
+
const existing = this.#nodes.get(created.node_id);
|
|
11
|
+
if (existing === undefined) {
|
|
12
|
+
this.#nodes.set(created.node_id, created);
|
|
13
|
+
return created;
|
|
14
|
+
}
|
|
15
|
+
const bySemanticId = new Map([...existing.observations, ...created.observations].map((observation) => [
|
|
16
|
+
observation.observation_id,
|
|
17
|
+
observationInput(observation),
|
|
18
|
+
]));
|
|
19
|
+
const observations = [...bySemanticId.entries()]
|
|
20
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
21
|
+
.slice(0, 64)
|
|
22
|
+
.map(([, observation]) => observation);
|
|
23
|
+
this.#omittedObservations += Math.max(0, bySemanticId.size - 64);
|
|
24
|
+
const merged = createJavaScriptApplicationNode({
|
|
25
|
+
kind: existing.kind,
|
|
26
|
+
identity: existing.identity,
|
|
27
|
+
observations,
|
|
28
|
+
});
|
|
29
|
+
this.#nodes.set(merged.node_id, merged);
|
|
30
|
+
return merged;
|
|
31
|
+
}
|
|
32
|
+
/** Create or replace one canonical edge by semantic identifier. */
|
|
33
|
+
addEdge(input) {
|
|
34
|
+
const edge = createJavaScriptApplicationEdge(input);
|
|
35
|
+
this.#edges.set(edge.edge_id, edge);
|
|
36
|
+
return edge;
|
|
37
|
+
}
|
|
38
|
+
/** Return all accumulated canonical nodes. */
|
|
39
|
+
nodes() {
|
|
40
|
+
return [...this.#nodes.values()];
|
|
41
|
+
}
|
|
42
|
+
/** Return all accumulated canonical edges. */
|
|
43
|
+
edges() {
|
|
44
|
+
return [...this.#edges.values()];
|
|
45
|
+
}
|
|
46
|
+
/** Count distinct node observations omitted by the graph contract bound. */
|
|
47
|
+
omittedObservations() {
|
|
48
|
+
return this.#omittedObservations;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const observationInput = (observation) => {
|
|
52
|
+
const { observation_id: _observationId, identifier_strategy: _strategy, ...input } = observation;
|
|
53
|
+
return input;
|
|
54
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createJavaScriptApplicationGraph, } from "../domain/javascriptApplicationGraph.js";
|
|
2
|
+
import { JavaScriptArtifactGraphAccumulator } from "./JavaScriptArtifactGraphAccumulator.js";
|
|
3
|
+
import { javaScriptAnalysisLimits, } from "./JavaScriptArtifactGraphContext.js";
|
|
4
|
+
import { addJavaScriptHtmlRoles, addJavaScriptSourceMapOriginals, } from "./JavaScriptArtifactGraphDocuments.js";
|
|
5
|
+
import { addJavaScriptStaticFindings } from "./JavaScriptArtifactGraphFindings.js";
|
|
6
|
+
import { completeReconstructionCoverage, partialReconstructionCoverage, } from "./JavaScriptArtifactGraphEvidence.js";
|
|
7
|
+
import { addJavaScriptArtifactContainers, addJavaScriptArtifactFiles, addJavaScriptBundlerNodes, addJavaScriptPackageNodes, createJavaScriptArtifactRootNode, } from "./JavaScriptArtifactGraphStructure.js";
|
|
8
|
+
import { addElectronBoundaries } from "./ElectronBoundaryGraph.js";
|
|
9
|
+
import { classifyElectronIpcPairings, collectElectronIpcRecords, } from "./ElectronBoundaryAnalysis.js";
|
|
10
|
+
/** Project bounded artifact and AST facts into JavaScript Application Graph v1. */
|
|
11
|
+
export const buildJavaScriptArtifactGraph = (snapshot, fileSet, analysis, input) => {
|
|
12
|
+
const accumulator = new JavaScriptArtifactGraphAccumulator();
|
|
13
|
+
const root = createJavaScriptArtifactRootNode(accumulator, snapshot);
|
|
14
|
+
const context = {
|
|
15
|
+
accumulator,
|
|
16
|
+
snapshot,
|
|
17
|
+
fileSet,
|
|
18
|
+
analysis,
|
|
19
|
+
input,
|
|
20
|
+
root,
|
|
21
|
+
filesByPath: new Map(fileSet.files.map((file) => [file.path, file])),
|
|
22
|
+
fileNodes: new Map(),
|
|
23
|
+
assetNodes: new Map(),
|
|
24
|
+
moduleNodes: new Map(),
|
|
25
|
+
containerNodes: new Map([[snapshot.manifest.root_sha256, root]]),
|
|
26
|
+
};
|
|
27
|
+
addJavaScriptArtifactContainers(context);
|
|
28
|
+
addJavaScriptArtifactFiles(context);
|
|
29
|
+
const packageRoots = addJavaScriptPackageNodes(context);
|
|
30
|
+
addJavaScriptBundlerNodes(context);
|
|
31
|
+
addJavaScriptStaticFindings(context);
|
|
32
|
+
addElectronBoundaries(context);
|
|
33
|
+
addJavaScriptHtmlRoles(context);
|
|
34
|
+
addJavaScriptSourceMapOriginals(context);
|
|
35
|
+
const coverage = graphCoverage(context);
|
|
36
|
+
return createJavaScriptApplicationGraph({
|
|
37
|
+
schema: "JavaScriptApplicationGraph",
|
|
38
|
+
schema_version: 1,
|
|
39
|
+
root_node_ids: packageRoots.length === 0
|
|
40
|
+
? [root.node_id]
|
|
41
|
+
: packageRoots.map(({ node_id: id }) => id),
|
|
42
|
+
nodes: accumulator.nodes(),
|
|
43
|
+
edges: accumulator.edges(),
|
|
44
|
+
coverage,
|
|
45
|
+
limitations: graphLimitations(context, coverage.status),
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const graphCoverage = (context) => {
|
|
49
|
+
const exactLimitOmissions = context.fileSet.limit_omitted_text_files +
|
|
50
|
+
context.accumulator.omittedObservations();
|
|
51
|
+
const sourceMapPolicyGap = context.analysis.source_maps.some(({ status }) => status === "not-approved");
|
|
52
|
+
const malformedStructuredData = context.analysis.packages.some(({ status }) => status !== "included") ||
|
|
53
|
+
context.analysis.source_maps.some(({ status }) => status === "invalid");
|
|
54
|
+
const partialJavaScript = context.analysis.files.some(({ javascript }) => javascript !== null && javascript.parse_status === "partial");
|
|
55
|
+
const unknownGap = context.analysis.parse_failures > 0 ||
|
|
56
|
+
context.fileSet.invalid_utf8_files > 0 ||
|
|
57
|
+
sourceMapPolicyGap ||
|
|
58
|
+
malformedStructuredData ||
|
|
59
|
+
partialJavaScript;
|
|
60
|
+
if (context.analysis.truncated_scopes > 0)
|
|
61
|
+
return partialReconstructionCoverage(reconstructionLimits(context.input), null, true);
|
|
62
|
+
if (exactLimitOmissions > 0)
|
|
63
|
+
return partialReconstructionCoverage(reconstructionLimits(context.input), exactLimitOmissions, true);
|
|
64
|
+
if (unknownGap)
|
|
65
|
+
return partialReconstructionCoverage(reconstructionLimits(context.input), null, false);
|
|
66
|
+
return completeReconstructionCoverage(reconstructionLimits(context.input));
|
|
67
|
+
};
|
|
68
|
+
const graphLimitations = (context, coverage) => {
|
|
69
|
+
const ipc = collectElectronIpcRecords(context.analysis);
|
|
70
|
+
const pairings = classifyElectronIpcPairings(ipc);
|
|
71
|
+
const electronFindings = context.analysis.files.reduce((count, { javascript }) => count +
|
|
72
|
+
(javascript === null
|
|
73
|
+
? 0
|
|
74
|
+
: javascript.electron.browser_windows.length +
|
|
75
|
+
javascript.electron.context_bridge_apis.length +
|
|
76
|
+
javascript.electron.ipc.length +
|
|
77
|
+
javascript.electron.sender_validations.length +
|
|
78
|
+
javascript.electron.utility_processes.length +
|
|
79
|
+
javascript.electron.native_addon_bindings.length), 0);
|
|
80
|
+
return [
|
|
81
|
+
...context.analysis.limitations,
|
|
82
|
+
"Webpack/Rspack factories were recovered from AST literals; REA did not invoke push handlers or bundle bootstrap code.",
|
|
83
|
+
"Static imports, entrypoints, workers, endpoints, and storage relationships do not prove runtime execution.",
|
|
84
|
+
...(electronFindings === 0
|
|
85
|
+
? []
|
|
86
|
+
: [
|
|
87
|
+
"Electron relationships are derived from inert syntax; runtime registration, reachability, defaults, and enforcement remain unproven.",
|
|
88
|
+
]),
|
|
89
|
+
...(ipc.some(({ finding }) => finding.channel === null)
|
|
90
|
+
? [
|
|
91
|
+
"Dynamic IPC channel expressions remain unresolved and are never paired by textual similarity.",
|
|
92
|
+
]
|
|
93
|
+
: []),
|
|
94
|
+
...(pairings.some(({ status }) => status === "ambiguous")
|
|
95
|
+
? [
|
|
96
|
+
"Some literal IPC channels have multiple compatible main handlers; ambiguous calls are not paired to any handler.",
|
|
97
|
+
]
|
|
98
|
+
: []),
|
|
99
|
+
...(context.analysis.files.some(({ javascript }) => (javascript?.electron.sender_validations.length ?? 0) > 0)
|
|
100
|
+
? [
|
|
101
|
+
"Sender, frame, URL, and origin checks are observations only; REA does not claim that they enforce a complete authorization policy.",
|
|
102
|
+
]
|
|
103
|
+
: []),
|
|
104
|
+
...(context.analysis.files.some(({ javascript }) => (javascript?.electron.native_addon_bindings.length ?? 0) > 0)
|
|
105
|
+
? [
|
|
106
|
+
"Native member names are requested by JavaScript syntax and are not verified binary exports in this workflow.",
|
|
107
|
+
]
|
|
108
|
+
: []),
|
|
109
|
+
...(context.accumulator.omittedObservations() === 0
|
|
110
|
+
? []
|
|
111
|
+
: [
|
|
112
|
+
"Repeated content identities exceeded the per-node observation bound; containment edges still preserve inventoried paths.",
|
|
113
|
+
]),
|
|
114
|
+
...(coverage === "complete"
|
|
115
|
+
? []
|
|
116
|
+
: [
|
|
117
|
+
"Graph coverage is incomplete; unavailable facts remain explicit and must not be read as absence.",
|
|
118
|
+
]),
|
|
119
|
+
];
|
|
120
|
+
};
|
|
121
|
+
const reconstructionLimits = (input) => [
|
|
122
|
+
{
|
|
123
|
+
name: "max-entries",
|
|
124
|
+
value: input.limits.max_entries,
|
|
125
|
+
unit: "items",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "max-total-artifact-bytes",
|
|
129
|
+
value: input.limits.max_total_artifact_bytes,
|
|
130
|
+
unit: "bytes",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "max-text-files",
|
|
134
|
+
value: input.limits.max_text_files,
|
|
135
|
+
unit: "items",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "max-total-text-bytes",
|
|
139
|
+
value: input.limits.max_total_text_bytes,
|
|
140
|
+
unit: "bytes",
|
|
141
|
+
},
|
|
142
|
+
...javaScriptAnalysisLimits(input),
|
|
143
|
+
{ name: "max-observations-per-node", value: 64, unit: "items" },
|
|
144
|
+
];
|
|
@@ -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
|
+
];
|