rea-agents 6.2.0 → 6.3.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 +18 -20
- package/bridge/hopper_bridge.py +2 -0
- package/dist/android/JadxProvider.js +8 -2
- package/dist/android/JadxSession.js +51 -9
- package/dist/application/BinaryTargetResolver.js +63 -17
- package/dist/application/BrowserScenarioEvidence.js +2 -2
- package/dist/application/ClientConfigurationDocument.js +28 -0
- package/dist/application/ClientRegistrationStatus.js +19 -6
- package/dist/application/DirectAnalysis.js +12 -6
- package/dist/application/DirectAnalysisCleanup.js +0 -1
- package/dist/application/DirectAnalysisStatus.js +2 -5
- package/dist/application/Doctor.js +25 -34
- package/dist/application/DoctorDiagnostics.js +1 -1
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/InvestigationProviders.js +4 -4
- package/dist/application/JsonFiles.js +15 -15
- package/dist/application/LinuxHopper.js +2 -1
- package/dist/application/ReconstructionObligationCandidates.js +9 -2
- package/dist/application/ReconstructionObligationLedgerProjection.js +3 -1
- package/dist/application/RegularFileRead.js +17 -0
- package/dist/application/Setup.js +18 -7
- package/dist/application/SetupClientConfiguration.js +18 -7
- package/dist/application/SetupHost.js +6 -4
- package/dist/application/SetupPlan.js +19 -15
- package/dist/application/SetupSkill.js +57 -8
- package/dist/application/SupportedClients.js +43 -0
- package/dist/application/Uninstall.js +18 -2
- package/dist/application/UpdateMaintenance.js +6 -2
- package/dist/application/Utf8JsonInput.js +64 -0
- package/dist/application/WebScriptExportService.js +13 -6
- package/dist/application/android/AndroidAnalysisService.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +5 -4
- package/dist/application/binary/BinarySession.js +16 -0
- package/dist/application/binary/BinarySessionOpen.js +9 -1
- package/dist/application/javascript/ElectronActiveEvidence.js +2 -2
- package/dist/application/javascript/ElectronBoundaryAnalysis.js +2 -2
- package/dist/application/javascript/ElectronBoundaryGraphNative.js +5 -1
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -0
- package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +107 -46
- package/dist/application/javascript/JavaScriptApplicationService.js +1 -0
- package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +59 -45
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +0 -6
- package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +8 -18
- package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +34 -26
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +44 -12
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +6 -3
- package/dist/application/javascript/JavaScriptArtifactReconstructionInput.js +1 -0
- package/dist/application/javascript/JavaScriptJsonModules.js +2 -2
- package/dist/application/javascript/JavaScriptModuleRelationships.js +5 -17
- package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +11 -27
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +2 -2
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +8 -6
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +1 -1
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +7 -5
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +1 -1
- package/dist/application/javascript/JavaScriptSemanticGraphEvidence.js +1 -1
- package/dist/application/javascript/JavaScriptSemanticGraphFingerprintProjection.js +2 -2
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +2 -2
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +1 -1
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +4 -4
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +1 -1
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +10 -7
- package/dist/application/javascript/JavaScriptSemanticTraceService.js +15 -10
- package/dist/application/native/NativeCallRoutes.js +2 -4
- package/dist/artifacts/ArtifactProvider.js +14 -16
- package/dist/artifacts/ArtifactProviderMetadata.js +1 -4
- package/dist/artifacts/AsarArtifactReader.js +81 -17
- package/dist/artifacts/DirectoryArtifactReader.js +2 -2
- package/dist/artifacts/NativeDmgArtifactReader.js +152 -67
- package/dist/artifacts/NativeDmgOwnership.js +109 -0
- package/dist/artifacts/apple/KeyedArchiveReader.js +54 -46
- package/dist/artifacts/apple/MachoLoadCommandReader.js +36 -6
- package/dist/artifacts/extraction/ArtifactExtraction.js +29 -11
- package/dist/artifacts/inventory/ArtifactGraphConstruction.js +7 -8
- package/dist/artifacts/inventory/classify.js +33 -14
- package/dist/artifacts/inventory/hashStableRootArtifact.js +37 -0
- package/dist/artifacts/inventory/scanCanonical.js +28 -22
- package/dist/artifacts/inventory/scanReader.js +7 -11
- package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +65 -27
- package/dist/browser/CdpBrowserProvider.js +4 -3
- package/dist/browser/CdpCaptureStorageFingerprints.js +3 -3
- package/dist/browser/CdpElectronInspection.js +2 -2
- package/dist/browser/CdpElectronProvider.js +4 -3
- package/dist/browser/CdpElectronScripts.js +2 -2
- package/dist/browser/CdpWebMcpDiscovery.js +16 -13
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +6 -6
- package/dist/browser/PlaywrightScenarioBrowser.js +145 -42
- package/dist/browser/PlaywrightScenarioSession.js +3 -11
- package/dist/browser/WebSourceMapFetcher.js +32 -6
- package/dist/browser/assets/ScenarioScriptCapture.js +8 -12
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +13 -9
- package/dist/cli/electronCommands.js +9 -10
- package/dist/cli/javascriptApplicationAnalysis.js +2 -12
- package/dist/cli/jsonOutput.js +3 -2
- package/dist/cli/streamedJsonOutput.js +83 -4
- package/dist/cli/utilityCommands.js +2 -4
- package/dist/cli.js +5 -2
- package/dist/cliJsonFile.js +179 -0
- package/dist/cliJsonInput.js +47 -34
- package/dist/cliObservationOptions.js +4 -0
- package/dist/composition/auxiliaryAnalysisProviders.js +5 -4
- package/dist/composition/browserScenario.js +2 -2
- package/dist/composition/directAnalysis.js +2 -3
- package/dist/contracts/artifactComparisonExample.js +5 -5
- package/dist/contracts/errorSchemas.js +3 -1
- package/dist/contracts/functionComparisonExample.js +8 -0
- package/dist/contracts/inputSchemaPresentation.js +94 -3
- package/dist/contracts/javascript/electronToolContracts.js +2 -1
- package/dist/contracts/javascript/javascriptApplicationWorkflowExamples.js +3 -2
- package/dist/contracts/javascript/javascriptExportShapeComparisonExample.js +6 -3
- package/dist/contracts/javascript/javascriptRuntimeReconciliationExample.js +8 -7
- package/dist/contracts/managed/managedToolContracts.js +3 -2
- package/dist/contracts/mobileApplicationGraphExamples.js +5 -5
- package/dist/contracts/native/nativeToolContracts.js +1 -1
- package/dist/contracts/sessionLifecycleInputs.js +4 -3
- package/dist/contracts/sessionStatusContract.js +3 -2
- package/dist/contracts/sessionToolContracts.js +3 -2
- package/dist/contracts/sessionToolSchemas.js +3 -2
- package/dist/contracts/toolOutputSchemaGroups.js +2 -2
- package/dist/contracts/toolOutputSchemaPrimitives.js +2 -5
- package/dist/domain/analysisErrorBase.js +5 -0
- package/dist/domain/analysisErrorPresentation.js +4 -2
- package/dist/domain/analysisSnapshot.js +3 -1
- package/dist/domain/analysisView/analysisView.js +1 -3
- package/dist/domain/android/androidPartialObservation.js +36 -0
- package/dist/domain/apple/dylibResolution.js +1 -1
- package/dist/domain/artifactComparison.js +4 -4
- package/dist/domain/artifactGraph.js +1 -1
- package/dist/domain/artifactIdentity.js +8 -8
- package/dist/domain/artifactInspection.js +8 -8
- package/dist/domain/artifactInventoryEvidence.js +5 -5
- package/dist/domain/browserCompleteness.js +3 -2
- package/dist/domain/browserScenarioCaptureValues.js +2 -2
- package/dist/domain/browserScenarioNormalization.js +3 -3
- package/dist/domain/browserScenarioValues.js +3 -2
- package/dist/domain/callPath.js +8 -10
- package/dist/domain/canonicalDigest.js +7 -1
- package/dist/domain/canonicalOrdering.js +1 -13
- package/dist/domain/changedBehavior.js +2 -3
- package/dist/domain/comparisonSemantics.js +0 -5
- package/dist/domain/digests.js +0 -3
- package/dist/domain/evidence.js +10 -5
- package/dist/domain/evidenceBundle.js +51 -33
- package/dist/domain/firmware/firmwareAnalysis.js +4 -3
- package/dist/domain/functionComparison.js +2 -1
- package/dist/domain/functionComparisonNormalization.js +4 -3
- package/dist/domain/functionComparisonResults.js +2 -2
- package/dist/domain/functionDossierEvidence.js +2 -1
- package/dist/domain/hopperValues.js +11 -15
- package/dist/domain/javascript/electronActiveObservation.js +4 -3
- package/dist/domain/javascript/electronStaticAnalysisBrowser.js +2 -2
- package/dist/domain/javascript/electronStaticAnalysisIpc.js +9 -1
- package/dist/domain/javascript/electronStaticAnalysisNative.js +5 -3
- package/dist/domain/javascript/electronStaticAnalysisValues.js +8 -4
- package/dist/domain/javascript/javascriptApplicationAnalysis.js +3 -1
- package/dist/domain/javascript/javascriptApplicationChangeGraph.js +12 -8
- package/dist/domain/javascript/javascriptApplicationEvidenceSchemas.js +4 -0
- package/dist/domain/javascript/javascriptApplicationGraph.js +15 -13
- package/dist/domain/javascript/javascriptApplicationVersionComparison.js +4 -4
- package/dist/domain/javascript/javascriptApplicationVersionComparisonSchemas.js +37 -22
- package/dist/domain/javascript/javascriptApplicationVersionItems.js +45 -34
- package/dist/domain/javascript/javascriptApplicationVersionKeys.js +7 -7
- package/dist/domain/javascript/javascriptAstFingerprint.js +2 -2
- package/dist/domain/javascript/javascriptAstValues.js +22 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +3 -2
- package/dist/domain/javascript/javascriptExportShapeComparisonSchemas.js +31 -23
- package/dist/domain/javascript/javascriptExportShapeSelection.js +2 -2
- package/dist/domain/javascript/javascriptExportShapeVariants.js +3 -2
- package/dist/domain/javascript/javascriptFeatureSeed.js +5 -3
- package/dist/domain/javascript/javascriptFeatureTrace.js +7 -6
- package/dist/domain/javascript/javascriptFeatureTraceSchemas.js +17 -9
- package/dist/domain/javascript/javascriptFeatureTraversal.js +4 -4
- package/dist/domain/javascript/javascriptNativeHandoff.js +6 -6
- package/dist/domain/javascript/javascriptRuntimeObservation.js +8 -5
- package/dist/domain/javascript/javascriptRuntimeReconciliation.js +12 -6
- package/dist/domain/javascript/javascriptRuntimeReconciliationActive.js +9 -8
- package/dist/domain/javascript/javascriptRuntimeReconciliationCaptureParsing.js +212 -0
- package/dist/domain/javascript/javascriptRuntimeReconciliationGraph.js +5 -4
- package/dist/domain/javascript/javascriptRuntimeReconciliationInputValidation.js +28 -0
- package/dist/domain/javascript/javascriptRuntimeReconciliationMatching.js +5 -5
- package/dist/domain/javascript/javascriptRuntimeReconciliationParsing.js +12 -223
- package/dist/domain/javascript/javascriptRuntimeReconciliationResult.js +2 -2
- package/dist/domain/javascript/javascriptRuntimeReconciliationRuntime.js +15 -4
- package/dist/domain/javascript/javascriptRuntimeReconciliationSchemas.js +2 -2
- package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +7 -6
- package/dist/domain/javascript/javascriptSemanticAnalysis.js +200 -8
- package/dist/domain/javascript/javascriptSemanticCallResolution.js +9 -7
- package/dist/domain/javascript/javascriptSemanticChildProcesses.js +2 -2
- package/dist/domain/javascript/javascriptSemanticCoverage.js +20 -0
- package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +7 -9
- package/dist/domain/javascript/javascriptSemanticDataEffects.js +4 -11
- package/dist/domain/javascript/javascriptSemanticFingerprints.js +2 -2
- package/dist/domain/javascript/javascriptSemanticGraph.js +142 -35
- package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +19 -1
- package/dist/domain/javascript/javascriptSemanticIr.js +0 -4
- package/dist/domain/javascript/javascriptSemanticIterationSources.js +106 -0
- package/dist/domain/javascript/javascriptSemanticMemberMutations.js +497 -113
- package/dist/domain/javascript/javascriptSemanticMutationInitializers.js +190 -0
- package/dist/domain/javascript/javascriptSemanticMutationValues.js +41 -27
- package/dist/domain/javascript/javascriptSemanticObjects.js +2 -6
- package/dist/domain/javascript/javascriptSemanticPrimitives.js +7 -2
- package/dist/domain/javascript/javascriptSemanticProjection.js +20 -14
- package/dist/domain/javascript/javascriptSemanticPromises.js +15 -21
- package/dist/domain/javascript/javascriptSemanticPropertyPaths.js +23 -0
- package/dist/domain/javascript/javascriptSemanticProvenance.js +3 -3
- package/dist/domain/javascript/javascriptSemanticQuery.js +8 -2
- package/dist/domain/javascript/javascriptSemanticQueryIdentity.js +3 -2
- package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -9
- package/dist/domain/javascript/javascriptSemanticRequireOrigin.js +16 -10
- package/dist/domain/javascript/javascriptSemanticReturns.js +23 -27
- package/dist/domain/javascript/javascriptSemanticTraversal.js +6 -1
- package/dist/domain/javascript/javascriptSemanticValues.js +135 -24
- package/dist/domain/javascript/javascriptSourceMapPaths.js +11 -1
- package/dist/domain/javascript/javascriptSourceParser.js +34 -9
- package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -6
- package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +26 -15
- package/dist/domain/javascript/sourceToBundleComparison.js +7 -5
- package/dist/domain/javascript/sourceToBundleSignals.js +7 -7
- package/dist/domain/jsonSerialization.js +109 -55
- package/dist/domain/jsonShape.js +46 -19
- package/dist/domain/localPath.js +10 -4
- package/dist/domain/managed/managedApplicationGraph.js +10 -6
- package/dist/domain/managed/managedApplicationGraphNodes.js +82 -57
- package/dist/domain/managed/managedMemberComparisonMatch.js +6 -7
- package/dist/domain/native/nativeCallObservation.js +2 -4
- package/dist/domain/native/nativeValueFlow.js +5 -11
- package/dist/domain/native/objcSwiftMetadata.js +15 -17
- package/dist/domain/process/processCapture.js +11 -10
- package/dist/domain/process/processCaptureCoverage.js +8 -5
- package/dist/domain/process/processCaptureExample.js +43 -0
- package/dist/domain/process/processCaptureValidation.js +4 -3
- package/dist/domain/process/processComparison.js +0 -23
- package/dist/domain/process/processScenario.js +3 -3
- package/dist/domain/process/processTraceEvaluation.js +4 -4
- package/dist/domain/process/processTraceSpecification.js +1 -3
- package/dist/domain/providerCleanupError.js +6 -2
- package/dist/domain/reconstructionCoverage.js +2 -1
- package/dist/domain/reconstructionUnknowns.js +4 -3
- package/dist/domain/reconstructionVerification.js +3 -2
- package/dist/domain/referenceSourceGraph.js +7 -7
- package/dist/domain/referenceSourceImportParsing.js +34 -45
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +2 -1
- package/dist/domain/swiftSymbolClassification.js +6 -1
- package/dist/domain/symbolAnalysis.js +17 -2
- package/dist/domain/unicodeCodePointOrder.js +6 -0
- package/dist/domain/webBundleAnalyzerInspection.js +9 -1
- package/dist/domain/webCaptureDiff.js +71 -29
- package/dist/domain/webCaptureDiffSchemas.js +2 -8
- package/dist/domain/webInventory.js +3 -2
- package/dist/domain/webMcpDiscovery.js +4 -3
- package/dist/domain/webScriptExport.js +1 -1
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +1 -2
- package/dist/dotnet/ManagedStaticProvider.js +10 -7
- package/dist/dotnet/ManagedStaticProviderMetadata.js +1 -4
- package/dist/filesystem/RegularFile.js +41 -0
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraClient.js +14 -4
- package/dist/ghidra/GhidraInstallation.js +111 -10
- package/dist/ghidra/GhidraLauncher.js +43 -19
- package/dist/hopper/HopperCleanup.js +1 -1
- package/dist/ida/IdaOperationRunner.js +2 -0
- package/dist/ida/IdaSessionClient.js +4 -2
- package/dist/inspector/JavaScriptRuntimeScope.js +9 -5
- package/dist/inspector/V8InspectorCaptureProjection.js +46 -13
- package/dist/inspector/V8InspectorProvider.js +60 -8
- package/dist/main/shutdown.js +76 -32
- package/dist/main/transport.js +1 -1
- package/dist/native/AppleObjcDispatchFacets.js +3 -3
- package/dist/native/AppleObjcProperties.js +17 -7
- package/dist/native/LldbCallTracer.js +241 -75
- package/dist/native/NativeMacOSProvider.js +10 -5
- package/dist/native/NativeMacOSProviderMetadata.js +1 -2
- package/dist/process/ProcessOwnershipObservation.js +9 -13
- package/dist/process/capture/FilesystemSnapshot.js +88 -18
- package/dist/process/capture/ProcessCaptureLifecycle.js +13 -27
- package/dist/process/capture/ProcessFilesystemEffects.js +19 -4
- package/dist/process/capture/ProcessHarness.js +8 -21
- package/dist/server/analysisAdmission.js +8 -0
- package/dist/server/createServer.js +22 -23
- package/dist/server/registerApplicationTools/compareExportShapes.js +2 -1
- package/dist/server/registerApplicationTools/helpers.js +3 -2
- package/dist/server/registerArtifactTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +6 -4
- package/dist/server/registerEnhancedTools.js +78 -73
- package/dist/server/registerEvidenceTools.js +54 -46
- package/dist/server/registerManagedTools.js +7 -4
- package/dist/server/registerOfficialTools.js +31 -25
- package/dist/server/registerSessionTools.js +9 -3
- package/native/windows/build/manifest.json +1 -1
- package/package.json +6 -3
- package/skills/reverse-engineer-anything/SKILL.md +5 -5
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +9 -0
- package/dist/artifacts/inventory/policy.js +0 -2
- package/dist/domain/managed/managedApplicationGraphNodeCoverage.js +0 -6
- package/dist/server/registerNativeTools.js +0 -6
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { relative, resolve, sep } from "node:path";
|
|
2
|
+
import { parse } from "plist";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const systemEntitySchema = z.object({
|
|
5
|
+
"dev-entry": z.string().startsWith("/dev/"),
|
|
6
|
+
"mount-point": z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
const attachOutputSchema = z.object({
|
|
9
|
+
"system-entities": z.array(systemEntitySchema),
|
|
10
|
+
});
|
|
11
|
+
const infoOutputSchema = z.object({
|
|
12
|
+
images: z.array(z.object({
|
|
13
|
+
"system-entities": z.array(systemEntitySchema),
|
|
14
|
+
})),
|
|
15
|
+
});
|
|
16
|
+
/** Parse hdiutil's current image inventory. */
|
|
17
|
+
export const parseNativeDmgInfo = (stdout) => infoOutputSchema.parse(parse(stdout));
|
|
18
|
+
/** Parse hdiutil's attach response while preserving device and mount facts. */
|
|
19
|
+
export const parseNativeDmgAttachOutput = (stdout) => attachOutputSchema.parse(parse(stdout));
|
|
20
|
+
/** Classify only fresh image groups tied to the exclusively owned mount root. */
|
|
21
|
+
export const resolveNativeDmgOwnership = (input) => {
|
|
22
|
+
const rootedImages = input.info.images.filter((image) => image["system-entities"].some(({ "mount-point": mountPoint }) => mountPoint !== undefined && isBelowRoot(input.mountRoot, mountPoint)));
|
|
23
|
+
if (rootedImages.length > 1)
|
|
24
|
+
return {
|
|
25
|
+
status: "unknown",
|
|
26
|
+
reason: "multiple hdiutil image groups report mounts beneath the owned root",
|
|
27
|
+
};
|
|
28
|
+
const rootedImage = rootedImages[0];
|
|
29
|
+
if (rootedImage !== undefined) {
|
|
30
|
+
const hasOutsideMount = rootedImage["system-entities"].some(({ "mount-point": mountPoint }) => mountPoint !== undefined && !isBelowRoot(input.mountRoot, mountPoint));
|
|
31
|
+
const groupDevices = rootedImage["system-entities"].map(({ "dev-entry": device }) => device);
|
|
32
|
+
const devices = topLevelDevices(groupDevices);
|
|
33
|
+
const overlapsBaseline = groupDevices.some((device) => input.baselineDevices.has(device));
|
|
34
|
+
if (hasOutsideMount || overlapsBaseline || devices.length === 0)
|
|
35
|
+
return {
|
|
36
|
+
status: "unknown",
|
|
37
|
+
reason: hasOutsideMount
|
|
38
|
+
? "the image group contains a mount outside the owned root"
|
|
39
|
+
: overlapsBaseline
|
|
40
|
+
? "the image group includes a device present before attachment"
|
|
41
|
+
: "the owned mount group contains no detachable device",
|
|
42
|
+
};
|
|
43
|
+
return { status: "owned", devices, source: "info" };
|
|
44
|
+
}
|
|
45
|
+
if (input.attachOutputReturned) {
|
|
46
|
+
const attachOwnership = ownershipFromAttachOutput(input.attachOutput, input.mountRoot, input.baselineDevices);
|
|
47
|
+
if (attachOwnership.status === "owned") {
|
|
48
|
+
return attachOwnership;
|
|
49
|
+
}
|
|
50
|
+
if (attachOwnership.status === "unknown")
|
|
51
|
+
return attachOwnership;
|
|
52
|
+
}
|
|
53
|
+
const newlyListedDevice = input.info.images.some((image) => image["system-entities"].some(({ "dev-entry": device }) => !input.baselineDevices.has(device)));
|
|
54
|
+
if (newlyListedDevice)
|
|
55
|
+
return {
|
|
56
|
+
status: "unknown",
|
|
57
|
+
reason: "hdiutil lists devices absent from the pre-attach baseline, but none is tied uniquely to the owned root; ownership is unknown",
|
|
58
|
+
};
|
|
59
|
+
return { status: "none" };
|
|
60
|
+
};
|
|
61
|
+
const ownershipFromAttachOutput = (output, mountRoot, baselineDevices) => {
|
|
62
|
+
if (output === undefined)
|
|
63
|
+
return {
|
|
64
|
+
status: "unknown",
|
|
65
|
+
reason: "hdiutil attach succeeded but its ownership facts were malformed",
|
|
66
|
+
};
|
|
67
|
+
const entities = output["system-entities"];
|
|
68
|
+
const hasRootMount = entities.some(({ "mount-point": mountPoint }) => mountPoint !== undefined && isBelowRoot(mountRoot, mountPoint));
|
|
69
|
+
const hasOutsideMount = entities.some(({ "mount-point": mountPoint }) => mountPoint !== undefined && !isBelowRoot(mountRoot, mountPoint));
|
|
70
|
+
const devices = entities.map(({ "dev-entry": device }) => device);
|
|
71
|
+
const uniqueDevices = topLevelDevices(devices);
|
|
72
|
+
const overlapsBaseline = devices.some((device) => baselineDevices.has(device));
|
|
73
|
+
if (hasRootMount) {
|
|
74
|
+
if (hasOutsideMount || overlapsBaseline || uniqueDevices.length === 0)
|
|
75
|
+
return {
|
|
76
|
+
status: "unknown",
|
|
77
|
+
reason: hasOutsideMount
|
|
78
|
+
? "the attach result includes a mount outside the owned root"
|
|
79
|
+
: overlapsBaseline
|
|
80
|
+
? "the attach result includes a device present before attachment"
|
|
81
|
+
: "the attach result's owned mount has no detachable device",
|
|
82
|
+
};
|
|
83
|
+
return { status: "owned", devices: uniqueDevices, source: "attach" };
|
|
84
|
+
}
|
|
85
|
+
if (devices.length === 0)
|
|
86
|
+
return {
|
|
87
|
+
status: "unknown",
|
|
88
|
+
reason: "hdiutil attach succeeded without reporting device ownership facts",
|
|
89
|
+
};
|
|
90
|
+
if (devices.some((device) => !baselineDevices.has(device)))
|
|
91
|
+
return {
|
|
92
|
+
status: "unknown",
|
|
93
|
+
reason: "the attach result reports new devices without a mount beneath the owned root",
|
|
94
|
+
};
|
|
95
|
+
return { status: "none" };
|
|
96
|
+
};
|
|
97
|
+
const topLevelDevices = (devices) => {
|
|
98
|
+
const unique = [...new Set(devices)];
|
|
99
|
+
return unique.filter((device) => !unique.some((parent) => /^\/dev\/disk\d+$/u.test(parent) &&
|
|
100
|
+
device.startsWith(`${parent}s`) &&
|
|
101
|
+
/^\d+$/u.test(device.slice(parent.length + 1))));
|
|
102
|
+
};
|
|
103
|
+
const isBelowRoot = (root, path) => {
|
|
104
|
+
const child = relative(root, resolve(path));
|
|
105
|
+
return (child !== "" &&
|
|
106
|
+
child !== "." &&
|
|
107
|
+
child !== ".." &&
|
|
108
|
+
!child.startsWith(`..${sep}`));
|
|
109
|
+
};
|
|
@@ -23,7 +23,8 @@ export const decodeKeyedArchiveBytes = (bytes, selection) => {
|
|
|
23
23
|
const parsed = binary
|
|
24
24
|
? { value: parseBinary(bytes), omittedPrototypeKeys: 0 }
|
|
25
25
|
: parseXmlPropertyList(xmlText ?? "");
|
|
26
|
-
const
|
|
26
|
+
const { value: plistValue, unknownRealCount } = projectPlistValue(parsed.value);
|
|
27
|
+
const graph = projectKeyedArchive(plistValue, selection);
|
|
27
28
|
const numbers = createPlistNumberProjection(binary
|
|
28
29
|
? binaryPlistNumberLiterals(bytes)
|
|
29
30
|
: xmlPlistNumberLiterals(xmlText ?? ""));
|
|
@@ -48,15 +49,14 @@ export const decodeKeyedArchiveBytes = (bytes, selection) => {
|
|
|
48
49
|
...(parsed.omittedPrototypeKeys === 0
|
|
49
50
|
? []
|
|
50
51
|
: [omittedPrototypeKeysLimitation(parsed.omittedPrototypeKeys)]),
|
|
52
|
+
...(unknownRealCount === 0
|
|
53
|
+
? []
|
|
54
|
+
: [
|
|
55
|
+
`${String(unknownRealCount)} non-finite real value(s) are reported as { "$plist_type": "real", "value": null } because JSON evidence cannot preserve NaN vs infinity.`,
|
|
56
|
+
]),
|
|
51
57
|
],
|
|
52
58
|
};
|
|
53
59
|
};
|
|
54
|
-
const normalizePlist = (value) => {
|
|
55
|
-
const projected = projectPlistValue(value);
|
|
56
|
-
if (projected.unknownRealCount > 0)
|
|
57
|
-
throw new RangeError("Keyed archive contains a non-finite real");
|
|
58
|
-
return projected.value;
|
|
59
|
-
};
|
|
60
60
|
/**
|
|
61
61
|
* Point to the workflow that reads the selected kind of file on this host.
|
|
62
62
|
* decode_interface_builder is portable; inspect_plist requires macOS.
|
|
@@ -80,51 +80,59 @@ export const inspectBundleKeyedArchive = async (input) => {
|
|
|
80
80
|
: `Archive path must be a canonical relative bundle path without empty, '.', or '..' segments: ${selected.path}`);
|
|
81
81
|
const reader = new DirectoryArtifactReader(input.bundlePath);
|
|
82
82
|
try {
|
|
83
|
+
// Match inventory spelling while retaining the raw path for I/O and evidence.
|
|
84
|
+
const wanted = selected.path.normalize("NFC");
|
|
85
|
+
const matches = [];
|
|
83
86
|
for await (const entry of reader.entries(input.signal)) {
|
|
84
|
-
if (entry.path !==
|
|
87
|
+
if (entry.path.normalize("NFC") !== wanted)
|
|
85
88
|
continue;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
matches.push(entry);
|
|
90
|
+
}
|
|
91
|
+
if (matches.length > 1)
|
|
92
|
+
throw archivePathError("invalid_value", `Archive path matches ${String(matches.length)} Unicode-equivalent bundle entries; selection is ambiguous: ${selected.path}`);
|
|
93
|
+
const entry = matches[0];
|
|
94
|
+
if (entry === undefined)
|
|
95
|
+
throw archivePathError("invalid_value", `No regular file exists at ${selected.path} in the active app bundle.`);
|
|
96
|
+
if (entry.kind !== "file")
|
|
97
|
+
throw archivePathError("invalid_value", `Archive path selects a ${entry.kind}, not a regular file: ${selected.path}`);
|
|
98
|
+
if ((entry.declaredSize ?? 0) > MAX_BYTES)
|
|
99
|
+
throw new ArtifactReaderFailure("limit", "Keyed archive exceeds 64 MiB");
|
|
100
|
+
const stream = await reader.open(entry, input.signal);
|
|
101
|
+
const chunks = [];
|
|
102
|
+
let size = 0;
|
|
103
|
+
for await (const chunk of stream) {
|
|
104
|
+
if (input.signal?.aborted) {
|
|
105
|
+
stream.destroy();
|
|
106
|
+
throw new ArtifactReaderFailure("cancelled", "Archive inspection cancelled");
|
|
107
|
+
}
|
|
108
|
+
const bytes = z.instanceof(Buffer).parse(chunk);
|
|
109
|
+
size += bytes.length;
|
|
110
|
+
if (size > MAX_BYTES) {
|
|
111
|
+
stream.destroy();
|
|
89
112
|
throw new ArtifactReaderFailure("limit", "Keyed archive exceeds 64 MiB");
|
|
90
|
-
const stream = await reader.open(entry, input.signal);
|
|
91
|
-
const chunks = [];
|
|
92
|
-
let size = 0;
|
|
93
|
-
for await (const chunk of stream) {
|
|
94
|
-
if (input.signal?.aborted) {
|
|
95
|
-
stream.destroy();
|
|
96
|
-
throw new ArtifactReaderFailure("cancelled", "Archive inspection cancelled");
|
|
97
|
-
}
|
|
98
|
-
const bytes = z.instanceof(Buffer).parse(chunk);
|
|
99
|
-
size += bytes.length;
|
|
100
|
-
if (size > MAX_BYTES) {
|
|
101
|
-
stream.destroy();
|
|
102
|
-
throw new ArtifactReaderFailure("limit", "Keyed archive exceeds 64 MiB");
|
|
103
|
-
}
|
|
104
|
-
chunks.push(bytes);
|
|
105
113
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
chunks.push(bytes);
|
|
115
|
+
}
|
|
116
|
+
const bytes = Buffer.concat(chunks, size);
|
|
117
|
+
try {
|
|
118
|
+
return keyedArchiveResultSchema.parse({
|
|
119
|
+
target_sha256: input.targetSha256,
|
|
120
|
+
archive_path: entry.path,
|
|
121
|
+
archive_sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
122
|
+
...decodeKeyedArchiveBytes(bytes, selected),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (cause) {
|
|
126
|
+
if (cause instanceof AnalysisInputError)
|
|
127
|
+
throw cause;
|
|
128
|
+
// An intact file of another kind is not a damaged archive.
|
|
129
|
+
if (cause instanceof KeyedArchiveKindError)
|
|
130
|
+
throw new AnalysisUnsupportedTargetError("inspect_keyed_archive", join(input.bundlePath, entry.path), cause.message, {
|
|
131
|
+
cause,
|
|
132
|
+
remediationAction: keyedArchiveKindRemediation(cause.kind, input.platform ?? process.platform),
|
|
113
133
|
});
|
|
114
|
-
}
|
|
115
|
-
catch (cause) {
|
|
116
|
-
if (cause instanceof AnalysisInputError)
|
|
117
|
-
throw cause;
|
|
118
|
-
// An intact file of another kind is not a damaged archive.
|
|
119
|
-
if (cause instanceof KeyedArchiveKindError)
|
|
120
|
-
throw new AnalysisUnsupportedTargetError("inspect_keyed_archive", join(input.bundlePath, entry.path), cause.message, {
|
|
121
|
-
cause,
|
|
122
|
-
remediationAction: keyedArchiveKindRemediation(cause.kind, input.platform ?? process.platform),
|
|
123
|
-
});
|
|
124
|
-
throw new ArtifactReaderFailure(cause instanceof RangeError ? "limit" : "format", `Cannot decode selected Foundation keyed archive: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
125
|
-
}
|
|
134
|
+
throw new ArtifactReaderFailure(cause instanceof RangeError ? "limit" : "format", `Cannot decode selected Foundation keyed archive: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
|
|
126
135
|
}
|
|
127
|
-
throw archivePathError("invalid_value", `No regular file exists at ${selected.path} in the active app bundle.`);
|
|
128
136
|
}
|
|
129
137
|
finally {
|
|
130
138
|
await reader.close();
|
|
@@ -29,6 +29,41 @@ const LC_VERSION_MIN_IPHONEOS = 0x25;
|
|
|
29
29
|
const LC_VERSION_MIN_TVOS = 0x2f;
|
|
30
30
|
const LC_VERSION_MIN_WATCHOS = 0x30;
|
|
31
31
|
const LC_BUILD_VERSION = 0x32;
|
|
32
|
+
// `mach-o/loader.h` `PLATFORM_*` numbers observed via Apple linker output.
|
|
33
|
+
const PLATFORM_MACOS = 1;
|
|
34
|
+
const PLATFORM_IOS = 2;
|
|
35
|
+
const PLATFORM_TVOS = 3;
|
|
36
|
+
const PLATFORM_WATCHOS = 4;
|
|
37
|
+
const PLATFORM_IOSSIMULATOR = 7;
|
|
38
|
+
const PLATFORM_TVOSSIMULATOR = 8;
|
|
39
|
+
const PLATFORM_WATCHOSSIMULATOR = 9;
|
|
40
|
+
const CPU_TYPE_I386 = 7;
|
|
41
|
+
const CPU_TYPE_X86_64 = 0x01000007;
|
|
42
|
+
/**
|
|
43
|
+
* Legacy `LC_VERSION_MIN_*` commands predate explicit simulator platform
|
|
44
|
+
* IDs. Per Apple's dyld rules the image CPU type distinguishes simulator
|
|
45
|
+
* from device: Intel images are simulator, ARM images are device
|
|
46
|
+
* (tvOS simulator applies to x86_64 only). `LC_BUILD_VERSION` platform
|
|
47
|
+
* numbers are preserved verbatim; unknown future numbers stay unknown
|
|
48
|
+
* downstream, never mapped to nearest.
|
|
49
|
+
*/
|
|
50
|
+
const legacyVersionMinPlatform = (command, cpuType) => {
|
|
51
|
+
const isIntel = cpuType === CPU_TYPE_I386 || cpuType === CPU_TYPE_X86_64;
|
|
52
|
+
switch (command) {
|
|
53
|
+
case LC_VERSION_MIN_MACOSX:
|
|
54
|
+
return PLATFORM_MACOS;
|
|
55
|
+
case LC_VERSION_MIN_IPHONEOS:
|
|
56
|
+
return isIntel ? PLATFORM_IOSSIMULATOR : PLATFORM_IOS;
|
|
57
|
+
case LC_VERSION_MIN_TVOS:
|
|
58
|
+
return cpuType === CPU_TYPE_X86_64
|
|
59
|
+
? PLATFORM_TVOSSIMULATOR
|
|
60
|
+
: PLATFORM_TVOS;
|
|
61
|
+
case LC_VERSION_MIN_WATCHOS:
|
|
62
|
+
return isIntel ? PLATFORM_WATCHOSSIMULATOR : PLATFORM_WATCHOS;
|
|
63
|
+
default:
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
32
67
|
/** `mach-o/loader.h` `dylib_use_command`: `nameoff == 28` and this marker. */
|
|
33
68
|
const DYLIB_USE_MARKER = 0x1a741800;
|
|
34
69
|
const DYLIB_USE_WEAK_LINK = 0x1;
|
|
@@ -258,12 +293,7 @@ const decodeCommand = (command, body, index, slice) => {
|
|
|
258
293
|
slice.platforms.push(platform);
|
|
259
294
|
return;
|
|
260
295
|
}
|
|
261
|
-
const legacyPlatform =
|
|
262
|
-
[LC_VERSION_MIN_MACOSX, 1],
|
|
263
|
-
[LC_VERSION_MIN_IPHONEOS, 2],
|
|
264
|
-
[LC_VERSION_MIN_TVOS, 3],
|
|
265
|
-
[LC_VERSION_MIN_WATCHOS, 4],
|
|
266
|
-
]).get(command);
|
|
296
|
+
const legacyPlatform = legacyVersionMinPlatform(command, slice.cpu_type);
|
|
267
297
|
if (legacyPlatform !== undefined) {
|
|
268
298
|
if (body.byteLength < 16)
|
|
269
299
|
throw new MachoFormatIssue("malformed", `load command ${index} is too short for a version-min command`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
|
3
|
+
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
|
|
4
4
|
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
|
5
5
|
import { ArtifactPathRegistry, normalizeArtifactPath, } from "../ArtifactPaths.js";
|
|
6
6
|
import { ArtifactReaderFailure, } from "../ArtifactReader.js";
|
|
@@ -22,9 +22,26 @@ export const extractArtifact = async (input, signal) => {
|
|
|
22
22
|
signal,
|
|
23
23
|
environment: input.environment,
|
|
24
24
|
});
|
|
25
|
+
return materializeArtifactInventory(input, sourcePath, snapshot, signal);
|
|
26
|
+
};
|
|
27
|
+
/** Materialize a scanned inventory, verifying every current occurrence against it. */
|
|
28
|
+
export const materializeArtifactInventory = async (input, sourcePath, snapshot, signal) => {
|
|
29
|
+
abortIfNeeded(signal);
|
|
25
30
|
const selectedOccurrences = snapshot.occurrences.filter((occurrence) => (occurrence.entry_kind === "file" || occurrence.entry_kind === "slice") &&
|
|
26
31
|
occurrence.logical_path !== ".");
|
|
27
|
-
const
|
|
32
|
+
const regularPaths = new Set(selectedOccurrences.map(({ logical_path: path }) => path));
|
|
33
|
+
const activeOccurrences = selectedOccurrences.filter(({ logical_path }) => {
|
|
34
|
+
// Nested members remain represented by their containing regular file;
|
|
35
|
+
// only entries exposed by the active reader are materialized.
|
|
36
|
+
let path = logical_path;
|
|
37
|
+
for (let slash = path.lastIndexOf("/"); slash >= 0; slash = path.lastIndexOf("/")) {
|
|
38
|
+
path = path.slice(0, slash);
|
|
39
|
+
if (regularPaths.has(path))
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
const selectedIds = new Set(activeOccurrences.map(({ occurrence_id: id }) => id));
|
|
28
45
|
const occurrences = new Map();
|
|
29
46
|
const neededNodes = new Set();
|
|
30
47
|
collectOccurrences(snapshot.occurrences, selectedIds, occurrences, neededNodes);
|
|
@@ -35,7 +52,7 @@ export const extractArtifact = async (input, signal) => {
|
|
|
35
52
|
occurrences,
|
|
36
53
|
nodes,
|
|
37
54
|
};
|
|
38
|
-
const selected =
|
|
55
|
+
const selected = activeOccurrences.map((occurrence) => {
|
|
39
56
|
// REA never decrypts archive entries, so an encrypted entry makes the
|
|
40
57
|
// complete extraction unsupported rather than the archive invalid.
|
|
41
58
|
if (occurrence.encrypted)
|
|
@@ -66,7 +83,6 @@ const materializeSelection = async ({ input, sourcePath, inventory, selected, si
|
|
|
66
83
|
const extracted = [];
|
|
67
84
|
try {
|
|
68
85
|
output = await SafeOutputTree.create(input.outputRoot);
|
|
69
|
-
const materialized = [];
|
|
70
86
|
const registry = new ArtifactPathRegistry();
|
|
71
87
|
for await (const entry of reader.entries(signal)) {
|
|
72
88
|
const path = normalizeArtifactPath(entry.path);
|
|
@@ -87,13 +103,15 @@ const materializeSelection = async ({ input, sourcePath, inventory, selected, si
|
|
|
87
103
|
bytes_written: written.bytesWritten,
|
|
88
104
|
created: true,
|
|
89
105
|
});
|
|
90
|
-
|
|
106
|
+
byPath.delete(path);
|
|
91
107
|
}
|
|
108
|
+
if (byPath.size > 0)
|
|
109
|
+
throw new ArtifactReaderFailure("integrity", `Inventoried regular artifact entries were not materialized: ${[...byPath.keys()].sort(compareUnicodeCodePoints).join(", ")}`);
|
|
92
110
|
readerCloseAttempted = true;
|
|
93
111
|
await reader.close();
|
|
94
112
|
readerClosed = true;
|
|
95
|
-
extracted.sort((left, right) =>
|
|
96
|
-
const result = createExtractionResult(input, inventory,
|
|
113
|
+
extracted.sort((left, right) => compareUnicodeCodePoints(left.relative_path, right.relative_path));
|
|
114
|
+
const result = createExtractionResult(input, inventory, selected, extracted);
|
|
97
115
|
await output.commit();
|
|
98
116
|
return result;
|
|
99
117
|
}
|
|
@@ -144,15 +162,15 @@ const createExtractionResult = (input, inventory, selected, extracted) => {
|
|
|
144
162
|
source_manifest_id: inventory.manifest.manifest_id,
|
|
145
163
|
selected_occurrence_ids: selected
|
|
146
164
|
.map(({ occurrence }) => occurrence.occurrence_id)
|
|
147
|
-
.sort(
|
|
148
|
-
files_sha256:
|
|
165
|
+
.sort(compareUnicodeCodePoints),
|
|
166
|
+
files_sha256: digestCanonicalValue(extracted, "Artifact"),
|
|
149
167
|
output_root_alias: "$OUTPUT_ROOT",
|
|
150
168
|
};
|
|
151
169
|
return artifactExtractionResultSchema.parse({
|
|
152
170
|
manifest: inventory.manifest,
|
|
153
171
|
extraction_manifest: {
|
|
154
172
|
...extractionSemantic,
|
|
155
|
-
extraction_id: `aex_${
|
|
173
|
+
extraction_id: `aex_${digestCanonicalValue(extractionSemantic, "Artifact")}`,
|
|
156
174
|
},
|
|
157
175
|
output_root: input.outputRoot,
|
|
158
176
|
artifacts: extracted,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
|
2
|
+
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
|
|
3
3
|
import { artifactEdgeId, artifactIdForContent, occurrenceIdForLocation, } from "../../domain/artifactIdentity.js";
|
|
4
4
|
import { hasZipSignature, zipPackageFormatForPath, } from "../../domain/zipPackageFormat.js";
|
|
5
5
|
import { mzWindowsHeaderOffset, parseDosMzHeader } from "../../domain/dosMz.js";
|
|
@@ -12,7 +12,7 @@ export const indexOccurrencesByPath = (occurrences) => {
|
|
|
12
12
|
return byPath;
|
|
13
13
|
};
|
|
14
14
|
export const createOccurrence = (entry, path, parent) => ({
|
|
15
|
-
occurrence_id: `occ_${
|
|
15
|
+
occurrence_id: `occ_${digestCanonicalValue({ path, kind: entry.kind }, "Artifact")}`,
|
|
16
16
|
artifact_id: null,
|
|
17
17
|
parent_occurrence_id: parent,
|
|
18
18
|
logical_path: path,
|
|
@@ -36,7 +36,6 @@ export const createOccurrence = (entry, path, parent) => ({
|
|
|
36
36
|
limitations: [...entry.limitations],
|
|
37
37
|
});
|
|
38
38
|
/** Compare exact child names independently of host locale and traversal order. */
|
|
39
|
-
export const compareDirectoryChildNames = compareCodePoints;
|
|
40
39
|
export const materializeDirectoryNodes = (occurrences, nodes) => {
|
|
41
40
|
const directories = occurrences
|
|
42
41
|
.filter(({ entry_kind: kind }) => kind === "directory")
|
|
@@ -59,9 +58,9 @@ export const materializeDirectoryNodes = (occurrences, nodes) => {
|
|
|
59
58
|
artifact_id,
|
|
60
59
|
entry_kind,
|
|
61
60
|
}))
|
|
62
|
-
.sort((left, right) =>
|
|
61
|
+
.sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
|
|
63
62
|
const node = createArtifactNode({
|
|
64
|
-
sha256:
|
|
63
|
+
sha256: digestCanonicalValue({ kind: "directory", children }, "Artifact"),
|
|
65
64
|
size: 0,
|
|
66
65
|
format: "directory",
|
|
67
66
|
contentState: "virtual",
|
|
@@ -73,14 +72,14 @@ export const materializeDirectoryNodes = (occurrences, nodes) => {
|
|
|
73
72
|
};
|
|
74
73
|
export const createRootNode = (input) => createArtifactNode({
|
|
75
74
|
sha256: input.digest?.sha256 ??
|
|
76
|
-
|
|
75
|
+
digestCanonicalValue({
|
|
77
76
|
kind: "directory-root",
|
|
78
77
|
children: input.occurrences
|
|
79
78
|
.map(({ logical_path, artifact_id }) => ({
|
|
80
79
|
logical_path,
|
|
81
80
|
artifact_id,
|
|
82
81
|
}))
|
|
83
|
-
.sort((left, right) =>
|
|
82
|
+
.sort((left, right) => compareUnicodeCodePoints(left.logical_path, right.logical_path)),
|
|
84
83
|
}, "Artifact"),
|
|
85
84
|
size: input.digest?.bytes ?? 0,
|
|
86
85
|
format: input.digest === null
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NonRegularFileReadError, openRegularFile, sameRegularFileState, } from "../../filesystem/RegularFile.js";
|
|
2
2
|
import { classifyArtifactContent } from "./ArtifactGraphConstruction.js";
|
|
3
3
|
import { ARTIFACT_CLASSIFICATION_PREFIX_BYTES } from "../ArtifactHash.js";
|
|
4
4
|
import { hasZipSignature, zipPackageFormatForPath, } from "../../domain/zipPackageFormat.js";
|
|
5
|
+
import { ArtifactReaderFailure } from "../ArtifactReader.js";
|
|
6
|
+
import { hashStableRootArtifactHandle } from "./hashStableRootArtifact.js";
|
|
5
7
|
const classifyContainerExtension = (path) => {
|
|
6
8
|
const lower = path.toLowerCase();
|
|
7
9
|
const zipPackage = zipPackageFormatForPath(lower);
|
|
@@ -12,23 +14,40 @@ const classifyContainerExtension = (path) => {
|
|
|
12
14
|
return format;
|
|
13
15
|
return undefined;
|
|
14
16
|
};
|
|
15
|
-
|
|
17
|
+
/** Classify and hash one file root through the same stable open descriptor. */
|
|
18
|
+
export const classifyAndHashRoot = async (path, directory, expectedMetadata, signal) => {
|
|
16
19
|
if (directory)
|
|
17
|
-
return "directory";
|
|
18
|
-
const
|
|
19
|
-
if (extensionFormat !== undefined)
|
|
20
|
-
return extensionFormat;
|
|
21
|
-
const handle = await open(path, "r");
|
|
20
|
+
return { format: "directory", digest: null };
|
|
21
|
+
const handle = await openRootFile(path, signal);
|
|
22
22
|
try {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
const initial = await handle.stat();
|
|
24
|
+
if (!sameRegularFileState(expectedMetadata, initial))
|
|
25
|
+
throw new ArtifactReaderFailure("integrity", `Root artifact changed before inventory: ${path}`);
|
|
26
|
+
const format = classifyContainerExtension(path) ??
|
|
27
|
+
(await classifyOpenedRoot(path, handle));
|
|
28
|
+
const digest = await hashStableRootArtifactHandle(path, handle, initial, signal);
|
|
29
|
+
return { format, digest };
|
|
30
30
|
}
|
|
31
31
|
finally {
|
|
32
32
|
await handle.close();
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
const openRootFile = async (path, signal) => {
|
|
36
|
+
try {
|
|
37
|
+
return await openRegularFile(path, { symlinks: "reject", signal });
|
|
38
|
+
}
|
|
39
|
+
catch (cause) {
|
|
40
|
+
if (cause instanceof NonRegularFileReadError)
|
|
41
|
+
throw new ArtifactReaderFailure("format", `Artifact root is not a regular file: ${path}`, { cause });
|
|
42
|
+
throw cause;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const classifyOpenedRoot = async (path, handle) => {
|
|
46
|
+
const magic = Buffer.alloc(ARTIFACT_CLASSIFICATION_PREFIX_BYTES);
|
|
47
|
+
const observed = await handle.read(magic, 0, magic.length, 0);
|
|
48
|
+
const prefix = magic.subarray(0, observed.bytesRead);
|
|
49
|
+
if (hasZipSignature(prefix))
|
|
50
|
+
return "zip";
|
|
51
|
+
return classifyArtifactContent(path, prefix, (await handle.stat()).size)
|
|
52
|
+
.format;
|
|
53
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { ArtifactReaderFailure } from "../ArtifactReader.js";
|
|
3
|
+
import { NonRegularFileReadError, openRegularFile, sameRegularFileState, } from "../../filesystem/RegularFile.js";
|
|
4
|
+
import { abortIfNeeded, hashReadable, } from "../ArtifactHash.js";
|
|
5
|
+
/** Hash one stable regular root file from the same descriptor that was checked. */
|
|
6
|
+
export const hashStableRootArtifact = async (path, signal) => {
|
|
7
|
+
let handle;
|
|
8
|
+
try {
|
|
9
|
+
handle = await openRegularFile(path, {
|
|
10
|
+
symlinks: "reject",
|
|
11
|
+
signal,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (cause) {
|
|
15
|
+
if (cause instanceof NonRegularFileReadError)
|
|
16
|
+
throw new ArtifactReaderFailure("format", `Artifact root is not a regular file: ${path}`, { cause });
|
|
17
|
+
throw cause;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const initial = await handle.stat();
|
|
21
|
+
return await hashStableRootArtifactHandle(path, handle, initial, signal);
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await handle.close();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/** Hash and revalidate the same open descriptor used for root classification. */
|
|
28
|
+
export const hashStableRootArtifactHandle = async (path, handle, initial, signal) => {
|
|
29
|
+
const digest = await hashReadable(handle.createReadStream({ autoClose: false }), signal);
|
|
30
|
+
abortIfNeeded(signal);
|
|
31
|
+
const [opened, currentPath] = await Promise.all([handle.stat(), lstat(path)]);
|
|
32
|
+
if (!sameRegularFileState(initial, opened) ||
|
|
33
|
+
!sameRegularFileState(initial, currentPath) ||
|
|
34
|
+
digest.bytes !== initial.size)
|
|
35
|
+
throw new ArtifactReaderFailure("integrity", `Root artifact changed during inventory: ${path}`);
|
|
36
|
+
return digest;
|
|
37
|
+
};
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
|
2
|
+
import { AsarArtifactReader } from "../AsarArtifactReader.js";
|
|
3
3
|
import { lstat } from "node:fs/promises";
|
|
4
4
|
import { ArtifactReaderFailure, } from "../ArtifactReader.js";
|
|
5
5
|
import { artifactInventoryResultSchema, } from "../../domain/artifactGraph.js";
|
|
6
6
|
import { createArtifactEdges, createRootNode, indexOccurrencesByPath, materializeDirectoryNodes, rekeyOccurrences, rootOccurrenceFor, } from "./ArtifactGraphConstruction.js";
|
|
7
7
|
import { artifactContradictionId, artifactGraphDigest, artifactManifestId, } from "../../domain/artifactIdentity.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { classifyAndHashRoot } from "./classify.js";
|
|
9
|
+
import {} from "../ArtifactHash.js";
|
|
10
|
+
import { hashStableRootArtifact } from "./hashStableRootArtifact.js";
|
|
10
11
|
import { createReader, inventoryLimitations } from "./reader.js";
|
|
11
12
|
import { scanReader, } from "./scanReader.js";
|
|
12
13
|
import { STRICT_INTEGRITY_POLICY, } from "./types.js";
|
|
13
14
|
export const scanCanonicalArtifactInventory = async (path, options = {}, readerFactory = createReader) => {
|
|
14
15
|
const integrity = options.integrity ?? STRICT_INTEGRITY_POLICY;
|
|
15
16
|
const metadata = await lstat(path);
|
|
16
|
-
const rootFormat = await
|
|
17
|
-
const rootDigest = metadata.isDirectory()
|
|
18
|
-
? null
|
|
19
|
-
: await hashReadable(createReadStream(path), options.signal);
|
|
17
|
+
const { format: rootFormat, digest: rootDigest } = await classifyAndHashRoot(path, metadata.isDirectory(), metadata, options.signal);
|
|
20
18
|
const reader = await readerFactory(path, rootFormat, options.environment ?? {}, options.signal);
|
|
19
|
+
const ownedReaders = reader === undefined ? [] : [reader];
|
|
21
20
|
let outcome;
|
|
22
21
|
try {
|
|
23
|
-
|
|
22
|
+
if (reader instanceof AsarArtifactReader)
|
|
23
|
+
await reader.prepareContainer(rootDigest?.sha256, options.signal);
|
|
24
|
+
const { nodes, occurrences, pendingContradictions } = await scanReader(reader, ownedReaders, options.signal, integrity);
|
|
24
25
|
outcome = {
|
|
25
26
|
kind: "completed",
|
|
26
27
|
snapshot: await buildInventorySnapshot({
|
|
@@ -39,16 +40,21 @@ export const scanCanonicalArtifactInventory = async (path, options = {}, readerF
|
|
|
39
40
|
catch (cause) {
|
|
40
41
|
outcome = { kind: "failed", cause };
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
let cleanupFailure;
|
|
44
|
+
for (const owned of ownedReaders.reverse()) {
|
|
45
|
+
try {
|
|
46
|
+
await owned.close();
|
|
47
|
+
}
|
|
48
|
+
catch (cleanupCause) {
|
|
49
|
+
const cleanup = ArtifactReaderFailure.cleanupObservation(cleanupCause, `artifact reader for ${path}`);
|
|
50
|
+
cleanupFailure = ArtifactReaderFailure.withCleanup(cleanupFailure ??
|
|
51
|
+
(outcome.kind === "failed" ? outcome.cause : cleanupCause), cleanup, outcome.kind === "completed"
|
|
52
|
+
? { kind: "artifact-inventory", inventory: outcome.snapshot }
|
|
53
|
+
: undefined);
|
|
54
|
+
}
|
|
51
55
|
}
|
|
56
|
+
if (cleanupFailure !== undefined)
|
|
57
|
+
throw cleanupFailure;
|
|
52
58
|
if (outcome.kind === "failed")
|
|
53
59
|
throw outcome.cause;
|
|
54
60
|
return outcome.snapshot;
|
|
@@ -142,14 +148,14 @@ const buildIntegrityContradictions = (pending, occurrenceByPath, occurrenceById,
|
|
|
142
148
|
const verifyRootDigest = async (path, rootDigest, signal) => {
|
|
143
149
|
if (rootDigest === null)
|
|
144
150
|
return;
|
|
145
|
-
const verified = await
|
|
151
|
+
const verified = await hashStableRootArtifact(path, signal);
|
|
146
152
|
if (verified.sha256 !== rootDigest.sha256 ||
|
|
147
153
|
verified.bytes !== rootDigest.bytes)
|
|
148
154
|
throw new ArtifactReaderFailure("integrity", "Root artifact changed during inventory");
|
|
149
155
|
};
|
|
150
|
-
const sortNodes = (nodes) => nodes.sort((left, right) =>
|
|
151
|
-
const sortOccurrences = (occurrences) => occurrences.sort((left, right) =>
|
|
152
|
-
const sortEdges = (edges) => edges.sort((left, right) =>
|
|
156
|
+
const sortNodes = (nodes) => nodes.sort((left, right) => compareUnicodeCodePoints(left.artifact_id, right.artifact_id));
|
|
157
|
+
const sortOccurrences = (occurrences) => occurrences.sort((left, right) => compareUnicodeCodePoints(left.logical_path, right.logical_path));
|
|
158
|
+
const sortEdges = (edges) => edges.sort((left, right) => compareUnicodeCodePoints(left.edge_id, right.edge_id));
|
|
153
159
|
const buildManifest = ({ rootNode, rootFormat, graphSha256, orderedNodes, orderedOccurrences, orderedEdges, }) => artifactInventoryResultSchema.shape.manifest.parse({
|
|
154
160
|
manifest_id: artifactManifestId(rootNode.artifact_id, graphSha256),
|
|
155
161
|
root_artifact_id: rootNode.artifact_id,
|