rea-agents 2.0.0 → 2.2.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 +103 -38
- package/bridge/ghidra/ReaGhidraBridge.java +96 -11
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ArtifactExtraction.js +1 -1
- package/dist/application/ArtifactInventory/classify.js +42 -0
- package/dist/application/ArtifactInventory/hash.js +43 -0
- package/dist/application/ArtifactInventory/reader.js +41 -0
- package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
- package/dist/application/ArtifactInventory/scanReader.js +124 -0
- package/dist/application/ArtifactInventory/types.js +10 -0
- package/dist/application/ArtifactInventory.js +9 -351
- package/dist/application/AuthorizedArtifactInventory.js +2 -2
- package/dist/application/BinarySession.js +47 -270
- package/dist/application/BinarySessionExecution.js +28 -0
- package/dist/application/BinarySessionOpen.js +39 -0
- package/dist/application/BinarySessionRecords.js +149 -0
- package/dist/application/BinarySessionStatus.js +75 -0
- package/dist/application/CapabilityInventory.js +24 -2
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CrossVersionInventory.js +8 -2
- package/dist/application/CrossVersionInvestigation.js +1 -23
- package/dist/application/CrossVersionInvestigationCollections.js +23 -0
- package/dist/application/DirectAnalysis.js +47 -44
- package/dist/application/Doctor.js +156 -238
- package/dist/application/DoctorDiagnostics.js +180 -0
- package/dist/application/ElectronBoundaryAnalysis.js +23 -7
- package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
- package/dist/application/EnhancedToolPagination.js +22 -0
- package/dist/application/EnhancedToolTypes.js +4 -0
- package/dist/application/EnhancedTools.js +20 -37
- package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/InvestigationWorkspaceStore.js +24 -10
- package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
- package/dist/application/JavaScriptArtifactFiles.js +6 -2
- package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
- package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
- package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
- package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
- package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
- package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
- package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
- package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
- package/dist/application/JavaScriptJsonModules.js +39 -0
- package/dist/application/JavaScriptModuleRelationships.js +262 -0
- package/dist/application/JavaScriptReplayPermission.js +16 -0
- package/dist/application/JavaScriptReplayService.js +182 -156
- package/dist/application/LinuxHopper.js +10 -0
- package/dist/application/MacHopper.js +22 -11
- package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
- package/dist/application/PermissionAuthority.js +106 -63
- package/dist/application/ProcessCaptureCapability.js +1 -1
- package/dist/application/ProcessCapturePermission.js +13 -0
- package/dist/application/ProcessCli.js +2 -12
- package/dist/application/ProcessHarness.js +1 -1
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProjectPermissionStore.js +48 -9
- package/dist/application/RealHopperAssertions.js +76 -0
- package/dist/application/ReconstructionCoverageService.js +69 -0
- package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
- package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
- package/dist/application/Setup.js +148 -260
- package/dist/application/SetupClientConfiguration.js +224 -0
- package/dist/application/SetupClients.js +24 -2
- package/dist/application/SetupPlan.js +124 -12
- package/dist/application/SetupSkill.js +54 -36
- package/dist/application/SupportedClients.js +1 -0
- package/dist/application/Uninstall.js +12 -3
- package/dist/application/Upgrade.js +16 -1
- package/dist/application/WindowsCapabilities.js +86 -0
- package/dist/artifacts/AsarArtifactReader.js +11 -0
- package/dist/browser/BrowserProcessStartup.js +88 -0
- package/dist/browser/CdpCaptureDocuments.js +12 -8
- package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
- package/dist/browser/CdpCaptureEventHandlers.js +336 -0
- package/dist/browser/CdpCaptureEventHelpers.js +71 -0
- package/dist/browser/CdpCaptureEventState.js +1 -0
- package/dist/browser/CdpCaptureEventTypes.js +1 -0
- package/dist/browser/CdpCaptureEvents.js +29 -496
- package/dist/browser/CdpObservationSession.js +51 -8
- package/dist/browser/CdpPageCapture.js +12 -133
- package/dist/browser/CdpPageCaptureScripts.js +97 -0
- package/dist/browser/CdpPageCaptureWorkers.js +46 -0
- package/dist/browser/CdpWebMcpDiscovery.js +38 -14
- package/dist/browser/PngVisualDiff.js +9 -2
- package/dist/catalogIdentity.js +4 -0
- package/dist/cli/artifactCommands.js +82 -0
- package/dist/cli/coreAnalysisCommands.js +232 -0
- package/dist/cli/managedCommands.js +397 -0
- package/dist/cli/managedProjectGraphCommand.js +24 -0
- package/dist/cli/options.js +9 -0
- package/dist/cli/setupCommands.js +113 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utilityCommands.js +122 -0
- package/dist/cli.js +10 -768
- package/dist/cliApplicationCommands.js +118 -4
- package/dist/cliBrowserAdvancedCommands.js +73 -16
- package/dist/cliBrowserCommands.js +39 -115
- package/dist/cliCommandNames.js +4 -0
- package/dist/cliElectronCommands.js +33 -62
- package/dist/cliEvidenceCommands.js +13 -2
- package/dist/cliInvestigationCommands.js +53 -10
- package/dist/cliObservationOptions.js +137 -0
- package/dist/cliPolicyCommands.js +80 -46
- package/dist/cliSetup.js +306 -0
- package/dist/config/environment.js +107 -0
- package/dist/config/parseConfig.js +228 -0
- package/dist/config/parsers.js +68 -0
- package/dist/config/permissions.js +30 -0
- package/dist/config/types.js +1 -0
- package/dist/config.js +1 -375
- package/dist/contracts/applicationToolContracts.js +134 -0
- package/dist/contracts/electronToolContracts.js +51 -3
- package/dist/contracts/managedToolContracts.js +8 -3
- package/dist/contracts/managedWorkflowToolContracts.js +3 -3
- package/dist/contracts/providerSelection.js +2 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +5 -12
- package/dist/contracts/toolEffects.js +14 -0
- package/dist/contracts/toolOutputSchemaGroups.js +274 -0
- package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
- package/dist/contracts/toolOutputSchemas.js +2 -550
- package/dist/domain/analysisErrorPresentation.js +234 -0
- package/dist/domain/analysisErrorProjection.js +284 -0
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/artifactComparison.js +34 -15
- package/dist/domain/binaryTarget.js +87 -37
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/browserObservation.js +1 -285
- package/dist/domain/browserObservationSchemas.js +286 -0
- package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
- package/dist/domain/errors.js +4 -486
- package/dist/domain/functionComparison.js +4 -165
- package/dist/domain/functionComparisonDimensions.js +164 -0
- package/dist/domain/javascriptApplicationVersionItems.js +18 -6
- package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
- package/dist/domain/javascriptExportInstrumentation.js +97 -0
- package/dist/domain/javascriptFeatureTraversal.js +19 -7
- package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
- package/dist/domain/javascriptSemanticAnalysis.js +386 -0
- package/dist/domain/javascriptSemanticIr.js +30 -0
- package/dist/domain/javascriptSemanticProjection.js +366 -0
- package/dist/domain/javascriptSemanticState.js +40 -0
- package/dist/domain/javascriptSemanticTraversal.js +22 -0
- package/dist/domain/javascriptSemanticValues.js +352 -0
- package/dist/domain/javascriptStaticAnalysis.js +5 -221
- package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
- package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
- package/dist/domain/knownPageCoverage.js +21 -0
- package/dist/domain/managedApplicationGraph.js +14 -364
- package/dist/domain/managedApplicationGraphCoverage.js +170 -0
- package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
- package/dist/domain/managedApplicationGraphNodes.js +336 -0
- package/dist/domain/managedArtifact.js +16 -8
- package/dist/domain/managedMemberComparison.js +37 -391
- package/dist/domain/managedMemberComparisonCoverage.js +72 -0
- package/dist/domain/managedMemberComparisonMatch.js +356 -0
- package/dist/domain/managedNativeVerification.js +27 -389
- package/dist/domain/managedNativeVerificationMatch.js +272 -0
- package/dist/domain/managedNativeVerificationSchemas.js +141 -0
- package/dist/domain/managedReconstruction.js +82 -69
- package/dist/domain/managedRuntimeCorrelation.js +12 -3
- package/dist/domain/nodeRuntimeCharacterization.js +66 -0
- package/dist/domain/permissionPolicy.js +2 -0
- package/dist/domain/processComparison.js +19 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/reconstructionCoverage.js +387 -0
- package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
- package/dist/domain/reconstructionVerificationSchemas.js +8 -0
- package/dist/domain/replayMachine.js +316 -0
- package/dist/domain/replayMachineRuntime.js +215 -0
- package/dist/domain/runtimeCharacterization.js +85 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/webBundleAnalyzer.js +3 -409
- package/dist/domain/webBundleAnalyzerAst.js +77 -0
- package/dist/domain/webBundleAnalyzerInspection.js +307 -0
- package/dist/domain/webBundleAnalyzerResult.js +120 -0
- package/dist/dotnet/ManagedArtifactInspector.js +50 -29
- package/dist/dotnet/ManagedMemberInspector.js +51 -785
- package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
- package/dist/dotnet/ManagedMemberRows.js +120 -0
- package/dist/dotnet/ManagedMetadataInventory.js +42 -306
- package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
- package/dist/dotnet/ManagedMetadataLayout.js +48 -111
- package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
- package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
- package/dist/dotnet/ManagedPeReader.js +59 -28
- package/dist/evaluation/CodexAgentEval.js +132 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +76 -91
- package/dist/ghidra/GhidraClientStartup.js +29 -0
- package/dist/ghidra/GhidraClientWire.js +34 -0
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraDiagnostics.js +6 -1
- package/dist/ghidra/GhidraInstallation.js +20 -15
- package/dist/ghidra/GhidraLauncher.js +93 -29
- package/dist/ghidra/GhidraProvider.js +61 -161
- package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
- package/dist/ghidra/GhidraSessionValues.js +2 -0
- package/dist/ghidra/GhidraSocketConnection.js +44 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
- package/dist/ghidra/GhidraTransport.js +71 -0
- package/dist/identity.js +4 -2
- package/dist/main/messages.js +5 -0
- package/dist/main/reload.js +49 -0
- package/dist/main/shutdown.js +25 -0
- package/dist/main/startup.js +14 -0
- package/dist/main/state.js +26 -0
- package/dist/main/transport.js +61 -0
- package/dist/main/types.js +1 -0
- package/dist/main.js +35 -146
- package/dist/process/PrivateRuntimeRoot.js +3 -2
- package/dist/process/ProcessOwnership.js +35 -0
- package/dist/process/ProviderProcess.js +2 -0
- package/dist/replay/JavaScriptReplayWorker.js +64 -50
- package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
- package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
- package/dist/replay/ReplayOutcome.js +95 -0
- package/dist/replay/ReplayProcessLifecycle.js +71 -0
- package/dist/server/ProcessCaptureElicitation.js +157 -0
- package/dist/server/createServer.js +124 -56
- package/dist/server/javascriptApplicationResult.js +85 -0
- package/dist/server/registerApplicationTools/characterization.js +57 -0
- package/dist/server/registerApplicationTools/compareVersions.js +34 -0
- package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
- package/dist/server/registerApplicationTools/coverage.js +84 -0
- package/dist/server/registerApplicationTools/helpers.js +42 -0
- package/dist/server/registerApplicationTools/traceFeature.js +26 -0
- package/dist/server/registerApplicationTools/types.js +1 -0
- package/dist/server/registerApplicationTools.js +10 -112
- package/dist/server/registerBrowserTools.js +59 -83
- package/dist/server/registerElectronTools.js +48 -49
- package/dist/server/registerEnhancedTools.js +34 -42
- package/dist/server/registerEvidenceResources.js +56 -0
- package/dist/server/registerInvestigationTools/helpers.js +74 -0
- package/dist/server/registerInvestigationTools/types.js +1 -0
- package/dist/server/registerInvestigationTools.js +83 -116
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
- package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
- package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
- package/dist/server/registerManagedWorkflowTools/types.js +1 -0
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
- package/dist/server/registerManagedWorkflowTools.js +10 -274
- package/dist/server/registerOfficialTools.js +73 -59
- package/dist/server/registerSessionRecordTools.js +137 -0
- package/dist/server/registerSessionStatusTool.js +97 -26
- package/dist/server/registerSessionTools.js +60 -164
- package/dist/server/sessionAvailabilityPolicy.js +10 -0
- package/dist/server/toolRegistrationOptions.js +101 -3
- package/dist/server/toolResult.js +15 -4
- package/package.json +11 -11
- package/skills/reverse-engineer-anything/SKILL.md +76 -0
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
- package/scripts/prepare-node-pty.mjs +0 -35
- package/scripts/rebuild-native.mjs +0 -22
- package/skills/rea-analysis/SKILL.md +0 -222
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveArtifactPathByContext } from "./JavaScriptArtifactPathResolution.js";
|
|
2
2
|
/** Flatten all per-file IPC facts into deterministic application records. */
|
|
3
3
|
export const collectElectronIpcRecords = (analysis) => analysis.files
|
|
4
4
|
.flatMap(({ file, javascript }) => (javascript?.electron.ipc ?? []).map((finding) => ({
|
|
@@ -70,9 +70,20 @@ export const summarizeElectronBoundaries = (analysis) => {
|
|
|
70
70
|
},
|
|
71
71
|
sender_validation_observations: validations.length,
|
|
72
72
|
utility_processes: utilities.length,
|
|
73
|
-
resolved_utility_entrypoints: utilities.filter(({ file, finding }) => resolvesToFile(
|
|
73
|
+
resolved_utility_entrypoints: utilities.filter(({ file, finding }) => resolvesToFile({
|
|
74
|
+
file,
|
|
75
|
+
specifier: finding.module_path,
|
|
76
|
+
context: finding.module_resolution_context ?? "module-specifier",
|
|
77
|
+
files,
|
|
78
|
+
})).length,
|
|
74
79
|
native_addon_bindings: nativeBindings.length,
|
|
75
|
-
resolved_native_addon_bindings: nativeBindings.filter(({ file, finding }) => resolvesToFile(
|
|
80
|
+
resolved_native_addon_bindings: nativeBindings.filter(({ file, finding }) => resolvesToFile({
|
|
81
|
+
file,
|
|
82
|
+
specifier: finding.specifier,
|
|
83
|
+
context: "module-specifier",
|
|
84
|
+
files,
|
|
85
|
+
kind: "native-addon",
|
|
86
|
+
})).length,
|
|
76
87
|
};
|
|
77
88
|
};
|
|
78
89
|
const compatibleIpcModes = (transmission, handler) => transmission.mode === "invoke"
|
|
@@ -85,12 +96,17 @@ const isRendererTransmission = ({ finding }) => finding.side === "renderer" &&
|
|
|
85
96
|
const isPairableRendererTransmission = (record) => isRendererTransmission(record) && record.finding.operation !== "send-to-host";
|
|
86
97
|
const isMainHandler = ({ finding }) => finding.side === "main" &&
|
|
87
98
|
(finding.mode === "listen" || finding.mode === "handle");
|
|
88
|
-
const resolvesToFile = (
|
|
89
|
-
if (specifier === null)
|
|
99
|
+
const resolvesToFile = (input) => {
|
|
100
|
+
if (input.specifier === null)
|
|
90
101
|
return false;
|
|
91
|
-
const resolved =
|
|
102
|
+
const resolved = resolveArtifactPathByContext({
|
|
103
|
+
declaredPath: input.specifier,
|
|
104
|
+
sourcePath: input.file.path,
|
|
105
|
+
context: input.context,
|
|
106
|
+
files: input.files,
|
|
107
|
+
}).resolved_path;
|
|
92
108
|
return (resolved !== null &&
|
|
93
|
-
(kind === undefined || files.get(resolved)?.kind === kind));
|
|
109
|
+
(input.kind === undefined || input.files.get(resolved)?.kind === input.kind));
|
|
94
110
|
};
|
|
95
111
|
const ipcRecordKey = (path, finding) => `${path}\0${String(finding.location.start.line)}:${String(finding.location.start.column)}\0${finding.side}\0${finding.operation}`;
|
|
96
112
|
const compareCodePoints = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { artifactLocalIdentity, createElectronRoleNode, javascriptAnalysisCoverage, linkElectronRoleToAsset,
|
|
1
|
+
import { artifactLocalIdentity, createElectronRoleNode, javascriptAnalysisCoverage, linkElectronRoleToAsset, } from "./JavaScriptArtifactGraphContext.js";
|
|
2
2
|
import { astObservationEvidence } from "./JavaScriptArtifactGraphEvidence.js";
|
|
3
3
|
import { addElectronAstEdge, addElectronInferenceEdge, electronFindingSourceNode, electronRangeKey, } from "./ElectronBoundaryGraphContext.js";
|
|
4
|
+
import { resolveArtifactPathByContext } from "./JavaScriptArtifactPathResolution.js";
|
|
4
5
|
/** Project BrowserWindow, contextBridge, preload, and utility-process facts. */
|
|
5
6
|
export const addElectronWindowBoundaries = (context) => {
|
|
6
7
|
for (const analyzed of context.analysis.files) {
|
|
@@ -40,6 +41,7 @@ const addBrowserWindow = (input) => {
|
|
|
40
41
|
web_preferences: value.web_preferences,
|
|
41
42
|
omitted_web_preferences: value.omitted_web_preferences,
|
|
42
43
|
preload_path: value.preload_path,
|
|
44
|
+
preload_resolution_context: value.preload_resolution_context,
|
|
43
45
|
absence_means_default: false,
|
|
44
46
|
},
|
|
45
47
|
evidence: astObservationEvidence({
|
|
@@ -71,12 +73,16 @@ const addWindowPreload = (input, window) => {
|
|
|
71
73
|
const declared = input.value.preload_path;
|
|
72
74
|
if (declared === null)
|
|
73
75
|
return;
|
|
74
|
-
const
|
|
76
|
+
const resolution = resolveArtifactPathByContext({
|
|
77
|
+
declaredPath: declared,
|
|
78
|
+
sourcePath: input.file.path,
|
|
79
|
+
context: input.value.preload_resolution_context ?? "module-specifier",
|
|
80
|
+
files: input.context.filesByPath,
|
|
81
|
+
});
|
|
75
82
|
const role = createElectronRoleNode(input.context, {
|
|
76
83
|
kind: "electron-preload",
|
|
77
84
|
anchor: input.file,
|
|
78
|
-
|
|
79
|
-
resolvedPath: resolved,
|
|
85
|
+
resolution,
|
|
80
86
|
mechanism: "BrowserWindow:webPreferences.preload",
|
|
81
87
|
range: input.value.location,
|
|
82
88
|
coverage: input.coverage,
|
|
@@ -89,12 +95,18 @@ const addWindowPreload = (input, window) => {
|
|
|
89
95
|
coverage: input.coverage,
|
|
90
96
|
relation: "loads",
|
|
91
97
|
operation: "associate-browser-window-preload",
|
|
92
|
-
properties: {
|
|
98
|
+
properties: {
|
|
99
|
+
declared_path: declared,
|
|
100
|
+
resolution_context: resolution.resolution_context,
|
|
101
|
+
resolved_path: resolution.resolved_path,
|
|
102
|
+
resolution_status: resolution.resolution_status,
|
|
103
|
+
limitations: resolution.limitations,
|
|
104
|
+
},
|
|
93
105
|
});
|
|
94
106
|
linkElectronRoleToAsset(input.context, {
|
|
95
107
|
role,
|
|
96
108
|
anchor: input.file,
|
|
97
|
-
|
|
109
|
+
resolution,
|
|
98
110
|
range: input.value.location,
|
|
99
111
|
coverage: input.coverage,
|
|
100
112
|
});
|
|
@@ -152,6 +164,7 @@ const addUtilityProcess = (input) => {
|
|
|
152
164
|
label: value.service_name ?? value.module_path,
|
|
153
165
|
properties: {
|
|
154
166
|
module_path: value.module_path,
|
|
167
|
+
module_resolution_context: value.module_resolution_context,
|
|
155
168
|
module_expression: value.module_expression,
|
|
156
169
|
service_name: value.service_name,
|
|
157
170
|
},
|
|
@@ -180,7 +193,13 @@ const addUtilityProcess = (input) => {
|
|
|
180
193
|
});
|
|
181
194
|
if (value.module_path === null)
|
|
182
195
|
return;
|
|
183
|
-
const
|
|
196
|
+
const resolution = resolveArtifactPathByContext({
|
|
197
|
+
declaredPath: value.module_path,
|
|
198
|
+
sourcePath: file.path,
|
|
199
|
+
context: value.module_resolution_context ?? "module-specifier",
|
|
200
|
+
files: context.filesByPath,
|
|
201
|
+
});
|
|
202
|
+
const resolved = resolution.resolved_path;
|
|
184
203
|
const target = resolved === null
|
|
185
204
|
? undefined
|
|
186
205
|
: (context.assetNodes.get(resolved) ?? context.fileNodes.get(resolved));
|
|
@@ -194,6 +213,11 @@ const addUtilityProcess = (input) => {
|
|
|
194
213
|
coverage,
|
|
195
214
|
relation: "maps_to",
|
|
196
215
|
operation: "resolve-utility-process-entrypoint",
|
|
197
|
-
properties: {
|
|
216
|
+
properties: {
|
|
217
|
+
declared_path: value.module_path,
|
|
218
|
+
resolution_context: resolution.resolution_context,
|
|
219
|
+
resolved_path: resolved,
|
|
220
|
+
resolution_status: resolution.resolution_status,
|
|
221
|
+
},
|
|
198
222
|
});
|
|
199
223
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AnalysisOutputError } from "../domain/errors.js";
|
|
2
|
+
import { parseAddressedPage } from "../domain/hopperValues.js";
|
|
3
|
+
import { err, ok } from "../domain/result.js";
|
|
4
|
+
/** Read all addressed entries while enforcing advancing provider pagination. */
|
|
5
|
+
export const readAllAddressed = async (input) => {
|
|
6
|
+
const entries = [];
|
|
7
|
+
let offset = 0;
|
|
8
|
+
while (true) {
|
|
9
|
+
const result = await input.call(input.tool, { offset, limit: 500 }, input.signal);
|
|
10
|
+
if (!result.ok)
|
|
11
|
+
return result;
|
|
12
|
+
const page = parseAddressedPage(result.value);
|
|
13
|
+
if (!page.ok)
|
|
14
|
+
return page;
|
|
15
|
+
entries.push(...page.value.items);
|
|
16
|
+
if (!page.value.hasMore || page.value.nextOffset === null)
|
|
17
|
+
return ok(entries);
|
|
18
|
+
if (page.value.nextOffset <= offset)
|
|
19
|
+
return err(new AnalysisOutputError(input.tool, "provider returned a non-advancing pagination offset"));
|
|
20
|
+
offset = page.value.nextOffset;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AnalysisInputError } from "../domain/errors.js";
|
|
2
|
+
import { err } from "../domain/result.js";
|
|
3
|
+
import { enhancedInputSchemas, } from "../contracts/enhancedInputs.js";
|
|
4
|
+
export const invalidEnhancedInput = (name, cause) => Promise.resolve(err(new AnalysisInputError(name, { cause })));
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
|
|
2
|
-
import {
|
|
3
|
-
import { addressDistance, parseDocuments, parseFunctionDossier,
|
|
2
|
+
import { AnalysisCancelledError, AnalysisOutputError, projectAnalysisError, } from "../domain/errors.js";
|
|
3
|
+
import { addressDistance, parseAddressedPage, parseDocuments, parseFunctionDossier, parseListCount, parseRelatedAddresses, parseSegments, } from "../domain/hopperValues.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
5
|
import { categorizeSwiftTypes, discoverObjcClasses, discoverObjcProtocols, discoverSwiftClasses, } from "../domain/symbolAnalysis.js";
|
|
6
|
+
import { invalidEnhancedInput, } from "./EnhancedToolTypes.js";
|
|
7
|
+
import { readAllAddressed } from "./EnhancedToolPagination.js";
|
|
6
8
|
/**
|
|
7
9
|
* Composes direct provider operations into bounded reverse-engineering tools.
|
|
8
10
|
* Direct callers use `execute`; adapters that own validation use
|
|
@@ -20,13 +22,13 @@ export class EnhancedTools {
|
|
|
20
22
|
const parsed = enhancedInputSchemas.swift_classes.safeParse(input);
|
|
21
23
|
return parsed.success
|
|
22
24
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
23
|
-
:
|
|
25
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
24
26
|
}
|
|
25
27
|
case "get_objc_classes": {
|
|
26
28
|
const parsed = enhancedInputSchemas.get_objc_classes.safeParse(input);
|
|
27
29
|
return parsed.success
|
|
28
30
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
29
|
-
:
|
|
31
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
30
32
|
}
|
|
31
33
|
case "get_objc_protocols":
|
|
32
34
|
return this.executeValidated({ name, input: {} }, signal);
|
|
@@ -34,13 +36,13 @@ export class EnhancedTools {
|
|
|
34
36
|
const parsed = enhancedInputSchemas.batch_decompile.safeParse(input);
|
|
35
37
|
return parsed.success
|
|
36
38
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
37
|
-
:
|
|
39
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
38
40
|
}
|
|
39
41
|
case "get_call_graph": {
|
|
40
42
|
const parsed = enhancedInputSchemas.get_call_graph.safeParse(input);
|
|
41
43
|
return parsed.success
|
|
42
44
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
43
|
-
:
|
|
45
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
44
46
|
}
|
|
45
47
|
case "analyze_swift_types":
|
|
46
48
|
return this.executeValidated({ name, input: {} }, signal);
|
|
@@ -48,25 +50,25 @@ export class EnhancedTools {
|
|
|
48
50
|
const parsed = enhancedInputSchemas.find_xrefs_to_name.safeParse(input);
|
|
49
51
|
return parsed.success
|
|
50
52
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
51
|
-
:
|
|
53
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
52
54
|
}
|
|
53
55
|
case "binary_overview": {
|
|
54
56
|
const parsed = enhancedInputSchemas.binary_overview.safeParse(input);
|
|
55
57
|
return parsed.success
|
|
56
58
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
57
|
-
:
|
|
59
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
58
60
|
}
|
|
59
61
|
case "analyze_function": {
|
|
60
62
|
const parsed = enhancedInputSchemas.analyze_function.safeParse(input);
|
|
61
63
|
return parsed.success
|
|
62
64
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
63
|
-
:
|
|
65
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
64
66
|
}
|
|
65
67
|
case "trace_feature": {
|
|
66
68
|
const parsed = enhancedInputSchemas.trace_feature.safeParse(input);
|
|
67
69
|
return parsed.success
|
|
68
70
|
? this.executeValidated({ name, input: parsed.data }, signal)
|
|
69
|
-
:
|
|
71
|
+
: invalidEnhancedInput(name, parsed.error);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
@@ -100,7 +102,7 @@ export class EnhancedTools {
|
|
|
100
102
|
return result.ok ? parseFunctionDossier(result.value) : result;
|
|
101
103
|
}
|
|
102
104
|
async #swiftClasses(pattern, signal) {
|
|
103
|
-
const procedures = await this.#
|
|
105
|
+
const procedures = await this.#allAddressed("list_procedures", signal);
|
|
104
106
|
return procedures.ok
|
|
105
107
|
? ok(discoverSwiftClasses(procedures.value, pattern))
|
|
106
108
|
: procedures;
|
|
@@ -189,7 +191,7 @@ export class EnhancedTools {
|
|
|
189
191
|
return ok(graph);
|
|
190
192
|
}
|
|
191
193
|
async #analyzeSwiftTypes(signal) {
|
|
192
|
-
const procedures = await this.#
|
|
194
|
+
const procedures = await this.#allAddressed("list_procedures", signal);
|
|
193
195
|
return procedures.ok
|
|
194
196
|
? ok(categorizeSwiftTypes(procedures.value))
|
|
195
197
|
: procedures;
|
|
@@ -218,7 +220,7 @@ export class EnhancedTools {
|
|
|
218
220
|
const [segmentsResult, documentsResult, procedures, stringsResult] = await Promise.all([
|
|
219
221
|
this.#call("list_segments", {}, signal),
|
|
220
222
|
this.#call("list_documents", {}, signal),
|
|
221
|
-
this.#
|
|
223
|
+
this.#allAddressed("list_procedures", signal),
|
|
222
224
|
this.#call("list_strings", {}, signal),
|
|
223
225
|
]);
|
|
224
226
|
if (!segmentsResult.ok)
|
|
@@ -358,28 +360,12 @@ export class EnhancedTools {
|
|
|
358
360
|
residual: [...residual],
|
|
359
361
|
});
|
|
360
362
|
}
|
|
361
|
-
async #allProcedures(signal) {
|
|
362
|
-
return this.#allAddressed("list_procedures", signal);
|
|
363
|
-
}
|
|
364
363
|
async #allAddressed(tool, signal) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return result;
|
|
371
|
-
const page = parseAddressedPage(result.value);
|
|
372
|
-
if (!page.ok)
|
|
373
|
-
return page;
|
|
374
|
-
entries.push(...page.value.items);
|
|
375
|
-
if (!page.value.hasMore || page.value.nextOffset === null) {
|
|
376
|
-
return ok(entries);
|
|
377
|
-
}
|
|
378
|
-
if (page.value.nextOffset <= offset) {
|
|
379
|
-
return err(new AnalysisOutputError(tool, "provider returned a non-advancing pagination offset"));
|
|
380
|
-
}
|
|
381
|
-
offset = page.value.nextOffset;
|
|
382
|
-
}
|
|
364
|
+
return readAllAddressed({
|
|
365
|
+
call: (name, arguments_, operationSignal) => this.#call(name, arguments_, operationSignal),
|
|
366
|
+
tool,
|
|
367
|
+
...(signal === undefined ? {} : { signal }),
|
|
368
|
+
});
|
|
383
369
|
}
|
|
384
370
|
async #call(name, arguments_, signal) {
|
|
385
371
|
if (signal?.aborted === true)
|
|
@@ -388,6 +374,3 @@ export class EnhancedTools {
|
|
|
388
374
|
return execution.ok ? ok(execution.value.result) : execution;
|
|
389
375
|
}
|
|
390
376
|
}
|
|
391
|
-
const invalidInput = (name, cause) => Promise.resolve(err(new AnalysisInputError(name, {
|
|
392
|
-
cause,
|
|
393
|
-
})));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { instrumentJavaScriptExport, } from "../domain/javascriptExportInstrumentation.js";
|
|
2
|
+
/** Decorate one exact replay source with deterministic in-memory instrumentation. */
|
|
3
|
+
export class InstrumentedJavaScriptReplayHost {
|
|
4
|
+
delegate;
|
|
5
|
+
input;
|
|
6
|
+
instrumentation = null;
|
|
7
|
+
constructor(delegate, input) {
|
|
8
|
+
this.delegate = delegate;
|
|
9
|
+
this.input = input;
|
|
10
|
+
}
|
|
11
|
+
async readSource(path, maximumBytes) {
|
|
12
|
+
const source = await this.delegate.readSource(path, maximumBytes);
|
|
13
|
+
if (path !== this.input.artifact_path)
|
|
14
|
+
return source;
|
|
15
|
+
const instrumented = instrumentJavaScriptExport(source.bytes, this.input);
|
|
16
|
+
if (instrumented.bytes.byteLength > maximumBytes)
|
|
17
|
+
throw new RangeError("Instrumented JavaScript exceeds the replay module byte limit");
|
|
18
|
+
this.instrumentation = instrumented;
|
|
19
|
+
return { canonicalPath: source.canonicalPath, bytes: instrumented.bytes };
|
|
20
|
+
}
|
|
21
|
+
identifyExecutable(path, versionArguments) {
|
|
22
|
+
return this.delegate.identifyExecutable(path, versionArguments);
|
|
23
|
+
}
|
|
24
|
+
identifyWorker() {
|
|
25
|
+
return this.delegate.identifyWorker();
|
|
26
|
+
}
|
|
27
|
+
identifyRuntimeClosure(nodePath) {
|
|
28
|
+
return this.delegate.identifyRuntimeClosure(nodePath);
|
|
29
|
+
}
|
|
30
|
+
seccompDigest() {
|
|
31
|
+
return this.delegate.seccompDigest();
|
|
32
|
+
}
|
|
33
|
+
probe(policy) {
|
|
34
|
+
return this.delegate.probe(policy);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AnalysisInputError, AnalysisProtocolError, } from "../domain/errors.js";
|
|
3
|
+
import { createEvidence, } from "../domain/evidence.js";
|
|
4
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
5
|
+
import { err, ok } from "../domain/result.js";
|
|
6
|
+
/** Parse one inventory projection and wrap its deterministic result in Evidence v2. */
|
|
7
|
+
export const projectInventoryEvidence = (options) => {
|
|
8
|
+
const parsed = options.schema.safeParse(options.rawInput);
|
|
9
|
+
if (!parsed.success)
|
|
10
|
+
return err(new AnalysisInputError(options.operation, { cause: parsed.error }));
|
|
11
|
+
try {
|
|
12
|
+
const result = options.project(parsed.data);
|
|
13
|
+
const first = parsed.data.inventory_evidence[0];
|
|
14
|
+
return ok(createEvidence(first?.subject === null || first?.subject === undefined
|
|
15
|
+
? undefined
|
|
16
|
+
: {
|
|
17
|
+
path: first.subject.local_path,
|
|
18
|
+
sha256: result.root_sha256,
|
|
19
|
+
format: options.subjectFormat(first),
|
|
20
|
+
}, options.provider, {
|
|
21
|
+
predicateType: options.predicateType,
|
|
22
|
+
operation: options.operation,
|
|
23
|
+
parameters: {
|
|
24
|
+
inventory_evidence_ids: [...result.source_evidence_ids],
|
|
25
|
+
limits: jsonValueSchema.parse(parsed.data.limits),
|
|
26
|
+
},
|
|
27
|
+
result: jsonValueSchema.parse(result),
|
|
28
|
+
rawResult: null,
|
|
29
|
+
confidence: "inferred",
|
|
30
|
+
authority: "analyst-inference",
|
|
31
|
+
environment: null,
|
|
32
|
+
limitations: result.limitations,
|
|
33
|
+
evidenceLinks: result.source_evidence_ids,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return err(cause instanceof TypeError || cause instanceof z.ZodError
|
|
38
|
+
? new AnalysisInputError(options.operation, { cause })
|
|
39
|
+
: new AnalysisProtocolError(options.protocolError, { cause }));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -27,6 +27,18 @@ export const MANAGED_WORKFLOW_PROVIDER = {
|
|
|
27
27
|
name: "REA managed-code workflows",
|
|
28
28
|
version: "1",
|
|
29
29
|
};
|
|
30
|
+
/** Provider identity for deterministic Apple application projections. */
|
|
31
|
+
export const APPLE_APPLICATION_PROVIDER = {
|
|
32
|
+
id: "rea-apple-application",
|
|
33
|
+
name: "REA Apple application workflows",
|
|
34
|
+
version: "1",
|
|
35
|
+
};
|
|
36
|
+
/** Provider identity for deterministic Android application projections. */
|
|
37
|
+
export const ANDROID_APPLICATION_PROVIDER = {
|
|
38
|
+
id: "rea-android-application",
|
|
39
|
+
name: "REA Android application workflows",
|
|
40
|
+
version: "1",
|
|
41
|
+
};
|
|
30
42
|
/** Provider identity for deterministic static JavaScript application analysis. */
|
|
31
43
|
export const JAVASCRIPT_APPLICATION_PROVIDER = {
|
|
32
44
|
id: "rea-javascript-application",
|
|
@@ -5,27 +5,41 @@ import { isJsonWithinLimits } from "../domain/jsonLimits.js";
|
|
|
5
5
|
import { InvestigationWorkspaceError } from "../domain/errors.js";
|
|
6
6
|
import { parseInvestigationWorkspace, serializeInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
|
|
7
7
|
import { err, ok } from "../domain/result.js";
|
|
8
|
+
const investigationWorkspaceCodec = {
|
|
9
|
+
parse: parseInvestigationWorkspace,
|
|
10
|
+
serialize: serializeInvestigationWorkspace,
|
|
11
|
+
validateNext: validateNextRevision,
|
|
12
|
+
};
|
|
8
13
|
/** Read a validated workspace, returning null only when it does not exist. */
|
|
9
|
-
export const readInvestigationWorkspace = async (path, policy) =>
|
|
14
|
+
export const readInvestigationWorkspace = async (path, policy) => readRevisionedWorkspace(path, policy, investigationWorkspaceCodec);
|
|
15
|
+
/** Read one root-confined, owner-only revisioned workspace document. */
|
|
16
|
+
export const readRevisionedWorkspace = async (path, policy, codec) => {
|
|
10
17
|
if (policy.roots.length === 0)
|
|
11
18
|
return err(new InvestigationWorkspaceError("read", "disabled"));
|
|
12
19
|
try {
|
|
13
20
|
const destination = await resolveDestination(path, policy.roots);
|
|
14
21
|
if (destination === null)
|
|
15
22
|
return err(new InvestigationWorkspaceError("read", "outside-root"));
|
|
16
|
-
return await readWorkspaceFile(destination, policy);
|
|
23
|
+
return await readWorkspaceFile(destination, policy, codec);
|
|
17
24
|
}
|
|
18
25
|
catch (cause) {
|
|
19
26
|
return err(new InvestigationWorkspaceError("read", "io", { cause }));
|
|
20
27
|
}
|
|
21
28
|
};
|
|
22
29
|
/** Atomically append one CAS-linked workspace revision under an exclusive lock. */
|
|
23
|
-
export const writeInvestigationWorkspace = async (workspace, path, expectedRevision, policy) => {
|
|
30
|
+
export const writeInvestigationWorkspace = async (workspace, path, expectedRevision, policy) => writeRevisionedWorkspace(path, policy, {
|
|
31
|
+
document: workspace,
|
|
32
|
+
expectedRevision,
|
|
33
|
+
codec: investigationWorkspaceCodec,
|
|
34
|
+
});
|
|
35
|
+
/** Atomically append one validated CAS-linked revision under an exclusive lock. */
|
|
36
|
+
export const writeRevisionedWorkspace = async (path, policy, input) => {
|
|
37
|
+
const { document, expectedRevision, codec } = input;
|
|
24
38
|
if (policy.roots.length === 0)
|
|
25
39
|
return err(new InvestigationWorkspaceError("update", "disabled"));
|
|
26
40
|
let encoded;
|
|
27
41
|
try {
|
|
28
|
-
encoded =
|
|
42
|
+
encoded = codec.serialize(document);
|
|
29
43
|
}
|
|
30
44
|
catch (cause) {
|
|
31
45
|
return err(new InvestigationWorkspaceError("update", "integrity", { cause }));
|
|
@@ -42,10 +56,10 @@ export const writeInvestigationWorkspace = async (workspace, path, expectedRevis
|
|
|
42
56
|
if (!acquired.ok)
|
|
43
57
|
return acquired;
|
|
44
58
|
lock = acquired.value;
|
|
45
|
-
const current = await readWorkspaceFile(destination, policy);
|
|
59
|
+
const current = await readWorkspaceFile(destination, policy, codec);
|
|
46
60
|
if (!current.ok)
|
|
47
61
|
return current;
|
|
48
|
-
const checked =
|
|
62
|
+
const checked = codec.validateNext(current.value, document, expectedRevision);
|
|
49
63
|
if (!checked.ok)
|
|
50
64
|
return checked;
|
|
51
65
|
await writeFileAtomic(destination, encoded, {
|
|
@@ -76,7 +90,7 @@ const resolveDestination = async (path, roots) => {
|
|
|
76
90
|
}
|
|
77
91
|
return null;
|
|
78
92
|
};
|
|
79
|
-
const readWorkspaceFile = async (destination, policy) => {
|
|
93
|
+
const readWorkspaceFile = async (destination, policy, codec) => {
|
|
80
94
|
const stats = await lstat(destination).catch((cause) => {
|
|
81
95
|
if (isFileNotFound(cause))
|
|
82
96
|
return undefined;
|
|
@@ -101,7 +115,7 @@ const readWorkspaceFile = async (destination, policy) => {
|
|
|
101
115
|
if (!isJsonWithinLimits(decoded, policy))
|
|
102
116
|
return err(new InvestigationWorkspaceError("read", "too-large"));
|
|
103
117
|
try {
|
|
104
|
-
return ok(
|
|
118
|
+
return ok(codec.parse(decoded));
|
|
105
119
|
}
|
|
106
120
|
catch (cause) {
|
|
107
121
|
return err(new InvestigationWorkspaceError("read", "integrity", { cause }));
|
|
@@ -168,7 +182,7 @@ const releaseLock = async (lock) => {
|
|
|
168
182
|
await lock.handle.close().catch(() => undefined);
|
|
169
183
|
await unlink(lock.path).catch(() => undefined);
|
|
170
184
|
};
|
|
171
|
-
|
|
185
|
+
function validateNextRevision(current, next, expectedRevision) {
|
|
172
186
|
if ((current?.revision ?? null) !== expectedRevision)
|
|
173
187
|
return err(new InvestigationWorkspaceError("update", "revision-conflict"));
|
|
174
188
|
if (current === null) {
|
|
@@ -182,7 +196,7 @@ const validateNextRevision = (current, next, expectedRevision) => {
|
|
|
182
196
|
next.previous_revision_digest === current.revision_digest
|
|
183
197
|
? ok(null)
|
|
184
198
|
: err(new InvestigationWorkspaceError("update", "revision-conflict"));
|
|
185
|
-
}
|
|
199
|
+
}
|
|
186
200
|
const isFileNotFound = (cause) => errorCode(cause) === "ENOENT";
|
|
187
201
|
const isAlreadyExists = (cause) => errorCode(cause) === "EEXIST";
|
|
188
202
|
const errorCode = (cause) => typeof cause === "object" && cause !== null && "code" in cause
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { analyzeJavaScriptStaticSource } from "../domain/javascriptStaticAnalysis.js";
|
|
3
|
+
import { analyzeJavaScriptSemantics } from "../domain/javascriptSemanticAnalysis.js";
|
|
4
|
+
import { DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS, } from "../domain/javascriptSemanticIr.js";
|
|
5
|
+
import { analyzeJavaScriptJsonModule } from "./JavaScriptJsonModules.js";
|
|
3
6
|
/** Analyze selected text files under one shared AST, finding, module, and time budget. */
|
|
4
7
|
export const analyzeJavaScriptArtifactFiles = (fileSet, input, now) => {
|
|
5
8
|
const state = emptyArtifactAnalysis();
|
|
@@ -23,6 +26,7 @@ const finalizeArtifactAnalysis = (state, input) => {
|
|
|
23
26
|
return {
|
|
24
27
|
files: state.files,
|
|
25
28
|
packages: state.packages,
|
|
29
|
+
json_modules: state.jsonModules,
|
|
26
30
|
html_scripts: state.htmlScripts.slice(0, input.limits.max_findings),
|
|
27
31
|
source_maps: state.sourceMaps,
|
|
28
32
|
visited_ast_nodes: state.visitedNodes,
|
|
@@ -46,14 +50,13 @@ const finalizeArtifactAnalysis = (state, input) => {
|
|
|
46
50
|
};
|
|
47
51
|
const analyzeArtifactFile = (file, context) => {
|
|
48
52
|
const { state, input } = context;
|
|
49
|
-
|
|
50
|
-
state.packages.push(parsePackage(file));
|
|
53
|
+
addStructuredObservations(file, state);
|
|
51
54
|
if (file.kind === "html" && file.text.included)
|
|
52
55
|
state.htmlScripts.push(...parseHtmlScripts(file.path, file.text.value, Math.max(0, input.limits.max_findings - state.findings)));
|
|
53
56
|
if (file.kind === "source-map")
|
|
54
57
|
addSourceMap(file, context);
|
|
55
58
|
if (file.kind !== "javascript" || !file.text.included) {
|
|
56
|
-
state.files.push({ file, javascript: null });
|
|
59
|
+
state.files.push({ file, javascript: null, semantic: null });
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
62
|
const remainingNodes = input.limits.max_ast_nodes - state.visitedNodes;
|
|
@@ -63,7 +66,7 @@ const analyzeArtifactFile = (file, context) => {
|
|
|
63
66
|
remainingFindings <= 0 ||
|
|
64
67
|
remainingModules <= 0 ||
|
|
65
68
|
context.now() > context.deadline) {
|
|
66
|
-
state.files.push({ file, javascript: null });
|
|
69
|
+
state.files.push({ file, javascript: null, semantic: null });
|
|
67
70
|
state.truncatedScopes += 1;
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
@@ -74,14 +77,33 @@ const analyzeArtifactFile = (file, context) => {
|
|
|
74
77
|
deadline: context.deadline,
|
|
75
78
|
now: context.now,
|
|
76
79
|
});
|
|
77
|
-
|
|
80
|
+
const staticFindings = findingCount(analysis);
|
|
81
|
+
const semanticFindingBudget = Math.max(0, input.limits.max_findings - state.findings - staticFindings);
|
|
82
|
+
const semanticLimits = {
|
|
83
|
+
...DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS,
|
|
84
|
+
maxScopes: remainingNodes,
|
|
85
|
+
maxBindings: remainingNodes,
|
|
86
|
+
maxCallables: remainingNodes,
|
|
87
|
+
maxReferences: remainingNodes,
|
|
88
|
+
maxModuleLinks: semanticFindingBudget,
|
|
89
|
+
};
|
|
90
|
+
const semantics = analysis.parse_status === "complete" || analysis.parse_status === "partial"
|
|
91
|
+
? analyzeJavaScriptSemantics(file.text.value, semanticLimits)
|
|
92
|
+
: null;
|
|
93
|
+
state.files.push({
|
|
94
|
+
file,
|
|
95
|
+
javascript: analysis,
|
|
96
|
+
semantic: semantics === null ? null : { ir: semantics, limits: semanticLimits },
|
|
97
|
+
});
|
|
78
98
|
state.visitedNodes += analysis.visited_ast_nodes;
|
|
79
|
-
state.findings +=
|
|
99
|
+
state.findings += staticFindings + (semantics?.moduleLinks.length ?? 0);
|
|
80
100
|
state.modules += analysis.bundler_registrations.reduce((count, registration) => count + registration.modules.length, 0);
|
|
81
101
|
if (analysis.parse_status === "failed")
|
|
82
102
|
state.parseFailures += 1;
|
|
83
103
|
if (analysis.parse_status === "truncated")
|
|
84
104
|
state.truncatedScopes += 1;
|
|
105
|
+
if (semantics?.coverage.status === "truncated")
|
|
106
|
+
state.truncatedScopes += 1;
|
|
85
107
|
};
|
|
86
108
|
const addSourceMap = (file, context) => {
|
|
87
109
|
const sourceMap = parseSourceMap(file, {
|
|
@@ -96,6 +118,7 @@ const addSourceMap = (file, context) => {
|
|
|
96
118
|
const emptyArtifactAnalysis = () => ({
|
|
97
119
|
files: [],
|
|
98
120
|
packages: [],
|
|
121
|
+
jsonModules: [],
|
|
99
122
|
htmlScripts: [],
|
|
100
123
|
sourceMaps: [],
|
|
101
124
|
visitedNodes: 0,
|
|
@@ -105,6 +128,16 @@ const emptyArtifactAnalysis = () => ({
|
|
|
105
128
|
truncatedScopes: 0,
|
|
106
129
|
sourceMapSources: 0,
|
|
107
130
|
});
|
|
131
|
+
const addStructuredObservations = (file, state) => {
|
|
132
|
+
if (file.kind === "package-json")
|
|
133
|
+
state.packages.push(parsePackage(file));
|
|
134
|
+
if (file.kind !== "json")
|
|
135
|
+
return;
|
|
136
|
+
const json = analyzeJavaScriptJsonModule(file);
|
|
137
|
+
state.jsonModules.push(json);
|
|
138
|
+
if (json.status === "invalid")
|
|
139
|
+
state.parseFailures += 1;
|
|
140
|
+
};
|
|
108
141
|
const parsePackage = (file) => {
|
|
109
142
|
if (!file.text.included)
|
|
110
143
|
return unavailablePackage(file, "Package metadata text was unavailable.");
|
|
@@ -146,6 +179,7 @@ const unavailablePackage = (file, limitation) => ({
|
|
|
146
179
|
});
|
|
147
180
|
const parseHtmlScripts = (path, text, maximum) => {
|
|
148
181
|
const scripts = [];
|
|
182
|
+
const baseHref = htmlBaseHref(text);
|
|
149
183
|
const pattern = /<script\b[^>]*\bsrc\s*=\s*(["'])([^"']+)\1[^>]*>/giu;
|
|
150
184
|
for (const match of text.matchAll(pattern)) {
|
|
151
185
|
const script = match[2]?.slice(0, 4_096);
|
|
@@ -155,11 +189,16 @@ const parseHtmlScripts = (path, text, maximum) => {
|
|
|
155
189
|
scripts.push({
|
|
156
190
|
html_path: path,
|
|
157
191
|
script_path: script,
|
|
192
|
+
base_href: baseHref,
|
|
158
193
|
location: rangeForOffsets(text, start, start + match[0].length),
|
|
159
194
|
});
|
|
160
195
|
}
|
|
161
196
|
return scripts;
|
|
162
197
|
};
|
|
198
|
+
const htmlBaseHref = (text) => {
|
|
199
|
+
const match = /<base\b[^>]*\bhref\s*=\s*(["'])([^"']+)\1[^>]*>/iu.exec(text);
|
|
200
|
+
return match?.[2]?.slice(0, 4_096) ?? null;
|
|
201
|
+
};
|
|
163
202
|
const parseSourceMap = (file, context) => {
|
|
164
203
|
if (!file.text.included)
|
|
165
204
|
return unavailableSourceMap(file);
|
|
@@ -197,6 +197,8 @@ const relevantKind = (path) => {
|
|
|
197
197
|
const lower = path.toLowerCase();
|
|
198
198
|
if (lower === "package.json" || lower.endsWith("/package.json"))
|
|
199
199
|
return "package-json";
|
|
200
|
+
if (lower.endsWith(".json"))
|
|
201
|
+
return "json";
|
|
200
202
|
if (/\.(?:cjs|mjs|js|jsx|ts|tsx)$/u.test(lower))
|
|
201
203
|
return "javascript";
|
|
202
204
|
if (/\.html?$/u.test(lower))
|
|
@@ -215,10 +217,12 @@ const filePriority = (kind) => {
|
|
|
215
217
|
return 1;
|
|
216
218
|
case "javascript":
|
|
217
219
|
return 2;
|
|
218
|
-
case "
|
|
220
|
+
case "json":
|
|
219
221
|
return 3;
|
|
220
|
-
case "
|
|
222
|
+
case "source-map":
|
|
221
223
|
return 4;
|
|
224
|
+
case "native-addon":
|
|
225
|
+
return 5;
|
|
222
226
|
}
|
|
223
227
|
};
|
|
224
228
|
const isFilesystemAsar = (entry, path) => entry.kind === "file" &&
|