rea-agents 2.0.0 → 2.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 (246) hide show
  1. package/README.md +78 -32
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/ArtifactExtraction.js +1 -1
  4. package/dist/application/ArtifactInventory/classify.js +42 -0
  5. package/dist/application/ArtifactInventory/hash.js +43 -0
  6. package/dist/application/ArtifactInventory/reader.js +41 -0
  7. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  8. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  9. package/dist/application/ArtifactInventory/types.js +10 -0
  10. package/dist/application/ArtifactInventory.js +9 -351
  11. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  12. package/dist/application/BinarySession.js +47 -270
  13. package/dist/application/BinarySessionExecution.js +28 -0
  14. package/dist/application/BinarySessionOpen.js +39 -0
  15. package/dist/application/BinarySessionRecords.js +149 -0
  16. package/dist/application/BinarySessionStatus.js +75 -0
  17. package/dist/application/CapabilityInventory.js +24 -2
  18. package/dist/application/ClientRegistrationStatus.js +1 -1
  19. package/dist/application/CrossVersionInventory.js +8 -2
  20. package/dist/application/CrossVersionInvestigation.js +1 -23
  21. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  22. package/dist/application/DirectAnalysis.js +47 -44
  23. package/dist/application/Doctor.js +156 -238
  24. package/dist/application/DoctorDiagnostics.js +180 -0
  25. package/dist/application/ElectronBoundaryAnalysis.js +23 -7
  26. package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
  27. package/dist/application/EnhancedToolPagination.js +22 -0
  28. package/dist/application/EnhancedToolTypes.js +4 -0
  29. package/dist/application/EnhancedTools.js +20 -37
  30. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  31. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  32. package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
  33. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  34. package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
  35. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  36. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  37. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  38. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  39. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  40. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  41. package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
  42. package/dist/application/JavaScriptJsonModules.js +39 -0
  43. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  44. package/dist/application/JavaScriptReplayPermission.js +16 -0
  45. package/dist/application/JavaScriptReplayService.js +182 -156
  46. package/dist/application/LinuxHopper.js +10 -0
  47. package/dist/application/MacHopper.js +22 -11
  48. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  49. package/dist/application/PermissionAuthority.js +106 -63
  50. package/dist/application/ProcessCaptureCapability.js +1 -1
  51. package/dist/application/ProcessCapturePermission.js +13 -0
  52. package/dist/application/ProcessCli.js +2 -12
  53. package/dist/application/ProcessHarness.js +1 -1
  54. package/dist/application/ProjectPermissionStore.js +48 -9
  55. package/dist/application/RealHopperAssertions.js +76 -0
  56. package/dist/application/ReconstructionCoverageService.js +69 -0
  57. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  58. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  59. package/dist/application/Setup.js +147 -259
  60. package/dist/application/SetupClientConfiguration.js +220 -0
  61. package/dist/application/SetupClients.js +24 -2
  62. package/dist/application/SetupPlan.js +123 -12
  63. package/dist/application/SetupSkill.js +26 -12
  64. package/dist/application/SupportedClients.js +1 -0
  65. package/dist/application/Uninstall.js +6 -3
  66. package/dist/application/WindowsCapabilities.js +86 -0
  67. package/dist/artifacts/AsarArtifactReader.js +11 -0
  68. package/dist/browser/BrowserProcessStartup.js +88 -0
  69. package/dist/browser/CdpCaptureDocuments.js +12 -8
  70. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  71. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  72. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  73. package/dist/browser/CdpCaptureEventState.js +1 -0
  74. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  75. package/dist/browser/CdpCaptureEvents.js +29 -496
  76. package/dist/browser/CdpObservationSession.js +51 -8
  77. package/dist/browser/CdpPageCapture.js +12 -133
  78. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  79. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  80. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  81. package/dist/browser/PngVisualDiff.js +9 -2
  82. package/dist/catalogIdentity.js +4 -0
  83. package/dist/cli/artifactCommands.js +82 -0
  84. package/dist/cli/coreAnalysisCommands.js +232 -0
  85. package/dist/cli/managedCommands.js +397 -0
  86. package/dist/cli/managedProjectGraphCommand.js +24 -0
  87. package/dist/cli/options.js +9 -0
  88. package/dist/cli/setupCommands.js +108 -0
  89. package/dist/cli/types.js +1 -0
  90. package/dist/cli/utilityCommands.js +122 -0
  91. package/dist/cli.js +10 -768
  92. package/dist/cliApplicationCommands.js +118 -4
  93. package/dist/cliBrowserAdvancedCommands.js +73 -16
  94. package/dist/cliBrowserCommands.js +39 -115
  95. package/dist/cliCommandNames.js +4 -0
  96. package/dist/cliElectronCommands.js +33 -62
  97. package/dist/cliEvidenceCommands.js +13 -2
  98. package/dist/cliInvestigationCommands.js +53 -10
  99. package/dist/cliObservationOptions.js +137 -0
  100. package/dist/cliPolicyCommands.js +80 -46
  101. package/dist/cliSetup.js +289 -0
  102. package/dist/config/environment.js +107 -0
  103. package/dist/config/parseConfig.js +228 -0
  104. package/dist/config/parsers.js +68 -0
  105. package/dist/config/permissions.js +30 -0
  106. package/dist/config/types.js +1 -0
  107. package/dist/config.js +1 -375
  108. package/dist/contracts/applicationToolContracts.js +134 -0
  109. package/dist/contracts/managedToolContracts.js +1 -1
  110. package/dist/contracts/managedWorkflowToolContracts.js +3 -3
  111. package/dist/contracts/providerSelection.js +2 -0
  112. package/dist/contracts/toolContracts.js +2 -2
  113. package/dist/contracts/toolEffects.js +14 -0
  114. package/dist/contracts/toolOutputSchemaGroups.js +232 -0
  115. package/dist/contracts/toolOutputSchemaPrimitives.js +327 -0
  116. package/dist/contracts/toolOutputSchemas.js +2 -550
  117. package/dist/domain/analysisErrorPresentation.js +234 -0
  118. package/dist/domain/analysisErrorProjection.js +284 -0
  119. package/dist/domain/artifactComparison.js +34 -15
  120. package/dist/domain/binaryTarget.js +87 -37
  121. package/dist/domain/browserObservation.js +1 -285
  122. package/dist/domain/browserObservationSchemas.js +286 -0
  123. package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
  124. package/dist/domain/errors.js +4 -486
  125. package/dist/domain/functionComparison.js +4 -165
  126. package/dist/domain/functionComparisonDimensions.js +164 -0
  127. package/dist/domain/javascriptApplicationVersionItems.js +18 -6
  128. package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
  129. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  130. package/dist/domain/javascriptFeatureTraversal.js +19 -7
  131. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
  132. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  133. package/dist/domain/javascriptSemanticIr.js +30 -0
  134. package/dist/domain/javascriptSemanticProjection.js +366 -0
  135. package/dist/domain/javascriptSemanticState.js +40 -0
  136. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  137. package/dist/domain/javascriptSemanticValues.js +352 -0
  138. package/dist/domain/javascriptStaticAnalysis.js +5 -221
  139. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  140. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  141. package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
  142. package/dist/domain/knownPageCoverage.js +21 -0
  143. package/dist/domain/managedApplicationGraph.js +14 -364
  144. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  145. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  146. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  147. package/dist/domain/managedArtifact.js +16 -8
  148. package/dist/domain/managedMemberComparison.js +37 -391
  149. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  150. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  151. package/dist/domain/managedNativeVerification.js +27 -389
  152. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  153. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  154. package/dist/domain/managedReconstruction.js +82 -69
  155. package/dist/domain/managedRuntimeCorrelation.js +12 -3
  156. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  157. package/dist/domain/permissionPolicy.js +2 -0
  158. package/dist/domain/reconstructionCoverage.js +387 -0
  159. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  160. package/dist/domain/reconstructionVerificationSchemas.js +8 -0
  161. package/dist/domain/runtimeCharacterization.js +85 -0
  162. package/dist/domain/webBundleAnalyzer.js +3 -409
  163. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  164. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  165. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  166. package/dist/dotnet/ManagedArtifactInspector.js +50 -29
  167. package/dist/dotnet/ManagedMemberInspector.js +51 -785
  168. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  169. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  170. package/dist/dotnet/ManagedMemberRows.js +120 -0
  171. package/dist/dotnet/ManagedMetadataInventory.js +42 -306
  172. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  173. package/dist/dotnet/ManagedMetadataLayout.js +48 -111
  174. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  175. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  176. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  177. package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
  178. package/dist/dotnet/ManagedPeReader.js +59 -28
  179. package/dist/generatedPackageMetadata.js +2 -2
  180. package/dist/ghidra/GhidraClient.js +76 -91
  181. package/dist/ghidra/GhidraClientStartup.js +29 -0
  182. package/dist/ghidra/GhidraClientWire.js +34 -0
  183. package/dist/ghidra/GhidraDefaults.js +1 -1
  184. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  185. package/dist/ghidra/GhidraInstallation.js +20 -15
  186. package/dist/ghidra/GhidraLauncher.js +93 -29
  187. package/dist/ghidra/GhidraProvider.js +61 -161
  188. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  189. package/dist/ghidra/GhidraSessionValues.js +2 -0
  190. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  191. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  192. package/dist/ghidra/GhidraTransport.js +71 -0
  193. package/dist/identity.js +3 -2
  194. package/dist/main/messages.js +5 -0
  195. package/dist/main/reload.js +49 -0
  196. package/dist/main/shutdown.js +25 -0
  197. package/dist/main/startup.js +14 -0
  198. package/dist/main/state.js +26 -0
  199. package/dist/main/transport.js +61 -0
  200. package/dist/main/types.js +1 -0
  201. package/dist/main.js +35 -146
  202. package/dist/process/PrivateRuntimeRoot.js +3 -2
  203. package/dist/process/ProcessOwnership.js +35 -0
  204. package/dist/process/ProviderProcess.js +2 -0
  205. package/dist/replay/JavaScriptReplayWorker.js +64 -50
  206. package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
  207. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  208. package/dist/replay/ReplayOutcome.js +95 -0
  209. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  210. package/dist/server/ProcessCaptureElicitation.js +157 -0
  211. package/dist/server/createServer.js +119 -54
  212. package/dist/server/registerApplicationTools/characterization.js +57 -0
  213. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  214. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  215. package/dist/server/registerApplicationTools/coverage.js +84 -0
  216. package/dist/server/registerApplicationTools/helpers.js +42 -0
  217. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  218. package/dist/server/registerApplicationTools/types.js +1 -0
  219. package/dist/server/registerApplicationTools.js +10 -112
  220. package/dist/server/registerBrowserTools.js +59 -83
  221. package/dist/server/registerElectronTools.js +31 -46
  222. package/dist/server/registerEnhancedTools.js +34 -42
  223. package/dist/server/registerEvidenceResources.js +54 -0
  224. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  225. package/dist/server/registerInvestigationTools/types.js +1 -0
  226. package/dist/server/registerInvestigationTools.js +83 -116
  227. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  228. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  229. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  230. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  231. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  232. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  233. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  234. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  235. package/dist/server/registerManagedWorkflowTools.js +10 -274
  236. package/dist/server/registerOfficialTools.js +73 -59
  237. package/dist/server/registerSessionRecordTools.js +137 -0
  238. package/dist/server/registerSessionStatusTool.js +2 -1
  239. package/dist/server/registerSessionTools.js +60 -164
  240. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  241. package/dist/server/toolRegistrationOptions.js +101 -3
  242. package/dist/server/toolResult.js +5 -0
  243. package/package.json +10 -11
  244. package/skills/{rea-analysis → reverse-engineer-anything}/SKILL.md +20 -7
  245. package/scripts/prepare-node-pty.mjs +0 -35
  246. package/scripts/rebuild-native.mjs +0 -22
