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
|
@@ -14,10 +14,10 @@ import {} from "../domain/analysisErrorBase.js";
|
|
|
14
14
|
import { interfaceBuilderLimitsSchema } from "../domain/apple/interfaceBuilderGraph.js";
|
|
15
15
|
import { err, ok } from "../domain/result.js";
|
|
16
16
|
import { ArtifactReaderFailure } from "./ArtifactReader.js";
|
|
17
|
-
import {
|
|
17
|
+
import { artifactCapabilities } from "./ArtifactProviderMetadata.js";
|
|
18
|
+
import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
|
|
18
19
|
import { createEvidence } from "../domain/evidence.js";
|
|
19
20
|
import { createArtifactInspection } from "../domain/artifactInspection.js";
|
|
20
|
-
import { resolveArtifactIntegrityPolicy } from "./inventory/policy.js";
|
|
21
21
|
/** Read-only inventory and exclusively owned extraction provider. */
|
|
22
22
|
export class ArtifactProvider {
|
|
23
23
|
#capabilities;
|
|
@@ -29,7 +29,7 @@ export class ArtifactProvider {
|
|
|
29
29
|
this.#platform = platform;
|
|
30
30
|
}
|
|
31
31
|
identity() {
|
|
32
|
-
return
|
|
32
|
+
return ARTIFACT_GRAPH_PROVIDER;
|
|
33
33
|
}
|
|
34
34
|
capabilities() {
|
|
35
35
|
return this.#capabilities;
|
|
@@ -49,9 +49,9 @@ class ArtifactClient {
|
|
|
49
49
|
}
|
|
50
50
|
async execute(operation, parameters, options) {
|
|
51
51
|
if (operation === "health")
|
|
52
|
-
return ok(createAnalysisExecution(null,
|
|
52
|
+
return ok(createAnalysisExecution(null, ARTIFACT_GRAPH_PROVIDER));
|
|
53
53
|
if (!isArtifactOperation(operation))
|
|
54
|
-
return err(new AnalysisCapabilityUnavailableError(
|
|
54
|
+
return err(new AnalysisCapabilityUnavailableError(ARTIFACT_GRAPH_PROVIDER.id, operation, "Operation is not implemented by artifact graph provider."));
|
|
55
55
|
try {
|
|
56
56
|
if (operation === "inspect_artifact") {
|
|
57
57
|
const inspected = await this.inspectArtifact(parameters, options);
|
|
@@ -69,7 +69,7 @@ class ArtifactClient {
|
|
|
69
69
|
limits,
|
|
70
70
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
71
71
|
});
|
|
72
|
-
return ok(createAnalysisExecution(result,
|
|
72
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
73
73
|
limitations: result.limitations,
|
|
74
74
|
locations: result.documents.map(({ relative_path: path }) => ({
|
|
75
75
|
kind: "artifact-path",
|
|
@@ -108,7 +108,7 @@ class ArtifactClient {
|
|
|
108
108
|
});
|
|
109
109
|
if (standalone && result.archive_sha256 !== this.target.sha256)
|
|
110
110
|
throw new ArtifactReaderFailure("integrity", `Active archive digest changed: expected ${this.target.sha256}, observed ${result.archive_sha256}`);
|
|
111
|
-
return ok(createAnalysisExecution(result,
|
|
111
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
112
112
|
limitations: result.limitations,
|
|
113
113
|
locations: [{ kind: "artifact-path", path: result.archive_path }],
|
|
114
114
|
}));
|
|
@@ -126,7 +126,7 @@ class ArtifactClient {
|
|
|
126
126
|
outputRoot: parsed.output_root,
|
|
127
127
|
environment: this.environment,
|
|
128
128
|
}, options?.signal);
|
|
129
|
-
return ok(createAnalysisExecution(result,
|
|
129
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
130
130
|
rawResult: null,
|
|
131
131
|
limitations: result.limitations,
|
|
132
132
|
subject: subjectFor(this.target.sourcePath ?? this.target.path, result.manifest),
|
|
@@ -138,7 +138,7 @@ class ArtifactClient {
|
|
|
138
138
|
}
|
|
139
139
|
const parsed = artifactInventoryInputSchema.parse(parameters);
|
|
140
140
|
const result = await this.inventory(parsed, options);
|
|
141
|
-
return ok(createAnalysisExecution(result,
|
|
141
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
142
142
|
rawResult: null,
|
|
143
143
|
limitations: result.limitations,
|
|
144
144
|
subject: subjectFor(this.target.sourcePath ?? this.target.path, result.manifest),
|
|
@@ -172,7 +172,7 @@ class ArtifactClient {
|
|
|
172
172
|
page: parameters,
|
|
173
173
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
174
174
|
});
|
|
175
|
-
return ok(createAnalysisExecution(result,
|
|
175
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
176
176
|
limitations: result.limitations,
|
|
177
177
|
locations: result.catalogs.map(({ path }) => ({
|
|
178
178
|
kind: "artifact-path",
|
|
@@ -196,7 +196,7 @@ class ArtifactClient {
|
|
|
196
196
|
parameters,
|
|
197
197
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
198
198
|
});
|
|
199
|
-
return ok(createAnalysisExecution(result,
|
|
199
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
200
200
|
limitations: result.limitations,
|
|
201
201
|
locations: result.images.map(({ path }) => ({
|
|
202
202
|
kind: "artifact-path",
|
|
@@ -221,7 +221,7 @@ class ArtifactClient {
|
|
|
221
221
|
kind: "artifact-path",
|
|
222
222
|
path,
|
|
223
223
|
}));
|
|
224
|
-
const inventoryEvidence = createEvidence(subject,
|
|
224
|
+
const inventoryEvidence = createEvidence(subject, ARTIFACT_GRAPH_PROVIDER, {
|
|
225
225
|
operation: "inventory_artifact",
|
|
226
226
|
parameters: inventoryParameters,
|
|
227
227
|
result: inventory,
|
|
@@ -236,7 +236,7 @@ class ArtifactClient {
|
|
|
236
236
|
total: 1,
|
|
237
237
|
message: "inventory substep completed",
|
|
238
238
|
});
|
|
239
|
-
return ok(createAnalysisExecution(result,
|
|
239
|
+
return ok(createAnalysisExecution(result, ARTIFACT_GRAPH_PROVIDER, {
|
|
240
240
|
rawResult: null,
|
|
241
241
|
limitations: result.limitations,
|
|
242
242
|
subject,
|
|
@@ -247,9 +247,7 @@ class ArtifactClient {
|
|
|
247
247
|
return inventoryArtifact(this.target.sourcePath ?? this.target.path, {
|
|
248
248
|
environment: this.environment,
|
|
249
249
|
...(options?.signal === undefined ? {} : { signal: options.signal }),
|
|
250
|
-
integrity:
|
|
251
|
-
mode: parsed.integrity_policy,
|
|
252
|
-
}),
|
|
250
|
+
integrity: { mode: parsed.integrity_policy },
|
|
253
251
|
});
|
|
254
252
|
}
|
|
255
253
|
}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
|
|
2
2
|
import { ARTIFACT_ANALYSIS_OPERATIONS } from "../contracts/artifactToolContracts.js";
|
|
3
|
-
/** Identity of artifact inventory and owned extraction observations. */
|
|
4
|
-
export const ARTIFACT_PROVIDER_IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
|
|
5
|
-
const IDENTITY = ARTIFACT_PROVIDER_IDENTITY;
|
|
6
3
|
/** Declare artifact coverage without constructing readers or acquiring resources. */
|
|
7
4
|
export const artifactCapabilities = (platform = process.platform) => Object.freeze(ARTIFACT_ANALYSIS_OPERATIONS.map((operation) => {
|
|
8
5
|
const common = {
|
|
9
|
-
provider:
|
|
6
|
+
provider: ARTIFACT_GRAPH_PROVIDER,
|
|
10
7
|
operation,
|
|
11
8
|
effects: Object.freeze({
|
|
12
9
|
mutatesArtifact: false,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { constants, createReadStream } from "node:fs";
|
|
2
|
-
import { lstat, open, realpath } from "node:fs/promises";
|
|
1
|
+
import { constants, createReadStream, createWriteStream, } from "node:fs";
|
|
2
|
+
import { lstat, mkdtemp, open, realpath, rm, } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
3
5
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
-
import { Readable } from "node:stream";
|
|
6
|
+
import { Readable, Transform } from "node:stream";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
5
8
|
import { getRawHeader, listPackage, statFile, uncache } from "@electron/asar";
|
|
6
9
|
import { ArtifactReaderFailure, } from "./ArtifactReader.js";
|
|
7
10
|
import { closeAsarHandle, readValidatedAsarEntry } from "./AsarEntryStream.js";
|
|
@@ -15,24 +18,28 @@ import { closeAsarHandle, readValidatedAsarEntry } from "./AsarEntryStream.js";
|
|
|
15
18
|
export class AsarArtifactReader {
|
|
16
19
|
path;
|
|
17
20
|
openUnpackedFile;
|
|
21
|
+
removeSnapshot;
|
|
18
22
|
format = "asar";
|
|
19
23
|
#archiveSize;
|
|
20
24
|
#headerSize;
|
|
21
25
|
#entries = new Map();
|
|
22
|
-
|
|
26
|
+
#snapshot;
|
|
27
|
+
#snapshotRoot;
|
|
28
|
+
constructor(path, openUnpackedFile = open, removeSnapshot = (path) => rm(path, { recursive: true, force: true })) {
|
|
23
29
|
this.path = path;
|
|
24
30
|
this.openUnpackedFile = openUnpackedFile;
|
|
31
|
+
this.removeSnapshot = removeSnapshot;
|
|
25
32
|
}
|
|
26
33
|
async *entries(signal) {
|
|
27
34
|
let paths;
|
|
28
35
|
try {
|
|
29
|
-
|
|
36
|
+
await this.prepareContainer(undefined, signal);
|
|
37
|
+
const snapshotPath = this.#snapshotPath();
|
|
30
38
|
this.#resetArchiveState();
|
|
31
39
|
this.#entries.clear();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const archiveMetadata = await lstat(this.path);
|
|
40
|
+
paths = listPackage(snapshotPath, { isPack: false }).sort((left, right) => left.localeCompare(right, "en"));
|
|
41
|
+
this.#headerSize = getRawHeader(snapshotPath).headerSize;
|
|
42
|
+
const archiveMetadata = await lstat(snapshotPath);
|
|
36
43
|
if (!archiveMetadata.isFile() || archiveMetadata.isSymbolicLink())
|
|
37
44
|
throw new ArtifactReaderFailure("format", "ASAR container is not a regular file");
|
|
38
45
|
this.#archiveSize = archiveMetadata.size;
|
|
@@ -54,7 +61,7 @@ export class AsarArtifactReader {
|
|
|
54
61
|
: listed;
|
|
55
62
|
let metadata;
|
|
56
63
|
try {
|
|
57
|
-
metadata = statFile(this
|
|
64
|
+
metadata = statFile(this.#snapshotPath(), providerPath, false);
|
|
58
65
|
}
|
|
59
66
|
catch (cause) {
|
|
60
67
|
throw asarFailure(this.path, `stat ${path}`, cause);
|
|
@@ -138,7 +145,7 @@ export class AsarArtifactReader {
|
|
|
138
145
|
throw new ArtifactReaderFailure("format", `ASAR entry range is outside its container: ${producedEntry.path}`);
|
|
139
146
|
let handle;
|
|
140
147
|
try {
|
|
141
|
-
const openedHandle = await open(this
|
|
148
|
+
const openedHandle = await open(this.#snapshotPath(), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
142
149
|
handle = openedHandle;
|
|
143
150
|
const observed = await openedHandle.stat();
|
|
144
151
|
abortIfNeeded(signal);
|
|
@@ -161,16 +168,73 @@ export class AsarArtifactReader {
|
|
|
161
168
|
throw asarFailure(this.path, `read ${producedEntry.path}`, cause);
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
|
-
/**
|
|
165
|
-
|
|
171
|
+
/** Capture once per reader lifetime; bind all interpretation to these exact bytes. */
|
|
172
|
+
async prepareContainer(expectedSha256, signal) {
|
|
166
173
|
abortIfNeeded(signal);
|
|
167
|
-
|
|
174
|
+
if (this.#snapshot === undefined && this.#snapshotRoot !== undefined)
|
|
175
|
+
await this.close();
|
|
176
|
+
if (this.#snapshot === undefined) {
|
|
177
|
+
try {
|
|
178
|
+
this.#snapshotRoot = await mkdtemp(join(tmpdir(), "rea-asar-snapshot-"));
|
|
179
|
+
const snapshotPath = join(this.#snapshotRoot, "container.asar");
|
|
180
|
+
const hash = createHash("sha256");
|
|
181
|
+
const handle = await open(this.path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
182
|
+
const source = handle.createReadStream();
|
|
183
|
+
const digesting = new Transform({
|
|
184
|
+
transform(chunk, _encoding, callback) {
|
|
185
|
+
hash.update(chunk);
|
|
186
|
+
callback(null, chunk);
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
await pipeline(source, digesting, createWriteStream(snapshotPath, { flags: "wx", mode: 0o600 }), signal === undefined ? {} : { signal });
|
|
190
|
+
this.#snapshot = { path: snapshotPath, sha256: hash.digest("hex") };
|
|
191
|
+
}
|
|
192
|
+
catch (cause) {
|
|
193
|
+
const primary = signal?.aborted === true
|
|
194
|
+
? new ArtifactReaderFailure("cancelled", "ASAR operation cancelled", { cause })
|
|
195
|
+
: asarFailure(this.path, "snapshot", cause);
|
|
196
|
+
try {
|
|
197
|
+
await this.close();
|
|
198
|
+
}
|
|
199
|
+
catch (cleanupCause) {
|
|
200
|
+
throw ArtifactReaderFailure.withCleanup(primary, ArtifactReaderFailure.cleanupObservation(cleanupCause, this.path));
|
|
201
|
+
}
|
|
202
|
+
throw primary;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (expectedSha256 !== undefined &&
|
|
206
|
+
this.#snapshot.sha256 !== expectedSha256)
|
|
207
|
+
throw new ArtifactReaderFailure("integrity", `ASAR container changed before interpretation: ${this.path}`);
|
|
208
|
+
}
|
|
209
|
+
/** Read the same captured container that supplies headers and packed members. */
|
|
210
|
+
async openContainer(signal) {
|
|
211
|
+
await this.prepareContainer(undefined, signal);
|
|
212
|
+
return createReadStream(this.#snapshotPath(), signal === undefined ? {} : { signal });
|
|
168
213
|
}
|
|
169
|
-
close() {
|
|
170
|
-
|
|
214
|
+
async close() {
|
|
215
|
+
const root = this.#snapshotRoot;
|
|
216
|
+
if (root !== undefined) {
|
|
217
|
+
try {
|
|
218
|
+
await this.removeSnapshot(root);
|
|
219
|
+
}
|
|
220
|
+
catch (cause) {
|
|
221
|
+
throw ArtifactReaderFailure.withCleanup(cause, {
|
|
222
|
+
reason: `Could not remove ASAR snapshot: ${root}`,
|
|
223
|
+
resources: [root],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
this.#snapshotRoot = undefined;
|
|
227
|
+
}
|
|
228
|
+
if (this.#snapshot !== undefined)
|
|
229
|
+
uncache(this.#snapshot.path);
|
|
230
|
+
this.#snapshot = undefined;
|
|
171
231
|
this.#resetArchiveState();
|
|
172
232
|
this.#entries.clear();
|
|
173
|
-
|
|
233
|
+
}
|
|
234
|
+
#snapshotPath() {
|
|
235
|
+
if (this.#snapshot === undefined)
|
|
236
|
+
throw new ArtifactReaderFailure("integrity", "ASAR snapshot is not available");
|
|
237
|
+
return this.#snapshot.path;
|
|
174
238
|
}
|
|
175
239
|
provenance() {
|
|
176
240
|
return [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { lstat, open, opendir, realpath } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
|
5
5
|
import { ArtifactReaderFailure, } from "./ArtifactReader.js";
|
|
6
6
|
/** Deterministic, non-symlink-following directory reader. */
|
|
7
7
|
export class DirectoryArtifactReader {
|
|
@@ -39,7 +39,7 @@ export class DirectoryArtifactReader {
|
|
|
39
39
|
await handle.close().catch(() => undefined);
|
|
40
40
|
}
|
|
41
41
|
await withDirectoryIoContext("inspect directory", directory, () => assertContainedDirectory(root, directory));
|
|
42
|
-
children.sort(
|
|
42
|
+
children.sort(compareUnicodeCodePoints);
|
|
43
43
|
const directories = [];
|
|
44
44
|
for (const name of children) {
|
|
45
45
|
abortIfNeeded(signal);
|
|
@@ -1,53 +1,48 @@
|
|
|
1
1
|
import { mkdtemp, realpath, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { basename, join } from "node:path";
|
|
4
|
-
import { parse } from "plist";
|
|
5
|
-
import { z } from "zod";
|
|
6
4
|
import { ArtifactReaderFailure, } from "./ArtifactReader.js";
|
|
7
5
|
import { DirectoryArtifactReader } from "./DirectoryArtifactReader.js";
|
|
8
|
-
import { execFileOutput } from "../process/ExecFileOutput.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* later detach can eject one, `hdiutil info` can still list an ejected device
|
|
13
|
-
* briefly, and a volume can report busy just after it was read.
|
|
14
|
-
*/
|
|
6
|
+
import { execFileOutput, execFileOutputFailure, } from "../process/ExecFileOutput.js";
|
|
7
|
+
import { parseNativeDmgAttachOutput, parseNativeDmgInfo, resolveNativeDmgOwnership, } from "./NativeDmgOwnership.js";
|
|
8
|
+
const HDIUTIL_COMMAND_TIMEOUT_MS = 120_000;
|
|
9
|
+
// APFS ejects and busy reporting can lag a detach attempt.
|
|
15
10
|
const DETACH_SETTLE_DELAYS_MS = [250, 500, 1000];
|
|
16
11
|
const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
})),
|
|
27
|
-
});
|
|
12
|
+
class NativeDmgCommandFailure extends ArtifactReaderFailure {
|
|
13
|
+
stdout;
|
|
14
|
+
processNotStarted;
|
|
15
|
+
constructor(reason, message, stdout, options) {
|
|
16
|
+
super(reason, message, options?.cause === undefined ? undefined : { cause: options.cause });
|
|
17
|
+
this.stdout = stdout;
|
|
18
|
+
this.processNotStarted = options?.processNotStarted ?? false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
28
21
|
const createSystemHost = (environment) => ({
|
|
29
22
|
delay,
|
|
30
23
|
async run(arguments_, signal, options) {
|
|
31
24
|
try {
|
|
32
25
|
const { stdout, stderr } = await execFileOutput("/usr/bin/hdiutil", [...arguments_], {
|
|
33
26
|
env: environment,
|
|
27
|
+
stopSignal: "SIGTERM",
|
|
34
28
|
...(options === undefined ? {} : { timeout: options.timeoutMs }),
|
|
35
29
|
...(signal === undefined ? {} : { signal }),
|
|
36
30
|
});
|
|
37
31
|
return { stdout, stderr, exitCode: 0 };
|
|
38
32
|
}
|
|
39
33
|
catch (cause) {
|
|
34
|
+
const output = execFileOutputFailure(cause);
|
|
40
35
|
if (cause instanceof Error && cause.name === "AbortError")
|
|
41
|
-
throw new
|
|
42
|
-
const exitCode = processExitCode(cause);
|
|
36
|
+
throw new NativeDmgCommandFailure("cancelled", "DMG operation cancelled", output?.stdout ?? "", { cause });
|
|
37
|
+
const exitCode = typeof output?.code === "number" ? output.code : processExitCode(cause);
|
|
43
38
|
if (exitCode !== undefined)
|
|
44
39
|
return {
|
|
45
|
-
stdout: processOutput(cause, "stdout"),
|
|
46
|
-
stderr: processOutput(cause, "stderr"),
|
|
40
|
+
stdout: output?.stdout ?? processOutput(cause, "stdout"),
|
|
41
|
+
stderr: output?.stderr ?? processOutput(cause, "stderr"),
|
|
47
42
|
exitCode,
|
|
48
43
|
cause,
|
|
49
44
|
};
|
|
50
|
-
throw new
|
|
45
|
+
throw new NativeDmgCommandFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, output?.stdout ?? "", { cause, processNotStarted: processNotStarted(cause) });
|
|
51
46
|
}
|
|
52
47
|
},
|
|
53
48
|
});
|
|
@@ -59,7 +54,13 @@ export class NativeDmgArtifactReader {
|
|
|
59
54
|
#provenance = [];
|
|
60
55
|
#directory;
|
|
61
56
|
#devices = [];
|
|
57
|
+
#inventoryConfirmedDevices = new Set();
|
|
62
58
|
#mountRoot;
|
|
59
|
+
#baselineDevices = new Set();
|
|
60
|
+
#attachOutput;
|
|
61
|
+
#attachOutputReturned = false;
|
|
62
|
+
#attachMayHaveMounted = false;
|
|
63
|
+
#ownershipUncertainty;
|
|
63
64
|
constructor(path, host) {
|
|
64
65
|
this.path = path;
|
|
65
66
|
this.host = host;
|
|
@@ -88,7 +89,7 @@ export class NativeDmgArtifactReader {
|
|
|
88
89
|
return structuredClone(this.#provenance);
|
|
89
90
|
}
|
|
90
91
|
async close() {
|
|
91
|
-
let detachFailure;
|
|
92
|
+
let detachFailure = await this.#reconcileAttachState();
|
|
92
93
|
let remainingDevices = [];
|
|
93
94
|
for (const device of [...this.#devices].reverse()) {
|
|
94
95
|
const failure = await this.#detach(device);
|
|
@@ -97,10 +98,7 @@ export class NativeDmgArtifactReader {
|
|
|
97
98
|
detachFailure ??= failure;
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
//
|
|
101
|
-
// it, so a device whose own detach failed may be gone by the end of the
|
|
102
|
-
// pass, and hdiutil can list an ejected device or report busy briefly.
|
|
103
|
-
// Only a device that stays attached through every recheck is a failure.
|
|
101
|
+
// APFS container detach may briefly leave its backing image listed.
|
|
104
102
|
for (const delayMs of DETACH_SETTLE_DELAYS_MS) {
|
|
105
103
|
if (remainingDevices.length === 0)
|
|
106
104
|
break;
|
|
@@ -120,13 +118,19 @@ export class NativeDmgArtifactReader {
|
|
|
120
118
|
if (remainingDevices.length === 0)
|
|
121
119
|
detachFailure = undefined;
|
|
122
120
|
this.#devices = remainingDevices.reverse();
|
|
123
|
-
if (this.#devices.length === 0 &&
|
|
121
|
+
if (this.#devices.length === 0 &&
|
|
122
|
+
this.#mountRoot !== undefined &&
|
|
123
|
+
!this.#attachMayHaveMounted)
|
|
124
124
|
await rm(this.#mountRoot, { recursive: true, force: true }).then(() => {
|
|
125
125
|
this.#mountRoot = undefined;
|
|
126
126
|
this.#directory = undefined;
|
|
127
127
|
}, (cause) => {
|
|
128
128
|
detachFailure ??= cause;
|
|
129
129
|
});
|
|
130
|
+
if (this.#attachMayHaveMounted) {
|
|
131
|
+
detachFailure ??= new Error(this.#ownershipUncertainty ??
|
|
132
|
+
"hdiutil attachment ownership remains unknown");
|
|
133
|
+
}
|
|
130
134
|
if (detachFailure !== undefined)
|
|
131
135
|
throw new ArtifactReaderFailure("unavailable", `DMG detach or mount-root cleanup failed for ${JSON.stringify(this.path)}: ${failureMessage(detachFailure)}`, {
|
|
132
136
|
cause: detachFailure,
|
|
@@ -134,6 +138,11 @@ export class NativeDmgArtifactReader {
|
|
|
134
138
|
reason: failureMessage(detachFailure),
|
|
135
139
|
resources: [
|
|
136
140
|
...this.#devices.map((device) => `DMG device ${device}`),
|
|
141
|
+
...(this.#attachMayHaveMounted
|
|
142
|
+
? [
|
|
143
|
+
`DMG attachment ownership unknown at mount root ${this.#mountRoot ?? this.path}`,
|
|
144
|
+
]
|
|
145
|
+
: []),
|
|
137
146
|
...(this.#mountRoot === undefined
|
|
138
147
|
? []
|
|
139
148
|
: [`DMG mount root ${this.#mountRoot}`]),
|
|
@@ -145,7 +154,7 @@ export class NativeDmgArtifactReader {
|
|
|
145
154
|
async #detach(device) {
|
|
146
155
|
try {
|
|
147
156
|
await runChecked(this.host, ["detach", device], undefined, {
|
|
148
|
-
timeoutMs:
|
|
157
|
+
timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS,
|
|
149
158
|
});
|
|
150
159
|
this.#provenance.push(command(["detach", device], ["mount"]));
|
|
151
160
|
return undefined;
|
|
@@ -154,18 +163,28 @@ export class NativeDmgArtifactReader {
|
|
|
154
163
|
return (await this.#isAttached(device)) ? cause : undefined;
|
|
155
164
|
}
|
|
156
165
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
async #reconcileAttachState() {
|
|
167
|
+
if (!this.#attachMayHaveMounted)
|
|
168
|
+
return undefined;
|
|
169
|
+
try {
|
|
170
|
+
await this.#resolveAttachOwnership();
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
catch (cause) {
|
|
174
|
+
this.#ownershipUncertainty = failureMessage(cause);
|
|
175
|
+
return cause;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Treat unknown hdiutil state as attached during cleanup. */
|
|
162
179
|
async #isAttached(device) {
|
|
163
180
|
try {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
181
|
+
const parsed = await this.#readInfo();
|
|
182
|
+
const isListed = parsed.images.some((image) => image["system-entities"].some((entity) => entity["dev-entry"] === device));
|
|
183
|
+
if (isListed) {
|
|
184
|
+
this.#inventoryConfirmedDevices.add(device);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
return !this.#inventoryConfirmedDevices.has(device);
|
|
169
188
|
}
|
|
170
189
|
catch {
|
|
171
190
|
return true;
|
|
@@ -174,44 +193,62 @@ export class NativeDmgArtifactReader {
|
|
|
174
193
|
async attach(signal) {
|
|
175
194
|
await runChecked(this.host, ["verify", this.path], signal);
|
|
176
195
|
this.#provenance.push(command(["verify", this.path], ["read"]));
|
|
177
|
-
|
|
196
|
+
const baseline = await this.#readInfo();
|
|
197
|
+
this.#baselineDevices = new Set(baseline.images.flatMap((image) => image["system-entities"].map(({ "dev-entry": device }) => device)));
|
|
198
|
+
const mountRoot = await realpath(await mkdtemp(join(tmpdir(), "rea-dmg-")));
|
|
199
|
+
this.#mountRoot = mountRoot;
|
|
178
200
|
try {
|
|
201
|
+
this.#attachMayHaveMounted = true;
|
|
179
202
|
const attached = await runChecked(this.host, [
|
|
180
203
|
"attach",
|
|
181
204
|
"-readonly",
|
|
182
205
|
"-nobrowse",
|
|
183
206
|
"-plist",
|
|
184
207
|
"-mountroot",
|
|
185
|
-
|
|
208
|
+
mountRoot,
|
|
186
209
|
this.path,
|
|
187
|
-
], signal);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (this.#devices.length === 0)
|
|
210
|
+
], signal, { timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS });
|
|
211
|
+
this.#attachOutputReturned = true;
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
parsed = parseNativeDmgAttachOutput(attached.stdout);
|
|
215
|
+
}
|
|
216
|
+
catch (cause) {
|
|
217
|
+
throw new ArtifactReaderFailure("format", "hdiutil returned a malformed attachment plist", { cause });
|
|
218
|
+
}
|
|
219
|
+
if (parsed["system-entities"].length === 0)
|
|
198
220
|
throw new ArtifactReaderFailure("format", "hdiutil returned no attached devices");
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
221
|
+
this.#attachOutput = parsed;
|
|
222
|
+
const ownership = await this.#resolveAttachOwnership();
|
|
223
|
+
if (ownership !== "owned")
|
|
224
|
+
throw new ArtifactReaderFailure(ownership === "unknown" ? "unavailable" : "path", ownership === "unknown"
|
|
225
|
+
? `DMG attachment ownership is uncertain: ${this.#ownershipUncertainty ?? "no unique mount under the owned root"}`
|
|
226
|
+
: "hdiutil did not mount a new image beneath the owned root");
|
|
203
227
|
this.#provenance.push(command([
|
|
204
228
|
"attach",
|
|
205
229
|
"-readonly",
|
|
206
230
|
"-nobrowse",
|
|
207
231
|
"-plist",
|
|
208
232
|
"-mountroot",
|
|
209
|
-
|
|
233
|
+
mountRoot,
|
|
210
234
|
this.path,
|
|
211
235
|
], ["read", "mount"]));
|
|
212
|
-
this.#directory = new DirectoryArtifactReader(
|
|
236
|
+
this.#directory = new DirectoryArtifactReader(mountRoot);
|
|
213
237
|
}
|
|
214
238
|
catch (cause) {
|
|
239
|
+
if (cause instanceof NativeDmgCommandFailure && cause.stdout !== "") {
|
|
240
|
+
try {
|
|
241
|
+
this.#attachOutput = parseNativeDmgAttachOutput(cause.stdout);
|
|
242
|
+
this.#attachOutputReturned = true;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// The original command failure remains primary; fresh info still recovers mounts.
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (cause instanceof NativeDmgCommandFailure && cause.processNotStarted) {
|
|
249
|
+
this.#attachMayHaveMounted = false;
|
|
250
|
+
this.#ownershipUncertainty = undefined;
|
|
251
|
+
}
|
|
215
252
|
let cleanupFailure;
|
|
216
253
|
try {
|
|
217
254
|
await this.close();
|
|
@@ -224,8 +261,57 @@ export class NativeDmgArtifactReader {
|
|
|
224
261
|
throw cause;
|
|
225
262
|
}
|
|
226
263
|
}
|
|
264
|
+
async #readInfo() {
|
|
265
|
+
const info = await runChecked(this.host, ["info", "-plist"], undefined, {
|
|
266
|
+
timeoutMs: HDIUTIL_COMMAND_TIMEOUT_MS,
|
|
267
|
+
});
|
|
268
|
+
return parseNativeDmgInfo(info.stdout);
|
|
269
|
+
}
|
|
270
|
+
async #resolveAttachOwnership() {
|
|
271
|
+
const mountRoot = this.#mountRoot;
|
|
272
|
+
if (mountRoot === undefined)
|
|
273
|
+
throw new Error("DMG mount root is unavailable for ownership discovery");
|
|
274
|
+
const ownership = resolveNativeDmgOwnership({
|
|
275
|
+
info: await this.#readInfo(),
|
|
276
|
+
...(this.#attachOutput === undefined
|
|
277
|
+
? {}
|
|
278
|
+
: { attachOutput: this.#attachOutput }),
|
|
279
|
+
attachOutputReturned: this.#attachOutputReturned,
|
|
280
|
+
mountRoot,
|
|
281
|
+
baselineDevices: this.#baselineDevices,
|
|
282
|
+
});
|
|
283
|
+
if (ownership.status === "owned") {
|
|
284
|
+
this.#devices = [...ownership.devices];
|
|
285
|
+
if (ownership.source === "info")
|
|
286
|
+
for (const device of ownership.devices)
|
|
287
|
+
this.#inventoryConfirmedDevices.add(device);
|
|
288
|
+
this.#attachMayHaveMounted = false;
|
|
289
|
+
this.#ownershipUncertainty = undefined;
|
|
290
|
+
return "owned";
|
|
291
|
+
}
|
|
292
|
+
if (ownership.status === "unknown") {
|
|
293
|
+
this.#ownershipUncertainty = ownership.reason;
|
|
294
|
+
return "unknown";
|
|
295
|
+
}
|
|
296
|
+
this.#attachMayHaveMounted = false;
|
|
297
|
+
this.#ownershipUncertainty = undefined;
|
|
298
|
+
this.#devices = [];
|
|
299
|
+
return "none";
|
|
300
|
+
}
|
|
227
301
|
}
|
|
228
302
|
const failureMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
303
|
+
const processNotStarted = (cause) => {
|
|
304
|
+
if (!(cause instanceof Error))
|
|
305
|
+
return false;
|
|
306
|
+
const syscall = Reflect.get(cause, "syscall");
|
|
307
|
+
const code = Reflect.get(cause, "code");
|
|
308
|
+
const errno = Reflect.get(cause, "errno");
|
|
309
|
+
return (typeof syscall === "string" &&
|
|
310
|
+
syscall.startsWith("spawn ") &&
|
|
311
|
+
(typeof code === "string" ||
|
|
312
|
+
typeof code === "number" ||
|
|
313
|
+
typeof errno === "number"));
|
|
314
|
+
};
|
|
229
315
|
const runChecked = async (host, arguments_, signal, options) => {
|
|
230
316
|
let result;
|
|
231
317
|
try {
|
|
@@ -235,10 +321,9 @@ const runChecked = async (host, arguments_, signal, options) => {
|
|
|
235
321
|
if (cause instanceof ArtifactReaderFailure)
|
|
236
322
|
throw cause;
|
|
237
323
|
if (cause instanceof Error && cause.name === "AbortError")
|
|
238
|
-
throw new
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
throw new ArtifactReaderFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, { cause });
|
|
324
|
+
throw new NativeDmgCommandFailure("cancelled", "DMG operation cancelled", execFileOutputFailure(cause)?.stdout ?? "", { cause });
|
|
325
|
+
const output = execFileOutputFailure(cause);
|
|
326
|
+
throw new NativeDmgCommandFailure(commandFailureReason(cause, arguments_[0]), `hdiutil ${arguments_[0] ?? "operation"} failed: ${describeCommandFailure(cause, arguments_)}`, output?.stdout ?? "", { cause, processNotStarted: processNotStarted(cause) });
|
|
242
327
|
}
|
|
243
328
|
if (result.exitCode !== 0) {
|
|
244
329
|
const failure = {
|
|
@@ -253,9 +338,9 @@ const runChecked = async (host, arguments_, signal, options) => {
|
|
|
253
338
|
const reason = commandFailureReason(failure, arguments_[0]);
|
|
254
339
|
const details = describeCommandFailure(failure, arguments_);
|
|
255
340
|
const unknownVerifyFailure = arguments_[0] === "verify" && reason === "unavailable";
|
|
256
|
-
throw new
|
|
341
|
+
throw new NativeDmgCommandFailure(reason, unknownVerifyFailure
|
|
257
342
|
? `hdiutil verify failed; captured diagnostics do not establish the failure cause: ${details}`
|
|
258
|
-
: `hdiutil ${arguments_[0] ?? "operation"} failed: ${details}`, result.
|
|
343
|
+
: `hdiutil ${arguments_[0] ?? "operation"} failed: ${details}`, result.stdout, { cause: result.cause ?? failure });
|
|
259
344
|
}
|
|
260
345
|
return { stdout: result.stdout, exitCode: 0 };
|
|
261
346
|
};
|