rea-agents 2.5.0 → 3.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 +20 -12
- package/bridge/ghidra/ReaGhidraBridge.java +477 -4
- package/bridge/hopper_bridge.py +54 -4
- package/dist/application/AnalysisContextQueries.js +142 -0
- package/dist/application/AnalysisSnapshotCache.js +4 -2
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
- package/dist/application/ArtifactInventory/policy.js +17 -0
- package/dist/application/ArtifactInventory/reader.js +1 -4
- package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
- package/dist/application/ArtifactInventory/scanReader.js +7 -7
- package/dist/application/ArtifactInventory/types.js +1 -5
- package/dist/application/BinarySession.js +19 -20
- package/dist/application/BinarySessionComposition.js +21 -0
- package/dist/application/BinarySessionRecords.js +106 -10
- package/dist/application/BinarySessionStatus.js +11 -5
- package/dist/application/BrowserEvidence.js +3 -5
- package/dist/application/CapabilityClientRequirements.js +26 -0
- package/dist/application/CapabilityInventory.js +140 -55
- package/dist/application/ClientRegistrationStatus.js +4 -1
- package/dist/application/CrossVersionInventory.js +7 -6
- package/dist/application/DirectAnalysis.js +6 -2
- package/dist/application/ElectronActiveEvidence.js +55 -0
- package/dist/application/ElectronActiveObservationPort.js +1 -0
- package/dist/application/ElectronActiveObservationService.js +81 -0
- package/dist/application/EnhancedTools.js +49 -17
- package/dist/application/EvidenceBundleCommands.js +5 -1
- package/dist/application/EvidenceLedger.js +53 -17
- package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
- package/dist/application/JavaScriptReplayPermission.js +1 -1
- package/dist/application/JavaScriptReplayService.js +23 -15
- package/dist/application/LazyAnalysisProvider.js +77 -0
- package/dist/application/LoopbackReplay.js +1 -1
- package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
- package/dist/application/NativeApiInspection.js +80 -0
- package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
- package/dist/application/ProcessCaptureLifecycle.js +5 -0
- package/dist/application/ProcessCli.js +2 -1
- package/dist/application/ProcessHarness.js +8 -3
- package/dist/application/ProcessNormalization.js +2 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +2 -2
- package/dist/application/SessionProviderRouter.js +6 -6
- package/dist/application/SetupClientConfiguration.js +10 -3
- package/dist/application/WindowsCapabilities.js +5 -4
- package/dist/application/runtime.js +26 -9
- package/dist/artifacts/ArtifactProvider.js +8 -10
- package/dist/artifacts/SafeOutputTree.js +7 -8
- package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
- package/dist/browser/CdpElectronInspection.js +3 -1
- package/dist/browser/JavaScriptRuntimeScope.js +5 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
- package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
- package/dist/browser/PlaywrightScenarioSession.js +4 -4
- package/dist/browser/SensitiveTextCapture.js +1 -1
- package/dist/browser/V8InspectorCaptureProjection.js +1 -0
- package/dist/browser/V8InspectorProvider.js +3 -3
- package/dist/catalogIdentity.js +15 -9
- package/dist/cli/artifactCommands.js +5 -3
- package/dist/cli/coreAnalysisCommands.js +41 -0
- package/dist/cli/managedCommands.js +1 -1
- package/dist/cliApplicationCommands.js +2 -2
- package/dist/cliBrowserAdvancedCommands.js +11 -5
- package/dist/cliBrowserCommands.js +14 -7
- package/dist/cliCommandNames.js +2 -0
- package/dist/cliElectronCommands.js +46 -4
- package/dist/cliJavaScriptRuntimeCommands.js +14 -6
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/browserScenario.js +77 -0
- package/dist/config/electronAutomation.js +35 -0
- package/dist/config/environment.js +6 -0
- package/dist/config/javascriptReplay.js +41 -0
- package/dist/config/managedRuntime.js +25 -0
- package/dist/config/parseConfig.js +56 -200
- package/dist/config/parsers.js +10 -1
- package/dist/config/passiveObservation.js +157 -0
- package/dist/config/processCapture.js +47 -0
- package/dist/contracts/applicationToolContracts.js +2 -5
- package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
- package/dist/contracts/artifactToolContracts.js +35 -37
- package/dist/contracts/browserToolContracts.js +2 -2
- package/dist/contracts/electronToolContracts.js +30 -1
- package/dist/contracts/enhancedInputs.js +21 -27
- package/dist/contracts/functionWorkflowToolContracts.js +29 -0
- package/dist/contracts/managedToolContracts.js +4 -9
- package/dist/contracts/managedWorkflowExamples.js +12 -9
- package/dist/contracts/managedWorkflowToolContracts.js +30 -27
- package/dist/contracts/nativeToolContracts.js +2 -3
- package/dist/contracts/promptContracts.js +14 -15
- package/dist/contracts/sessionToolSchemas.js +61 -0
- package/dist/contracts/toolContractExamples.js +4 -0
- package/dist/contracts/toolContractHelpers.js +23 -0
- package/dist/contracts/toolContracts.js +12 -67
- package/dist/contracts/toolEffects.js +19 -0
- package/dist/contracts/toolOutputSchemaGroups.js +66 -11
- package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
- package/dist/contracts/toolSchemaMetadata.js +128 -0
- package/dist/doctorRuntime.js +1 -1
- package/dist/domain/analysisErrorPresentation.js +8 -2
- package/dist/domain/analysisErrorProjection.js +1 -0
- package/dist/domain/androidApplication.js +2 -0
- package/dist/domain/appleApplication.js +2 -0
- package/dist/domain/binaryTarget.js +7 -5
- package/dist/domain/binaryTargetTypes.js +1 -0
- package/dist/domain/browserCaptureComparison.js +21 -69
- package/dist/domain/browserObservation.js +22 -9
- package/dist/domain/bytecodeProvider.js +177 -0
- package/dist/domain/callPath.js +3 -2
- package/dist/domain/conformancePackage.js +169 -0
- package/dist/domain/conformanceReplay.js +104 -0
- package/dist/domain/conformanceTrustGate.js +328 -0
- package/dist/domain/customProtocolCapture.js +137 -0
- package/dist/domain/electronActiveObservation.js +277 -0
- package/dist/domain/electronObservation.js +15 -11
- package/dist/domain/electronStaticAnalysisValues.js +4 -1
- package/dist/domain/eventProcessTree.js +217 -0
- package/dist/domain/evidenceBundle.js +47 -0
- package/dist/domain/functionComparisonNormalization.js +15 -8
- package/dist/domain/functionDossierEvidence.js +9 -4
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +68 -68
- package/dist/domain/inputIssueProjection.js +11 -2
- package/dist/domain/investigationWorkspace.js +16 -15
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
- package/dist/domain/javascriptReplay.js +79 -63
- package/dist/domain/javascriptRuntimeObservation.js +1 -0
- package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
- package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +8 -12
- package/dist/domain/javascriptSourceParser.js +15 -0
- package/dist/domain/javascriptStaticAnalysis.js +8 -12
- package/dist/domain/knownPageCoverage.js +15 -7
- package/dist/domain/mobileApplicationInvestigation.js +159 -0
- package/dist/domain/nativeApiBoundary.js +138 -0
- package/dist/domain/nodeRuntimeCharacterization.js +14 -13
- package/dist/domain/objcSwiftMetadata.js +150 -0
- package/dist/domain/packageAnalysis.js +123 -0
- package/dist/domain/peInspection.js +196 -0
- package/dist/domain/processCaptureReactiveValidation.js +8 -3
- package/dist/domain/processCaptureValidation.js +7 -3
- package/dist/domain/processComparison.js +12 -7
- package/dist/domain/processObservation.js +5 -6
- package/dist/domain/processReactiveMatching.js +14 -7
- package/dist/domain/processReactiveTransition.js +9 -3
- package/dist/domain/processScenario.js +6 -3
- package/dist/domain/processTraceComparison.js +3 -2
- package/dist/domain/protocolCapture.js +245 -0
- package/dist/domain/reconstructionReadinessSchemas.js +30 -2
- package/dist/domain/referenceSourceImportParsing.js +2 -2
- package/dist/domain/replayMachineRuntime.js +5 -3
- package/dist/domain/sourceToBundleComparison.js +2 -2
- package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
- package/dist/domain/sourceToBundleSignals.js +7 -1
- package/dist/domain/staticRuntimeCorrelation.js +7 -5
- package/dist/domain/webBundleAnalysis.js +14 -14
- package/dist/domain/webCaptureDiff.js +15 -12
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
- package/dist/dotnet/ManagedStaticProvider.js +34 -5
- package/dist/generatedMcpToolCatalog.js +37 -0
- package/dist/generatedPackageMetadata.js +4 -4
- package/dist/ghidra/GhidraClient.js +1 -1
- package/dist/ghidra/GhidraClientStartup.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +44 -16
- package/dist/ghidra/GhidraInventoryValues.js +57 -47
- package/dist/ghidra/GhidraProvider.js +18 -10
- package/dist/ghidra/GhidraProviderCapabilities.js +45 -39
- package/dist/ghidra/GhidraSocketConnection.js +1 -1
- package/dist/ghidra/protocol.js +19 -42
- package/dist/hopper/BridgeLauncher.js +3 -1
- package/dist/hopper/HopperAnalysisProfile.js +2 -4
- package/dist/hopper/HopperCleanup.js +5 -6
- package/dist/hopper/HopperClient.js +27 -3
- package/dist/hopper/HopperProvider.js +42 -44
- package/dist/hopper/HopperRequestQueue.js +32 -4
- package/dist/hopper/HopperResponseStream.js +2 -2
- package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
- package/dist/hopper/protocol.js +63 -21
- package/dist/main/reload.js +1 -6
- package/dist/main/state.js +0 -14
- package/dist/main/transport.js +31 -41
- package/dist/main.js +17 -13
- package/dist/mcpDoctor.js +4 -26
- package/dist/mcpStartupPolicy.js +14 -0
- package/dist/process/PrivateRuntimeRoot.js +17 -0
- package/dist/process/ProcessOwnership.js +64 -128
- package/dist/process/ProcessOwnershipIdentity.js +13 -0
- package/dist/process/ProcessOwnershipObservation.js +88 -0
- package/dist/process/ProcessOwnershipProcessTree.js +22 -0
- package/dist/process/ProviderDeadline.js +8 -12
- package/dist/process/WindowsAuthority.js +32 -0
- package/dist/reference/ReferenceSourceReader.js +3 -3
- package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
- package/dist/replay/JavaScriptReplayWorker.js +95 -11
- package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
- package/dist/replay/ReplayOutcome.js +26 -15
- package/dist/replay/ReplayWorkerProtocol.js +34 -29
- package/dist/server/createServer.js +101 -144
- package/dist/server/mcpClientMetadata.js +32 -0
- package/dist/server/registerApplicationTools/characterization.js +6 -13
- package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
- package/dist/server/registerApplicationTools/compareVersions.js +3 -8
- package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
- package/dist/server/registerApplicationTools/coverage.js +13 -19
- package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
- package/dist/server/registerApplicationTools/readiness.js +1 -6
- package/dist/server/registerApplicationTools/traceFeature.js +3 -8
- package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
- package/dist/server/registerArtifactComparisonTool.js +12 -16
- package/dist/server/registerBrowserScenarioTool.js +1 -6
- package/dist/server/registerBrowserTools.js +37 -79
- package/dist/server/registerBundleComparisonTool.js +10 -14
- package/dist/server/registerCloseLifecycleTool.js +6 -14
- package/dist/server/registerElectronTools.js +30 -47
- package/dist/server/registerEnhancedTools.js +124 -122
- package/dist/server/registerEvidenceResources.js +50 -101
- package/dist/server/registerEvidenceTools.js +5 -9
- package/dist/server/registerFunctionComparisonTool.js +11 -15
- package/dist/server/registerInvestigationTools.js +42 -59
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
- package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
- package/dist/server/registerOfficialTools.js +8 -9
- package/dist/server/registerProcessComparisonTool.js +19 -24
- package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
- package/dist/server/registerReconstructionReadinessResource.js +0 -7
- package/dist/server/registerReplayMachineTool.js +4 -10
- package/dist/server/registerSessionRecordTools.js +146 -47
- package/dist/server/registerSessionStatusTool.js +14 -29
- package/dist/server/registerSessionTools.js +45 -41
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/sessionToolPolicies.js +1 -5
- package/dist/server/toolRegistrationOptions.js +4 -177
- package/dist/server/toolResult.js +14 -18
- package/package.json +35 -16
- package/scripts/electron-active-hook-boundaries.cjs +394 -0
- package/scripts/electron-active-hook.cjs +501 -0
- package/skills/reverse-engineer-anything/SKILL.md +10 -3
- package/dist/server/DynamicToolAvailability.js +0 -70
- package/dist/server/toolInputValidation.js +0 -77
package/dist/mcpDoctor.js
CHANGED
|
@@ -4,13 +4,14 @@ import { Formatter } from "incur";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
import { CATALOG_IDENTITY, MCP_RESOURCE_CATALOG, MCP_RESOURCE_TEMPLATE_CATALOG, } from "./catalogIdentity.js";
|
|
6
6
|
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
7
|
+
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
|
|
7
8
|
import { PRODUCT_IDENTITY } from "./identity.js";
|
|
8
|
-
|
|
9
|
+
import { MCP_STARTUP_POLICY } from "./mcpStartupPolicy.js";
|
|
9
10
|
const STDERR_LIMIT_BYTES = 64 * 1_024;
|
|
10
11
|
const OUTPUT_FORMATS = ["toon", "json", "yaml", "md", "jsonl"];
|
|
11
12
|
/** Diagnose the actual production stdio adapter with one bounded SDK session. */
|
|
12
13
|
export const runProductionMcpDoctor = async (options) => {
|
|
13
|
-
const deadline = Date.now() + (options.deadlineMs ??
|
|
14
|
+
const deadline = Date.now() + (options.deadlineMs ?? MCP_STARTUP_POLICY.doctorDeadlineMs);
|
|
14
15
|
const controller = new AbortController();
|
|
15
16
|
const timer = setTimeout(() => controller.abort(new Error("Production MCP doctor deadline expired")), Math.max(1, deadline - Date.now()));
|
|
16
17
|
const stderr = boundedStderr();
|
|
@@ -63,9 +64,8 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
63
64
|
client.readResource({ uri: "rea://server/identity" }, request),
|
|
64
65
|
client.callTool({ name: "binary_session", arguments: { detail: "full" } }, request),
|
|
65
66
|
]);
|
|
66
|
-
const expectedToolNames = availableToolNames(requestFlow.structuredContent) ?? [];
|
|
67
67
|
const inventory = {
|
|
68
|
-
tools: compareInventory(
|
|
68
|
+
tools: compareInventory(TOOL_CONTRACTS.map(({ name }) => name), tools.tools.map(({ name }) => name)),
|
|
69
69
|
prompts: compareInventory(PROMPT_CONTRACTS.map(({ name }) => name), prompts.prompts.map(({ name }) => name)),
|
|
70
70
|
resources: compareInventory(MCP_RESOURCE_CATALOG.map(({ name }) => name), resources.resources.map(({ name }) => name)),
|
|
71
71
|
resource_templates: compareInventory(MCP_RESOURCE_TEMPLATE_CATALOG.map(({ name }) => name), templates.resourceTemplates.map(({ name }) => name)),
|
|
@@ -117,28 +117,6 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
117
117
|
checks,
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
|
-
const availableToolNames = (structuredContent) => {
|
|
121
|
-
if (typeof structuredContent !== "object" || structuredContent === null)
|
|
122
|
-
return undefined;
|
|
123
|
-
const result = Reflect.get(structuredContent, "result");
|
|
124
|
-
if (typeof result !== "object" || result === null)
|
|
125
|
-
return undefined;
|
|
126
|
-
const availability = Reflect.get(result, "tool_availability");
|
|
127
|
-
if (!Array.isArray(availability))
|
|
128
|
-
return undefined;
|
|
129
|
-
const names = [];
|
|
130
|
-
for (const item of availability) {
|
|
131
|
-
if (typeof item !== "object" || item === null)
|
|
132
|
-
return undefined;
|
|
133
|
-
const name = Reflect.get(item, "name");
|
|
134
|
-
const available = Reflect.get(item, "available");
|
|
135
|
-
if (typeof name !== "string" || typeof available !== "boolean")
|
|
136
|
-
return undefined;
|
|
137
|
-
if (available)
|
|
138
|
-
names.push(name);
|
|
139
|
-
}
|
|
140
|
-
return names;
|
|
141
|
-
};
|
|
142
120
|
/**
|
|
143
121
|
* Dispatcher entry for `rea mcp doctor`, kept separate from Incur MCP setup.
|
|
144
122
|
* @public
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported MCP startup deadlines and regression budgets.
|
|
3
|
+
*
|
|
4
|
+
* The Codex timeout is caller-facing configuration. The smaller phase budgets
|
|
5
|
+
* preserve operational headroom, while the doctor deadline covers additional
|
|
6
|
+
* inventory requests after initialization.
|
|
7
|
+
*/
|
|
8
|
+
export const MCP_STARTUP_POLICY = {
|
|
9
|
+
codexStartupTimeoutSeconds: 30,
|
|
10
|
+
initializeBudgetMs: 15_000,
|
|
11
|
+
toolsListBudgetMs: 5_000,
|
|
12
|
+
firstCatalogBudgetMs: 20_000,
|
|
13
|
+
doctorDeadlineMs: 30_000,
|
|
14
|
+
};
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { chmod, mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { windowsNativeCapabilities, } from "./WindowsAuthority.js";
|
|
5
|
+
/** Report whether this process can establish a private runtime root. */
|
|
6
|
+
export const privateRuntimeRootCapability = (platform = process.platform) => platform === "win32"
|
|
7
|
+
? windowsNativeCapabilities(platform).private_runtime_dacl
|
|
8
|
+
: { available: true, reason: null, proof: "posix-mode-0700" };
|
|
9
|
+
/** Expected failure when a runtime root's privacy boundary is unavailable. */
|
|
10
|
+
export class PrivateRuntimeRootUnavailableError extends Error {
|
|
11
|
+
code = "private-runtime-root-authority-unavailable";
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "PrivateRuntimeRootUnavailableError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Owns one POSIX mode-0700 temporary runtime root and removes it idempotently.
|
|
6
19
|
*
|
|
@@ -16,6 +29,10 @@ export class PrivateRuntimeRoot {
|
|
|
16
29
|
}
|
|
17
30
|
/** Allocate a new private runtime root without performing protocol work. */
|
|
18
31
|
static async create(options = {}) {
|
|
32
|
+
const platform = options.platform ?? process.platform;
|
|
33
|
+
const capability = privateRuntimeRootCapability(platform);
|
|
34
|
+
if (!capability.available)
|
|
35
|
+
throw new PrivateRuntimeRootUnavailableError(capability.reason);
|
|
19
36
|
const path = await mkdtemp(join(options.parent ?? tmpdir(), options.prefix ?? "rea-provider-"));
|
|
20
37
|
try {
|
|
21
38
|
await chmod(path, 0o700);
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
|
-
import {
|
|
4
|
+
import { observeOwnedProcessGroupWithHost, observeOwnedProcessLineageWithHost, } from "./ProcessOwnershipObservation.js";
|
|
5
|
+
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
6
|
+
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
5
7
|
const execFileAsync = promisify(execFile);
|
|
6
8
|
/**
|
|
7
9
|
* Terminate one Windows process tree through the platform utility.
|
|
8
10
|
*
|
|
9
11
|
* This is a bounded P0 cleanup mechanism, not Job Object ownership proof. The
|
|
10
|
-
* caller-visible Windows
|
|
12
|
+
* caller-visible Windows capability report remains unavailable until a native
|
|
13
|
+
* authority verifies Job Object creation, membership, and cleanup semantics.
|
|
11
14
|
*/
|
|
12
15
|
export const cleanupWindowsProcessTree = async (rootPid, host = systemWindowsProcessTreeHost) => {
|
|
13
16
|
if (!Number.isSafeInteger(rootPid) || rootPid <= 0)
|
|
@@ -70,8 +73,11 @@ const systemHost = {
|
|
|
70
73
|
}
|
|
71
74
|
const { stdout } = await execFileAsync("ps", ["eww", "-p", String(pid)]);
|
|
72
75
|
const environment = {};
|
|
73
|
-
for (const match of stdout.matchAll(/(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu))
|
|
74
|
-
|
|
76
|
+
for (const match of stdout.matchAll(/(?:^|\s)([A-Za-z_][A-Za-z0-9_]*)=([^\s]*)/gu)) {
|
|
77
|
+
const name = match[1];
|
|
78
|
+
if (name !== undefined)
|
|
79
|
+
environment[name] = match[2] ?? "";
|
|
80
|
+
}
|
|
75
81
|
return environment;
|
|
76
82
|
},
|
|
77
83
|
signalGroup(processGroupId, signal) {
|
|
@@ -97,13 +103,9 @@ const systemWindowsProcessTreeHost = {
|
|
|
97
103
|
export const readProcessRunId = async (pid, host = systemHost) => (await host.environment(pid)).REA_PROCESS_RUN_ID;
|
|
98
104
|
/** Token-validate every rooted POSIX process group before signaling any. */
|
|
99
105
|
export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
processes = liveProcesses(await host.listProcesses());
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
106
|
+
const processes = await readLiveProcessTable(host);
|
|
107
|
+
if (processes === undefined)
|
|
105
108
|
return { cleaned: false, reason: "process table could not be inspected" };
|
|
106
|
-
}
|
|
107
109
|
const plan = await createOwnedCleanupPlan(ownership, processes, host);
|
|
108
110
|
if ("cleaned" in plan)
|
|
109
111
|
return plan;
|
|
@@ -126,10 +128,38 @@ export const cleanupOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
126
128
|
}
|
|
127
129
|
return { cleaned: true, signaled };
|
|
128
130
|
};
|
|
131
|
+
/** Verify that no live process still carries an owned capture run token. */
|
|
132
|
+
export const verifyNoTokenOwnedProcesses = async (runId, host = systemHost) => {
|
|
133
|
+
const processes = await readLiveProcessTable(host);
|
|
134
|
+
if (processes === undefined)
|
|
135
|
+
return { cleaned: false, reason: "process table could not be inspected" };
|
|
136
|
+
const scan = await scanTokenOwnedProcesses(processes, runId, host);
|
|
137
|
+
if (scan.failures.length > 0)
|
|
138
|
+
return cleanupValidationFailure(scan.failures);
|
|
139
|
+
return scan.owned.length === 0
|
|
140
|
+
? { cleaned: true, signaled: false }
|
|
141
|
+
: { cleaned: false, reason: "token-owned process remained after cleanup" };
|
|
142
|
+
};
|
|
143
|
+
const readLiveProcessTable = async (host) => {
|
|
144
|
+
try {
|
|
145
|
+
return liveProcesses(await host.listProcesses());
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
129
151
|
const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
152
|
+
const tokenOwned = ownership.sweepTokenOwnedProcesses === true
|
|
153
|
+
? await scanTokenOwnedProcesses(processes, ownership.runId, host)
|
|
154
|
+
: { owned: [], failures: [] };
|
|
155
|
+
if (tokenOwned.failures.length > 0)
|
|
156
|
+
return cleanupValidationFailure(tokenOwned.failures);
|
|
157
|
+
const tokenOwnedGroupIds = new Set(tokenOwned.owned.map(({ processGroupId }) => processGroupId));
|
|
130
158
|
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
131
159
|
const rootMembers = processes.filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
132
|
-
if (launcher === undefined &&
|
|
160
|
+
if (launcher === undefined &&
|
|
161
|
+
rootMembers.length === 0 &&
|
|
162
|
+
tokenOwned.owned.length === 0)
|
|
133
163
|
return { cleaned: true, signaled: false };
|
|
134
164
|
let descendants = [];
|
|
135
165
|
if (launcher !== undefined) {
|
|
@@ -142,11 +172,14 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
142
172
|
const processGroupIds = new Set([ownership.processGroupId]);
|
|
143
173
|
for (const descendant of descendants)
|
|
144
174
|
processGroupIds.add(descendant.processGroupId);
|
|
175
|
+
for (const process of tokenOwned.owned)
|
|
176
|
+
processGroupIds.add(process.processGroupId);
|
|
145
177
|
for (const processGroupId of processGroupIds) {
|
|
146
178
|
if (processGroupId === ownership.processGroupId)
|
|
147
179
|
continue;
|
|
148
180
|
const groupLeader = processes.find(({ pid, processGroupId: observedGroupId }) => pid === processGroupId && observedGroupId === processGroupId);
|
|
149
|
-
if (groupLeader === undefined || !descendantPids.has(groupLeader.pid))
|
|
181
|
+
if ((groupLeader === undefined || !descendantPids.has(groupLeader.pid)) &&
|
|
182
|
+
!tokenOwnedGroupIds.has(processGroupId))
|
|
150
183
|
return {
|
|
151
184
|
cleaned: false,
|
|
152
185
|
reason: "descendant process-group leader identity could not be verified",
|
|
@@ -162,17 +195,6 @@ const createOwnedCleanupPlan = async (ownership, processes, host) => {
|
|
|
162
195
|
.sort((left, right) => left - right)),
|
|
163
196
|
};
|
|
164
197
|
};
|
|
165
|
-
const launcherIdentityFailure = (launcher, ownership) => {
|
|
166
|
-
if (launcher.processGroupId !== ownership.processGroupId)
|
|
167
|
-
return "owned launcher process-group identity did not match";
|
|
168
|
-
if (ownership.expectedParentPid !== undefined &&
|
|
169
|
-
launcher.parentPid !== ownership.expectedParentPid)
|
|
170
|
-
return "owned launcher parent identity did not match";
|
|
171
|
-
if (ownership.expectedCommand !== undefined &&
|
|
172
|
-
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
173
|
-
return `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`;
|
|
174
|
-
return null;
|
|
175
|
-
};
|
|
176
198
|
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
177
199
|
let members;
|
|
178
200
|
try {
|
|
@@ -197,7 +219,8 @@ const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
|
197
219
|
}
|
|
198
220
|
if (processGroupId !== ownership.processGroupId &&
|
|
199
221
|
members.length > 0 &&
|
|
200
|
-
!members.some(({ pid }) => pid === processGroupId)
|
|
222
|
+
!members.some(({ pid }) => pid === processGroupId) &&
|
|
223
|
+
ownership.sweepTokenOwnedProcesses !== true)
|
|
201
224
|
return {
|
|
202
225
|
cleaned: false,
|
|
203
226
|
reason: "descendant process-group leader identity could not be revalidated",
|
|
@@ -230,39 +253,28 @@ const processOwnershipFailures = async (members, runId, host) => {
|
|
|
230
253
|
}
|
|
231
254
|
return failures;
|
|
232
255
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
members = (await host.listProcesses()).filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
return {
|
|
241
|
-
state: "unverifiable",
|
|
242
|
-
reason: "process group could not be inspected",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
const liveMembers = liveProcesses(members);
|
|
246
|
-
if (liveMembers.length === 0)
|
|
247
|
-
return { state: "empty" };
|
|
248
|
-
for (const member of liveMembers) {
|
|
256
|
+
const scanTokenOwnedProcesses = async (processes, runId, host) => {
|
|
257
|
+
const owned = [];
|
|
258
|
+
const failures = [];
|
|
259
|
+
for (const process of processes) {
|
|
249
260
|
try {
|
|
250
|
-
if ((await host.environment(
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
state: "unverifiable",
|
|
254
|
-
reason: "process ownership did not match",
|
|
255
|
-
};
|
|
261
|
+
if ((await host.environment(process.pid)).REA_PROCESS_RUN_ID === runId)
|
|
262
|
+
owned.push(process);
|
|
256
263
|
}
|
|
257
264
|
catch {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
265
|
+
try {
|
|
266
|
+
const live = liveProcesses(await host.listProcesses());
|
|
267
|
+
if (!live.some(({ pid }) => pid === process.pid))
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
catch { }
|
|
271
|
+
failures.push({ pid: process.pid, reason: "environment-unreadable" });
|
|
262
272
|
}
|
|
263
273
|
}
|
|
264
|
-
return {
|
|
274
|
+
return { owned, failures };
|
|
265
275
|
};
|
|
276
|
+
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
277
|
+
export const observeOwnedProcessGroup = async (ownership, host = systemHost) => observeOwnedProcessGroupWithHost(ownership, host);
|
|
266
278
|
/**
|
|
267
279
|
* Record the live launcher and descendant lineage after run-token validation.
|
|
268
280
|
*
|
|
@@ -270,80 +282,4 @@ export const observeOwnedProcessGroup = async (ownership, host = systemHost) =>
|
|
|
270
282
|
* list means no descendants were live during this observation, not that the
|
|
271
283
|
* run never created a short-lived child.
|
|
272
284
|
*/
|
|
273
|
-
export const observeOwnedProcessLineage = async (ownership, host = systemHost) =>
|
|
274
|
-
let processes;
|
|
275
|
-
try {
|
|
276
|
-
processes = liveProcesses(await host.listProcesses());
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
return unavailableLineage(ownership, "process table could not be inspected");
|
|
280
|
-
}
|
|
281
|
-
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
282
|
-
if (launcher === undefined)
|
|
283
|
-
return unavailableLineage(ownership, "owned launcher is not live");
|
|
284
|
-
if (launcher.processGroupId !== ownership.processGroupId)
|
|
285
|
-
return unavailableLineage(ownership, "owned launcher process-group identity did not match");
|
|
286
|
-
if (ownership.expectedParentPid !== undefined &&
|
|
287
|
-
launcher.parentPid !== ownership.expectedParentPid)
|
|
288
|
-
return unavailableLineage(ownership, "owned launcher parent identity did not match");
|
|
289
|
-
if (ownership.expectedCommand !== undefined &&
|
|
290
|
-
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
291
|
-
return unavailableLineage(ownership, `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`);
|
|
292
|
-
const descendants = descendantsOf(launcher.pid, processes);
|
|
293
|
-
for (const member of [launcher, ...descendants]) {
|
|
294
|
-
try {
|
|
295
|
-
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
296
|
-
ownership.runId)
|
|
297
|
-
return unavailableLineage(ownership, "process lineage contains an unowned or PID-reused process");
|
|
298
|
-
}
|
|
299
|
-
catch {
|
|
300
|
-
return unavailableLineage(ownership, "process ownership could not be revalidated");
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
status: "verified",
|
|
305
|
-
observedAt: new Date().toISOString(),
|
|
306
|
-
lineage: {
|
|
307
|
-
schemaVersion: 1,
|
|
308
|
-
runId: ownership.runId,
|
|
309
|
-
launcherPid: launcher.pid,
|
|
310
|
-
launcherParentPid: launcher.parentPid,
|
|
311
|
-
processGroupId: launcher.processGroupId,
|
|
312
|
-
descendants: descendants
|
|
313
|
-
.sort((left, right) => left.pid - right.pid)
|
|
314
|
-
.map(({ pid, parentPid, processGroupId }) => ({
|
|
315
|
-
pid,
|
|
316
|
-
parentPid,
|
|
317
|
-
processGroupId,
|
|
318
|
-
})),
|
|
319
|
-
},
|
|
320
|
-
};
|
|
321
|
-
};
|
|
322
|
-
const descendantsOf = (launcherPid, processes) => {
|
|
323
|
-
const childrenByParent = new Map();
|
|
324
|
-
for (const process of processes) {
|
|
325
|
-
const siblings = childrenByParent.get(process.parentPid) ?? [];
|
|
326
|
-
siblings.push(process);
|
|
327
|
-
childrenByParent.set(process.parentPid, siblings);
|
|
328
|
-
}
|
|
329
|
-
const descendants = [];
|
|
330
|
-
const pending = [...(childrenByParent.get(launcherPid) ?? [])];
|
|
331
|
-
const visited = new Set([launcherPid]);
|
|
332
|
-
for (const process of pending) {
|
|
333
|
-
if (visited.has(process.pid))
|
|
334
|
-
continue;
|
|
335
|
-
visited.add(process.pid);
|
|
336
|
-
descendants.push(process);
|
|
337
|
-
pending.push(...(childrenByParent.get(process.pid) ?? []));
|
|
338
|
-
}
|
|
339
|
-
return descendants;
|
|
340
|
-
};
|
|
341
|
-
const unavailableLineage = (ownership, reason) => ({
|
|
342
|
-
status: "unavailable",
|
|
343
|
-
observedAt: new Date().toISOString(),
|
|
344
|
-
runId: ownership.runId,
|
|
345
|
-
launcherPid: ownership.leaderPid,
|
|
346
|
-
processGroupId: ownership.processGroupId,
|
|
347
|
-
reason,
|
|
348
|
-
});
|
|
349
|
-
const liveProcesses = (members) => members.filter(({ state }) => !state.startsWith("Z"));
|
|
285
|
+
export const observeOwnedProcessLineage = async (ownership, host = systemHost) => observeOwnedProcessLineageWithHost(ownership, host);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
|
|
2
|
+
/** Compare a live launcher against the identity committed at process launch. */
|
|
3
|
+
export const launcherIdentityFailure = (launcher, ownership) => {
|
|
4
|
+
if (launcher.processGroupId !== ownership.processGroupId)
|
|
5
|
+
return "owned launcher process-group identity did not match";
|
|
6
|
+
if (ownership.expectedParentPid !== undefined &&
|
|
7
|
+
launcher.parentPid !== ownership.expectedParentPid)
|
|
8
|
+
return "owned launcher parent identity did not match";
|
|
9
|
+
if (ownership.expectedCommand !== undefined &&
|
|
10
|
+
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
11
|
+
return `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`;
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { launcherIdentityFailure } from "./ProcessOwnershipIdentity.js";
|
|
2
|
+
import { descendantsOf, liveProcesses } from "./ProcessOwnershipProcessTree.js";
|
|
3
|
+
/** Observe one group without signaling it, failing closed on identity doubt. */
|
|
4
|
+
export const observeOwnedProcessGroupWithHost = async (ownership, host) => {
|
|
5
|
+
let members;
|
|
6
|
+
try {
|
|
7
|
+
members = (await host.listProcesses()).filter(({ processGroupId }) => processGroupId === ownership.processGroupId);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return {
|
|
11
|
+
state: "unverifiable",
|
|
12
|
+
reason: "process group could not be inspected",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
const liveMembers = liveProcesses(members);
|
|
16
|
+
if (liveMembers.length === 0)
|
|
17
|
+
return { state: "empty" };
|
|
18
|
+
for (const member of liveMembers) {
|
|
19
|
+
try {
|
|
20
|
+
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
21
|
+
ownership.runId)
|
|
22
|
+
return {
|
|
23
|
+
state: "unverifiable",
|
|
24
|
+
reason: "process ownership did not match",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {
|
|
29
|
+
state: "unverifiable",
|
|
30
|
+
reason: "process ownership could not be revalidated",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { state: "alive" };
|
|
35
|
+
};
|
|
36
|
+
/** Record the live launcher and descendant lineage after run-token validation. */
|
|
37
|
+
export const observeOwnedProcessLineageWithHost = async (ownership, host) => {
|
|
38
|
+
let processes;
|
|
39
|
+
try {
|
|
40
|
+
processes = liveProcesses(await host.listProcesses());
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return unavailableLineage(ownership, "process table could not be inspected");
|
|
44
|
+
}
|
|
45
|
+
const launcher = processes.find(({ pid }) => pid === ownership.leaderPid);
|
|
46
|
+
if (launcher === undefined)
|
|
47
|
+
return unavailableLineage(ownership, "owned launcher is not live");
|
|
48
|
+
const identityFailure = launcherIdentityFailure(launcher, ownership);
|
|
49
|
+
if (identityFailure !== null)
|
|
50
|
+
return unavailableLineage(ownership, identityFailure);
|
|
51
|
+
const descendants = descendantsOf(launcher.pid, processes);
|
|
52
|
+
for (const member of [launcher, ...descendants]) {
|
|
53
|
+
try {
|
|
54
|
+
if ((await host.environment(member.pid)).REA_PROCESS_RUN_ID !==
|
|
55
|
+
ownership.runId)
|
|
56
|
+
return unavailableLineage(ownership, "process lineage contains an unowned or PID-reused process");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return unavailableLineage(ownership, "process ownership could not be revalidated");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
status: "verified",
|
|
64
|
+
observedAt: new Date().toISOString(),
|
|
65
|
+
lineage: {
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
runId: ownership.runId,
|
|
68
|
+
launcherPid: launcher.pid,
|
|
69
|
+
launcherParentPid: launcher.parentPid,
|
|
70
|
+
processGroupId: launcher.processGroupId,
|
|
71
|
+
descendants: descendants
|
|
72
|
+
.sort((left, right) => left.pid - right.pid)
|
|
73
|
+
.map(({ pid, parentPid, processGroupId }) => ({
|
|
74
|
+
pid,
|
|
75
|
+
parentPid,
|
|
76
|
+
processGroupId,
|
|
77
|
+
})),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
const unavailableLineage = (ownership, reason) => ({
|
|
82
|
+
status: "unavailable",
|
|
83
|
+
observedAt: new Date().toISOString(),
|
|
84
|
+
runId: ownership.runId,
|
|
85
|
+
launcherPid: ownership.leaderPid,
|
|
86
|
+
processGroupId: ownership.processGroupId,
|
|
87
|
+
reason,
|
|
88
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Exclude process-table entries that the operating system has already reaped. */
|
|
2
|
+
export const liveProcesses = (members) => members.filter(({ state }) => !state.startsWith("Z"));
|
|
3
|
+
/** Return every live-table descendant of a launcher in breadth-first order. */
|
|
4
|
+
export const descendantsOf = (launcherPid, processes) => {
|
|
5
|
+
const childrenByParent = new Map();
|
|
6
|
+
for (const process of processes) {
|
|
7
|
+
const siblings = childrenByParent.get(process.parentPid) ?? [];
|
|
8
|
+
siblings.push(process);
|
|
9
|
+
childrenByParent.set(process.parentPid, siblings);
|
|
10
|
+
}
|
|
11
|
+
const descendants = [];
|
|
12
|
+
const pending = [...(childrenByParent.get(launcherPid) ?? [])];
|
|
13
|
+
const visited = new Set([launcherPid]);
|
|
14
|
+
for (const process of pending) {
|
|
15
|
+
if (visited.has(process.pid))
|
|
16
|
+
continue;
|
|
17
|
+
visited.add(process.pid);
|
|
18
|
+
descendants.push(process);
|
|
19
|
+
pending.push(...(childrenByParent.get(process.pid) ?? []));
|
|
20
|
+
}
|
|
21
|
+
return descendants;
|
|
22
|
+
};
|
|
@@ -26,8 +26,7 @@ export class ProviderStartupDeadline {
|
|
|
26
26
|
#externalSignal;
|
|
27
27
|
#onExternalAbort;
|
|
28
28
|
#timer;
|
|
29
|
-
#
|
|
30
|
-
#cancelled = false;
|
|
29
|
+
#interruption;
|
|
31
30
|
constructor(timeoutMs, signal) {
|
|
32
31
|
this.timeoutMs = timeoutMs;
|
|
33
32
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
@@ -38,11 +37,11 @@ export class ProviderStartupDeadline {
|
|
|
38
37
|
if (this.#controller.signal.aborted)
|
|
39
38
|
return;
|
|
40
39
|
if (Date.now() >= this.#deadlineAt) {
|
|
41
|
-
this.#
|
|
40
|
+
this.#interruption = "timeout";
|
|
42
41
|
this.#abort(new DOMException("Provider startup deadline elapsed", "TimeoutError"));
|
|
43
42
|
return;
|
|
44
43
|
}
|
|
45
|
-
this.#
|
|
44
|
+
this.#interruption = "cancelled";
|
|
46
45
|
this.#abort(signal?.reason);
|
|
47
46
|
};
|
|
48
47
|
if (signal?.aborted === true) {
|
|
@@ -53,7 +52,7 @@ export class ProviderStartupDeadline {
|
|
|
53
52
|
this.#timer = setTimeout(() => {
|
|
54
53
|
if (this.#controller.signal.aborted)
|
|
55
54
|
return;
|
|
56
|
-
this.#
|
|
55
|
+
this.#interruption = "timeout";
|
|
57
56
|
this.#abort(new DOMException("Provider startup deadline elapsed", "TimeoutError"));
|
|
58
57
|
}, timeoutMs);
|
|
59
58
|
}
|
|
@@ -61,13 +60,10 @@ export class ProviderStartupDeadline {
|
|
|
61
60
|
get signal() {
|
|
62
61
|
return this.#controller.signal;
|
|
63
62
|
}
|
|
64
|
-
/**
|
|
65
|
-
get
|
|
66
|
-
return this.#
|
|
67
|
-
|
|
68
|
-
/** True when the absolute startup deadline has elapsed. */
|
|
69
|
-
get timedOut() {
|
|
70
|
-
return (this.#timedOut || (!this.#cancelled && Date.now() >= this.#deadlineAt));
|
|
63
|
+
/** First control path that interrupted startup, if any. */
|
|
64
|
+
get interruption() {
|
|
65
|
+
return (this.#interruption ??
|
|
66
|
+
(Date.now() >= this.#deadlineAt ? "timeout" : undefined));
|
|
71
67
|
}
|
|
72
68
|
/** Milliseconds still available to the next startup phase. */
|
|
73
69
|
remainingMs() {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Stable explanation used whenever the native Windows authority is absent. */
|
|
2
|
+
export const WINDOWS_NATIVE_AUTHORITY_UNAVAILABLE_REASON = "Verified Windows native authority is unavailable: Job Object process ownership, private runtime DACL enforcement, and reparse-safe path admission are not implemented; chmod(0700) is insufficient for Windows private DACL proof, and taskkill is not a Job Object security proof";
|
|
3
|
+
/**
|
|
4
|
+
* Describe native Windows controls without inferring them from taskkill,
|
|
5
|
+
* chmod, symlink creation, or a numeric POSIX open flag.
|
|
6
|
+
*/
|
|
7
|
+
export const windowsNativeCapabilities = (platform) => {
|
|
8
|
+
if (platform !== "win32") {
|
|
9
|
+
const notApplicable = {
|
|
10
|
+
available: false,
|
|
11
|
+
reason: "Windows native authority is not applicable on this host",
|
|
12
|
+
proof: "not-applicable",
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
job_object_process_ownership: notApplicable,
|
|
16
|
+
private_runtime_dacl: notApplicable,
|
|
17
|
+
reparse_safe_path_admission: notApplicable,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const notProven = {
|
|
21
|
+
available: false,
|
|
22
|
+
reason: WINDOWS_NATIVE_AUTHORITY_UNAVAILABLE_REASON,
|
|
23
|
+
proof: "not-proven",
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
job_object_process_ownership: notProven,
|
|
27
|
+
private_runtime_dacl: notProven,
|
|
28
|
+
reparse_safe_path_admission: notProven,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/** Whether every native control needed for Windows isolation is proven. */
|
|
32
|
+
export const hasWindowsNativeAuthority = (platform) => Object.values(windowsNativeCapabilities(platform)).every(({ available }) => available);
|
|
@@ -35,7 +35,7 @@ export const readReferenceSource = async (root, limits = DEFAULT_LIMITS, options
|
|
|
35
35
|
bytesRead: 0,
|
|
36
36
|
filesSeen: 0,
|
|
37
37
|
truncated: false,
|
|
38
|
-
|
|
38
|
+
stopReason: undefined,
|
|
39
39
|
};
|
|
40
40
|
const traversal = await traverse(state);
|
|
41
41
|
if (!traversal.ok)
|
|
@@ -48,14 +48,14 @@ export const readReferenceSource = async (root, limits = DEFAULT_LIMITS, options
|
|
|
48
48
|
truncated: state.truncated,
|
|
49
49
|
limitations: [
|
|
50
50
|
PATH_RACE_LIMITATION,
|
|
51
|
-
...(state.
|
|
51
|
+
...(state.stopReason === "entry-limit"
|
|
52
52
|
? ["Traversal stopped because the entry limit was reached."]
|
|
53
53
|
: []),
|
|
54
54
|
],
|
|
55
55
|
});
|
|
56
56
|
};
|
|
57
57
|
const traverse = async (state) => {
|
|
58
|
-
while (state.pending.length > 0 &&
|
|
58
|
+
while (state.pending.length > 0 && state.stopReason === undefined) {
|
|
59
59
|
if (isAborted(state.signal))
|
|
60
60
|
return err({
|
|
61
61
|
tag: "reference-source-reader",
|
|
@@ -35,7 +35,7 @@ export const traverseDirectory = async (state, current) => {
|
|
|
35
35
|
const result = await processEntry(state, current, name, directories);
|
|
36
36
|
if (!result.ok)
|
|
37
37
|
return result;
|
|
38
|
-
if (state.
|
|
38
|
+
if (state.stopReason !== undefined)
|
|
39
39
|
break;
|
|
40
40
|
}
|
|
41
41
|
directories.reverse();
|
|
@@ -201,6 +201,6 @@ const reserveEntry = (state) => {
|
|
|
201
201
|
return true;
|
|
202
202
|
}
|
|
203
203
|
state.truncated = true;
|
|
204
|
-
state.
|
|
204
|
+
state.stopReason = "entry-limit";
|
|
205
205
|
return false;
|
|
206
206
|
};
|