@@ -1,433 +1,216 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomBytes } from "node:crypto";
3
- import { open, readFile } from "node:fs/promises";
4
- import { dirname } from "node:path";
5
- import { digestBytes, digestJson, } from "../application/JavaScriptReplayPlanning.js";
6
- import { jsonValueSchema } from "../domain/jsonValue.js";
7
- import { buildLinuxX64ReplaySeccomp, linuxX64ReplaySeccompDigest, } from "./LinuxSeccompPolicy.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import { digestBytes, } from "../application/JavaScriptReplayPlanning.js";
5
+ import { linuxX64ReplaySeccompDigest } from "./LinuxSeccompPolicy.js";
8
6
  import { resolveLinuxRuntimeClosure } from "./LinuxRuntimeClosure.js";
7
+ import { assertRuntimeCommitment, systemdArguments, temporaryFilterHandle, } from "./LinuxJavaScriptReplaySandbox.js";
8
+ import { boundedCollector, killUnit, observeCleanup, observeUnitResult, } from "./ReplayProcessLifecycle.js";
9
+ import { commitOutcomes, compareOutcomes, terminationResult, workerRequest, } from "./ReplayOutcome.js";
9
10
  import { parseReplayWorkerResponse, } from "./ReplayWorkerProtocol.js";
10
11
  const isAborted = (signal) => signal?.aborted === true;
