rea-agents 5.0.0 → 6.0.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 +157 -650
- package/bridge/mitmproxy/capture.py +210 -0
- package/bridge/native/rea_lldb_tracer.py +303 -0
- package/bridge/process/ProcessRunTokenReader.swift +263 -0
- package/bridge/process/ProcessRunTokenReaderMain.swift +35 -0
- package/bridge/pwndbg/core_context.py +101 -0
- package/bridge/pwndbg/launch.py +39 -0
- package/bridge/pwndbg/launch_impl.py +34 -0
- package/bridge/pwntools/decoder_runtime.py +59 -0
- package/bridge/pwntools/layout.py +39 -0
- package/bridge/pwntools/layout_impl.py +309 -0
- package/bridge/pwntools/recorded_crash.py +39 -0
- package/bridge/pwntools/recorded_crash_impl.py +143 -0
- package/dist/android/JadxProvider.js +1 -0
- package/dist/application/CapabilityInventory.js +21 -0
- package/dist/application/DirectAnalysis.js +97 -23
- package/dist/application/EvidenceBundleFiles.js +1 -1
- package/dist/application/ReconstructionObligationCandidates.js +5 -1
- package/dist/application/WebNetworkCapturePort.js +1 -0
- package/dist/application/WebNetworkCaptureService.js +132 -0
- package/dist/application/WindowsCapabilities.js +1 -1
- package/dist/application/binary/AnalysisSnapshotCache.js +49 -6
- package/dist/application/binary/BinarySession.js +38 -10
- package/dist/application/binary/BinarySessionOpen.js +16 -3
- package/dist/application/binary/BinarySessionRecords.js +20 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutPort.js +1 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +57 -0
- package/dist/application/binaryDiagnostics/RecordedCrashPort.js +1 -0
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +60 -0
- package/dist/application/evm/EvmInterfacePort.js +1 -0
- package/dist/application/evm/EvmInterfaceService.js +62 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +1 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +15 -2
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +42 -23
- package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +26 -2
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +74 -24
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +23 -10
- package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +145 -70
- package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +14 -15
- package/dist/application/javascript/JavaScriptSemanticGraphConstruction.js +41 -1
- package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +7 -7
- package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +5 -9
- package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphProjection.js +118 -12
- package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +5 -5
- package/dist/application/{ProcessCli.js → process/ProcessCli.js} +10 -10
- package/dist/application/{ProcessEvidence.js → process/ProcessEvidence.js} +4 -4
- package/dist/artifacts/ArtifactProvider.js +30 -3
- package/dist/artifacts/ArtifactProviderMetadata.js +2 -1
- package/dist/artifacts/ZipArtifactReader.js +3 -0
- package/dist/artifacts/apple/DylibResolutionReader.js +259 -0
- package/dist/artifacts/apple/MachoLoadCommandReader.js +294 -0
- package/dist/{application → artifacts/extraction}/ArtifactExtraction.js +10 -10
- package/dist/{application → artifacts/inventory}/ArtifactGraphConstruction.js +3 -3
- package/dist/{application → artifacts/inventory}/ArtifactInventory.js +4 -4
- package/dist/{application/ArtifactInventory → artifacts/inventory}/classify.js +2 -2
- package/dist/{application/ArtifactInventory → artifacts/inventory}/reader.js +5 -5
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanCanonical.js +3 -3
- package/dist/{application/ArtifactInventory → artifacts/inventory}/scanReader.js +5 -5
- package/dist/artifacts/readStableArtifact.js +20 -4
- package/dist/browser/CdpCaptureEventHandlers.js +90 -2
- package/dist/browser/CdpCaptureEvents.js +9 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +12 -3
- package/dist/browser/history/CaptureFailures.js +101 -0
- package/dist/browser/history/CaptureFormatError.js +10 -0
- package/dist/browser/history/CaptureRedaction.js +65 -0
- package/dist/browser/history/CaptureRelease.js +12 -0
- package/dist/browser/history/HarCapture.js +165 -0
- package/dist/browser/history/HarCaptureAdapter.js +20 -0
- package/dist/browser/history/HarCaptureProcess.js +57 -0
- package/dist/browser/history/HarJson.js +91 -0
- package/dist/browser/history/HarSchema.js +72 -0
- package/dist/browser/history/HistoricalCaptureDecoder.js +186 -0
- package/dist/browser/history/HistoricalCaptureFormatAdapter.js +1 -0
- package/dist/browser/history/MitmproxyCaptureAdapter.js +42 -0
- package/dist/cli/artifactCommands.js +26 -1
- package/dist/cli/binaryDiagnosticsCommands.js +39 -0
- package/dist/cli/coreAnalysisCommands.js +2 -2
- package/dist/cli/coreBinaryCommands.js +8 -8
- package/dist/cli/electronCommands.js +16 -9
- package/dist/cli/evmCommands.js +24 -0
- package/dist/cli/firmwareCommands.js +6 -3
- package/dist/cli/javascriptApplicationAnalysis.js +16 -3
- package/dist/cli/jsonOutput.js +133 -0
- package/dist/cli/nativeCallCommands.js +35 -0
- package/dist/cli/processCommands.js +1 -1
- package/dist/cli/streamedJsonOutput.js +56 -0
- package/dist/cli/utilityCommands.js +2 -0
- package/dist/cli/webNetworkCaptureCommands.js +40 -0
- package/dist/cli.js +9 -3
- package/dist/cliBrowserScenarioCommands.js +2 -2
- package/dist/cliCommandNames.js +6 -0
- package/dist/cliJsonInput.js +30 -1
- package/dist/cliOutput.js +21 -6
- package/dist/composition/binaryDiagnostics.js +8 -0
- package/dist/composition/evm.js +4 -0
- package/dist/composition/webNetworkCaptures.js +6 -0
- package/dist/contracts/artifactToolContracts.js +6 -0
- package/dist/contracts/browserCaptureToolInputSchema.js +43 -0
- package/dist/contracts/browserToolContracts.js +4 -3
- package/dist/contracts/errorSchemas.js +8 -1
- package/dist/contracts/evm/evmToolContracts.js +20 -0
- package/dist/contracts/investigationExamples.js +2 -2
- package/dist/contracts/managed/managedToolContracts.js +7 -3
- package/dist/contracts/native/binaryDiagnosticsToolContracts.js +42 -0
- package/dist/contracts/native/nativeToolContracts.js +17 -1
- package/dist/contracts/officialToolContracts.js +12 -9
- package/dist/contracts/process/processCaptureExample.js +2 -0
- package/dist/contracts/sessionLifecycleInputs.js +22 -5
- package/dist/contracts/sessionStatusContract.js +10 -2
- package/dist/contracts/sessionToolContracts.js +8 -1
- package/dist/contracts/sessionToolSchemas.js +10 -3
- package/dist/contracts/toolContractExamples.js +1 -1
- package/dist/contracts/toolContracts.js +6 -0
- package/dist/contracts/toolEffects.js +30 -0
- package/dist/contracts/toolOutputSchemaGroups.js +5 -1
- package/dist/contracts/toolOutputSchemaPrimitives.js +1 -1
- package/dist/contracts/webNetworkCaptureToolContracts.js +24 -0
- package/dist/domain/analysisErrorBase.js +15 -0
- package/dist/domain/analysisErrorCore.js +59 -5
- package/dist/domain/analysisErrorPresentation.js +29 -1
- package/dist/domain/analysisErrorProjection.js +52 -2
- package/dist/domain/analysisSnapshot.js +166 -2
- package/dist/domain/apple/dyldPaths.js +81 -0
- package/dist/domain/apple/dylibResolution.js +367 -0
- package/dist/domain/apple/dylibResolutionFindings.js +75 -0
- package/dist/domain/artifactPathSyntax.js +1 -1
- package/dist/domain/browserObservation.js +1 -1
- package/dist/domain/browserObservationSchemas.js +13 -0
- package/dist/domain/browserScenarioValues.js +9 -2
- package/dist/domain/callPathSchemas.js +1 -1
- package/dist/domain/canonicalDigest.js +13 -1
- package/dist/domain/changedBehavior.js +3 -3
- package/dist/domain/comparisonStatus.js +2 -30
- package/dist/domain/conformanceTrustGate.js +0 -16
- package/dist/domain/evidence.js +17 -5
- package/dist/domain/evm/evmInterface.js +63 -0
- package/dist/domain/explicitSensitiveFailure.js +92 -0
- package/dist/domain/explicitSensitiveValues.js +10 -0
- package/dist/domain/firmware/firmwareAnalysis.js +8 -1
- package/dist/domain/javascript/electronActiveObservation.js +9 -4
- package/dist/domain/javascript/htmlArtifactReferences.js +40 -0
- package/dist/domain/javascript/javascriptExportShapeComparison.js +21 -5
- package/dist/domain/javascript/javascriptExportShapeVariants.js +0 -10
- package/dist/domain/javascript/javascriptSemanticGraph.js +21 -5
- package/dist/domain/jsonValue.js +4 -0
- package/dist/domain/localPath.js +2 -0
- package/dist/domain/native/binaryLayout.js +357 -0
- package/dist/domain/native/nativeCallObservation.js +164 -0
- package/dist/domain/native/objcSwiftMetadata.js +0 -39
- package/dist/domain/native/recordedCrash.js +266 -0
- package/dist/domain/native/recordedCrashNoteCoverage.js +73 -0
- package/dist/domain/{processCapture.js → process/processCapture.js} +139 -2
- package/dist/domain/{processCaptureExample.js → process/processCaptureExample.js} +5 -0
- package/dist/domain/process/processCaptureParsing.js +54 -0
- package/dist/domain/{processCaptureValidation.js → process/processCaptureValidation.js} +18 -2
- package/dist/domain/{processComparison.js → process/processComparison.js} +1 -1
- package/dist/domain/{processObservation.js → process/processObservation.js} +1 -1
- package/dist/domain/{processTraceSpecification.js → process/processTraceSpecification.js} +1 -1
- package/dist/domain/providerSelectionError.js +4 -2
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/reconstructionVerificationSchemas.js +1 -1
- package/dist/domain/residualUnknown.js +3 -2
- package/dist/domain/staticRuntimeCorrelation.js +3 -3
- package/dist/domain/webCaptureDiff.js +1 -0
- package/dist/domain/webNetworkCapture.js +118 -0
- package/dist/domain/webNetworkCaptureCoordinates.js +58 -0
- package/dist/dotnet/ManagedExceptionRegionValidation.js +41 -0
- package/dist/dotnet/ManagedMemberInspector.js +4 -2
- package/dist/dotnet/ManagedMemberInspectorCore.js +38 -32
- package/dist/dotnet/ManagedMemberInstructionDecoder.js +23 -7
- package/dist/dotnet/ManagedMemberRows.js +14 -3
- package/dist/dotnet/ManagedMetadataHeaps.js +20 -2
- package/dist/dotnet/ManagedMetadataInventory.js +38 -28
- package/dist/dotnet/ManagedMetadataInventoryRows.js +24 -10
- package/dist/dotnet/ManagedMethodBodyReader.js +7 -1
- package/dist/dotnet/ManagedNativeBoundaryHelpers.js +15 -3
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +3 -1
- package/dist/dotnet/ManagedPeReader.js +11 -0
- package/dist/evm/EvmBytecodeCarrier.js +21 -0
- package/dist/evm/EvmWorkerLimits.js +40 -0
- package/dist/evm/EvmoleFailures.js +126 -0
- package/dist/evm/EvmoleInterfaceAdapter.js +53 -0
- package/dist/evm/EvmoleInterfaceProvider.js +273 -0
- package/dist/evm/EvmoleInterfaceWorker.js +87 -0
- package/dist/evm/EvmoleRelease.js +16 -0
- package/dist/firmware/FirmwareCommand.js +1 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraLauncher.js +1 -0
- package/dist/ghidra/GhidraProvider.js +2 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +1 -1
- package/dist/hopper/BridgeLauncher.js +2 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +4 -2
- package/dist/javascript/recovery/WakaruCommand.js +3 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +1 -0
- package/dist/native/CommandRunner.js +2 -1
- package/dist/native/LldbCallTracer.js +278 -0
- package/dist/native/NativeCallObservation.js +135 -0
- package/dist/native/NativeMacOSProvider.js +19 -3
- package/dist/native/NativeMacOSProviderMetadata.js +8 -3
- package/dist/native/pwndbg/PwndbgCoreContext.js +160 -0
- package/dist/native/pwntools/PwntoolsDecoder.js +203 -0
- package/dist/native/pwntools/PwntoolsFailures.js +180 -0
- package/dist/native/pwntools/PwntoolsLayoutProvider.js +36 -0
- package/dist/native/pwntools/PwntoolsRecordedCrashProvider.js +75 -0
- package/dist/native/pwntools/PwntoolsRelease.js +19 -0
- package/dist/native/pwntools/PwntoolsResourceLimits.js +76 -0
- package/dist/native/pwntools/RecordedCrashSourceBindings.js +46 -0
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +101 -0
- package/dist/process/DarwinProcessRunTokenReader.js +294 -0
- package/dist/process/OwnedCommand.js +113 -0
- package/dist/process/ProcessOwnership.js +263 -33
- package/dist/process/ProcessOwnershipObservation.js +161 -24
- package/dist/process/ProviderProcess.js +30 -3
- package/dist/{application → process/capture}/ProcessCaptureCapability.js +15 -0
- package/dist/{application → process/capture}/ProcessCaptureError.js +17 -1
- package/dist/process/capture/ProcessCaptureLifecycle.js +615 -0
- package/dist/{application → process/capture}/ProcessHarness.js +195 -29
- package/dist/{application → process/capture}/ProcessSampling.js +2 -2
- package/dist/server/createServer.js +22 -0
- package/dist/server/promptCompletion.js +10 -3
- package/dist/server/registerBinaryDiagnosticsTools.js +17 -0
- package/dist/server/registerEvidenceTools.js +1 -1
- package/dist/server/registerEvmTools.js +17 -0
- package/dist/server/registerProcessComparisonTool.js +1 -1
- package/dist/server/registerRecordedCrashTools.js +17 -0
- package/dist/server/registerSessionTools.js +2 -2
- package/dist/server/registerWebNetworkCaptureTool.js +28 -0
- package/dist/server/sessionAvailabilityPolicy.js +3 -0
- package/dist/server/sessionToolPolicies.js +1 -1
- package/dist/server/toolResult.js +1 -1
- package/native/windows/build/manifest.json +2 -2
- package/native/windows/build/rea-windows-x64.node +0 -0
- package/package.json +37 -9
- package/scripts/rea.mjs +11 -3
- package/skills/reverse-engineer-anything/SKILL.md +23 -3
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +29 -0
- package/third_party/README.md +60 -0
- package/third_party/evmole/LICENSE +21 -0
- package/third_party/evmole/README.md +22 -0
- package/third_party/pwndbg/LICENSE.md +21 -0
- package/third_party/pwndbg/README.md +23 -0
- package/third_party/pwntools/README.md +40 -0
- package/dist/application/AuthorizedArtifactInventory.js +0 -7
- package/dist/application/ProcessCaptureLifecycle.js +0 -299
- package/dist/application/StoreFileLock.js +0 -124
- package/dist/contracts/processCaptureExample.js +0 -2
- package/dist/domain/bytecodeProvider.js +0 -178
- package/dist/domain/customProtocolCapture.js +0 -137
- package/dist/domain/eventProcessTree.js +0 -225
- package/dist/domain/mobileApplicationInvestigation.js +0 -159
- package/dist/domain/packageAnalysis.js +0 -123
- package/dist/domain/peInspection.js +0 -197
- package/dist/domain/processCaptureParsing.js +0 -21
- package/dist/domain/protocolCapture.js +0 -217
- package/dist/domain/runtimeIdentification.js +0 -212
- package/dist/generatedMcpToolCatalog.js +0 -37
- /package/dist/application/{ArtifactExtractionDestination.js → artifacts/ArtifactExtractionDestination.js} +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/policy.js +0 -0
- /package/dist/{application/ArtifactInventory → artifacts/inventory}/types.js +0 -0
- /package/dist/domain/{processEvidenceProvider.js → process/processEvidenceProvider.js} +0 -0
- /package/dist/domain/{processScenario.js → process/processScenario.js} +0 -0
- /package/dist/domain/{processTraceComparison.js → process/processTraceComparison.js} +0 -0
- /package/dist/domain/{processTraceDimensionProjection.js → process/processTraceDimensionProjection.js} +0 -0
- /package/dist/domain/{processTraceEvaluation.js → process/processTraceEvaluation.js} +0 -0
- /package/dist/{application → process/capture}/FilesystemSnapshot.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureEnvironment.js +0 -0
- /package/dist/{application → process/capture}/ProcessCaptureJournal.js +0 -0
- /package/dist/{application → process/capture}/ProcessFilesystemEffects.js +0 -0
- /package/dist/{application → process/capture}/ProcessNormalization.js +0 -0
- /package/dist/{application → process/capture}/ProcessScenarioRuntimeValidation.js +0 -0
- /package/dist/{application → process/capture}/ProcessTimer.js +0 -0
- /package/dist/{application → process/capture}/TerminalRenderer.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import {
|
|
3
|
+
import { errorMessage, systemProcessOwnershipHost as systemHost, } from "./ProcessOwnershipObservation.js";
|
|
4
4
|
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
5
5
|
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
@@ -28,7 +28,6 @@ export const cleanupWindowsProcessTree = async (rootPid, host = systemWindowsPro
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
const systemHost = createSystemProcessOwnershipHost();
|
|
32
31
|
const systemWindowsProcessTreeHost = {
|
|
33
32
|
async terminateTree(rootPid) {
|
|
34
33
|
try {
|
|
@@ -59,7 +58,7 @@ export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
59
58
|
return plan;
|
|
60
59
|
let signaled = false;
|
|
61
60
|
for (const processGroupId of plan.signalOrder) {
|
|
62
|
-
const revalidation = await revalidateOwnedProcessGroup(processGroupId, ownership, host);
|
|
61
|
+
const revalidation = await revalidateOwnedProcessGroup(processGroupId, ownership, host, plan.tokenOwnedIdentities);
|
|
63
62
|
if ("cleaned" in revalidation)
|
|
64
63
|
return revalidation;
|
|
65
64
|
if (revalidation.empty)
|
|
@@ -77,17 +76,19 @@ export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
77
76
|
};
|
|
78
77
|
}
|
|
79
78
|
}
|
|
79
|
+
if (plan.unresolved.length > 0)
|
|
80
|
+
return cleanupValidationFailure(plan.unresolved);
|
|
80
81
|
return { cleaned: true, signaled };
|
|
81
82
|
};
|
|
82
83
|
/** Verify that no live process still carries an owned capture run token. */
|
|
83
|
-
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost) => {
|
|
84
|
+
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost, captureBaseline) => {
|
|
84
85
|
const processTable = await readLiveProcessTable(host);
|
|
85
86
|
if (!processTable.available)
|
|
86
87
|
return {
|
|
87
88
|
cleaned: false,
|
|
88
89
|
reason: `process table could not be inspected: ${processTable.reason}`,
|
|
89
90
|
};
|
|
90
|
-
const scan = await scanTokenOwnedProcesses(processTable.processes, runId, host);
|
|
91
|
+
const scan = await scanTokenOwnedProcesses(processTable.processes, runId, host, captureBaseline);
|
|
91
92
|
if (scan.failures.length > 0)
|
|
92
93
|
return cleanupValidationFailure(scan.failures);
|
|
93
94
|
return scan.owned.length === 0
|
|
@@ -106,18 +107,24 @@ const readLiveProcessTable = async (host) => {
|
|
|
106
107
|
}
|
|
107
108
|
};
|
|
108
109
|
const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
110
|
+
const sampledProcessGroupIds = new Set((ownership.sampledProcessGroupIds ?? []).filter((processGroupId) => Number.isSafeInteger(processGroupId) && processGroupId > 0));
|
|
109
111
|
const tokenOwned = ownership.sweepTokenOwnedProcesses === true
|
|
110
|
-
? await scanTokenOwnedProcesses(processes, ownership.runId, host)
|
|
111
|
-
: { owned: [], failures: [] };
|
|
112
|
-
if (tokenOwned.failures.length > 0)
|
|
113
|
-
return cleanupValidationFailure(tokenOwned.failures);
|
|
112
|
+
? await scanTokenOwnedProcesses(processes, ownership.runId, host, ownership.captureBaseline)
|
|
113
|
+
: { owned: [], failures: [], identities: new Map() };
|
|
114
114
|
const tokenOwnedGroupIds = new Set(tokenOwned.owned.map(({ processGroupId }) => processGroupId));
|
|
115
115
|
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
116
116
|
const rootMembers = processes.filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
117
117
|
if (launcher === undefined &&
|
|
118
118
|
rootMembers.length === 0 &&
|
|
119
|
-
tokenOwned.owned.length === 0
|
|
120
|
-
|
|
119
|
+
tokenOwned.owned.length === 0 &&
|
|
120
|
+
!processes.some(({ processGroupId }) => sampledProcessGroupIds.has(processGroupId)))
|
|
121
|
+
return tokenOwned.failures.length > 0
|
|
122
|
+
? {
|
|
123
|
+
signalOrder: [],
|
|
124
|
+
tokenOwnedIdentities: tokenOwned.identities,
|
|
125
|
+
unresolved: tokenOwned.failures,
|
|
126
|
+
}
|
|
127
|
+
: { cleaned: true, signaled: false };
|
|
121
128
|
let descendants = [];
|
|
122
129
|
if (launcher !== undefined) {
|
|
123
130
|
const identityFailure = launcherIdentityFailure(launcher, ownership);
|
|
@@ -131,28 +138,61 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
131
138
|
processGroupIds.add(descendant.processGroupId);
|
|
132
139
|
for (const process of tokenOwned.owned)
|
|
133
140
|
processGroupIds.add(process.processGroupId);
|
|
141
|
+
for (const processGroupId of sampledProcessGroupIds)
|
|
142
|
+
processGroupIds.add(processGroupId);
|
|
143
|
+
const signalableGroups = new Set();
|
|
144
|
+
const unresolved = [...tokenOwned.failures];
|
|
145
|
+
const unverifiableSampledGroups = new Set();
|
|
134
146
|
for (const processGroupId of processGroupIds) {
|
|
135
147
|
if (processGroupId === ownership.processGroupId)
|
|
136
148
|
continue;
|
|
137
149
|
const groupLeader = processes.find(({ pid, processGroupId: observedGroupId }) => pid === processGroupId && observedGroupId === processGroupId);
|
|
138
150
|
if ((groupLeader === undefined || !descendantPids.has(groupLeader.pid)) &&
|
|
139
|
-
!tokenOwnedGroupIds.has(processGroupId)
|
|
151
|
+
!tokenOwnedGroupIds.has(processGroupId) &&
|
|
152
|
+
!sampledProcessGroupIds.has(processGroupId))
|
|
140
153
|
return {
|
|
141
154
|
cleaned: false,
|
|
142
155
|
reason: "descendant process-group leader identity could not be verified",
|
|
143
156
|
};
|
|
157
|
+
if ((groupLeader === undefined || !descendantPids.has(groupLeader.pid)) &&
|
|
158
|
+
!tokenOwnedGroupIds.has(processGroupId))
|
|
159
|
+
unverifiableSampledGroups.add(processGroupId);
|
|
144
160
|
}
|
|
145
161
|
const liveMembers = processes.filter(({ processGroupId }) => processGroupIds.has(processGroupId));
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
for (const processGroupId of processGroupIds) {
|
|
163
|
+
const members = liveMembers.filter(({ processGroupId: observedGroupId }) => observedGroupId === processGroupId);
|
|
164
|
+
if (members.length === 0)
|
|
165
|
+
continue;
|
|
166
|
+
const failures = await processOwnershipFailures(members, ownership.runId, host, tokenOwned.identities);
|
|
167
|
+
if (failures.length === 0 && unverifiableSampledGroups.has(processGroupId))
|
|
168
|
+
unresolved.push(...members.map(({ pid }) => ({
|
|
169
|
+
pid,
|
|
170
|
+
reason: "process-identity-unavailable",
|
|
171
|
+
diagnostic: "sampled process-group leader identity could not be verified",
|
|
172
|
+
})));
|
|
173
|
+
const scanFailures = tokenOwned.failures.filter(({ pid }) => members.some((member) => member.pid === pid));
|
|
174
|
+
if (failures.length > 0)
|
|
175
|
+
unresolved.push(...failures);
|
|
176
|
+
if (failures.length === 0 &&
|
|
177
|
+
scanFailures.length === 0 &&
|
|
178
|
+
!unverifiableSampledGroups.has(processGroupId))
|
|
179
|
+
signalableGroups.add(processGroupId);
|
|
180
|
+
}
|
|
181
|
+
if (ownership.sweepTokenOwnedProcesses !== true && unresolved.length > 0)
|
|
182
|
+
return cleanupValidationFailure(unresolved);
|
|
149
183
|
return {
|
|
150
|
-
signalOrder: [
|
|
151
|
-
.filter((
|
|
152
|
-
.sort((left, right) => left
|
|
184
|
+
signalOrder: [...signalableGroups]
|
|
185
|
+
.filter((groupId) => processGroupIds.has(groupId))
|
|
186
|
+
.sort((left, right) => left === ownership.processGroupId
|
|
187
|
+
? -1
|
|
188
|
+
: right === ownership.processGroupId
|
|
189
|
+
? 1
|
|
190
|
+
: left - right),
|
|
191
|
+
tokenOwnedIdentities: tokenOwned.identities,
|
|
192
|
+
unresolved,
|
|
153
193
|
};
|
|
154
194
|
};
|
|
155
|
-
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
195
|
+
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host, expectedIdentities) => {
|
|
156
196
|
let members;
|
|
157
197
|
try {
|
|
158
198
|
members = liveProcesses(await host.listProcesses()).filter(({ processGroupId: observedGroupId }) => observedGroupId === processGroupId);
|
|
@@ -163,7 +203,7 @@ const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
|
163
203
|
reason: `process ownership could not be revalidated: ${errorMessage(cause)}`,
|
|
164
204
|
};
|
|
165
205
|
}
|
|
166
|
-
const failures = await processOwnershipFailures(members, ownership.runId, host);
|
|
206
|
+
const failures = await processOwnershipFailures(members, ownership.runId, host, expectedIdentities);
|
|
167
207
|
if (failures.length > 0)
|
|
168
208
|
return cleanupValidationFailure(failures);
|
|
169
209
|
if (processGroupId === ownership.processGroupId) {
|
|
@@ -184,15 +224,93 @@ const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
|
184
224
|
};
|
|
185
225
|
return { empty: members.length === 0 };
|
|
186
226
|
};
|
|
187
|
-
const cleanupValidationFailure = (failures) =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
const cleanupValidationFailure = (failures) => {
|
|
228
|
+
const unreadable = failures.filter(({ reason }) => reason === "environment-unreadable");
|
|
229
|
+
if (failures.some(({ reason }) => reason === "run-token-mismatch"))
|
|
230
|
+
return {
|
|
231
|
+
cleaned: false,
|
|
232
|
+
reason: "process tree contains an unowned or PID-reused process",
|
|
233
|
+
failures,
|
|
234
|
+
};
|
|
235
|
+
if (failures.some(({ reason }) => reason === "process-identity-unavailable"))
|
|
236
|
+
return {
|
|
237
|
+
cleaned: false,
|
|
238
|
+
reason: "process identity could not be revalidated",
|
|
239
|
+
failures,
|
|
240
|
+
};
|
|
241
|
+
if (unreadable.length > 0) {
|
|
242
|
+
const counts = new Map();
|
|
243
|
+
for (const { diagnostic } of unreadable) {
|
|
244
|
+
const category = sanitizedTokenReadFailure(diagnostic);
|
|
245
|
+
counts.set(category, (counts.get(category) ?? 0) + 1);
|
|
246
|
+
}
|
|
247
|
+
const breakdown = [...counts]
|
|
248
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
249
|
+
.map(([category, count]) => `${category}=${String(count)}`)
|
|
250
|
+
.join(", ");
|
|
251
|
+
return {
|
|
252
|
+
cleaned: false,
|
|
253
|
+
reason: `process ownership token could not be read for ${String(unreadable.length)} live process(es): ${breakdown}`,
|
|
254
|
+
failures,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
cleaned: false,
|
|
259
|
+
reason: "process ownership could not be revalidated",
|
|
260
|
+
failures,
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
const sanitizedTokenReadFailure = (diagnostic) => {
|
|
264
|
+
if (diagnostic === "environment_unavailable")
|
|
265
|
+
return diagnostic;
|
|
266
|
+
const environmentErrno = /^(EACCES|EPERM|ENOENT|ESRCH):/u.exec(diagnostic ?? "");
|
|
267
|
+
if (environmentErrno?.[1] !== undefined)
|
|
268
|
+
return `environment_errno_${environmentErrno[1]}`;
|
|
269
|
+
if (diagnostic === "apple_vector_unavailable")
|
|
270
|
+
return diagnostic;
|
|
271
|
+
if (diagnostic === "ambiguous_environment_boundary")
|
|
272
|
+
return diagnostic;
|
|
273
|
+
if (diagnostic === "malformed_procargs")
|
|
274
|
+
return diagnostic;
|
|
275
|
+
if (diagnostic === "process_identity_changed_during_token_read")
|
|
276
|
+
return diagnostic;
|
|
277
|
+
if (diagnostic === "process_not_in_snapshot")
|
|
278
|
+
return diagnostic;
|
|
279
|
+
const processTableFailure = /^process_table_failed_(\d+)$/u.exec(diagnostic ?? "");
|
|
280
|
+
if (processTableFailure?.[1] !== undefined)
|
|
281
|
+
return `process_table_errno_${processTableFailure[1]}`;
|
|
282
|
+
const sysctlFailure = /^sysctl_failed_(\d+)$/u.exec(diagnostic ?? "");
|
|
283
|
+
if (sysctlFailure?.[1] !== undefined)
|
|
284
|
+
return `sysctl_errno_${sysctlFailure[1]}`;
|
|
285
|
+
if (diagnostic === "reader_failure")
|
|
286
|
+
return diagnostic;
|
|
287
|
+
if (diagnostic === "duplicate_run_token")
|
|
288
|
+
return diagnostic;
|
|
289
|
+
if (diagnostic === "invalid_run_token_encoding")
|
|
290
|
+
return diagnostic;
|
|
291
|
+
return "other_unavailable";
|
|
292
|
+
};
|
|
293
|
+
const processOwnershipFailures = async (members, runId, host, expectedIdentities = new Map()) => {
|
|
195
294
|
const failures = [];
|
|
295
|
+
if (expectedIdentities.size > 0 && host.processIdentities !== undefined) {
|
|
296
|
+
const expectedMembers = members.filter(({ pid }) => expectedIdentities.has(pid));
|
|
297
|
+
const observed = await host.processIdentities(expectedMembers);
|
|
298
|
+
for (const member of expectedMembers) {
|
|
299
|
+
const expected = expectedIdentities.get(member.pid);
|
|
300
|
+
const current = observed.get(member.pid);
|
|
301
|
+
if (expected === null ||
|
|
302
|
+
current?.state !== "readable" ||
|
|
303
|
+
current.identity !== expected) {
|
|
304
|
+
if (await processIsGone(host, member.pid))
|
|
305
|
+
continue;
|
|
306
|
+
failures.push({
|
|
307
|
+
pid: member.pid,
|
|
308
|
+
reason: "process-identity-unavailable",
|
|
309
|
+
diagnostic: "process identity changed or became unavailable before signal",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
196
314
|
for (const member of members) {
|
|
197
315
|
try {
|
|
198
316
|
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !== runId)
|
|
@@ -219,15 +337,94 @@ const processOwnershipFailures = async (members, runId, host) => {
|
|
|
219
337
|
});
|
|
220
338
|
}
|
|
221
339
|
}
|
|
340
|
+
if (failures.length === 0 &&
|
|
341
|
+
expectedIdentities.size > 0 &&
|
|
342
|
+
host.processIdentities !== undefined) {
|
|
343
|
+
const expectedMembers = members.filter(({ pid }) => expectedIdentities.has(pid));
|
|
344
|
+
const observed = await host.processIdentities(expectedMembers);
|
|
345
|
+
for (const member of expectedMembers) {
|
|
346
|
+
const expected = expectedIdentities.get(member.pid);
|
|
347
|
+
const current = observed.get(member.pid);
|
|
348
|
+
if (expected !== null &&
|
|
349
|
+
current?.state === "readable" &&
|
|
350
|
+
current.identity === expected)
|
|
351
|
+
continue;
|
|
352
|
+
if (await processIsGone(host, member.pid))
|
|
353
|
+
continue;
|
|
354
|
+
failures.push({
|
|
355
|
+
pid: member.pid,
|
|
356
|
+
reason: "process-identity-unavailable",
|
|
357
|
+
diagnostic: "process identity changed or became unavailable during token validation",
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
222
361
|
return failures;
|
|
223
362
|
};
|
|
224
|
-
const scanTokenOwnedProcesses = async (processes, runId, host) => {
|
|
225
|
-
|
|
363
|
+
const scanTokenOwnedProcesses = async (processes, runId, host, captureBaseline) => {
|
|
364
|
+
let candidates = processes;
|
|
365
|
+
let candidateIdentities = new Map();
|
|
366
|
+
if (host.processIdentities !== undefined) {
|
|
367
|
+
const current = await host.processIdentities(processes);
|
|
368
|
+
candidateIdentities = current;
|
|
369
|
+
if (captureBaseline !== undefined) {
|
|
370
|
+
const baseline = new Map(captureBaseline.map(({ pid, identity }) => [pid, identity]));
|
|
371
|
+
candidates = processes.filter((process) => {
|
|
372
|
+
const before = baseline.get(process.pid);
|
|
373
|
+
const now = current.get(process.pid);
|
|
374
|
+
return !(before !== undefined &&
|
|
375
|
+
before !== null &&
|
|
376
|
+
now?.state === "readable" &&
|
|
377
|
+
now.identity === before);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
226
381
|
const failures = [];
|
|
227
|
-
|
|
382
|
+
if (captureBaseline !== undefined && host.processIdentities !== undefined) {
|
|
383
|
+
const stableCandidates = [];
|
|
384
|
+
for (const process of candidates) {
|
|
385
|
+
if (candidateIdentities.get(process.pid)?.state === "readable") {
|
|
386
|
+
stableCandidates.push(process);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (!(await processIsGone(host, process.pid)))
|
|
390
|
+
failures.push({
|
|
391
|
+
pid: process.pid,
|
|
392
|
+
reason: "process-identity-unavailable",
|
|
393
|
+
diagnostic: "process identity was unavailable during token scan",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
candidates = stableCandidates;
|
|
397
|
+
}
|
|
398
|
+
let bulkTokens;
|
|
399
|
+
if (host.runTokens !== undefined)
|
|
400
|
+
bulkTokens = await host.runTokens(candidates);
|
|
401
|
+
const owned = [];
|
|
402
|
+
const ownedIdentities = new Map();
|
|
403
|
+
for (const process of candidates) {
|
|
404
|
+
const bulkToken = bulkTokens?.get(process.pid);
|
|
405
|
+
if (bulkToken !== undefined) {
|
|
406
|
+
if (bulkToken.state === "readable") {
|
|
407
|
+
if (bulkToken.runId === runId) {
|
|
408
|
+
owned.push(process);
|
|
409
|
+
const identity = candidateIdentities.get(process.pid);
|
|
410
|
+
ownedIdentities.set(process.pid, identity?.state === "readable" ? identity.identity : null);
|
|
411
|
+
}
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (!(await processIsGone(host, process.pid)))
|
|
415
|
+
failures.push({
|
|
416
|
+
pid: process.pid,
|
|
417
|
+
reason: "environment-unreadable",
|
|
418
|
+
diagnostic: bulkToken.reason,
|
|
419
|
+
});
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
228
422
|
try {
|
|
229
|
-
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId)
|
|
423
|
+
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId) {
|
|
230
424
|
owned.push(process);
|
|
425
|
+
const identity = candidateIdentities.get(process.pid);
|
|
426
|
+
ownedIdentities.set(process.pid, identity?.state === "readable" ? identity.identity : null);
|
|
427
|
+
}
|
|
231
428
|
}
|
|
232
429
|
catch (cause) {
|
|
233
430
|
try {
|
|
@@ -250,5 +447,38 @@ const scanTokenOwnedProcesses = async (processes, runId, host) => {
|
|
|
250
447
|
});
|
|
251
448
|
}
|
|
252
449
|
}
|
|
253
|
-
|
|
450
|
+
if (candidates.length > 0 && host.processIdentities !== undefined) {
|
|
451
|
+
const afterRead = await host.processIdentities(candidates);
|
|
452
|
+
const stillOwned = [];
|
|
453
|
+
const ownedPids = new Set(owned.map(({ pid }) => pid));
|
|
454
|
+
for (const process of candidates) {
|
|
455
|
+
const before = candidateIdentities.get(process.pid);
|
|
456
|
+
const current = afterRead.get(process.pid);
|
|
457
|
+
if (before?.state === "readable" &&
|
|
458
|
+
current?.state === "readable" &&
|
|
459
|
+
current.identity === before.identity) {
|
|
460
|
+
if (ownedPids.has(process.pid))
|
|
461
|
+
stillOwned.push(process);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (await processIsGone(host, process.pid))
|
|
465
|
+
continue;
|
|
466
|
+
failures.push({
|
|
467
|
+
pid: process.pid,
|
|
468
|
+
reason: "process-identity-unavailable",
|
|
469
|
+
diagnostic: "process identity changed or became unavailable during token validation",
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return { owned: stillOwned, failures, identities: ownedIdentities };
|
|
473
|
+
}
|
|
474
|
+
return { owned, failures, identities: ownedIdentities };
|
|
475
|
+
};
|
|
476
|
+
const processIsGone = async (host, pid) => {
|
|
477
|
+
try {
|
|
478
|
+
return !liveProcesses(await host.listProcesses()).some((process) => process.pid === pid);
|
|
479
|
+
}
|
|
480
|
+
catch (cause) {
|
|
481
|
+
void cause;
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
254
484
|
};
|
|
@@ -2,6 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
3
3
|
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
4
4
|
import { execFileOutput } from "./ExecFileOutput.js";
|
|
5
|
+
import { createDarwinProcessRunTokenReader } from "./DarwinProcessRunTokenReader.js";
|
|
5
6
|
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
6
7
|
export const observeOwnedProcessGroupWithHost = async (ownership, host) => {
|
|
7
8
|
let members;
|
|
@@ -105,13 +106,34 @@ export const parseProcessEnvironment = (value) => Object.fromEntries(value
|
|
|
105
106
|
const separator = entry.indexOf("=");
|
|
106
107
|
return [entry.slice(0, separator), entry.slice(separator + 1)];
|
|
107
108
|
}));
|
|
109
|
+
/** Read Linux stat field 22, tolerating spaces and closing parentheses in comm. */
|
|
110
|
+
export const parseLinuxProcessStartTime = (value) => {
|
|
111
|
+
const commandEnd = value.lastIndexOf(")");
|
|
112
|
+
if (commandEnd < 0)
|
|
113
|
+
return undefined;
|
|
114
|
+
const fields = value
|
|
115
|
+
.slice(commandEnd + 1)
|
|
116
|
+
.trim()
|
|
117
|
+
.split(/\s+/u);
|
|
118
|
+
const startTime = fields[19];
|
|
119
|
+
return startTime !== undefined && /^\d+$/u.test(startTime)
|
|
120
|
+
? startTime
|
|
121
|
+
: undefined;
|
|
122
|
+
};
|
|
123
|
+
const isExpectedAbort = (cause, signal) => signal?.aborted === true &&
|
|
124
|
+
(cause === signal.reason ||
|
|
125
|
+
(cause instanceof Error && cause.name === "AbortError"));
|
|
108
126
|
/** Create the operating-system process inspector for an explicit host context. */
|
|
109
|
-
export const createSystemProcessOwnershipHost = (platform = process.platform, hostEnvironment = process.env) =>
|
|
110
|
-
platform
|
|
111
|
-
|
|
127
|
+
export const createSystemProcessOwnershipHost = (platform = process.platform, hostEnvironment = process.env, options = {}) => {
|
|
128
|
+
const darwinTokens = platform === "darwin"
|
|
129
|
+
? createDarwinProcessRunTokenReader(options.darwinXcrun === undefined
|
|
130
|
+
? {}
|
|
131
|
+
: { xcrun: options.darwinXcrun })
|
|
132
|
+
: undefined;
|
|
133
|
+
const listProcesses = async (signal) => {
|
|
112
134
|
if (platform === "win32")
|
|
113
135
|
return [];
|
|
114
|
-
const { stdout } = await execFileOutput("ps", ["-axo", "pid=,ppid=,pgid=,stat=,command="], { env: hostEnvironment });
|
|
136
|
+
const { stdout } = await execFileOutput("ps", ["-axo", "pid=,ppid=,pgid=,stat=,command="], { env: hostEnvironment, ...(signal === undefined ? {} : { signal }) });
|
|
115
137
|
return stdout
|
|
116
138
|
.split("\n")
|
|
117
139
|
.map((line) => /\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/u.exec(line))
|
|
@@ -123,28 +145,143 @@ export const createSystemProcessOwnershipHost = (platform = process.platform, ho
|
|
|
123
145
|
state: match[4] ?? "",
|
|
124
146
|
command: match[5] ?? "",
|
|
125
147
|
}));
|
|
126
|
-
}
|
|
127
|
-
async
|
|
148
|
+
};
|
|
149
|
+
const processIdentities = async (processes, signal) => {
|
|
150
|
+
if (processes.length === 0)
|
|
151
|
+
return new Map();
|
|
128
152
|
if (platform === "linux")
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
return new Map(await Promise.all(processes.map(async ({ pid }) => {
|
|
154
|
+
try {
|
|
155
|
+
const stat = await readFile(`/proc/${String(pid)}/stat`, {
|
|
156
|
+
encoding: "utf8",
|
|
157
|
+
...(signal === undefined ? {} : { signal }),
|
|
158
|
+
});
|
|
159
|
+
const identity = parseLinuxProcessStartTime(stat);
|
|
160
|
+
return [
|
|
161
|
+
pid,
|
|
162
|
+
identity === undefined
|
|
163
|
+
? {
|
|
164
|
+
state: "unavailable",
|
|
165
|
+
reason: "malformed_proc_stat",
|
|
166
|
+
}
|
|
167
|
+
: {
|
|
168
|
+
state: "readable",
|
|
169
|
+
identity: `linux-starttime:${identity}`,
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
catch (cause) {
|
|
174
|
+
if (isExpectedAbort(cause, signal))
|
|
175
|
+
throw cause;
|
|
176
|
+
return [
|
|
177
|
+
pid,
|
|
178
|
+
{ state: "unavailable", reason: errorMessage(cause) },
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
})));
|
|
182
|
+
if (darwinTokens === undefined)
|
|
183
|
+
return new Map();
|
|
184
|
+
try {
|
|
185
|
+
return await darwinTokens.identities(processes, signal);
|
|
138
186
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
187
|
+
catch (cause) {
|
|
188
|
+
if (isExpectedAbort(cause, signal))
|
|
189
|
+
throw cause;
|
|
190
|
+
const reason = errorMessage(cause);
|
|
191
|
+
return new Map(processes.map(({ pid }) => [
|
|
192
|
+
pid,
|
|
193
|
+
{ state: "unavailable", reason },
|
|
194
|
+
]));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const captureBaseline = async (signal) => {
|
|
198
|
+
if (platform !== "linux" && darwinTokens === undefined)
|
|
199
|
+
return [];
|
|
200
|
+
const processes = liveProcesses(await listProcesses(signal));
|
|
201
|
+
let identities = await processIdentities(processes, signal);
|
|
202
|
+
const unreadable = processes.filter(({ pid }) => identities.get(pid)?.state !== "readable");
|
|
203
|
+
if (unreadable.length > 0) {
|
|
204
|
+
const stillLive = liveProcesses(await listProcesses(signal)).filter(({ pid }) => unreadable.some((entry) => entry.pid === pid));
|
|
205
|
+
const retried = await processIdentities(stillLive, signal);
|
|
206
|
+
identities = new Map([...identities, ...retried]);
|
|
207
|
+
const unresolved = stillLive.filter(({ pid }) => identities.get(pid)?.state !== "readable");
|
|
208
|
+
const liveAfterRetry = liveProcesses(await listProcesses(signal));
|
|
209
|
+
const stillUnresolved = unresolved.filter(({ pid }) => liveAfterRetry.some((entry) => entry.pid === pid));
|
|
210
|
+
if (stillUnresolved.length > 0)
|
|
211
|
+
throw new Error(`process identity snapshot is unavailable for ${String(stillUnresolved.length)} live processes`);
|
|
212
|
+
}
|
|
213
|
+
return processes.map(({ pid }) => {
|
|
214
|
+
const observation = identities.get(pid);
|
|
215
|
+
return {
|
|
216
|
+
pid,
|
|
217
|
+
identity: observation?.state === "readable" ? observation.identity : null,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
return {
|
|
222
|
+
platform,
|
|
223
|
+
prepare: async (signal) => {
|
|
224
|
+
await darwinTokens?.prepare(signal);
|
|
225
|
+
},
|
|
226
|
+
listProcesses,
|
|
227
|
+
async environment(pid) {
|
|
228
|
+
if (platform === "linux")
|
|
229
|
+
return parseProcessEnvironment(await readFile(`/proc/${pid}/environ`, "utf8"));
|
|
230
|
+
if (platform === "darwin") {
|
|
231
|
+
const process = (await listProcesses()).find((entry) => entry.pid === pid);
|
|
232
|
+
if (process === undefined)
|
|
233
|
+
throw new Error(`process ${String(pid)} is not live`);
|
|
234
|
+
const observation = (await darwinTokens?.read([process]))?.get(pid);
|
|
235
|
+
if (observation === undefined || observation.state === "unavailable")
|
|
236
|
+
throw new Error(observation?.reason ?? "process run token could not be read");
|
|
237
|
+
return observation.runId === undefined
|
|
238
|
+
? {}
|
|
239
|
+
: { REA_PROCESS_RUN_ID: observation.runId };
|
|
240
|
+
}
|
|
241
|
+
const { stdout } = await execFileOutput("ps", ["eww", "-p", String(pid)], {
|
|
242
|
+
env: hostEnvironment,
|
|
243
|
+
});
|
|
244
|
+
const observedEnvironment = {};
|
|
245
|
+
for (const match of stdout.matchAll(/(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu)) {
|
|
246
|
+
const name = match[1];
|
|
247
|
+
if (name !== undefined)
|
|
248
|
+
observedEnvironment[name] = match[2] ?? "";
|
|
249
|
+
}
|
|
250
|
+
return observedEnvironment;
|
|
251
|
+
},
|
|
252
|
+
async runTokens(processes) {
|
|
253
|
+
if (platform !== "darwin" || processes.length === 0)
|
|
254
|
+
return new Map();
|
|
255
|
+
try {
|
|
256
|
+
return (await darwinTokens?.read(processes)) ?? new Map();
|
|
257
|
+
}
|
|
258
|
+
catch (cause) {
|
|
259
|
+
void cause;
|
|
260
|
+
return new Map(processes.map(({ pid }) => [
|
|
261
|
+
pid,
|
|
262
|
+
{ state: "unavailable", reason: "reader_failure" },
|
|
263
|
+
]));
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
...(platform !== "linux" && darwinTokens === undefined
|
|
267
|
+
? {}
|
|
268
|
+
: {
|
|
269
|
+
processIdentities,
|
|
270
|
+
captureBaseline,
|
|
271
|
+
...(darwinTokens === undefined ? {} : { close: darwinTokens.close }),
|
|
272
|
+
}),
|
|
273
|
+
signalGroup(processGroupId, signal) {
|
|
274
|
+
process.kill(-processGroupId, signal);
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
export const systemProcessOwnershipHost = createSystemProcessOwnershipHost();
|
|
279
|
+
/** Prepare native token inspection before REA launches a captured child. */
|
|
280
|
+
export const prepareProcessOwnershipInspection = async (signal) => {
|
|
281
|
+
await systemProcessOwnershipHost.prepare?.(signal);
|
|
282
|
+
};
|
|
146
283
|
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
147
|
-
export const observeOwnedProcessGroup = async (ownership, host =
|
|
284
|
+
export const observeOwnedProcessGroup = async (ownership, host = systemProcessOwnershipHost) => observeOwnedProcessGroupWithHost(ownership, host);
|
|
148
285
|
/**
|
|
149
286
|
* Record the live launcher and descendant lineage after run-token validation.
|
|
150
287
|
*
|
|
@@ -152,7 +289,7 @@ export const observeOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
152
289
|
* list means no descendants were live during this observation, not that the
|
|
153
290
|
* run never created a short-lived child.
|
|
154
291
|
*/
|
|
155
|
-
export const observeOwnedProcessLineage = async (ownership, host =
|
|
292
|
+
export const observeOwnedProcessLineage = async (ownership, host = systemProcessOwnershipHost) => observeOwnedProcessLineageWithHost(ownership, host);
|
|
156
293
|
const processIsGone = async (host, pid) => {
|
|
157
294
|
try {
|
|
158
295
|
return !liveProcesses(await host.listProcesses()).some((process) => process.pid === pid);
|