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.
Files changed (253) hide show
  1. package/README.md +20 -12
  2. package/bridge/ghidra/ReaGhidraBridge.java +477 -4
  3. package/bridge/hopper_bridge.py +54 -4
  4. package/dist/application/AnalysisContextQueries.js +142 -0
  5. package/dist/application/AnalysisSnapshotCache.js +4 -2
  6. package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
  7. package/dist/application/ArtifactInventory/policy.js +17 -0
  8. package/dist/application/ArtifactInventory/reader.js +1 -4
  9. package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
  10. package/dist/application/ArtifactInventory/scanReader.js +7 -7
  11. package/dist/application/ArtifactInventory/types.js +1 -5
  12. package/dist/application/BinarySession.js +19 -20
  13. package/dist/application/BinarySessionComposition.js +21 -0
  14. package/dist/application/BinarySessionRecords.js +106 -10
  15. package/dist/application/BinarySessionStatus.js +11 -5
  16. package/dist/application/BrowserEvidence.js +3 -5
  17. package/dist/application/CapabilityClientRequirements.js +26 -0
  18. package/dist/application/CapabilityInventory.js +140 -55
  19. package/dist/application/ClientRegistrationStatus.js +4 -1
  20. package/dist/application/CrossVersionInventory.js +7 -6
  21. package/dist/application/DirectAnalysis.js +6 -2
  22. package/dist/application/ElectronActiveEvidence.js +55 -0
  23. package/dist/application/ElectronActiveObservationPort.js +1 -0
  24. package/dist/application/ElectronActiveObservationService.js +81 -0
  25. package/dist/application/EnhancedTools.js +49 -17
  26. package/dist/application/EvidenceBundleCommands.js +5 -1
  27. package/dist/application/EvidenceLedger.js +53 -17
  28. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  29. package/dist/application/JavaScriptReplayPermission.js +1 -1
  30. package/dist/application/JavaScriptReplayService.js +23 -15
  31. package/dist/application/LazyAnalysisProvider.js +77 -0
  32. package/dist/application/LoopbackReplay.js +1 -1
  33. package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
  34. package/dist/application/NativeApiInspection.js +80 -0
  35. package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
  36. package/dist/application/ProcessCaptureLifecycle.js +5 -0
  37. package/dist/application/ProcessCli.js +2 -1
  38. package/dist/application/ProcessHarness.js +8 -3
  39. package/dist/application/ProcessNormalization.js +2 -1
  40. package/dist/application/ProjectPermissionStore.js +1 -0
  41. package/dist/application/ReferenceSourceImport.js +2 -2
  42. package/dist/application/SessionProviderRouter.js +6 -6
  43. package/dist/application/SetupClientConfiguration.js +10 -3
  44. package/dist/application/WindowsCapabilities.js +5 -4
  45. package/dist/application/runtime.js +26 -9
  46. package/dist/artifacts/ArtifactProvider.js +8 -10
  47. package/dist/artifacts/SafeOutputTree.js +7 -8
  48. package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
  49. package/dist/browser/CdpElectronInspection.js +3 -1
  50. package/dist/browser/JavaScriptRuntimeScope.js +5 -0
  51. package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
  52. package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
  53. package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
  54. package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
  55. package/dist/browser/PlaywrightScenarioSession.js +4 -4
  56. package/dist/browser/SensitiveTextCapture.js +1 -1
  57. package/dist/browser/V8InspectorCaptureProjection.js +1 -0
  58. package/dist/browser/V8InspectorProvider.js +3 -3
  59. package/dist/catalogIdentity.js +15 -9
  60. package/dist/cli/artifactCommands.js +5 -3
  61. package/dist/cli/coreAnalysisCommands.js +41 -0
  62. package/dist/cli/managedCommands.js +1 -1
  63. package/dist/cliApplicationCommands.js +2 -2
  64. package/dist/cliBrowserAdvancedCommands.js +11 -5
  65. package/dist/cliBrowserCommands.js +14 -7
  66. package/dist/cliCommandNames.js +2 -0
  67. package/dist/cliElectronCommands.js +46 -4
  68. package/dist/cliJavaScriptRuntimeCommands.js +14 -6
  69. package/dist/cliObservationOptions.js +4 -0
  70. package/dist/cliPolicyCommands.js +1 -0
  71. package/dist/config/browserScenario.js +77 -0
  72. package/dist/config/electronAutomation.js +35 -0
  73. package/dist/config/environment.js +6 -0
  74. package/dist/config/javascriptReplay.js +41 -0
  75. package/dist/config/managedRuntime.js +25 -0
  76. package/dist/config/parseConfig.js +56 -200
  77. package/dist/config/parsers.js +10 -1
  78. package/dist/config/passiveObservation.js +157 -0
  79. package/dist/config/processCapture.js +47 -0
  80. package/dist/contracts/applicationToolContracts.js +2 -5
  81. package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
  82. package/dist/contracts/artifactToolContracts.js +35 -37
  83. package/dist/contracts/browserToolContracts.js +2 -2
  84. package/dist/contracts/electronToolContracts.js +30 -1
  85. package/dist/contracts/enhancedInputs.js +21 -27
  86. package/dist/contracts/functionWorkflowToolContracts.js +29 -0
  87. package/dist/contracts/managedToolContracts.js +4 -9
  88. package/dist/contracts/managedWorkflowExamples.js +12 -9
  89. package/dist/contracts/managedWorkflowToolContracts.js +30 -27
  90. package/dist/contracts/nativeToolContracts.js +2 -3
  91. package/dist/contracts/promptContracts.js +14 -15
  92. package/dist/contracts/sessionToolSchemas.js +61 -0
  93. package/dist/contracts/toolContractExamples.js +4 -0
  94. package/dist/contracts/toolContractHelpers.js +23 -0
  95. package/dist/contracts/toolContracts.js +12 -67
  96. package/dist/contracts/toolEffects.js +19 -0
  97. package/dist/contracts/toolOutputSchemaGroups.js +66 -11
  98. package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
  99. package/dist/contracts/toolSchemaMetadata.js +128 -0
  100. package/dist/doctorRuntime.js +1 -1
  101. package/dist/domain/analysisErrorPresentation.js +8 -2
  102. package/dist/domain/analysisErrorProjection.js +1 -0
  103. package/dist/domain/androidApplication.js +2 -0
  104. package/dist/domain/appleApplication.js +2 -0
  105. package/dist/domain/binaryTarget.js +7 -5
  106. package/dist/domain/binaryTargetTypes.js +1 -0
  107. package/dist/domain/browserCaptureComparison.js +21 -69
  108. package/dist/domain/browserObservation.js +22 -9
  109. package/dist/domain/bytecodeProvider.js +177 -0
  110. package/dist/domain/callPath.js +3 -2
  111. package/dist/domain/conformancePackage.js +169 -0
  112. package/dist/domain/conformanceReplay.js +104 -0
  113. package/dist/domain/conformanceTrustGate.js +328 -0
  114. package/dist/domain/customProtocolCapture.js +137 -0
  115. package/dist/domain/electronActiveObservation.js +277 -0
  116. package/dist/domain/electronObservation.js +15 -11
  117. package/dist/domain/electronStaticAnalysisValues.js +4 -1
  118. package/dist/domain/eventProcessTree.js +217 -0
  119. package/dist/domain/evidenceBundle.js +47 -0
  120. package/dist/domain/functionComparisonNormalization.js +15 -8
  121. package/dist/domain/functionDossierEvidence.js +9 -4
  122. package/dist/domain/hopperStartupFailure.js +1 -1
  123. package/dist/domain/hopperValues.js +68 -68
  124. package/dist/domain/inputIssueProjection.js +11 -2
  125. package/dist/domain/investigationWorkspace.js +16 -15
  126. package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
  127. package/dist/domain/javascriptReplay.js +79 -63
  128. package/dist/domain/javascriptRuntimeObservation.js +1 -0
  129. package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
  130. package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
  131. package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
  132. package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
  133. package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
  134. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  135. package/dist/domain/javascriptSemanticAnalysis.js +8 -12
  136. package/dist/domain/javascriptSourceParser.js +15 -0
  137. package/dist/domain/javascriptStaticAnalysis.js +8 -12
  138. package/dist/domain/knownPageCoverage.js +15 -7
  139. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  140. package/dist/domain/nativeApiBoundary.js +138 -0
  141. package/dist/domain/nodeRuntimeCharacterization.js +14 -13
  142. package/dist/domain/objcSwiftMetadata.js +150 -0
  143. package/dist/domain/packageAnalysis.js +123 -0
  144. package/dist/domain/peInspection.js +196 -0
  145. package/dist/domain/processCaptureReactiveValidation.js +8 -3
  146. package/dist/domain/processCaptureValidation.js +7 -3
  147. package/dist/domain/processComparison.js +12 -7
  148. package/dist/domain/processObservation.js +5 -6
  149. package/dist/domain/processReactiveMatching.js +14 -7
  150. package/dist/domain/processReactiveTransition.js +9 -3
  151. package/dist/domain/processScenario.js +6 -3
  152. package/dist/domain/processTraceComparison.js +3 -2
  153. package/dist/domain/protocolCapture.js +245 -0
  154. package/dist/domain/reconstructionReadinessSchemas.js +30 -2
  155. package/dist/domain/referenceSourceImportParsing.js +2 -2
  156. package/dist/domain/replayMachineRuntime.js +5 -3
  157. package/dist/domain/sourceToBundleComparison.js +2 -2
  158. package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
  159. package/dist/domain/sourceToBundleSignals.js +7 -1
  160. package/dist/domain/staticRuntimeCorrelation.js +7 -5
  161. package/dist/domain/webBundleAnalysis.js +14 -14
  162. package/dist/domain/webCaptureDiff.js +15 -12
  163. package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
  164. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  165. package/dist/generatedMcpToolCatalog.js +37 -0
  166. package/dist/generatedPackageMetadata.js +4 -4
  167. package/dist/ghidra/GhidraClient.js +1 -1
  168. package/dist/ghidra/GhidraClientStartup.js +1 -1
  169. package/dist/ghidra/GhidraDefaults.js +1 -1
  170. package/dist/ghidra/GhidraFunctionValues.js +44 -16
  171. package/dist/ghidra/GhidraInventoryValues.js +57 -47
  172. package/dist/ghidra/GhidraProvider.js +18 -10
  173. package/dist/ghidra/GhidraProviderCapabilities.js +45 -39
  174. package/dist/ghidra/GhidraSocketConnection.js +1 -1
  175. package/dist/ghidra/protocol.js +19 -42
  176. package/dist/hopper/BridgeLauncher.js +3 -1
  177. package/dist/hopper/HopperAnalysisProfile.js +2 -4
  178. package/dist/hopper/HopperCleanup.js +5 -6
  179. package/dist/hopper/HopperClient.js +27 -3
  180. package/dist/hopper/HopperProvider.js +42 -44
  181. package/dist/hopper/HopperRequestQueue.js +32 -4
  182. package/dist/hopper/HopperResponseStream.js +2 -2
  183. package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
  184. package/dist/hopper/protocol.js +63 -21
  185. package/dist/main/reload.js +1 -6
  186. package/dist/main/state.js +0 -14
  187. package/dist/main/transport.js +31 -41
  188. package/dist/main.js +17 -13
  189. package/dist/mcpDoctor.js +4 -26
  190. package/dist/mcpStartupPolicy.js +14 -0
  191. package/dist/process/PrivateRuntimeRoot.js +17 -0
  192. package/dist/process/ProcessOwnership.js +64 -128
  193. package/dist/process/ProcessOwnershipIdentity.js +13 -0
  194. package/dist/process/ProcessOwnershipObservation.js +88 -0
  195. package/dist/process/ProcessOwnershipProcessTree.js +22 -0
  196. package/dist/process/ProviderDeadline.js +8 -12
  197. package/dist/process/WindowsAuthority.js +32 -0
  198. package/dist/reference/ReferenceSourceReader.js +3 -3
  199. package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
  200. package/dist/replay/JavaScriptReplayWorker.js +95 -11
  201. package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
  202. package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
  203. package/dist/replay/ReplayOutcome.js +26 -15
  204. package/dist/replay/ReplayWorkerProtocol.js +34 -29
  205. package/dist/server/createServer.js +101 -144
  206. package/dist/server/mcpClientMetadata.js +32 -0
  207. package/dist/server/registerApplicationTools/characterization.js +6 -13
  208. package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
  209. package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
  210. package/dist/server/registerApplicationTools/compareVersions.js +3 -8
  211. package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
  212. package/dist/server/registerApplicationTools/coverage.js +13 -19
  213. package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
  214. package/dist/server/registerApplicationTools/readiness.js +1 -6
  215. package/dist/server/registerApplicationTools/traceFeature.js +3 -8
  216. package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
  217. package/dist/server/registerArtifactComparisonTool.js +12 -16
  218. package/dist/server/registerBrowserScenarioTool.js +1 -6
  219. package/dist/server/registerBrowserTools.js +37 -79
  220. package/dist/server/registerBundleComparisonTool.js +10 -14
  221. package/dist/server/registerCloseLifecycleTool.js +6 -14
  222. package/dist/server/registerElectronTools.js +30 -47
  223. package/dist/server/registerEnhancedTools.js +124 -122
  224. package/dist/server/registerEvidenceResources.js +50 -101
  225. package/dist/server/registerEvidenceTools.js +5 -9
  226. package/dist/server/registerFunctionComparisonTool.js +11 -15
  227. package/dist/server/registerInvestigationTools.js +42 -59
  228. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  229. package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
  230. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
  231. package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
  232. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
  233. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
  234. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
  235. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
  236. package/dist/server/registerOfficialTools.js +8 -9
  237. package/dist/server/registerProcessComparisonTool.js +19 -24
  238. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
  239. package/dist/server/registerReconstructionReadinessResource.js +0 -7
  240. package/dist/server/registerReplayMachineTool.js +4 -10
  241. package/dist/server/registerSessionRecordTools.js +146 -47
  242. package/dist/server/registerSessionStatusTool.js +14 -29
  243. package/dist/server/registerSessionTools.js +45 -41
  244. package/dist/server/sessionAvailabilityPolicy.js +2 -1
  245. package/dist/server/sessionToolPolicies.js +1 -5
  246. package/dist/server/toolRegistrationOptions.js +4 -177
  247. package/dist/server/toolResult.js +14 -18
  248. package/package.json +35 -16
  249. package/scripts/electron-active-hook-boundaries.cjs +394 -0
  250. package/scripts/electron-active-hook.cjs +501 -0
  251. package/skills/reverse-engineer-anything/SKILL.md +10 -3
  252. package/dist/server/DynamicToolAvailability.js +0 -70
  253. 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
