rea-agents 6.0.0 → 6.1.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 +23 -17
- package/bridge/android/ReaJadxBridge.java +14 -5
- package/bridge/ghidra/ReaGhidraBridge.java +173 -47
- package/bridge/hopper_bridge.py +310 -128
- package/bridge/mitmproxy/capture.py +0 -2
- package/bridge/native/rea_lldb_tracer.py +630 -94
- package/bridge/process/ProcessRunTokenReader.swift +39 -5
- package/bridge/pwntools/recorded_crash_impl.py +4 -0
- package/dist/android/JadxConfiguration.js +196 -33
- package/dist/android/JadxMcpTransport.js +4 -0
- package/dist/android/JadxProtocol.js +6 -0
- package/dist/android/JadxProvider.js +19 -2
- package/dist/android/JadxSession.js +23 -4
- package/dist/application/AnalysisContextQueries.js +8 -35
- package/dist/application/AnalysisDocument.js +32 -0
- package/dist/application/AppBundleExecutable.js +67 -21
- package/dist/application/CallPathTracing.js +19 -3
- package/dist/application/CapabilityInventory.js +6 -3
- package/dist/application/ClientConfigurationDocument.js +871 -6
- package/dist/application/ClientRegistrationIdentity.js +26 -5
- package/dist/application/ClientRegistrationStatus.js +29 -14
- package/dist/application/DirectAnalysis.js +27 -37
- package/dist/application/Doctor.js +4 -2
- package/dist/application/DoctorScope.js +25 -13
- package/dist/application/EnhancedLiteralTracing.js +22 -25
- package/dist/application/EnhancedTools.js +13 -11
- package/dist/application/EvidenceBundleFiles.js +23 -11
- package/dist/application/FunctionAnalysisEvidence.js +2 -3
- package/dist/application/InventoryProjectionEvidence.js +4 -3
- package/dist/application/InvestigationProviders.js +3 -2
- package/dist/application/JsonFiles.js +50 -28
- package/dist/application/ProcedureAddressResolution.js +11 -0
- package/dist/application/ReconstructionCoverageService.js +7 -4
- package/dist/application/ReconstructionObligationLedgerService.js +4 -3
- package/dist/application/Setup.js +13 -1
- package/dist/application/SetupClientConfiguration.js +10 -15
- package/dist/application/SetupHost.js +5 -2
- package/dist/application/SetupPlan.js +5 -3
- package/dist/application/SupportedClients.js +34 -1
- package/dist/application/Uninstall.js +4 -3
- package/dist/application/Update.js +18 -3
- package/dist/application/UpdateRuntime.js +46 -25
- package/dist/application/WebModuleTraceService.js +4 -2
- package/dist/application/WebNetworkCaptureService.js +4 -2
- package/dist/application/WebRuntimeService.js +5 -3
- package/dist/application/WebScriptExportService.js +2 -2
- package/dist/application/WebSourceLocationService.js +4 -2
- package/dist/application/WorkflowEvidence.js +82 -0
- package/dist/application/binary/AnalysisProviderEvaluation.js +21 -5
- package/dist/application/binary/AnalysisProviderRegistry.js +2 -0
- package/dist/application/binary/AnalysisSnapshotCache.js +3 -6
- package/dist/application/binary/BinarySession.js +95 -68
- package/dist/application/binary/BinarySessionOpen.js +3 -3
- package/dist/application/binary/BinarySessionRecords.js +25 -3
- package/dist/application/binary/BinarySessionStatus.js +17 -1
- package/dist/application/binary/CompositeProvider.js +13 -0
- package/dist/application/binary/LazyAnalysisProvider.js +3 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +4 -2
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +4 -2
- package/dist/application/evm/EvmInterfaceService.js +4 -2
- package/dist/application/investigation/EvidenceLedger.js +21 -7
- package/dist/application/investigation/InvestigationRecords.js +8 -0
- package/dist/application/javascript/JavaScriptAnalysisControl.js +34 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +24 -3
- package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +19 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +22 -15
- package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +5 -5
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +5 -4
- package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +39 -7
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +6 -3
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +11 -3
- package/dist/application/javascript/JavaScriptModuleRelationships.js +37 -8
- package/dist/application/javascript/JavaScriptRecoveryService.js +4 -2
- package/dist/application/javascript/JavaScriptRuntimeObservationService.js +3 -1
- package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +63 -61
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +23 -1
- package/dist/application/managed/ManagedApplicationGraphService.js +4 -3
- package/dist/application/managed/ManagedMemberComparisonService.js +7 -4
- package/dist/application/managed/ManagedNativeVerificationService.js +4 -3
- package/dist/application/managed/ManagedReconstructionService.js +4 -3
- package/dist/application/process/ProcessCli.js +14 -6
- package/dist/application/process/ProcessEvidence.js +4 -3
- package/dist/application/workflowInputError.js +18 -0
- package/dist/artifacts/ArtifactProvider.js +12 -2
- package/dist/artifacts/MachOSliceArtifactReader.js +98 -14
- package/dist/artifacts/NativeDmgArtifactReader.js +13 -6
- package/dist/artifacts/apple/AppleAssetCatalogAnalysis.js +22 -3
- package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +176 -56
- package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +195 -0
- package/dist/artifacts/apple/KeyedArchiveReader.js +36 -22
- package/dist/artifacts/apple/MachoLoadCommandReader.js +92 -31
- package/dist/artifacts/apple/NibArchive.js +81 -14
- package/dist/artifacts/apple/NibViewHierarchy.js +10 -3
- package/dist/artifacts/apple/PlistNumberLiterals.js +164 -0
- package/dist/artifacts/inventory/ArtifactGraphConstruction.js +13 -8
- package/dist/artifacts/inventory/scanCanonical.js +11 -14
- package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +6 -2
- package/dist/browser/CdpCaptureCompleteness.js +2 -0
- package/dist/browser/CdpCaptureEventHandlers.js +76 -11
- package/dist/browser/CdpCaptureEventTypes.js +5 -1
- package/dist/browser/CdpCaptureEvents.js +25 -0
- package/dist/browser/CdpConnection.js +30 -11
- package/dist/browser/CdpOptionalCommand.js +2 -3
- package/dist/browser/CdpPageCapture.js +23 -1
- package/dist/browser/PlaywrightElectronActiveActions.js +32 -23
- package/dist/browser/PlaywrightElectronActiveProjection.js +141 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +123 -124
- package/dist/browser/WebSourceMapFetcher.js +426 -72
- package/dist/browser/assets/LocalWebScriptArtifacts.js +2 -2
- package/dist/browser/history/CaptureFailures.js +37 -1
- package/dist/browser/history/CaptureRelease.js +5 -0
- package/dist/browser/history/HarCaptureAdapter.js +3 -3
- package/dist/browser/history/HarJson.js +3 -2
- package/dist/browser/history/HistoricalCaptureDecoder.js +4 -2
- package/dist/cli/analysisSelector.js +23 -0
- package/dist/cli/applicationCommands.js +2 -3
- package/dist/cli/coreBinaryCommands.js +70 -12
- package/dist/cli/electronCommands.js +7 -6
- package/dist/cli/javascriptApplicationAnalysis.js +7 -3
- package/dist/cli/jsonOutput.js +4 -99
- package/dist/cli/nativeCallCommands.js +2 -3
- package/dist/cli/webRuntimeCommands.js +8 -8
- package/dist/cli.js +2 -1
- package/dist/cliCommandNames.js +0 -1
- package/dist/cliJsonInput.js +9 -4
- package/dist/config/environment.js +4 -0
- package/dist/config/ghidraStartupTimeout.js +17 -0
- package/dist/config/mcpResponseBudget.js +22 -0
- package/dist/config/parseConfig.js +2 -1
- package/dist/contracts/enhancedInputs.js +5 -3
- package/dist/contracts/enhancedToolContracts.js +3 -3
- package/dist/contracts/hopperMemoryContracts.js +2 -5
- package/dist/contracts/javascript/electronToolContracts.js +1 -1
- package/dist/contracts/managed/managedToolContracts.js +2 -4
- package/dist/contracts/native/nativeToolContracts.js +4 -2
- package/dist/contracts/officialToolContracts.js +14 -15
- package/dist/contracts/promptContracts.js +3 -3
- package/dist/contracts/sessionLifecycleInputs.js +3 -7
- package/dist/contracts/sessionStatusContract.js +2 -4
- package/dist/contracts/sessionToolContracts.js +4 -6
- package/dist/contracts/sessionToolSchemas.js +2 -5
- package/dist/contracts/toolContractExamples.js +0 -1
- package/dist/contracts/toolContractHelpers.js +4 -1
- package/dist/contracts/toolEffects.js +0 -1
- package/dist/contracts/toolOutputSchemaGroups.js +28 -27
- package/dist/contracts/toolOutputSchemaPrimitives.js +18 -0
- package/dist/contracts/toolSchemaMetadata.js +69 -10
- package/dist/contracts/webNetworkCaptureToolContracts.js +1 -1
- package/dist/domain/analysisErrorBase.js +8 -3
- package/dist/domain/analysisErrorCore.js +3 -1
- package/dist/domain/analysisErrorPresentation.js +72 -17
- package/dist/domain/analysisErrorProjection.js +106 -27
- package/dist/domain/android/androidAnalysis.js +2 -4
- package/dist/domain/android/androidApplication.js +17 -9
- package/dist/domain/apple/appleApplication.js +41 -14
- package/dist/domain/apple/dyldEnvironment.js +73 -0
- package/dist/domain/apple/dylibResolution.js +120 -9
- package/dist/domain/apple/dylibResolutionFindings.js +9 -7
- package/dist/domain/apple/interfaceBuilderGraph.js +25 -13
- package/dist/domain/apple/interfaceBuilderKeyedArchive.js +13 -3
- package/dist/domain/apple/keyedArchive.js +9 -1
- package/dist/domain/apple/machoContainer.js +54 -0
- package/dist/domain/apple/plistNumbers.js +76 -0
- package/dist/domain/artifactIdentity.js +29 -0
- package/dist/domain/artifactInventoryEvidence.js +19 -24
- package/dist/domain/bridgeCandidateProjection.js +71 -0
- package/dist/domain/browserCompleteness.js +1 -0
- package/dist/domain/browserObservationError.js +24 -5
- package/dist/domain/browserScenarioValues.js +2 -6
- package/dist/domain/bundleComparison.js +3 -2
- package/dist/domain/canonicalJsonDigestSteps.js +133 -0
- package/dist/domain/canonicalOrdering.js +9 -5
- package/dist/domain/changedBehavior.js +6 -6
- package/dist/domain/comparisonSourceEvidence.js +40 -0
- package/dist/domain/configurationErrors.js +1 -1
- package/dist/domain/evidence.js +64 -9
- package/dist/domain/evidenceBundle.js +42 -1
- package/dist/domain/evidenceErrors.js +12 -1
- package/dist/domain/explicitSensitiveFailure.js +17 -1
- package/dist/domain/firmware/firmwareAnalysis.js +6 -7
- package/dist/domain/hopperErrors.js +23 -2
- package/dist/domain/hopperValues.js +51 -8
- package/dist/domain/immutableJson.js +114 -0
- package/dist/domain/inputIssueProjection.js +3 -0
- package/dist/domain/javascript/electronActiveObservation.js +30 -4
- package/dist/domain/javascript/electronStaticAnalysisValues.js +6 -12
- package/dist/domain/javascript/javascriptApplicationAnalysis.js +71 -23
- package/dist/domain/javascript/javascriptApplicationGraph.js +13 -0
- package/dist/domain/javascript/javascriptAstValues.js +20 -0
- package/dist/domain/javascript/javascriptRecovery.js +3 -8
- package/dist/domain/javascript/javascriptSemanticAnalysis.js +97 -2
- package/dist/domain/javascript/javascriptSemanticCoverage.js +6 -5
- package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +9 -17
- package/dist/domain/javascript/javascriptSemanticDataEffects.js +22 -5
- package/dist/domain/javascript/javascriptSemanticGraph.js +13 -0
- package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +1 -0
- package/dist/domain/javascript/javascriptSemanticPrimitives.js +25 -17
- package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -4
- package/dist/domain/javascript/javascriptSemanticResourceLimits.js +77 -0
- package/dist/domain/javascript/javascriptSemanticValues.js +83 -11
- package/dist/domain/javascript/javascriptSourceParser.js +6 -2
- package/dist/domain/javascript/javascriptStaticAnalysis.js +22 -17
- package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -3
- package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +14 -4
- package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +66 -7
- package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +28 -16
- package/dist/domain/javascript/javascriptStaticAnalysisState.js +3 -2
- package/dist/domain/jsonSerialization.js +109 -0
- package/dist/domain/jsonValue.js +90 -6
- package/dist/domain/localPath.js +6 -0
- package/dist/domain/managed/managedApplicationGraph.js +10 -10
- package/dist/domain/managed/managedInspectionEvidence.js +16 -0
- package/dist/domain/managed/managedMemberComparisonMatch.js +5 -9
- package/dist/domain/managed/managedNativeVerification.js +3 -9
- package/dist/domain/native/fileOffsetPartialObservation.js +1 -0
- package/dist/domain/native/nativeCallObservation.js +33 -11
- package/dist/domain/native/nativeCallPartialObservation.js +33 -0
- package/dist/domain/process/processCapture.js +8 -4
- package/dist/domain/process/processComparison.js +38 -4
- package/dist/domain/process/processScenario.js +11 -10
- package/dist/domain/propertyListKeys.js +1 -1
- package/dist/domain/propertyListXmlText.js +19 -0
- package/dist/domain/providerAdapterError.js +1 -1
- package/dist/domain/providerOperationHealth.js +54 -0
- package/dist/domain/reconstructionVerification.js +6 -43
- package/dist/domain/reconstructionVerificationSchemas.js +2 -2
- package/dist/domain/staticRuntimeCorrelation.js +6 -43
- package/dist/domain/webBundleAnalysis.js +20 -8
- package/dist/domain/webBundleAnalyzerInspection.js +13 -8
- package/dist/domain/webBundleAnalyzerResult.js +41 -2
- package/dist/domain/webRuntime.js +19 -5
- package/dist/domain/webSourceLocation.js +1 -0
- package/dist/firmware/FirmwareFiles.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraAnalysisProfile.js +5 -0
- package/dist/ghidra/GhidraClient.js +47 -14
- package/dist/ghidra/GhidraDiagnostics.js +7 -7
- package/dist/ghidra/GhidraFunctionValues.js +10 -7
- package/dist/ghidra/GhidraInstallation.js +18 -21
- package/dist/ghidra/GhidraInventoryValues.js +14 -4
- package/dist/ghidra/GhidraJavaLaunch.js +62 -0
- package/dist/ghidra/GhidraLauncher.js +33 -16
- package/dist/ghidra/GhidraProviderCapabilities.js +14 -2
- package/dist/ghidra/GhidraProviderClient.js +23 -12
- package/dist/ghidra/GhidraSessionError.js +11 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +47 -12
- package/dist/ghidra/GhidraTransport.js +20 -0
- package/dist/hopper/BridgeLauncher.js +31 -11
- package/dist/hopper/HopperAnalysisProfile.js +62 -4
- package/dist/hopper/HopperCleanup.js +15 -3
- package/dist/hopper/HopperClient.js +222 -14
- package/dist/hopper/HopperFileOffset.js +237 -0
- package/dist/hopper/HopperMachOImage.js +196 -0
- package/dist/hopper/HopperOperationStage.js +6 -0
- package/dist/hopper/HopperProvider.js +63 -9
- package/dist/hopper/HopperProviderCapabilities.js +19 -1
- package/dist/hopper/HopperRegexSearch.js +137 -0
- package/dist/hopper/HopperRegexWorker.js +27 -0
- package/dist/hopper/HopperRequestQueue.js +68 -19
- package/dist/hopper/HopperTargetLease.js +3 -2
- package/dist/inspector/JavaScriptRuntimeScope.js +9 -1
- package/dist/inspector/V8InspectorCaptureProjection.js +8 -3
- package/dist/inspector/V8InspectorProvider.js +47 -11
- package/dist/javascript/sourceMaps/DecodedSourceMap.js +47 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -0
- package/dist/javascript/sourceMaps/SourceMapFormat.js +79 -31
- package/dist/javascript/sourceMaps/TraceSourceMap.js +14 -28
- package/dist/mcpDoctor.js +62 -2
- package/dist/native/AppleDispatchCoverage.js +47 -6
- package/dist/native/AppleDispatchDecodeFacts.js +6 -0
- package/dist/native/AppleDispatchMetadata.js +6 -3
- package/dist/native/AppleMachoFixups.js +48 -24
- package/dist/native/AppleMachoSelection.js +32 -16
- package/dist/native/AppleObjcCategories.js +6 -2
- package/dist/native/AppleObjcDispatchFacets.js +5 -2
- package/dist/native/AppleObjcProperties.js +2 -1
- package/dist/native/AppleSwiftDispatchFacets.js +27 -21
- package/dist/native/AppleSwiftVtables.js +21 -15
- package/dist/native/CommandRunner.js +81 -84
- package/dist/native/LldbCallTracer.js +365 -102
- package/dist/native/LldbRetainedObservations.js +178 -0
- package/dist/native/NativeCallObservation.js +62 -3
- package/dist/native/NativeMacOSProvider.js +98 -17
- package/dist/native/SignatureTargetBinding.js +155 -0
- package/dist/native/parsers/lipo.js +71 -1
- package/dist/native/pwndbg/PwndbgCoreContext.js +25 -5
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +2 -0
- package/dist/process/DarwinProcessRunTokenReader.js +8 -9
- package/dist/process/ExecFileOutput.js +20 -0
- package/dist/process/OwnedCommand.js +7 -2
- package/dist/process/ProcessOwnership.js +203 -53
- package/dist/process/ProcessOwnershipObservation.js +49 -4
- package/dist/process/ProviderProcess.js +38 -7
- package/dist/process/capture/ProcessCaptureError.js +2 -2
- package/dist/process/capture/ProcessCaptureLifecycle.js +100 -34
- package/dist/server/EvidenceMcpServer.js +146 -0
- package/dist/server/createServer.js +15 -8
- package/dist/server/registerAndroidTools.js +2 -4
- package/dist/server/registerApplicationTools/helpers.js +2 -4
- package/dist/server/registerArtifactComparisonTool.js +11 -7
- package/dist/server/registerBinaryDiagnosticsTools.js +2 -4
- package/dist/server/registerBrowserScenarioTool.js +2 -4
- package/dist/server/registerBrowserTools.js +2 -4
- package/dist/server/registerBundleComparisonTool.js +2 -3
- package/dist/server/registerCloseLifecycleTool.js +8 -18
- package/dist/server/registerElectronTools.js +2 -4
- package/dist/server/registerEnhancedTools.js +18 -59
- package/dist/server/registerEvidenceTools.js +2 -4
- package/dist/server/registerEvmTools.js +2 -4
- package/dist/server/registerFirmwareTools.js +2 -4
- package/dist/server/registerFunctionComparisonTool.js +10 -6
- package/dist/server/registerInvestigationTools.js +7 -7
- package/dist/server/registerJavaScriptRecoveryTool.js +2 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +2 -4
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +2 -4
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +2 -4
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +2 -4
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +2 -4
- package/dist/server/registerOfficialTools.js +2 -4
- package/dist/server/registerProcessComparisonTool.js +71 -45
- package/dist/server/registerRecordedCrashTools.js +2 -4
- package/dist/server/registerSessionRecordTools.js +5 -6
- package/dist/server/registerSessionStatusTool.js +17 -3
- package/dist/server/registerSessionTools.js +11 -3
- package/dist/server/registerWebModuleTool.js +2 -4
- package/dist/server/registerWebNetworkCaptureTool.js +2 -4
- package/dist/server/registerWebRuntimeTools.js +3 -7
- package/dist/server/registerWebScriptTool.js +2 -4
- package/dist/server/registerWebSourceLocationTool.js +2 -4
- package/dist/server/runDerivedOperation.js +15 -3
- package/dist/server/sessionAvailabilityPolicy.js +0 -1
- package/dist/server/toolRegistrationOptions.js +2 -2
- package/dist/server/toolResult.js +62 -10
- package/dist/server/toolResultEncoding.js +33 -0
- package/install.sh +1 -1
- package/native/windows/build/manifest.json +1 -1
- package/package.json +8 -3
- package/scripts/electron-active-hook-boundaries.cjs +18 -20
- package/scripts/electron-active-hook.cjs +241 -37
- package/skills/reverse-engineer-anything/SKILL.md +11 -3
- package/skills/reverse-engineer-anything/references/javascript-applications.md +8 -1
- package/dist/ghidra/GhidraDefaults.js +0 -2
|
@@ -77,23 +77,35 @@ export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
if (plan.unresolved.length > 0)
|
|
80
|
-
return cleanupValidationFailure(plan.unresolved);
|
|
81
|
-
return {
|
|
80
|
+
return cleanupValidationFailure(plan.unresolved, plan.unverified);
|
|
81
|
+
return {
|
|
82
|
+
cleaned: true,
|
|
83
|
+
signaled,
|
|
84
|
+
...(plan.unverified.length === 0 ? {} : { unverified: plan.unverified }),
|
|
85
|
+
};
|
|
82
86
|
};
|
|
83
87
|
/** Verify that no live process still carries an owned capture run token. */
|
|
84
|
-
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost, captureBaseline) => {
|
|
88
|
+
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost, captureBaseline, relation) => {
|
|
85
89
|
const processTable = await readLiveProcessTable(host);
|
|
86
90
|
if (!processTable.available)
|
|
87
91
|
return {
|
|
88
92
|
cleaned: false,
|
|
89
93
|
reason: `process table could not be inspected: ${processTable.reason}`,
|
|
90
94
|
};
|
|
91
|
-
const scan = await scanTokenOwnedProcesses(processTable.processes, runId, host, captureBaseline);
|
|
95
|
+
const scan = await scanTokenOwnedProcesses(processTable.processes, runId, host, captureBaseline, relation);
|
|
92
96
|
if (scan.failures.length > 0)
|
|
93
|
-
return cleanupValidationFailure(scan.failures);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
return cleanupValidationFailure(scan.failures, scan.unverified);
|
|
98
|
+
if (scan.owned.length > 0)
|
|
99
|
+
return {
|
|
100
|
+
cleaned: false,
|
|
101
|
+
reason: "token-owned process remained after cleanup",
|
|
102
|
+
...(scan.unverified.length === 0 ? {} : { unverified: scan.unverified }),
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
cleaned: true,
|
|
106
|
+
signaled: false,
|
|
107
|
+
...(scan.unverified.length === 0 ? {} : { unverified: scan.unverified }),
|
|
108
|
+
};
|
|
97
109
|
};
|
|
98
110
|
const readLiveProcessTable = async (host) => {
|
|
99
111
|
try {
|
|
@@ -109,8 +121,14 @@ const readLiveProcessTable = async (host) => {
|
|
|
109
121
|
const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
110
122
|
const sampledProcessGroupIds = new Set((ownership.sampledProcessGroupIds ?? []).filter((processGroupId) => Number.isSafeInteger(processGroupId) && processGroupId > 0));
|
|
111
123
|
const tokenOwned = ownership.sweepTokenOwnedProcesses === true
|
|
112
|
-
? await scanTokenOwnedProcesses(processes, ownership.runId, host, ownership.captureBaseline
|
|
113
|
-
|
|
124
|
+
? await scanTokenOwnedProcesses(processes, ownership.runId, host, ownership.captureBaseline, {
|
|
125
|
+
leaderPid: ownership.leaderPid,
|
|
126
|
+
processGroupId: ownership.processGroupId,
|
|
127
|
+
...(sampledProcessGroupIds.size === 0
|
|
128
|
+
? {}
|
|
129
|
+
: { sampledProcessGroupIds: [...sampledProcessGroupIds] }),
|
|
130
|
+
})
|
|
131
|
+
: { owned: [], failures: [], unverified: [], identities: new Map() };
|
|
114
132
|
const tokenOwnedGroupIds = new Set(tokenOwned.owned.map(({ processGroupId }) => processGroupId));
|
|
115
133
|
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
116
134
|
const rootMembers = processes.filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
@@ -123,8 +141,15 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
123
141
|
signalOrder: [],
|
|
124
142
|
tokenOwnedIdentities: tokenOwned.identities,
|
|
125
143
|
unresolved: tokenOwned.failures,
|
|
144
|
+
unverified: tokenOwned.unverified,
|
|
126
145
|
}
|
|
127
|
-
: {
|
|
146
|
+
: {
|
|
147
|
+
cleaned: true,
|
|
148
|
+
signaled: false,
|
|
149
|
+
...(tokenOwned.unverified.length === 0
|
|
150
|
+
? {}
|
|
151
|
+
: { unverified: tokenOwned.unverified }),
|
|
152
|
+
};
|
|
128
153
|
let descendants = [];
|
|
129
154
|
if (launcher !== undefined) {
|
|
130
155
|
const identityFailure = launcherIdentityFailure(launcher, ownership);
|
|
@@ -179,7 +204,7 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
179
204
|
signalableGroups.add(processGroupId);
|
|
180
205
|
}
|
|
181
206
|
if (ownership.sweepTokenOwnedProcesses !== true && unresolved.length > 0)
|
|
182
|
-
return cleanupValidationFailure(unresolved);
|
|
207
|
+
return cleanupValidationFailure(unresolved, tokenOwned.unverified);
|
|
183
208
|
return {
|
|
184
209
|
signalOrder: [...signalableGroups]
|
|
185
210
|
.filter((groupId) => processGroupIds.has(groupId))
|
|
@@ -190,6 +215,7 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
190
215
|
: left - right),
|
|
191
216
|
tokenOwnedIdentities: tokenOwned.identities,
|
|
192
217
|
unresolved,
|
|
218
|
+
unverified: tokenOwned.unverified,
|
|
193
219
|
};
|
|
194
220
|
};
|
|
195
221
|
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host, expectedIdentities) => {
|
|
@@ -224,19 +250,22 @@ const revalidateOwnedProcessGroup = async (processGroupId, ownership, host, expe
|
|
|
224
250
|
};
|
|
225
251
|
return { empty: members.length === 0 };
|
|
226
252
|
};
|
|
227
|
-
const cleanupValidationFailure = (failures) => {
|
|
253
|
+
const cleanupValidationFailure = (failures, unverified = []) => {
|
|
254
|
+
const unverifiedEntry = unverified.length === 0 ? {} : { unverified };
|
|
228
255
|
const unreadable = failures.filter(({ reason }) => reason === "environment-unreadable");
|
|
229
256
|
if (failures.some(({ reason }) => reason === "run-token-mismatch"))
|
|
230
257
|
return {
|
|
231
258
|
cleaned: false,
|
|
232
259
|
reason: "process tree contains an unowned or PID-reused process",
|
|
233
260
|
failures,
|
|
261
|
+
...unverifiedEntry,
|
|
234
262
|
};
|
|
235
263
|
if (failures.some(({ reason }) => reason === "process-identity-unavailable"))
|
|
236
264
|
return {
|
|
237
265
|
cleaned: false,
|
|
238
266
|
reason: "process identity could not be revalidated",
|
|
239
267
|
failures,
|
|
268
|
+
...unverifiedEntry,
|
|
240
269
|
};
|
|
241
270
|
if (unreadable.length > 0) {
|
|
242
271
|
const counts = new Map();
|
|
@@ -248,21 +277,28 @@ const cleanupValidationFailure = (failures) => {
|
|
|
248
277
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
249
278
|
.map(([category, count]) => `${category}=${String(count)}`)
|
|
250
279
|
.join(", ");
|
|
280
|
+
const candidates = unreadable
|
|
281
|
+
.map(({ pid, diagnostic }) => `${String(pid)}=${sanitizedTokenReadFailure(diagnostic)}`)
|
|
282
|
+
.sort((left, right) => left.localeCompare(right));
|
|
251
283
|
return {
|
|
252
284
|
cleaned: false,
|
|
253
|
-
reason: `process ownership token could not be read for ${String(unreadable.length)} live process(es): ${breakdown}`,
|
|
285
|
+
reason: `process ownership token could not be read for ${String(unreadable.length)} live process(es): ${breakdown}; live candidates ${candidates.join(", ")}`,
|
|
254
286
|
failures,
|
|
287
|
+
...unverifiedEntry,
|
|
255
288
|
};
|
|
256
289
|
}
|
|
257
290
|
return {
|
|
258
291
|
cleaned: false,
|
|
259
292
|
reason: "process ownership could not be revalidated",
|
|
260
293
|
failures,
|
|
294
|
+
...unverifiedEntry,
|
|
261
295
|
};
|
|
262
296
|
};
|
|
263
297
|
const sanitizedTokenReadFailure = (diagnostic) => {
|
|
264
298
|
if (diagnostic === "environment_unavailable")
|
|
265
299
|
return diagnostic;
|
|
300
|
+
if (diagnostic === "platform_binary_environment_withheld")
|
|
301
|
+
return diagnostic;
|
|
266
302
|
const environmentErrno = /^(EACCES|EPERM|ENOENT|ESRCH):/u.exec(diagnostic ?? "");
|
|
267
303
|
if (environmentErrno?.[1] !== undefined)
|
|
268
304
|
return `environment_errno_${environmentErrno[1]}`;
|
|
@@ -360,7 +396,26 @@ const processOwnershipFailures = async (members, runId, host, expectedIdentities
|
|
|
360
396
|
}
|
|
361
397
|
return failures;
|
|
362
398
|
};
|
|
363
|
-
const
|
|
399
|
+
const TOKEN_READ_RETRY_ATTEMPTS = 3;
|
|
400
|
+
const TOKEN_READ_RETRY_DELAY_MS = 40;
|
|
401
|
+
/** Darwin reports this when the kernel withholds a platform binary's environment. */
|
|
402
|
+
const PLATFORM_BINARY_ENVIRONMENT_WITHHELD = "platform_binary_environment_withheld";
|
|
403
|
+
const currentProcessUid = () => typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
404
|
+
/**
|
|
405
|
+
* Whether the operating system provably withheld this candidate's run token:
|
|
406
|
+
* current macOS omits the environment of Apple platform binaries from
|
|
407
|
+
* KERN_PROCARGS2 and fails with EINVAL for processes owned by another
|
|
408
|
+
* account, and neither class can be token-verified by any caller. Such a
|
|
409
|
+
* candidate is recorded as unverified instead of failing an otherwise clean
|
|
410
|
+
* capture; every other unreadable candidate stays fail-closed.
|
|
411
|
+
*/
|
|
412
|
+
const environmentWithheldFromInspection = (entry, diagnostic) => {
|
|
413
|
+
const uid = currentProcessUid();
|
|
414
|
+
if (uid !== undefined && entry.uid !== undefined && entry.uid !== uid)
|
|
415
|
+
return true;
|
|
416
|
+
return diagnostic === PLATFORM_BINARY_ENVIRONMENT_WITHHELD;
|
|
417
|
+
};
|
|
418
|
+
const scanTokenOwnedProcesses = async (processes, runId, host, captureBaseline, relation) => {
|
|
364
419
|
let candidates = processes;
|
|
365
420
|
let candidateIdentities = new Map();
|
|
366
421
|
if (host.processIdentities !== undefined) {
|
|
@@ -395,58 +450,89 @@ const scanTokenOwnedProcesses = async (processes, runId, host, captureBaseline)
|
|
|
395
450
|
}
|
|
396
451
|
candidates = stableCandidates;
|
|
397
452
|
}
|
|
453
|
+
const owned = [];
|
|
454
|
+
const ownedIdentities = new Map();
|
|
455
|
+
const recordOwned = (process) => {
|
|
456
|
+
owned.push(process);
|
|
457
|
+
const identity = candidateIdentities.get(process.pid);
|
|
458
|
+
ownedIdentities.set(process.pid, identity?.state === "readable" ? identity.identity : null);
|
|
459
|
+
};
|
|
398
460
|
let bulkTokens;
|
|
399
461
|
if (host.runTokens !== undefined)
|
|
400
462
|
bulkTokens = await host.runTokens(candidates);
|
|
401
|
-
const
|
|
402
|
-
const ownedIdentities = new Map();
|
|
463
|
+
const unreadable = [];
|
|
403
464
|
for (const process of candidates) {
|
|
404
465
|
const bulkToken = bulkTokens?.get(process.pid);
|
|
405
466
|
if (bulkToken !== undefined) {
|
|
406
467
|
if (bulkToken.state === "readable") {
|
|
407
|
-
if (bulkToken.runId === runId)
|
|
408
|
-
|
|
409
|
-
const identity = candidateIdentities.get(process.pid);
|
|
410
|
-
ownedIdentities.set(process.pid, identity?.state === "readable" ? identity.identity : null);
|
|
411
|
-
}
|
|
468
|
+
if (bulkToken.runId === runId)
|
|
469
|
+
recordOwned(process);
|
|
412
470
|
continue;
|
|
413
471
|
}
|
|
414
|
-
|
|
415
|
-
failures.push({
|
|
416
|
-
pid: process.pid,
|
|
417
|
-
reason: "environment-unreadable",
|
|
418
|
-
diagnostic: bulkToken.reason,
|
|
419
|
-
});
|
|
472
|
+
unreadable.push({ process, diagnostic: bulkToken.reason });
|
|
420
473
|
continue;
|
|
421
474
|
}
|
|
422
475
|
try {
|
|
423
|
-
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId)
|
|
424
|
-
|
|
425
|
-
const identity = candidateIdentities.get(process.pid);
|
|
426
|
-
ownedIdentities.set(process.pid, identity?.state === "readable" ? identity.identity : null);
|
|
427
|
-
}
|
|
476
|
+
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId)
|
|
477
|
+
recordOwned(process);
|
|
428
478
|
}
|
|
429
479
|
catch (cause) {
|
|
430
|
-
|
|
431
|
-
const live = liveProcesses(await host.listProcesses());
|
|
432
|
-
if (!live.some(({ pid }) => pid === process.pid))
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
catch (recheckCause) {
|
|
436
|
-
failures.push({
|
|
437
|
-
pid: process.pid,
|
|
438
|
-
reason: "environment-unreadable",
|
|
439
|
-
diagnostic: `${errorMessage(cause)}; process liveness recheck failed: ${errorMessage(recheckCause)}`,
|
|
440
|
-
});
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
failures.push({
|
|
444
|
-
pid: process.pid,
|
|
445
|
-
reason: "environment-unreadable",
|
|
446
|
-
diagnostic: errorMessage(cause),
|
|
447
|
-
});
|
|
480
|
+
unreadable.push({ process, diagnostic: errorMessage(cause) });
|
|
448
481
|
}
|
|
449
482
|
}
|
|
483
|
+
const remaining = await settleUnreadableTokenCandidates(unreadable, runId, host, recordOwned);
|
|
484
|
+
const relatedPids = new Set();
|
|
485
|
+
const relatedGroupIds = new Set();
|
|
486
|
+
if (relation !== undefined) {
|
|
487
|
+
relatedPids.add(relation.leaderPid);
|
|
488
|
+
for (const descendant of descendantsOf(relation.leaderPid, processes))
|
|
489
|
+
relatedPids.add(descendant.pid);
|
|
490
|
+
relatedGroupIds.add(relation.processGroupId);
|
|
491
|
+
for (const processGroupId of relation.sampledProcessGroupIds ?? [])
|
|
492
|
+
if (Number.isSafeInteger(processGroupId) && processGroupId > 0)
|
|
493
|
+
relatedGroupIds.add(processGroupId);
|
|
494
|
+
for (const process of owned)
|
|
495
|
+
relatedGroupIds.add(process.processGroupId);
|
|
496
|
+
}
|
|
497
|
+
const unverified = [];
|
|
498
|
+
const strict = [];
|
|
499
|
+
const withheld = [];
|
|
500
|
+
for (const candidate of remaining) {
|
|
501
|
+
const isRelated = relation === undefined ||
|
|
502
|
+
relatedPids.has(candidate.process.pid) ||
|
|
503
|
+
relatedGroupIds.has(candidate.process.processGroupId);
|
|
504
|
+
if (!isRelated &&
|
|
505
|
+
environmentWithheldFromInspection(candidate.process, candidate.diagnostic)) {
|
|
506
|
+
withheld.push(candidate);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
strict.push(candidate);
|
|
510
|
+
}
|
|
511
|
+
let finalLive;
|
|
512
|
+
try {
|
|
513
|
+
finalLive = new Set(liveProcesses(await host.listProcesses()).map(({ pid }) => pid));
|
|
514
|
+
}
|
|
515
|
+
catch (cause) {
|
|
516
|
+
void cause;
|
|
517
|
+
finalLive = undefined;
|
|
518
|
+
}
|
|
519
|
+
for (const candidate of strict) {
|
|
520
|
+
if (finalLive !== undefined && !finalLive.has(candidate.process.pid))
|
|
521
|
+
continue;
|
|
522
|
+
failures.push({
|
|
523
|
+
pid: candidate.process.pid,
|
|
524
|
+
reason: "environment-unreadable",
|
|
525
|
+
diagnostic: candidate.diagnostic,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
for (const candidate of withheld) {
|
|
529
|
+
if (finalLive !== undefined && !finalLive.has(candidate.process.pid))
|
|
530
|
+
continue;
|
|
531
|
+
unverified.push({
|
|
532
|
+
pid: candidate.process.pid,
|
|
533
|
+
diagnostic: candidate.diagnostic,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
450
536
|
if (candidates.length > 0 && host.processIdentities !== undefined) {
|
|
451
537
|
const afterRead = await host.processIdentities(candidates);
|
|
452
538
|
const stillOwned = [];
|
|
@@ -469,9 +555,73 @@ const scanTokenOwnedProcesses = async (processes, runId, host, captureBaseline)
|
|
|
469
555
|
diagnostic: "process identity changed or became unavailable during token validation",
|
|
470
556
|
});
|
|
471
557
|
}
|
|
472
|
-
return {
|
|
558
|
+
return {
|
|
559
|
+
owned: stillOwned,
|
|
560
|
+
failures,
|
|
561
|
+
unverified,
|
|
562
|
+
identities: ownedIdentities,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return { owned, failures, unverified, identities: ownedIdentities };
|
|
566
|
+
};
|
|
567
|
+
/**
|
|
568
|
+
* Re-read run tokens for unresolved sweep candidates so transient exec and
|
|
569
|
+
* exit states settle before classification. Each attempt revalidates
|
|
570
|
+
* liveness, refreshes the diagnostic, and is bounded by a fixed attempt
|
|
571
|
+
* budget; a still-unreadable candidate keeps its latest diagnostic.
|
|
572
|
+
*/
|
|
573
|
+
const settleUnreadableTokenCandidates = async (pending, runId, host, recordOwned) => {
|
|
574
|
+
let remaining = pending;
|
|
575
|
+
for (let attempt = 0; attempt < TOKEN_READ_RETRY_ATTEMPTS && remaining.length > 0; attempt += 1) {
|
|
576
|
+
await new Promise((resolve) => setTimeout(resolve, TOKEN_READ_RETRY_DELAY_MS));
|
|
577
|
+
let live;
|
|
578
|
+
try {
|
|
579
|
+
live = new Set(liveProcesses(await host.listProcesses()).map(({ pid }) => pid));
|
|
580
|
+
}
|
|
581
|
+
catch (cause) {
|
|
582
|
+
void cause;
|
|
583
|
+
live = undefined;
|
|
584
|
+
}
|
|
585
|
+
let observations;
|
|
586
|
+
if (host.runTokens !== undefined) {
|
|
587
|
+
try {
|
|
588
|
+
observations = await host.runTokens(remaining.map(({ process }) => process));
|
|
589
|
+
}
|
|
590
|
+
catch (cause) {
|
|
591
|
+
void cause;
|
|
592
|
+
observations = undefined;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const stillUnreadable = [];
|
|
596
|
+
for (const candidate of remaining) {
|
|
597
|
+
const pid = candidate.process.pid;
|
|
598
|
+
if (live !== undefined && !live.has(pid))
|
|
599
|
+
continue;
|
|
600
|
+
const observation = observations?.get(pid) ?? (await readEnvironmentRunToken(host, pid));
|
|
601
|
+
if (observation.state === "readable") {
|
|
602
|
+
if (observation.runId === runId)
|
|
603
|
+
recordOwned(candidate.process);
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
stillUnreadable.push({
|
|
607
|
+
process: candidate.process,
|
|
608
|
+
diagnostic: observation.reason,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
remaining = stillUnreadable;
|
|
612
|
+
}
|
|
613
|
+
return remaining;
|
|
614
|
+
};
|
|
615
|
+
const readEnvironmentRunToken = async (host, pid) => {
|
|
616
|
+
try {
|
|
617
|
+
return {
|
|
618
|
+
state: "readable",
|
|
619
|
+
runId: (await host.environment(pid)).REA_PROCESS_RUN_ID,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch (cause) {
|
|
623
|
+
return { state: "unavailable", reason: errorMessage(cause) };
|
|
473
624
|
}
|
|
474
|
-
return { owned, failures, identities: ownedIdentities };
|
|
475
625
|
};
|
|
476
626
|
const processIsGone = async (host, pid) => {
|
|
477
627
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { AnalysisCapabilityUnavailableError } from "../domain/analysisErrorCore.js";
|
|
2
3
|
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
3
4
|
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
4
5
|
import { execFileOutput } from "./ExecFileOutput.js";
|
|
@@ -133,17 +134,18 @@ export const createSystemProcessOwnershipHost = (platform = process.platform, ho
|
|
|
133
134
|
const listProcesses = async (signal) => {
|
|
134
135
|
if (platform === "win32")
|
|
135
136
|
return [];
|
|
136
|
-
const { stdout } = await execFileOutput("ps", ["-axo", "pid=,ppid=,pgid=,stat=,command="], { env: hostEnvironment, ...(signal === undefined ? {} : { signal }) });
|
|
137
|
+
const { stdout } = await execFileOutput("ps", ["-axo", "pid=,ppid=,pgid=,uid=,stat=,command="], { env: hostEnvironment, ...(signal === undefined ? {} : { signal }) });
|
|
137
138
|
return stdout
|
|
138
139
|
.split("\n")
|
|
139
|
-
.map((line) => /\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/u.exec(line))
|
|
140
|
+
.map((line) => /\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/u.exec(line))
|
|
140
141
|
.filter((match) => match !== null)
|
|
141
142
|
.map((match) => ({
|
|
142
143
|
pid: Number(match[1]),
|
|
143
144
|
parentPid: Number(match[2]),
|
|
144
145
|
processGroupId: Number(match[3]),
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
uid: Number(match[4]),
|
|
147
|
+
state: match[5] ?? "",
|
|
148
|
+
command: match[6] ?? "",
|
|
147
149
|
}));
|
|
148
150
|
};
|
|
149
151
|
const processIdentities = async (processes, signal) => {
|
|
@@ -222,6 +224,19 @@ export const createSystemProcessOwnershipHost = (platform = process.platform, ho
|
|
|
222
224
|
platform,
|
|
223
225
|
prepare: async (signal) => {
|
|
224
226
|
await darwinTokens?.prepare(signal);
|
|
227
|
+
if (platform === "linux") {
|
|
228
|
+
try {
|
|
229
|
+
const processes = await listProcesses(signal);
|
|
230
|
+
if (!processes.some(({ pid }) => pid === process.pid))
|
|
231
|
+
throw new Error("ps did not report REA's current process");
|
|
232
|
+
}
|
|
233
|
+
catch (cause) {
|
|
234
|
+
if (isExpectedAbort(cause, signal))
|
|
235
|
+
throw cause;
|
|
236
|
+
const reason = `Linux process ownership inspection requires a procps-compatible ps on REA's PATH before launching a child: ${errorMessage(cause)}`;
|
|
237
|
+
throw new AnalysisCapabilityUnavailableError("process-ownership", "prepare_owned_process", reason, { cause, userMessage: reason });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
225
240
|
},
|
|
226
241
|
listProcesses,
|
|
227
242
|
async environment(pid) {
|
|
@@ -276,6 +291,36 @@ export const createSystemProcessOwnershipHost = (platform = process.platform, ho
|
|
|
276
291
|
};
|
|
277
292
|
};
|
|
278
293
|
export const systemProcessOwnershipHost = createSystemProcessOwnershipHost();
|
|
294
|
+
/** Observe the current OS start identity for one live PID, when supported. */
|
|
295
|
+
export const observeProcessStartIdentity = async (pid, host = systemProcessOwnershipHost) => {
|
|
296
|
+
const process = (await host.listProcesses()).find((entry) => entry.pid === pid);
|
|
297
|
+
if (process === undefined || liveProcesses([process]).length === 0)
|
|
298
|
+
return undefined;
|
|
299
|
+
if (host.processIdentities === undefined)
|
|
300
|
+
return { state: "unavailable", reason: "process identity is unsupported" };
|
|
301
|
+
return ((await host.processIdentities([process])).get(pid) ?? {
|
|
302
|
+
state: "unavailable",
|
|
303
|
+
reason: "process identity was not returned",
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
/** Revalidate a launch-time start identity immediately before signaling a PID. */
|
|
307
|
+
export const signalProcessWithStartIdentity = async (pid, expectedIdentity, signal, options = {}) => {
|
|
308
|
+
const host = options.host ?? systemProcessOwnershipHost;
|
|
309
|
+
try {
|
|
310
|
+
const observed = await observeProcessStartIdentity(pid, host);
|
|
311
|
+
if (observed === undefined)
|
|
312
|
+
return "gone";
|
|
313
|
+
if (observed.state !== "readable")
|
|
314
|
+
return "unverified";
|
|
315
|
+
if (observed.identity !== expectedIdentity)
|
|
316
|
+
return "identity-changed";
|
|
317
|
+
(options.sendSignal ?? process.kill)(pid, signal);
|
|
318
|
+
return "signaled";
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return "unverified";
|
|
322
|
+
}
|
|
323
|
+
};
|
|
279
324
|
/** Prepare native token inspection before REA launches a captured child. */
|
|
280
325
|
export const prepareProcessOwnershipInspection = async (signal) => {
|
|
281
326
|
await systemProcessOwnershipHost.prepare?.(signal);
|
|
@@ -83,6 +83,12 @@ export class ProviderProcessSupervisor {
|
|
|
83
83
|
#onStderr = (chunk) => {
|
|
84
84
|
this.#capture("stderr", this.#stderr, chunk);
|
|
85
85
|
};
|
|
86
|
+
#onStdoutError = (cause) => {
|
|
87
|
+
this.#recordStreamError("stdout", cause);
|
|
88
|
+
};
|
|
89
|
+
#onStderrError = (cause) => {
|
|
90
|
+
this.#recordStreamError("stderr", cause);
|
|
91
|
+
};
|
|
86
92
|
#onExit = (code, signal) => {
|
|
87
93
|
this.#recordExit(code, signal);
|
|
88
94
|
};
|
|
@@ -135,9 +141,13 @@ export class ProviderProcessSupervisor {
|
|
|
135
141
|
async waitForOutputClose(timeoutMs) {
|
|
136
142
|
return waitForProcessEvent(this.#outputClosed, this.#outputClose.promise, timeoutMs);
|
|
137
143
|
}
|
|
138
|
-
/**
|
|
144
|
+
/** Share each stop attempt; incomplete cleanup retains ownership and permits retry. */
|
|
139
145
|
stop(options = {}) {
|
|
140
|
-
this.#stopPromise ??= this.#stop(options)
|
|
146
|
+
this.#stopPromise ??= this.#stop(options).then((result) => {
|
|
147
|
+
if (result.status === "incomplete")
|
|
148
|
+
this.#stopPromise = undefined;
|
|
149
|
+
return result;
|
|
150
|
+
});
|
|
141
151
|
return this.#stopPromise;
|
|
142
152
|
}
|
|
143
153
|
/** Detach capture and process listeners after an adapter releases ownership. */
|
|
@@ -146,12 +156,26 @@ export class ProviderProcessSupervisor {
|
|
|
146
156
|
return;
|
|
147
157
|
this.#disposed = true;
|
|
148
158
|
this.launch.process.stdout?.off("data", this.#onStdout);
|
|
159
|
+
this.launch.process.stdout?.off("error", this.#onStdoutError);
|
|
149
160
|
this.launch.process.stderr?.off("data", this.#onStderr);
|
|
161
|
+
this.launch.process.stderr?.off("error", this.#onStderrError);
|
|
150
162
|
this.launch.process.off("exit", this.#onExit);
|
|
151
163
|
this.launch.process.off("close", this.#onClose);
|
|
152
164
|
this.launch.process.off("error", this.#onError);
|
|
153
165
|
}
|
|
154
166
|
async #stop(options) {
|
|
167
|
+
let completed = false;
|
|
168
|
+
try {
|
|
169
|
+
const result = await this.#stopWithin(options);
|
|
170
|
+
completed = result.status !== "incomplete";
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
if (completed)
|
|
175
|
+
this.dispose();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async #stopWithin(options) {
|
|
155
179
|
try {
|
|
156
180
|
if (!this.launch.ownsProcessLifetime)
|
|
157
181
|
return { status: "not-owned" };
|
|
@@ -193,9 +217,6 @@ export class ProviderProcessSupervisor {
|
|
|
193
217
|
: "owned provider process cleanup failed",
|
|
194
218
|
};
|
|
195
219
|
}
|
|
196
|
-
finally {
|
|
197
|
-
this.dispose();
|
|
198
|
-
}
|
|
199
220
|
}
|
|
200
221
|
#ownedCleanup(supplied) {
|
|
201
222
|
if (supplied !== undefined)
|
|
@@ -214,10 +235,20 @@ export class ProviderProcessSupervisor {
|
|
|
214
235
|
this.launch.process.kill(signal);
|
|
215
236
|
}
|
|
216
237
|
#attach(stream, name) {
|
|
217
|
-
if (name === "stdout")
|
|
238
|
+
if (name === "stdout") {
|
|
218
239
|
stream?.on("data", this.#onStdout);
|
|
219
|
-
|
|
240
|
+
stream?.on("error", this.#onStdoutError);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
220
243
|
stream?.on("data", this.#onStderr);
|
|
244
|
+
stream?.on("error", this.#onStderrError);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
#recordStreamError(stream, cause) {
|
|
248
|
+
this.#options.onDiagnostic?.({
|
|
249
|
+
type: "error",
|
|
250
|
+
message: `${stream} stream failed: ${cause.message}`,
|
|
251
|
+
});
|
|
221
252
|
}
|
|
222
253
|
#capture(stream, capture, chunk) {
|
|
223
254
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
@@ -12,14 +12,14 @@ export class ProcessCaptureError extends AnalysisError {
|
|
|
12
12
|
cleanupReport;
|
|
13
13
|
constructor(message, options) {
|
|
14
14
|
super(message, options);
|
|
15
|
-
this.userMessage = options?.userMessage;
|
|
15
|
+
this.userMessage = options?.userMessage ?? message;
|
|
16
16
|
this.userCategory = options?.userCategory;
|
|
17
17
|
this.reason =
|
|
18
18
|
options?.reason ??
|
|
19
19
|
(options?.userCategory === "cancelled" ? "cancelled" : "capture_failed");
|
|
20
20
|
this.cleanupIncomplete = this.reason === "cleanup_incomplete";
|
|
21
21
|
this.cleanupResources = options?.cleanupResources ?? [];
|
|
22
|
-
this.executionFailure = options?.executionFailure;
|
|
22
|
+
this.executionFailure = options?.executionFailure ?? message;
|
|
23
23
|
this.partialObservation = options?.partialObservation;
|
|
24
24
|
this.cleanupReport = options?.cleanupReport;
|
|
25
25
|
}
|