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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AsarArtifactReader } from "../../artifacts/AsarArtifactReader.js";
|
|
2
|
+
import { ArtifactReaderFailure, } from "../../artifacts/ArtifactReader.js";
|
|
3
|
+
import { DirectoryArtifactReader } from "../../artifacts/DirectoryArtifactReader.js";
|
|
4
|
+
import { MachOSliceArtifactReader } from "../../artifacts/MachOSliceArtifactReader.js";
|
|
5
|
+
import { NativeDmgArtifactReader } from "../../artifacts/NativeDmgArtifactReader.js";
|
|
6
|
+
import { ZipArtifactReader } from "../../artifacts/ZipArtifactReader.js";
|
|
7
|
+
export const createReader = async (path, format, nativeMount, signal) => {
|
|
8
|
+
switch (format) {
|
|
9
|
+
case "directory":
|
|
10
|
+
return new DirectoryArtifactReader(path);
|
|
11
|
+
case "zip":
|
|
12
|
+
case "ipa":
|
|
13
|
+
case "apk":
|
|
14
|
+
return new ZipArtifactReader(path, format);
|
|
15
|
+
case "asar":
|
|
16
|
+
return new AsarArtifactReader(path);
|
|
17
|
+
case "mach-o-universal":
|
|
18
|
+
return process.platform === "darwin"
|
|
19
|
+
? new MachOSliceArtifactReader(path)
|
|
20
|
+
: undefined;
|
|
21
|
+
case "dmg":
|
|
22
|
+
if (!nativeMount.nativeMountApproved)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!nativeMount.nativeMountEnabled)
|
|
25
|
+
throw new ArtifactReaderFailure("unavailable", "Native DMG mounting is disabled by operator policy");
|
|
26
|
+
return NativeDmgArtifactReader.create(path, signal);
|
|
27
|
+
default:
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export const inventoryLimitations = (format, reader) => {
|
|
32
|
+
if (reader !== undefined)
|
|
33
|
+
return [];
|
|
34
|
+
if (format === "dmg" || format === "pkg")
|
|
35
|
+
return [
|
|
36
|
+
`${format.toUpperCase()} root hash is observed; child inventory requires an approved native macOS adapter.`,
|
|
37
|
+
];
|
|
38
|
+
if (format === "mach-o-universal" && process.platform !== "darwin")
|
|
39
|
+
return ["Universal Mach-O slices require the native macOS lipo adapter."];
|
|
40
|
+
return ["Artifact has no child container entries."];
|
|
41
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createReadStream } from "node:fs";
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
3
|
+
import { ArtifactReaderFailure, } from "../../artifacts/ArtifactReader.js";
|
|
4
|
+
import { artifactInventoryResultSchema, } from "../../domain/artifactGraph.js";
|
|
5
|
+
import { createArtifactEdges, createRootNode, digestCanonical, indexOccurrencesByPath, materializeDirectoryNodes, rekeyOccurrences, rootOccurrenceFor, toOutputLimits, } from "../ArtifactGraphConstruction.js";
|
|
6
|
+
import { classifyRoot } from "./classify.js";
|
|
7
|
+
import { hashReadable } from "./hash.js";
|
|
8
|
+
import { createReader, inventoryLimitations } from "./reader.js";
|
|
9
|
+
import { scanReader, } from "./scanReader.js";
|
|
10
|
+
import { NATIVE_MOUNT_DISABLED, STRICT_INTEGRITY_POLICY, } from "./types.js";
|
|
11
|
+
export const scanCanonicalArtifactInventory = async (path, limits, options = {}) => {
|
|
12
|
+
const integrity = options.integrity ?? STRICT_INTEGRITY_POLICY;
|
|
13
|
+
if (integrity.mode === "record-and-continue" &&
|
|
14
|
+
(!integrity.approved || !integrity.enabled))
|
|
15
|
+
throw new ArtifactReaderFailure("unavailable", "Integrity continuation requires explicit approval and operator policy");
|
|
16
|
+
const metadata = await lstat(path);
|
|
17
|
+
const rootFormat = await classifyRoot(path, metadata.isDirectory());
|
|
18
|
+
const rootDigest = metadata.isDirectory()
|
|
19
|
+
? null
|
|
20
|
+
: await hashReadable(createReadStream(path), limits.maxTotalBytes, options.signal);
|
|
21
|
+
const reader = await createReader(path, rootFormat, options.nativeMount ?? NATIVE_MOUNT_DISABLED, options.signal);
|
|
22
|
+
try {
|
|
23
|
+
const { nodes, occurrences, pendingContradictions } = await scanReader(reader, limits, options.signal, integrity);
|
|
24
|
+
return await buildInventorySnapshot({
|
|
25
|
+
path,
|
|
26
|
+
metadata,
|
|
27
|
+
rootFormat,
|
|
28
|
+
rootDigest,
|
|
29
|
+
reader,
|
|
30
|
+
limits,
|
|
31
|
+
signal: options.signal,
|
|
32
|
+
nodes,
|
|
33
|
+
occurrences,
|
|
34
|
+
pendingContradictions,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await reader?.close();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const buildInventorySnapshot = async (input) => {
|
|
42
|
+
const { path, metadata, rootFormat, rootDigest, reader, limits, signal } = input;
|
|
43
|
+
materializeDirectoryNodes(input.occurrences, input.nodes);
|
|
44
|
+
const rootNode = createRootNode({
|
|
45
|
+
path,
|
|
46
|
+
format: rootFormat,
|
|
47
|
+
directory: metadata.isDirectory(),
|
|
48
|
+
digest: rootDigest,
|
|
49
|
+
occurrences: input.occurrences,
|
|
50
|
+
});
|
|
51
|
+
input.nodes.set(rootNode.artifact_id, rootNode);
|
|
52
|
+
rekeyOccurrences(rootNode.artifact_id, input.occurrences);
|
|
53
|
+
const rootOccurrence = rootOccurrenceFor(rootNode, metadata.size);
|
|
54
|
+
for (const occurrence of input.occurrences)
|
|
55
|
+
if (occurrence.parent_occurrence_id === null)
|
|
56
|
+
occurrence.parent_occurrence_id = rootOccurrence.occurrence_id;
|
|
57
|
+
input.occurrences.unshift(rootOccurrence);
|
|
58
|
+
const occurrenceById = new Map(input.occurrences.map((occurrence) => [
|
|
59
|
+
occurrence.occurrence_id,
|
|
60
|
+
occurrence,
|
|
61
|
+
]));
|
|
62
|
+
const occurrenceByPath = indexOccurrencesByPath(input.occurrences);
|
|
63
|
+
const integrityContradictions = buildIntegrityContradictions(input.pendingContradictions, occurrenceByPath, occurrenceById, rootNode);
|
|
64
|
+
const edges = createArtifactEdges(rootNode.artifact_id, input.occurrences, reader?.provenance()[0]);
|
|
65
|
+
const orderedNodes = sortNodes([...input.nodes.values()]);
|
|
66
|
+
const orderedOccurrences = sortOccurrences(input.occurrences);
|
|
67
|
+
const orderedEdges = sortEdges(edges);
|
|
68
|
+
await verifyRootDigest(path, rootDigest, limits.maxTotalBytes, signal);
|
|
69
|
+
const graphSha256 = digestCanonical({
|
|
70
|
+
nodes: orderedNodes,
|
|
71
|
+
occurrences: orderedOccurrences,
|
|
72
|
+
edges: orderedEdges,
|
|
73
|
+
integrity_contradictions: integrityContradictions,
|
|
74
|
+
});
|
|
75
|
+
const manifest = buildManifest({
|
|
76
|
+
rootNode,
|
|
77
|
+
graphSha256,
|
|
78
|
+
orderedNodes,
|
|
79
|
+
orderedOccurrences,
|
|
80
|
+
orderedEdges,
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
manifest,
|
|
84
|
+
nodes: orderedNodes,
|
|
85
|
+
occurrences: orderedOccurrences,
|
|
86
|
+
edges: orderedEdges,
|
|
87
|
+
limits: toOutputLimits(limits),
|
|
88
|
+
provenance: reader?.provenance() ?? [],
|
|
89
|
+
integrity_contradictions: integrityContradictions,
|
|
90
|
+
limitations: buildLimitations(rootFormat, reader, integrityContradictions),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
const buildIntegrityContradictions = (pending, occurrenceByPath, occurrenceById, rootNode) => pending.map((contradiction) => {
|
|
94
|
+
const occurrence = occurrenceByPath.get(contradiction.logicalPath);
|
|
95
|
+
if (occurrence === undefined)
|
|
96
|
+
throw new ArtifactReaderFailure("integrity", "Integrity contradiction lost its graph occurrence");
|
|
97
|
+
const parent = occurrence.parent_occurrence_id === null
|
|
98
|
+
? undefined
|
|
99
|
+
: occurrenceById.get(occurrence.parent_occurrence_id);
|
|
100
|
+
const parentArtifactId = parent?.artifact_id ?? rootNode.artifact_id;
|
|
101
|
+
return {
|
|
102
|
+
contradiction_id: `ic_${digestCanonical({
|
|
103
|
+
root_artifact_id: rootNode.artifact_id,
|
|
104
|
+
logical_path: contradiction.logicalPath,
|
|
105
|
+
declared_sha256: contradiction.declaredSha256,
|
|
106
|
+
observed_sha256: contradiction.observedSha256,
|
|
107
|
+
})}`,
|
|
108
|
+
occurrence_id: occurrence.occurrence_id,
|
|
109
|
+
parent_artifact_id: parentArtifactId,
|
|
110
|
+
logical_path: contradiction.logicalPath,
|
|
111
|
+
declared_sha256: contradiction.declaredSha256,
|
|
112
|
+
observed_sha256: contradiction.observedSha256,
|
|
113
|
+
entry_kind: contradiction.entryKind,
|
|
114
|
+
unpacked: contradiction.unpacked,
|
|
115
|
+
trust: "observed-untrusted",
|
|
116
|
+
provenance: "container-integrity-metadata-versus-observed-bytes",
|
|
117
|
+
limitations: [
|
|
118
|
+
"Observed bytes contradict declared integrity metadata and cannot support equivalence.",
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const verifyRootDigest = async (path, rootDigest, maxTotalBytes, signal) => {
|
|
123
|
+
if (rootDigest === null)
|
|
124
|
+
return;
|
|
125
|
+
const verified = await hashReadable(createReadStream(path), maxTotalBytes, signal);
|
|
126
|
+
if (verified.sha256 !== rootDigest.sha256 ||
|
|
127
|
+
verified.bytes !== rootDigest.bytes)
|
|
128
|
+
throw new ArtifactReaderFailure("integrity", "Root artifact changed during inventory");
|
|
129
|
+
};
|
|
130
|
+
const sortNodes = (nodes) => nodes.sort((left, right) => left.artifact_id.localeCompare(right.artifact_id));
|
|
131
|
+
const sortOccurrences = (occurrences) => occurrences.sort((left, right) => left.logical_path.localeCompare(right.logical_path, "en"));
|
|
132
|
+
const sortEdges = (edges) => edges.sort((left, right) => left.edge_id.localeCompare(right.edge_id));
|
|
133
|
+
const buildManifest = ({ rootNode, graphSha256, orderedNodes, orderedOccurrences, orderedEdges, }) => artifactInventoryResultSchema.shape.manifest.parse({
|
|
134
|
+
schema_version: 1,
|
|
135
|
+
manifest_id: `agm_${digestCanonical({
|
|
136
|
+
schema_version: 1,
|
|
137
|
+
root_artifact_id: rootNode.artifact_id,
|
|
138
|
+
graph_sha256: graphSha256,
|
|
139
|
+
})}`,
|
|
140
|
+
root_artifact_id: rootNode.artifact_id,
|
|
141
|
+
root_sha256: rootNode.sha256,
|
|
142
|
+
root_format: rootNode.format,
|
|
143
|
+
graph_sha256: graphSha256,
|
|
144
|
+
node_count: orderedNodes.length,
|
|
145
|
+
occurrence_count: orderedOccurrences.length,
|
|
146
|
+
edge_count: orderedEdges.length,
|
|
147
|
+
});
|
|
148
|
+
const buildLimitations = (rootFormat, reader, integrityContradictions) => [
|
|
149
|
+
...inventoryLimitations(rootFormat, reader),
|
|
150
|
+
...(integrityContradictions.length === 0
|
|
151
|
+
? []
|
|
152
|
+
: [
|
|
153
|
+
`${String(integrityContradictions.length)} integrity contradiction(s) were recorded; mismatched content is observed-untrusted.`,
|
|
154
|
+
]),
|
|
155
|
+
];
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../../artifacts/ArtifactPaths.js";
|
|
2
|
+
import { ArtifactReaderFailure, } from "../../artifacts/ArtifactReader.js";
|
|
3
|
+
import { AsarArtifactReader } from "../../artifacts/AsarArtifactReader.js";
|
|
4
|
+
import { classifyArtifactContent, createArtifactNode, createOccurrence, nearestParent, } from "../ArtifactGraphConstruction.js";
|
|
5
|
+
import { STRICT_INTEGRITY_POLICY, } from "./types.js";
|
|
6
|
+
import { hashReadable, preflightEntry } from "./hash.js";
|
|
7
|
+
const UNAVAILABLE_UNPACKED_LIMITATION = "ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced.";
|
|
8
|
+
const emptyScan = () => ({ nodes: new Map(), occurrences: [], pendingContradictions: [] });
|
|
9
|
+
export const scanReader = async (reader, limits, signal, integrity = STRICT_INTEGRITY_POLICY) => {
|
|
10
|
+
const { nodes, occurrences, pendingContradictions } = emptyScan();
|
|
11
|
+
if (reader === undefined)
|
|
12
|
+
return { nodes, occurrences, pendingContradictions };
|
|
13
|
+
const context = {
|
|
14
|
+
reader,
|
|
15
|
+
limits,
|
|
16
|
+
signal,
|
|
17
|
+
integrity,
|
|
18
|
+
nodes,
|
|
19
|
+
occurrences,
|
|
20
|
+
pendingContradictions,
|
|
21
|
+
occurrenceByPath: new Map(),
|
|
22
|
+
registry: new ArtifactPathRegistry(),
|
|
23
|
+
totalBytes: 0,
|
|
24
|
+
};
|
|
25
|
+
await visitArtifactEntries(context, reader, "");
|
|
26
|
+
return { nodes, occurrences, pendingContradictions };
|
|
27
|
+
};
|
|
28
|
+
const visitArtifactEntries = async (context, currentReader, prefix) => {
|
|
29
|
+
for await (const entry of currentReader.entries(context.signal)) {
|
|
30
|
+
if (context.occurrences.length >= context.limits.maxEntries)
|
|
31
|
+
throw new ArtifactReaderFailure("limit", "Artifact entry limit exceeded");
|
|
32
|
+
const logicalPath = normalizeArtifactPath(prefix.length === 0 ? entry.path : `${prefix}/${entry.path}`, context.limits);
|
|
33
|
+
const expandableAsar = isExpandableAsar(entry, logicalPath);
|
|
34
|
+
context.registry.add(logicalPath, expandableAsar ? "directory" : entry.kind);
|
|
35
|
+
preflightEntry(entry, context.limits);
|
|
36
|
+
const parent = nearestParent(logicalPath, context.occurrenceByPath);
|
|
37
|
+
const occurrence = createOccurrence(entry, logicalPath, parent?.occurrence_id ?? null);
|
|
38
|
+
let digested;
|
|
39
|
+
try {
|
|
40
|
+
digested = await digestArtifactEntry(context, currentReader, entry, logicalPath);
|
|
41
|
+
}
|
|
42
|
+
catch (cause) {
|
|
43
|
+
if (!isUnavailableUnpackedEntry(cause, entry))
|
|
44
|
+
throw cause;
|
|
45
|
+
occurrence.hash_status = "unavailable";
|
|
46
|
+
occurrence.limitations.push(UNAVAILABLE_UNPACKED_LIMITATION);
|
|
47
|
+
}
|
|
48
|
+
if (digested !== undefined) {
|
|
49
|
+
context.nodes.set(digested.node.artifact_id, digested.node);
|
|
50
|
+
occurrence.artifact_id = digested.node.artifact_id;
|
|
51
|
+
occurrence.hash_status = digested.mismatched ? "mismatched" : "verified";
|
|
52
|
+
if (digested.mismatched)
|
|
53
|
+
occurrence.limitations.push("Declared integrity metadata contradicts observed bytes.");
|
|
54
|
+
}
|
|
55
|
+
context.occurrences.push(occurrence);
|
|
56
|
+
context.occurrenceByPath.set(logicalPath, occurrence);
|
|
57
|
+
if (expandableAsar && digested?.mismatched !== true)
|
|
58
|
+
await visitNestedAsar(context, entry.adapterKey, logicalPath);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const digestArtifactEntry = async (context, currentReader, entry, logicalPath) => {
|
|
62
|
+
if ((entry.kind !== "file" && entry.kind !== "slice") || entry.encrypted)
|
|
63
|
+
return undefined;
|
|
64
|
+
const remainingBytes = context.limits.maxTotalBytes - context.totalBytes;
|
|
65
|
+
if (remainingBytes <= 0)
|
|
66
|
+
throw new ArtifactReaderFailure("limit", "Artifact total byte limit exceeded");
|
|
67
|
+
if (entry.declaredSize !== null && entry.declaredSize > remainingBytes)
|
|
68
|
+
throw new ArtifactReaderFailure("limit", "Declared artifact bytes exceed remaining cumulative limit");
|
|
69
|
+
const digest = await hashReadable(await currentReader.open(entry, context.signal), Math.min(context.limits.maxEntryBytes, remainingBytes), context.signal);
|
|
70
|
+
context.totalBytes += digest.bytes;
|
|
71
|
+
const mismatched = entry.declaredSha256 !== null && entry.declaredSha256 !== digest.sha256;
|
|
72
|
+
if (mismatched && context.integrity.mode === "fail")
|
|
73
|
+
throw new ArtifactReaderFailure("integrity", `Artifact integrity metadata disagrees with content: ${logicalPath}`, undefined, {
|
|
74
|
+
logicalPath,
|
|
75
|
+
declaredSha256: entry.declaredSha256,
|
|
76
|
+
calculatedSha256: digest.sha256,
|
|
77
|
+
unpacked: entry.unpacked,
|
|
78
|
+
});
|
|
79
|
+
if (mismatched && entry.declaredSha256 !== null) {
|
|
80
|
+
if (context.pendingContradictions.length >= context.integrity.maxMismatches)
|
|
81
|
+
throw new ArtifactReaderFailure("limit", "Artifact integrity mismatch limit exceeded");
|
|
82
|
+
context.pendingContradictions.push({
|
|
83
|
+
logicalPath,
|
|
84
|
+
declaredSha256: entry.declaredSha256,
|
|
85
|
+
observedSha256: digest.sha256,
|
|
86
|
+
entryKind: entry.kind,
|
|
87
|
+
unpacked: entry.unpacked,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const classified = entry.kind === "slice"
|
|
91
|
+
? { kind: "universal-slice", format: "mach-o" }
|
|
92
|
+
: classifyArtifactContent(logicalPath, digest.prefix);
|
|
93
|
+
return {
|
|
94
|
+
node: createArtifactNode({
|
|
95
|
+
sha256: digest.sha256,
|
|
96
|
+
size: digest.bytes,
|
|
97
|
+
kind: classified.kind,
|
|
98
|
+
format: classified.format,
|
|
99
|
+
executable: entry.executable,
|
|
100
|
+
contentState: "embedded",
|
|
101
|
+
limitations: mismatched
|
|
102
|
+
? [
|
|
103
|
+
"Observed content contradicts declared integrity metadata and is untrusted.",
|
|
104
|
+
]
|
|
105
|
+
: [],
|
|
106
|
+
}),
|
|
107
|
+
mismatched,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const visitNestedAsar = async (context, adapterKey, logicalPath) => {
|
|
111
|
+
const nested = new AsarArtifactReader(adapterKey);
|
|
112
|
+
try {
|
|
113
|
+
await visitArtifactEntries(context, nested, logicalPath);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await nested.close();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const isExpandableAsar = (entry, logicalPath) => entry.kind === "file" &&
|
|
120
|
+
logicalPath.toLowerCase().endsWith(".asar") &&
|
|
121
|
+
entry.adapterKey.startsWith("/");
|
|
122
|
+
const isUnavailableUnpackedEntry = (cause, entry) => entry.unpacked &&
|
|
123
|
+
cause instanceof ArtifactReaderFailure &&
|
|
124
|
+
cause.reason === "unavailable";
|