- const DEFAULT_DEADLINE_MS = 20_000;
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 ?? DEFAULT_DEADLINE_MS);
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(expectedToolNames, tools.tools.map(({ name }) => name)),
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 { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
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 limitations must preserve that distinction.
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
- environment[match[1]] = match[2] ?? "";
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
- let processes;
101
- try {
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 && rootMembers.length === 0)
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
- /** Observe one group without signaling it, failing closed on identity doubt. */
234
- export const observeOwnedProcessGroup = async (ownership, host = systemHost) => {
235
- let members;
236
- try {
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(member.pid)).REA_PROCESS_RUN_ID !==
251
- ownership.runId)
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
- return {
259
- state: "unverifiable",
260
- reason: "process ownership could not be revalidated",
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 { state: "alive" };
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
- #timedOut = false;
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.#timedOut = true;
40
+ this.#interruption = "timeout";
42
41
  this.#abort(new DOMException("Provider startup deadline elapsed", "TimeoutError"));
43
42
  return;
44
43
  }
45
- this.#cancelled = true;
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.#timedOut = true;
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
- /** True only when the external caller signal initiated cancellation. */
65
- get cancelled() {
66
- return this.#cancelled;
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
- stopped: false,
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.stopped
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 && !state.stopped) {
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.stopped)
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.stopped = true;
204
+ state.stopReason = "entry-limit";
205
205
  return false;
206
206
  };