11
12
  /** Run one prepared experiment under the exact Linux ADR-0002 boundary. */
12
13
  export class LinuxJavaScriptReplayRunner {
13
14
  async execute(prepared, policy, signal) {
14
15
  if (isAborted(signal))
15
- return terminationResult(prepared, "", "cancelled", {
16
- state: "complete",
17
- residual_resources: [],
16
+ return terminationResult({
17
+ prepared,
18
+ stderr: "",
19
+ termination: "cancelled",
20
+ cleanup: { state: "complete", residual_resources: [] },
18
21
  });
19
22
  const unit = `rea-replay-${randomBytes(8).toString("hex")}.service`;
20
- const workerPath = prepared.publicPlan.runtime.worker.path;
21
- const closure = await resolveLinuxRuntimeClosure(policy.nodePath);
22
- assertRuntimeCommitment(prepared, closure);
23
- if (digestBytes(await readFile(workerPath)) !==
24
- prepared.publicPlan.runtime.worker.sha256)
25
- throw new TypeError("Replay worker changed after approval");
26
- if (linuxX64ReplaySeccompDigest() !==
27
- prepared.publicPlan.sandbox.seccomp_sha256)
28
- throw new TypeError("Replay seccomp policy changed after approval");
29
- const request = workerRequest(prepared);
30
- const encoded = Buffer.from(JSON.stringify(request));
31
- if (encoded.byteLength > prepared.publicPlan.limits.protocol_bytes)
32
- throw new RangeError("Replay worker protocol input exceeds the committed limit");
23
+ const closure = await validateReplayCommitments(prepared, policy);
24
+ const encoded = buildWorkerPayload(prepared);
33
25
  const filter = await temporaryFilterHandle();
34
- const arguments_ = systemdArguments(unit, policy, prepared, workerPath, closure, filter.path);
35
- let child;
36
- let timeout;
37
- let cancelled = false;
38
- let timedOut = false;
39
- let terminationRequest;
40
- const requestTermination = () => {
41
- terminationRequest ??= killUnit(policy.systemctlPath, unit);
42
- child?.kill("SIGKILL");
43
- };
44
- const terminate = () => {
45
- cancelled = true;
46
- requestTermination();
47
- };
26
+ const replayProcess = launchReplayProcess({
27
+ prepared,
28
+ policy,
29
+ encoded,
30
+ closure,
31
+ filterPath: filter.path,
32
+ unit,
33
+ signal,
34
+ });
48
35
  try {
49
- const completion = new Promise((resolve, reject) => {
50
- child = spawn(policy.systemdRunPath, arguments_, {
51
- stdio: ["pipe", "pipe", "pipe"],
52
- env: {
53
- PATH: "/usr/bin:/bin",
54
- LANG: "C",
55
- LC_ALL: "C",
56
- TZ: "UTC",
57
- ...(process.env.XDG_RUNTIME_DIR === undefined
58
- ? {}
59
- : { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR }),
60
- ...(process.env.DBUS_SESSION_BUS_ADDRESS === undefined
61
- ? {}
62
- : {
63
- DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS,
64
- }),
65
- },
66
- });
67
- const output = boundedCollector(prepared.publicPlan.limits.output_bytes);
68
- const diagnostics = boundedCollector(prepared.publicPlan.limits.stderr_bytes);
69
- child.stdout?.on("data", output.append);
70
- child.stderr?.on("data", diagnostics.append);
71
- child.once("error", reject);
72
- child.once("close", (code, signalName) => resolve({
73
- code,
74
- signal: signalName,
75
- stdout: output.value(),
76
- stderr: diagnostics.value(),
77
- outputExceeded: output.exceeded(),
78
- }));
79
- child.stdin?.end(encoded);
80
- });
81
- signal?.addEventListener("abort", terminate, { once: true });
82
- if (isAborted(signal))
83
- terminate();
84
- timeout = setTimeout(() => {
85
- timedOut = true;
86
- requestTermination();
87
- }, prepared.publicPlan.limits.wall_time_ms);
88
- timeout.unref();
89
- const collected = await completion;
90
- if (terminationRequest !== undefined)
91
- await terminationRequest;
36
+ const collected = await replayProcess.completion;
37
+ if (replayProcess.terminationRequest !== undefined)
38
+ await replayProcess.terminationRequest;
92
39
  const unitResult = await observeUnitResult(policy.systemctlPath, unit);
93
40
  const cleanup = await observeCleanup(policy.systemctlPath, unit);
94
- if (cancelled || timedOut)
95
- return terminationResult(prepared, collected.stderr, cancelled ? "cancelled" : "timeout", cleanup);
96
- if (collected.outputExceeded)
97
- return terminationResult(prepared, collected.stderr, "protocol_error", cleanup, "Worker stdout exceeded the committed bound");
98
- if (collected.code !== 0)
99
- return terminationResult(prepared, collected.stderr, collected.code === 137 || unitResult === "oom-kill" ? "oom" : "crash", cleanup);
100
- let response;
101
- try {
102
- response = parseReplayWorkerResponse(JSON.parse(collected.stdout), prepared.publicPlan.cases, prepared.publicPlan.right !== undefined);
103
- }
104
- catch {
105
- return terminationResult(prepared, collected.stderr, "protocol_error", cleanup);
106
- }
107
- const outcomes = commitOutcomes(response.left);
108
- const right = response.right === undefined
109
- ? undefined
110
- : commitOutcomes(response.right);
111
- return {
112
- schema_version: 1,
113
- plan_digest: prepared.publicPlan.plan_digest,
114
- outcomes: [...outcomes, ...(right ?? [])],
115
- ...(right === undefined
116
- ? {}
117
- : { comparison: compareOutcomes(outcomes, right) }),
118
- stderr: collected.stderr,
119
- termination: "completed",
41
+ return resolveReplayResult({
42
+ prepared,
43
+ collected,
44
+ unitResult,
120
45
  cleanup,
121
- limitations: [
122
- "Controlled replay observes the isolated extracted modules, not the original application runtime.",
123
- "Network and host filesystem access were unavailable; unsupported dependencies may change behavior.",
124
- ],
125
- reproducer: null,
126
- };
46
+ cancelled: replayProcess.cancelled,
47
+ timedOut: replayProcess.timedOut,
48
+ });
127
49
  }
128
50
  finally {
129
- signal?.removeEventListener("abort", terminate);
130
- if (timeout !== undefined)
131
- clearTimeout(timeout);
132
- if (child !== undefined && child.exitCode === null) {
133
- requestTermination();
134
- await terminationRequest;
51
+ signal?.removeEventListener("abort", replayProcess.terminate);
52
+ if (replayProcess.timeout !== undefined)
53
+ clearTimeout(replayProcess.timeout);
54
+ if (replayProcess.child !== undefined &&
55
+ replayProcess.child.exitCode === null) {
56
+ replayProcess.requestTermination();
57
+ await replayProcess.terminationRequest;
135
58
  }
136
59
  await filter.close();
137
60
  }
138
61
  }
139
62
  }
140
- const workerRequest = (prepared) => ({
141
- schemaVersion: 1,
142
- left: workerSide(prepared.publicPlan.left, prepared.leftSources),
143
- ...(prepared.publicPlan.right === undefined ||
144
- prepared.rightSources === undefined
145
- ? {}
146
- : { right: workerSide(prepared.publicPlan.right, prepared.rightSources) }),
147
- cases: prepared.publicPlan.cases.map((item) => ({
148
- caseId: item.case_id,
149
- arguments: item.arguments,
150
- inputSha256: item.sha256,
151
- })),
152
- determinism: {
153
- clockIso: prepared.publicPlan.determinism.clock_iso,
154
- randomSeed: prepared.publicPlan.determinism.random_seed,
155
- },
156
- limits: {
157
- resultDepth: prepared.publicPlan.limits.result_depth,
158
- resultNodes: prepared.publicPlan.limits.result_nodes,
159
- exceptionBytes: Math.min(64 * 1024, prepared.publicPlan.limits.output_bytes),
160
- },
161
- });
162
- const workerSide = (side, sources) => ({
163
- modules: side.modules.map((module) => ({
164
- alias: module.alias,
165
- format: module.format,
166
- dependencies: module.dependencies,
167
- source: sources[module.alias] ?? "",
168
- })),
169
- entryAlias: side.entry_alias,
170
- entryExport: side.entry_export,
171
- });
172
- const systemdArguments = (unit, policy, prepared, workerPath, closure, filterPath) => {
173
- const limits = prepared.publicPlan.limits;
174
- const descriptorPaths = [
175
- filterPath,
176
- ...closure.map(({ sourcePath }) => sourcePath),
177
- workerPath,
178
- ];
179
- const workerFd = descriptorPaths.length + 2;
180
- const wrapper = descriptorPaths
181
- .map((_, index) => `exec ${String(index + 3)}<"$1"; shift`)
182
- .concat('exec "$@"')
183
- .join("\n");
184
- return [
185
- "--user",
186
- "--pipe",
187
- "--wait",
188
- "--service-type=exec",
189
- "--quiet",
190
- `--unit=${unit}`,
191
- `--property=MemoryMax=${String(limits.memory_bytes)}`,
192
- "--property=MemorySwapMax=0",
193
- `--property=TasksMax=${String(limits.tasks)}`,
194
- `--property=CPUQuota=${String(limits.cpu_quota_percent)}%`,
195
- "--property=KillMode=control-group",
196
- "--property=SendSIGKILL=yes",
197
- "--",
198
- policy.shellPath,
199
- "-c",
200
- wrapper,
201
- "rea-replay-wrapper",
202
- ...descriptorPaths,
203
- policy.bubblewrapPath,
204
- "--unshare-all",
205
- "--unshare-user",
206
- "--disable-userns",
207
- "--new-session",
208
- "--die-with-parent",
209
- "--cap-drop",
210
- "ALL",
211
- "--clearenv",
212
- "--setenv",
213
- "HOME",
214
- "/work",
215
- "--setenv",
216
- "TMPDIR",
217
- "/tmp",
218
- "--setenv",
219
- "TZ",
220
- "UTC",
221
- "--setenv",
222
- "LANG",
223
- "C",
224
- "--setenv",
225
- "LC_ALL",
226
- "C",
227
- "--tmpfs",
228
- "/",
229
- "--dir",
230
- "/runtime",
231
- "--dir",
232
- "/work",
233
- "--size",
234
- String(limits.tmpfs_bytes),
235
- "--tmpfs",
236
- "/tmp",
237
- ...bindArguments(closure),
238
- "--ro-bind-fd",
239
- String(workerFd),
240
- "/runtime/worker.js",
241
- "--proc",
242
- "/proc",
243
- "--dev",
244
- "/dev",
245
- "--remount-ro",
246
- "/",
247
- "--chdir",
248
- "/work",
249
- "--seccomp",
250
- "3",
251
- "--",
252
- "/runtime/node",
253
- `--max-old-space-size=${String(Math.max(8, Math.floor(limits.memory_bytes / 1024 / 1024 / 2)))}`,
254
- "--experimental-vm-modules",
255
- "--permission",
256
- "--allow-fs-read=/runtime/worker.js",
257
- "/runtime/worker.js",
258
- ];
259
- };
260
- const bindArguments = (closure) => {
261
- const arguments_ = [];
262
- const directories = new Set();
263
- for (const [index, item] of closure.entries()) {
264
- const destination = item.destinationPath;
265
- let parent = dirname(destination);
266
- const chain = [];
267
- while (parent !== "/" && !directories.has(parent)) {
268
- chain.push(parent);
269
- parent = dirname(parent);
270
- }
271
- for (const directory of chain.reverse()) {
272
- directories.add(directory);
273
- arguments_.push("--dir", directory);
274
- }
275
- arguments_.push("--ro-bind-fd", String(index + 4), destination);
276
- }
277
- return arguments_;
63
+ const validateReplayCommitments = async (prepared, policy) => {
64
+ const closure = await resolveLinuxRuntimeClosure(policy.nodePath);
65
+ assertRuntimeCommitment(prepared, closure);
66
+ const workerPath = prepared.publicPlan.runtime.worker.path;
67
+ if (digestBytes(await readFile(workerPath)) !==
68
+ prepared.publicPlan.runtime.worker.sha256)
69
+ throw new TypeError("Replay worker changed after approval");
70
+ if (linuxX64ReplaySeccompDigest() !== prepared.publicPlan.sandbox.seccomp_sha256)
71
+ throw new TypeError("Replay seccomp policy changed after approval");
72
+ return closure;
278
73
  };
279
- const assertRuntimeCommitment = (prepared, closure) => {
280
- const observed = closure.map((item) => ({
281
- source_path: item.sourcePath,
282
- destination_path: item.destinationPath,
283
- sha256: item.sha256,
284
- }));
285
- if (digestJson(observed) !==
286
- digestJson(prepared.publicPlan.runtime.read_only_files))
287
- throw new TypeError("Replay runtime closure changed after approval");
74
+ const buildWorkerPayload = (prepared) => {
75
+ const request = workerRequest(prepared);
76
+ const encoded = Buffer.from(JSON.stringify(request));
77
+ if (encoded.byteLength > prepared.publicPlan.limits.protocol_bytes)
78
+ throw new RangeError("Replay worker protocol input exceeds the committed limit");
79
+ return encoded;
288
80
  };
289
- const temporaryFilterHandle = async () => {
290
- const { mkdtemp, writeFile, rm } = await import("node:fs/promises");
291
- const { tmpdir } = await import("node:os");
292
- const { join } = await import("node:path");
293
- const directory = await mkdtemp(join(tmpdir(), "rea-replay-filter-"));
294
- const path = join(directory, "seccomp.bpf");
295
- await writeFile(path, buildLinuxX64ReplaySeccomp(), { mode: 0o600 });
296
- const handle = await open(path, "r");
297
- return {
298
- path,
299
- handle,
300
- close: async () => {
301
- await handle.close();
302
- await rm(directory, { recursive: true, force: true });
303
- },
81
+ const launchReplayProcess = (options) => {
82
+ const { prepared, policy, encoded, closure, filterPath, unit, signal } = options;
83
+ const arguments_ = systemdArguments({
84
+ unit,
85
+ policy,
86
+ prepared,
87
+ workerPath: prepared.publicPlan.runtime.worker.path,
88
+ closure,
89
+ filterPath,
90
+ });
91
+ let child;
92
+ let timeout;
93
+ let cancelled = false;
94
+ let timedOut = false;
95
+ let terminationRequest;
96
+ const requestTermination = () => {
97
+ terminationRequest ??= killUnit(policy.systemctlPath, unit);
98
+ child?.kill("SIGKILL");
304
99
  };
305
- };
306
- const boundedCollector = (maximum) => {
307
- const chunks = [];
308
- let bytes = 0;
309
- let overflow = false;
100
+ const terminate = () => {
101
+ cancelled = true;
102
+ requestTermination();
103
+ };
104
+ const completion = new Promise((resolve, reject) => {
105
+ child = spawn(policy.systemdRunPath, arguments_, {
106
+ stdio: ["pipe", "pipe", "pipe"],
107
+ env: replayProcessEnvironment(),
108
+ });
109
+ const output = boundedCollector(prepared.publicPlan.limits.output_bytes);
110
+ const diagnostics = boundedCollector(prepared.publicPlan.limits.stderr_bytes);
111
+ child.stdout?.on("data", output.append);
112
+ child.stderr?.on("data", diagnostics.append);
113
+ child.once("error", reject);
114
+ child.once("close", (code, signalName) => resolve({
115
+ code,
116
+ signal: signalName,
117
+ stdout: output.value(),
118
+ stderr: diagnostics.value(),
119
+ outputExceeded: output.exceeded(),
120
+ }));
121
+ child.stdin?.end(encoded);
122
+ });
123
+ signal?.addEventListener("abort", terminate, { once: true });
124
+ if (isAborted(signal))
125
+ terminate();
126
+ timeout = setTimeout(() => {
127
+ timedOut = true;
128
+ requestTermination();
129
+ }, prepared.publicPlan.limits.wall_time_ms);
130
+ timeout.unref();
131
+ if (child === undefined || timeout === undefined)
132
+ throw new TypeError("Replay process was not launched");
310
133
  return {
311
- append: (chunk) => {
312
- const retained = chunk.subarray(0, Math.max(0, maximum - bytes));
313
- if (retained.byteLength > 0)
314
- chunks.push(retained);
315
- bytes += retained.byteLength;
316
- if (retained.byteLength < chunk.byteLength)
317
- overflow = true;
134
+ child,
135
+ completion,
136
+ timeout,
137
+ terminate,
138
+ requestTermination,
139
+ get terminationRequest() {
140
+ return terminationRequest;
141
+ },
142
+ get cancelled() {
143
+ return cancelled;
144
+ },
145
+ get timedOut() {
146
+ return timedOut;
318
147
  },
319
- value: () => Buffer.concat(chunks).toString("utf8"),
320
- exceeded: () => overflow,
321
148
  };
322
149
  };
323
- const collect = async (executable, arguments_, maximum) => new Promise((resolve, reject) => {
324
- const child = spawn(executable, [...arguments_], {
325
- stdio: ["ignore", "pipe", "pipe"],
326
- });
327
- const stdout = boundedCollector(maximum);
328
- const stderr = boundedCollector(maximum);
329
- const timeout = setTimeout(() => child.kill("SIGKILL"), 5_000);
330
- timeout.unref();
331
- child.stdout?.on("data", stdout.append);
332
- child.stderr?.on("data", stderr.append);
333
- child.once("error", (error) => {
334
- clearTimeout(timeout);
335
- reject(error);
336
- });
337
- child.once("close", (code) => {
338
- clearTimeout(timeout);
339
- resolve({ code, stdout: stdout.value(), stderr: stderr.value() });
340
- });
150
+ const replayProcessEnvironment = () => ({
151
+ PATH: "/usr/bin:/bin",
152
+ LANG: "C",
153
+ LC_ALL: "C",
154
+ TZ: "UTC",
155
+ ...(process.env.XDG_RUNTIME_DIR === undefined
156
+ ? {}
157
+ : { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR }),
158
+ ...(process.env.DBUS_SESSION_BUS_ADDRESS === undefined
159
+ ? {}
160
+ : { DBUS_SESSION_BUS_ADDRESS: process.env.DBUS_SESSION_BUS_ADDRESS }),
341
161
  });
342
- const killUnit = async (systemctl, unit) => {
343
- try {
344
- await collect(systemctl, ["--user", "kill", "--kill-whom=all", "--signal=SIGKILL", unit], 4096);
345
- }
346
- catch {
347
- /* cleanup continues */
348
- }
349
- };
350
- const observeCleanup = async (systemctl, unit) => {
162
+ const resolveReplayResult = (options) => {
163
+ const { prepared, collected, unitResult, cleanup, cancelled, timedOut } = options;
164
+ if (cancelled || timedOut)
165
+ return terminationResult({
166
+ prepared,
167
+ stderr: collected.stderr,
168
+ termination: cancelled ? "cancelled" : "timeout",
169
+ cleanup,
170
+ });
171
+ if (collected.outputExceeded)
172
+ return terminationResult({
173
+ prepared,
174
+ stderr: collected.stderr,
175
+ termination: "protocol_error",
176
+ cleanup,
177
+ limitation: "Worker stdout exceeded the committed bound",
178
+ });
179
+ if (collected.code !== 0)
180
+ return terminationResult({
181
+ prepared,
182
+ stderr: collected.stderr,
183
+ termination: collected.code === 137 || unitResult === "oom-kill" ? "oom" : "crash",
184
+ cleanup,
185
+ });
186
+ let response;
351
187
  try {
352
- const state = await collect(systemctl, ["--user", "show", "--property=ActiveState", "--value", unit], 4096);
353
- if (state.code !== 0 ||
354
- state.stdout.trim() === "inactive" ||
355
- state.stdout.trim() === "failed" ||
356
- state.stdout.trim().length === 0) {
357
- await collect(systemctl, ["--user", "reset-failed", unit], 4096);
358
- return { state: "complete", residual_resources: [] };
359
- }
360
- return { state: "incomplete", residual_resources: [unit] };
188
+ const rawResponse = JSON.parse(collected.stdout);
189
+ response = parseReplayWorkerResponse(rawResponse, prepared.publicPlan.cases, prepared.publicPlan.right !== undefined);
361
190
  }
362
191
  catch {
363
- return { state: "incomplete", residual_resources: [unit] };
192
+ return terminationResult({
193
+ prepared,
194
+ stderr: collected.stderr,
195
+ termination: "protocol_error",
196
+ cleanup,
197
+ });
364
198
  }
365
- };
366
- const observeUnitResult = async (systemctl, unit) => {
367
- try {
368
- const result = await collect(systemctl, ["--user", "show", "--property=Result", "--value", unit], 4096);
369
- return result.stdout.trim();
370
- }
371
- catch {
372
- return "unknown";
373
- }
374
- };
375
- const commitOutcomes = (outcomes) => outcomes.map((outcome) => {
376
- const semantic = outcome.value === undefined ? (outcome.exception ?? null) : outcome.value;
377
- return {
378
- case_id: outcome.case_id,
379
- outcome: outcome.outcome,
380
- ...(outcome.value === undefined
381
- ? {}
382
- : { value: jsonValueSchema.parse(outcome.value) }),
383
- ...(outcome.exception === undefined
384
- ? {}
385
- : { exception: outcome.exception }),
386
- input_sha256: outcome.input_sha256,
387
- output_sha256: digestJson(semantic),
388
- truncated: false,
389
- };
390
- });
391
- const compareOutcomes = (left, right) => left.map((item, index) => ({
392
- case_id: item.case_id,
393
- status: right[index] === undefined
394
- ? "unknown"
395
- : item.outcome === right[index].outcome &&
396
- item.output_sha256 === right[index].output_sha256
397
- ? "equal"
398
- : "changed",
399
- left_index: index,
400
- right_index: index,
401
- }));
402
- const terminationResult = (prepared, stderr, termination, cleanup, limitation) => {
403
- const outcomes = prepared.publicPlan.cases.map((item) => ({
404
- case_id: item.case_id,
405
- outcome: termination,
406
- input_sha256: item.sha256,
407
- output_sha256: null,
408
- truncated: termination === "protocol_error",
409
- }));
410
- const differential = prepared.publicPlan.right !== undefined;
199
+ const outcomes = commitOutcomes(response.left);
200
+ const right = response.right === undefined ? undefined : commitOutcomes(response.right);
411
201
  return {
412
202
  schema_version: 1,
413
203
  plan_digest: prepared.publicPlan.plan_digest,
414
- outcomes: [...outcomes, ...(differential ? outcomes : [])],
415
- ...(differential
416
- ? {
417
- comparison: outcomes.map((item, index) => ({
418
- case_id: item.case_id,
419
- status: "unknown",
420
- left_index: index,
421
- right_index: index,
422
- })),
423
- }
424
- : {}),
425
- stderr,
426
- termination,
204
+ outcomes: [...outcomes, ...(right ?? [])],
205
+ ...(right === undefined
206
+ ? {}
207
+ : { comparison: compareOutcomes(outcomes, right) }),
208
+ stderr: collected.stderr,
209
+ termination: "completed",
427
210
  cleanup,
428
211
  limitations: [
429
- limitation ??
430
- `Replay terminated with ${termination}; the requested functional result remains unknown.`,
212
+ "Controlled replay observes the isolated extracted modules, not the original application runtime.",
213
+ "Network and host filesystem access were unavailable; unsupported dependencies may change behavior.",
431
214
  ],
432
215
  reproducer: null,
433
216
  };