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
@@ -10,6 +10,16 @@ const RELEASES_URL = "https://www.hopperapp.com/include/files-api.php?request=re
10
10
  const DOWNLOAD_PREFIX = "https://www.hopperapp.com:443/downloader/public/";
11
11
  const MAX_PACKAGE_BYTES = 100_000_000;
12
12
  const BUNDLE_IDENTIFIER = "com.cryptic-apps.hopper-web-4";
13
+ /** Stable network and command effects disclosed before macOS Hopper setup. */
14
+ export const macHopperInstallDisclosure = {
15
+ networkOrigins: [RELEASES_URL, DOWNLOAD_PREFIX],
16
+ commands: [
17
+ "hdiutil attach -readonly -nobrowse -mountpoint <private-mount> <verified-hopper.dmg>",
18
+ "codesign --verify --deep --strict <mounted-hopper.app>",
19
+ "ditto <mounted-hopper.app> <user-Applications-stage>",
20
+ "open <installed-hopper.app>",
21
+ ],
22
+ };
13
23
  /** Download, verify, and install Hopper into the current user's Applications directory. */
14
24
  export const installMacHopper = async (options = {}, host = systemMacHopperInstallHost()) => {
15
25
  let temporary;
@@ -66,17 +76,7 @@ export const installMacHopper = async (options = {}, host = systemMacHopperInsta
66
76
  }
67
77
  };
68
78
  const systemMacHopperInstallHost = () => ({
69
- async download(url) {
70
- const response = await fetch(url, {
71
- headers: { accept: "application/json", "user-agent": "rea-installer" },
72
- signal: AbortSignal.timeout(120_000),
73
- });
74
- const length = Number(response.headers.get("content-length") ?? "0");
75
- if (length > MAX_PACKAGE_BYTES)
76
- return { ok: false, bytes: new Uint8Array() };
77
- const bytes = new Uint8Array(await response.arrayBuffer());
78
- return { ok: response.ok && bytes.byteLength <= MAX_PACKAGE_BYTES, bytes };
79
- },
79
+ download: downloadPackage,
80
80
  createTemporaryDirectory: () => mkdtemp(join(tmpdir(), "rea-hopper-mac-")),
81
81
  createMountDirectory: (path) => mkdir(path),
82
82
  writePackage: (path, bytes) => writeFile(path, bytes, { mode: 0o600 }),
@@ -170,6 +170,17 @@ const systemMacHopperInstallHost = () => ({
170
170
  cleanup: (path) => rm(path, { recursive: true, force: true }),
171
171
  destination: () => join(homedir(), "Applications/Hopper Disassembler.app"),
172
172
  });
173
+ const downloadPackage = async (url) => {
174
+ const response = await fetch(url, {
175
+ headers: { accept: "application/json", "user-agent": "rea-installer" },
176
+ signal: AbortSignal.timeout(120_000),
177
+ });
178
+ const length = Number(response.headers.get("content-length") ?? "0");
179
+ if (length > MAX_PACKAGE_BYTES)
180
+ return { ok: false, bytes: new Uint8Array() };
181
+ const bytes = new Uint8Array(await response.arrayBuffer());
182
+ return { ok: response.ok && bytes.byteLength <= MAX_PACKAGE_BYTES, bytes };
183
+ };
173
184
  const parseJson = (bytes) => JSON.parse(new TextDecoder().decode(bytes));
174
185
  const integrityMatches = (bytes, release) => bytes.byteLength === Number(release.file_length) &&
175
186
  bytes.byteLength <= MAX_PACKAGE_BYTES &&
@@ -0,0 +1,196 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { nodeCharacterizationExecutionInputSchema, nodeCharacterizationExecutionOutputSchema, nodeCharacterizationPreparationInputSchema, nodeCharacterizationPreparationOutputSchema, } from "../domain/nodeRuntimeCharacterization.js";
4
+ import { AnalysisInputError, AnalysisProtocolError, ReplayPlanStaleError, } from "../domain/errors.js";
5
+ import { createEvidence } from "../domain/evidence.js";
6
+ import { controlledReplayOutputSchema } from "../domain/javascriptReplay.js";
7
+ import { createRuntimeCharacterizationPlan, } from "../domain/runtimeCharacterization.js";
8
+ import { err, ok } from "../domain/result.js";
9
+ import { jsonValueSchema } from "../domain/jsonValue.js";
10
+ import { InstrumentedJavaScriptReplayHost } from "./InstrumentedJavaScriptReplayHost.js";
11
+ import { runControlledReplayValidated, } from "./JavaScriptReplayService.js";
12
+ const PREPARE_OPERATION = "prepare_node_characterization";
13
+ const EXECUTE_OPERATION = "execute_node_characterization";
14
+ const PROVIDER = {
15
+ id: "rea-node-characterization",
16
+ name: "REA Node runtime characterization",
17
+ version: "1",
18
+ };
19
+ /** Prepare exact Node/JavaScript characterization without executing target code. */
20
+ export const prepareNodeCharacterization = async (dependencies, rawInput, options = {}) => {
21
+ const parsed = nodeCharacterizationPreparationInputSchema.safeParse(rawInput);
22
+ if (!parsed.success)
23
+ return err(new AnalysisInputError(PREPARE_OPERATION, { cause: parsed.error }));
24
+ const prepared = await prepareValidated(dependencies, parsed.data, options);
25
+ return prepared.ok
26
+ ? ok(jsonValueSchema.parse(nodeCharacterizationPreparationOutputSchema.parse(prepared.value)))
27
+ : prepared;
28
+ };
29
+ /** Execute only a freshly recomputed and separately approved characterization plan. */
30
+ export const executeNodeCharacterization = async (dependencies, rawInput, options = {}) => {
31
+ const parsed = nodeCharacterizationExecutionInputSchema.safeParse(rawInput);
32
+ if (!parsed.success)
33
+ return err(new AnalysisInputError(EXECUTE_OPERATION, { cause: parsed.error }));
34
+ const prepared = await prepareValidated(dependencies, parsed.data.preparation, options);
35
+ if (!prepared.ok)
36
+ return prepared;
37
+ if (prepared.value.plan.plan_sha256 !== parsed.data.approved_plan_sha256)
38
+ return err(new ReplayPlanStaleError(parsed.data.approved_plan_sha256, prepared.value.plan.plan_sha256));
39
+ const plannedReplay = controlledReplayOutputSchema.parse(prepared.value.replay);
40
+ if (plannedReplay.phase !== "plan" || plannedReplay.plan === null)
41
+ return err(new AnalysisProtocolError("Node characterization preparation did not produce a replay plan"));
42
+ const host = new InstrumentedJavaScriptReplayHost(dependencies.host, parsed.data.preparation.instrumentation);
43
+ const executed = await runControlledReplayValidated({ ...dependencies, host }, {
44
+ ...parsed.data.preparation.replay,
45
+ mode: "execute",
46
+ approved: true,
47
+ plan_digest: plannedReplay.plan.plan_digest,
48
+ }, options);
49
+ if (!executed.ok)
50
+ return executed;
51
+ if (host.instrumentation === null)
52
+ return err(new AnalysisProtocolError("Node characterization execution omitted instrumentation evidence"));
53
+ return ok(jsonValueSchema.parse(nodeCharacterizationExecutionOutputSchema.parse({
54
+ schema_version: 1,
55
+ phase: "execution",
56
+ plan: prepared.value.plan,
57
+ transformation: host.instrumentation.manifest,
58
+ transformation_evidence: prepared.value.transformation_evidence,
59
+ evidence: createCharacterizationEvidence(prepared.value, controlledReplayOutputSchema.parse(executed.value)),
60
+ replay: controlledReplayOutputSchema.parse(executed.value),
61
+ })));
62
+ };
63
+ const prepareValidated = async (dependencies, input, options) => {
64
+ const host = new InstrumentedJavaScriptReplayHost(dependencies.host, input.instrumentation);
65
+ const replay = await runControlledReplayValidated({ ...dependencies, host }, input.replay, options);
66
+ if (!replay.ok)
67
+ return replay;
68
+ const output = controlledReplayOutputSchema.parse(replay.value);
69
+ if (output.phase !== "plan" ||
70
+ output.plan === null ||
71
+ host.instrumentation === null)
72
+ return err(new AnalysisProtocolError("Node characterization preparation omitted committed plan evidence"));
73
+ const module = output.plan.left.modules.find(({ alias }) => alias === input.selected_alias);
74
+ if (module === undefined)
75
+ return err(new AnalysisProtocolError("Node characterization replay plan omitted selected module"));
76
+ const plan = createCharacterizationPlan(input, host.instrumentation, output.plan);
77
+ return ok({
78
+ schema_version: 1,
79
+ phase: "preparation",
80
+ plan,
81
+ transformation: host.instrumentation.manifest,
82
+ transformation_evidence: createTransformationEvidence(input, host.instrumentation),
83
+ replay: output,
84
+ });
85
+ };
86
+ const createTransformationEvidence = (input, instrumentation) => createEvidence({
87
+ path: input.instrumentation.artifact_path,
88
+ sha256: input.instrumentation.artifact_sha256,
89
+ format: "javascript-bundle",
90
+ }, PROVIDER, {
91
+ predicateType: "rea.javascript-export-transformation/v1",
92
+ operation: PREPARE_OPERATION,
93
+ parameters: {
94
+ selected_alias: input.selected_alias,
95
+ selected_sha256: input.instrumentation.selection.selected_sha256,
96
+ },
97
+ result: jsonValueSchema.parse(instrumentation.manifest),
98
+ confidence: "derived",
99
+ authority: "shipped-artifact",
100
+ locations: [
101
+ {
102
+ kind: "file-offset-range",
103
+ start: input.instrumentation.selection.byte_start,
104
+ end: input.instrumentation.selection.byte_end,
105
+ },
106
+ ],
107
+ limitations: [
108
+ "The transformation is deterministic derived evidence; execution authority is established only by a separately approved characterization run.",
109
+ ],
110
+ });
111
+ const createCharacterizationEvidence = (prepared, replay) => {
112
+ if (replay.phase !== "execute" || replay.evidence === null)
113
+ throw new AnalysisProtocolError("Node characterization execution omitted replay Evidence");
114
+ return createEvidence({
115
+ path: prepared.plan.artifact.path,
116
+ sha256: prepared.plan.artifact.sha256,
117
+ format: "javascript-bundle",
118
+ }, PROVIDER, {
119
+ predicateType: "rea.runtime-characterization/v1",
120
+ operation: EXECUTE_OPERATION,
121
+ parameters: {
122
+ plan_sha256: prepared.plan.plan_sha256,
123
+ replay_evidence_id: replay.evidence.evidence_id,
124
+ },
125
+ result: jsonValueSchema.parse({
126
+ plan: prepared.plan,
127
+ transformation_evidence_id: prepared.transformation_evidence.evidence_id,
128
+ replay_evidence_id: replay.evidence.evidence_id,
129
+ execution: replay.evidence.normalized_result,
130
+ }),
131
+ confidence: "observed",
132
+ authority: "controlled-replay",
133
+ environment: replay.evidence.environment,
134
+ limitations: [
135
+ "This Evidence proves only the finite approved characterization plan and transformed controlled-replay authority, not unmodified shipped-runtime equivalence.",
136
+ ...replay.evidence.limitations,
137
+ ],
138
+ evidenceLinks: [
139
+ prepared.transformation_evidence.evidence_id,
140
+ replay.evidence.evidence_id,
141
+ ],
142
+ });
143
+ };
144
+ const createCharacterizationPlan = (input, instrumentation, replayPlan) => createRuntimeCharacterizationPlan({
145
+ schema_version: 1,
146
+ preparation_sha256: digestPreparation(instrumentation.manifest.instrumented_sha256, replayPlan.plan_digest),
147
+ artifact: {
148
+ path: input.instrumentation.artifact_path,
149
+ sha256: input.instrumentation.artifact_sha256,
150
+ byte_length: instrumentation.manifest.original_byte_length,
151
+ },
152
+ runtime: {
153
+ family: "ecmascript",
154
+ provider_id: "node-javascript",
155
+ executable_path: replayPlan.runtime.executable.path,
156
+ executable_sha256: replayPlan.runtime.executable.sha256,
157
+ version: replayPlan.runtime.executable.version,
158
+ profile_sha256: replayPlan.policy_sha256,
159
+ },
160
+ callable: {
161
+ callable_id: `callable/${input.instrumentation.selection.selected_sha256}`,
162
+ module_id: stableModuleId(input.selected_alias),
163
+ export_name: input.instrumentation.selection.export_name,
164
+ semantic_evidence_id: null,
165
+ selector_sha256: input.instrumentation.selection.selected_sha256,
166
+ },
167
+ working_directory: "/work",
168
+ isolated_home: "/work",
169
+ expected_effect: input.expected_effect,
170
+ allowed_boundaries: [],
171
+ limits: {
172
+ max_calls: replayPlan.cases.length,
173
+ max_processes: 0,
174
+ max_files: 0,
175
+ max_bytes: replayPlan.limits.output_bytes,
176
+ max_handles: replayPlan.limits.tasks,
177
+ timeout_ms: replayPlan.limits.wall_time_ms,
178
+ idle_timeout_ms: replayPlan.limits.wall_time_ms,
179
+ },
180
+ determinism: {
181
+ clock: "fixed",
182
+ randomness: "seeded",
183
+ identifiers: "deterministic",
184
+ seed: replayPlan.determinism.random_seed,
185
+ },
186
+ authority: {
187
+ preparation_approved: true,
188
+ execution_approved: false,
189
+ network: "none",
190
+ provider_owned_process_only: true,
191
+ },
192
+ });
193
+ const digestPreparation = (instrumentedSha256, replayPlanDigest) => createHash("sha256")
194
+ .update(`rea.node-characterization-preparation/v1\0${instrumentedSha256}\0${replayPlanDigest}`)
195
+ .digest("hex");
196
+ const stableModuleId = (alias) => `module/${createHash("sha256").update(alias).digest("hex")}`;
@@ -73,31 +73,52 @@ const errorReason = (error) => isNotFound(error) ? "not_found" : "io";
73
73
  /** Stateful owner for permission evaluation, consumption, reload, and revoke. */
74
74
  export class PermissionAuthority {
75
75
  policy;
76
- constructor(policy) {
76
+ configuredAuthority;
77
+ constructor(policy, configuredAuthority) {
77
78
  this.policy = policy;
79
+ this.configuredAuthority = configuredAuthority;
80
+ }
81
+ /** Create a connection-owned overlay for once and session grants. */
82
+ createConnectionAuthority() {
83
+ return new PermissionAuthority(createPermissionPolicy([]), this.configuredAuthority ?? this);
78
84
  }
79
85
  /** Canonicalize and authorize one exact operation before its side effect. */
80
86
  async authorize(request, access, options = {}) {
81
- const evaluated = await this.explain(request, access, options);
87
+ const evaluated = await this.evaluateRequest(request, access, options);
82
88
  if (!evaluated.ok)
83
89
  return evaluated;
84
- const decision = evaluated.value;
85
- this.policy = consumePermission(this.policy, decision);
86
- return ok(decision);
90
+ const { decision, owner, request: canonicalRequest } = evaluated.value;
91
+ owner.policy = consumePermission(owner.policy, decision);
92
+ return ok({ ...decision, request: canonicalRequest });
87
93
  }
88
94
  /** Dry-run the exact shared evaluation without consuming a once grant. */
89
95
  async explain(request, access, options = {}) {
96
+ const evaluated = await this.evaluateRequest(request, access, options);
97
+ return evaluated.ok
98
+ ? ok({
99
+ ...evaluated.value.decision,
100
+ request: evaluated.value.request,
101
+ })
102
+ : evaluated;
103
+ }
104
+ async evaluateRequest(request, access, options) {
90
105
  const canonical = await canonicalizePermissionRequest(request, access);
91
106
  if (!canonical.ok)
92
107
  return canonical;
93
- const decision = evaluatePermission(this.policy, canonical.value);
94
- if (!decision.allowed)
95
- return err(new PermissionRequiredError(canonical.value, decision.missing, ceilingFor(this.policy, canonical.value.capability), options.elicitationSupported ?? false, options.restartRequired ?? false));
96
- return ok({ ...decision, request: canonical.value });
108
+ const evaluated = this.evaluateCanonical(canonical.value, options);
109
+ return evaluated.ok
110
+ ? ok({ ...evaluated.value, request: canonical.value })
111
+ : evaluated;
97
112
  }
98
113
  /** Add authority beneath the administrator ceiling. */
99
114
  grant(grant) {
100
- const result = grantPermission(this.policy, grant);
115
+ const policy = this.configuredAuthority === undefined
116
+ ? this.policy
117
+ : {
118
+ ...this.policy,
119
+ ceilings: this.configuredAuthority.effectivePolicy().ceilings,
120
+ };
121
+ const result = grantPermission(policy, grant);
101
122
  if (result.ok)
102
123
  this.policy = result.value;
103
124
  return result;
@@ -110,44 +131,61 @@ export class PermissionAuthority {
110
131
  clearSessionGrants() {
111
132
  this.policy = clearSessionPermissions(this.policy);
112
133
  }
113
- /** Atomically replace optional persisted project grants. */
114
- async replaceProjectGrants(grants) {
115
- let candidate = {
116
- ...this.policy,
117
- grants: this.policy.grants.filter(({ lifetime }) => lifetime !== "project"),
134
+ effectivePolicy() {
135
+ if (this.configuredAuthority === undefined)
136
+ return this.policy;
137
+ const configured = this.configuredAuthority.effectivePolicy();
138
+ return {
139
+ ceilings: configured.ceilings,
140
+ grants: [...configured.grants, ...this.policy.grants],
141
+ revoked_grant_ids: new Set([
142
+ ...configured.revoked_grant_ids,
143
+ ...this.policy.revoked_grant_ids,
144
+ ]),
145
+ consumed_grant_ids: new Set([
146
+ ...configured.consumed_grant_ids,
147
+ ...this.policy.consumed_grant_ids,
148
+ ]),
118
149
  };
119
- for (const grant of grants) {
120
- const canonical = await canonicalizeGrant(grant);
121
- if (!canonical.ok)
122
- return canonical;
123
- const added = grantPermission(candidate, canonical.value);
124
- if (!added.ok)
125
- return err(new PermissionPathError(grant.grant_id, "io", {
126
- cause: added.error,
127
- }));
128
- candidate = added.value;
150
+ }
151
+ evaluateCanonical(request, options) {
152
+ const configured = this.configuredAuthority;
153
+ if (configured !== undefined) {
154
+ const configuredDecision = evaluatePermission(configured.effectivePolicy(), request);
155
+ if (configuredDecision.allowed)
156
+ return ok({ decision: configuredDecision, owner: configured });
129
157
  }
130
- this.policy = candidate;
131
- return ok(null);
158
+ const localPolicy = configured === undefined
159
+ ? this.policy
160
+ : { ...this.policy, ceilings: configured.effectivePolicy().ceilings };
161
+ const localDecision = evaluatePermission(localPolicy, request);
162
+ if (localDecision.allowed)
163
+ return ok({ decision: localDecision, owner: this });
164
+ const effective = this.effectivePolicy();
165
+ const denied = evaluatePermission(effective, request);
166
+ if (denied.allowed)
167
+ return ok({ decision: denied, owner: this });
168
+ return err(new PermissionRequiredError(request, denied.missing, ceilingFor(effective, request.capability), options.elicitationSupported ?? false, options.restartRequired ?? false));
169
+ }
170
+ /** Atomically replace optional persisted project grants. */
171
+ async replaceProjectGrants(grants) {
172
+ return this.replaceGrants(grants, "project");
132
173
  }
133
174
  /** Replace grants derived from the currently loaded administrator ceiling. */
134
175
  async replaceAdministratorGrants(grants) {
135
- let candidate = {
176
+ return this.replaceGrants(grants, "administrator");
177
+ }
178
+ async replaceGrants(grants, lifetime) {
179
+ const canonical = await canonicalizeGrants(grants);
180
+ if (!canonical.ok)
181
+ return canonical;
182
+ const replaced = addGrantsToPolicy({
136
183
  ...this.policy,
137
- grants: this.policy.grants.filter(({ lifetime }) => lifetime !== "administrator"),
138
- };
139
- for (const grant of grants) {
140
- const canonical = await canonicalizeGrant(grant);
141
- if (!canonical.ok)
142
- return canonical;
143
- const added = grantPermission(candidate, canonical.value);
144
- if (!added.ok)
145
- return err(new PermissionPathError(grant.grant_id, "io", {
146
- cause: added.error,
147
- }));
148
- candidate = added.value;
149
- }
150
- this.policy = candidate;
184
+ grants: this.policy.grants.filter((grant) => grant.lifetime !== lifetime),
185
+ }, canonical.value);
186
+ if (!replaced.ok)
187
+ return replaced;
188
+ this.policy = replaced.value;
151
189
  return ok(null);
152
190
  }
153
191
  /** Atomically replace reloadable ceilings and persisted grants. */
@@ -155,29 +193,20 @@ export class PermissionAuthority {
155
193
  const ceilings = await canonicalizePermissionCeilings(configuration.ceilings);
156
194
  if (!ceilings.ok)
157
195
  return ceilings;
158
- const grants = [];
159
- for (const grant of [
196
+ const grants = await canonicalizeGrants([
160
197
  ...configuration.administratorGrants,
161
198
  ...configuration.projectGrants,
162
- ]) {
163
- const canonical = await canonicalizeGrant(grant);
164
- if (!canonical.ok)
165
- return canonical;
166
- grants.push(canonical.value);
167
- }
168
- let candidate = {
199
+ ]);
200
+ if (!grants.ok)
201
+ return grants;
202
+ const candidate = {
169
203
  ...reloadPermissionCeilings(this.policy, ceilings.value),
170
204
  grants: this.policy.grants.filter(({ lifetime }) => lifetime !== "administrator" && lifetime !== "project"),
171
205
  };
172
- for (const grant of grants) {
173
- const added = grantPermission(candidate, grant);
174
- if (!added.ok)
175
- return err(new PermissionPathError(grant.grant_id, "io", {
176
- cause: added.error,
177
- }));
178
- candidate = added.value;
179
- }
180
- this.policy = candidate;
206
+ const replaced = addGrantsToPolicy(candidate, grants.value);
207
+ if (!replaced.ok)
208
+ return replaced;
209
+ this.policy = replaced.value;
181
210
  return ok(null);
182
211
  }
183
212
  /** Replace ceilings without restarting the MCP process. */
@@ -194,17 +223,31 @@ export const createPermissionAuthority = async (ceilings, grants = []) => {
194
223
  const canonical = await canonicalizePermissionCeilings(ceilings);
195
224
  if (!canonical.ok)
196
225
  return canonical;
197
- let policy = createPermissionPolicy(canonical.value);
226
+ const canonicalGrants = await canonicalizeGrants(grants);
227
+ if (!canonicalGrants.ok)
228
+ return canonicalGrants;
229
+ const policy = addGrantsToPolicy(createPermissionPolicy(canonical.value), canonicalGrants.value);
230
+ return policy.ok ? ok(new PermissionAuthority(policy.value)) : policy;
231
+ };
232
+ const canonicalizeGrants = async (grants) => {
233
+ const canonical = [];
198
234
  for (const grant of grants) {
199
235
  const canonicalGrant = await canonicalizeGrant(grant);
200
236
  if (!canonicalGrant.ok)
201
237
  return canonicalGrant;
202
- const added = grantPermission(policy, canonicalGrant.value);
238
+ canonical.push(canonicalGrant.value);
239
+ }
240
+ return ok(canonical);
241
+ };
242
+ const addGrantsToPolicy = (initial, grants) => {
243
+ let policy = initial;
244
+ for (const grant of grants) {
245
+ const added = grantPermission(policy, grant);
203
246
  if (!added.ok)
204
247
  return err(new PermissionPathError(grant.grant_id, "io", { cause: added.error }));
205
248
  policy = added.value;
206
249
  }
207
- return ok(new PermissionAuthority(policy));
250
+ return ok(policy);
208
251
  };
209
252
  const canonicalizeGrant = async (grant) => {
210
253
  const scope = await canonicalizeScope(grant, "read");
@@ -1,8 +1,8 @@
1
1
  import { tmpdir } from "node:os";
2
- import { spawn } from "node-pty";
3
2
  /** Probe the actual native PTY seam instead of inferring support from the OS name. */
4
3
  export const probeProcessCaptureCapability = async () => {
5
4
  try {
5
+ const { spawn } = await import("@lydell/node-pty");
6
6
  const terminal = spawn(process.platform === "win32" ? "cmd.exe" : "/bin/sh", process.platform === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
7
7
  cwd: tmpdir(),
8
8
  env: { HOME: tmpdir(), TERM: "xterm-256color" },
@@ -0,0 +1,13 @@
1
+ /** Build the exact permission request shared by process-capture adapters. */
2
+ export const processCapturePermissionRequest = (scenario) => ({
3
+ capability: "process_capture",
4
+ roots: [scenario.working_directory, ...scenario.filesystem_roots],
5
+ executables: [scenario.executable],
6
+ environment_names: [
7
+ ...Object.keys(scenario.environment),
8
+ ...scenario.inherit_environment,
9
+ ],
10
+ network: scenario.network_access === "host" ? "external" : "none",
11
+ mount: false,
12
+ operation_identity: `capture_process_scenario:${scenario.executable}`,
13
+ });
@@ -5,6 +5,7 @@ import { createEvidence, parseEvidence } from "../domain/evidence.js";
5
5
  import { jsonValueSchema } from "../domain/jsonValue.js";
6
6
  import { compareProcessCaptures, LEGACY_PROCESS_CAPTURE_MESSAGE, parseProcessScenario, parseProcessCapture, } from "../domain/processCapture.js";
7
7
  import { captureProcessScenario } from "./ProcessHarness.js";
8
+ import { processCapturePermissionRequest } from "./ProcessCapturePermission.js";
8
9
  import { PROCESS_PROVIDER, createProcessCaptureEvidence, } from "./ProcessEvidence.js";
9
10
  import { loadConfiguredPermissionAuthority } from "./PermissionConfiguration.js";
10
11
  const MAX_INPUT_BYTES = 64 * 1024 * 1024;
@@ -25,18 +26,7 @@ export const captureProcessScenarioFile = async (path) => {
25
26
  const authority = await loadConfiguredPermissionAuthority(config.value);
26
27
  if (!authority.ok)
27
28
  return cliAnalysisError(authority.error);
28
- const authorized = await authority.value.authorize({
29
- capability: "process_capture",
30
- roots: [scenario.working_directory, ...scenario.filesystem_roots],
31
- executables: [scenario.executable],
32
- environment_names: [
33
- ...Object.keys(scenario.environment),
34
- ...scenario.inherit_environment,
35
- ],
36
- network: scenario.network_access === "host" ? "external" : "none",
37
- mount: false,
38
- operation_identity: `capture_process_scenario:${scenario.executable}`,
39
- }, "read");
29
+ const authorized = await authority.value.authorize(processCapturePermissionRequest(scenario), "read");
40
30
  if (!authorized.ok)
41
31
  return cliAnalysisError(authorized.error instanceof PermissionRequiredError
42
32
  ? authorized.error
@@ -1,4 +1,3 @@
1
- import { spawn } from "node-pty";
2
1
  import { validateProcessCapture } from "../domain/processCapture.js";
3
2
  import { err, ok } from "../domain/result.js";
4
3
  import { ProcessCaptureError, processCaptureCancelled, } from "./ProcessCaptureError.js";
@@ -111,6 +110,7 @@ const startCaptureRuntime = async (options) => {
111
110
  let shimReplay;
112
111
  let terminal;
113
112
  try {
113
+ const { spawn } = await import("@lydell/node-pty");
114
114
  replay = await startLoopbackReplay(scenario);
115
115
  const startedAt = new Date();
116
116
  const started = Date.now();
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { lstat, mkdir, open, readFile, realpath, stat, unlink, } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { lstat, mkdir, open, readFile, realpath, unlink, } from "node:fs/promises";
3
4
  import { dirname } from "node:path";
4
5
  import { setTimeout as delay } from "node:timers/promises";
5
6
  import writeFileAtomic from "write-file-atomic";
@@ -22,6 +23,7 @@ const grantSchema = z.object({
22
23
  "native_mount",
23
24
  "reference_read",
24
25
  "javascript_replay",
26
+ "managed_runtime",
25
27
  ]),
26
28
  roots: z.array(z.string()),
27
29
  executables: z.array(z.string()),
@@ -66,11 +68,17 @@ export const readProjectPermissionStore = async (path, projectRoot) => {
66
68
  const project = await identifyPermissionProject(projectRoot);
67
69
  if (!project.ok)
68
70
  return project;
71
+ if (process.getuid === undefined)
72
+ return err(new ProjectPermissionStoreError("not_owner_only"));
73
+ let handle;
69
74
  try {
70
- const metadata = await stat(path);
71
- if ((metadata.mode & 0o077) !== 0)
72
- return err(new ProjectPermissionStoreError("not_owner_only"));
73
- const encoded = await readFile(path, "utf8");
75
+ const opened = await openPrivateStore(path);
76
+ if (!opened.ok)
77
+ return opened;
78
+ if (opened.value === null)
79
+ return ok(null);
80
+ handle = opened.value;
81
+ const encoded = await handle.readFile("utf8");
74
82
  let decoded;
75
83
  try {
76
84
  decoded = JSON.parse(encoded);
@@ -90,6 +98,9 @@ export const readProjectPermissionStore = async (path, projectRoot) => {
90
98
  return ok(null);
91
99
  return err(new ProjectPermissionStoreError("io", { cause }));
92
100
  }
101
+ finally {
102
+ await handle?.close().catch(() => undefined);
103
+ }
93
104
  };
94
105
  /** Atomically replace explicit project grants with owner-only permissions. */
95
106
  export const writeProjectPermissionStore = async (path, projectRoot, grants) => {
@@ -104,16 +115,20 @@ export const writeProjectPermissionStore = async (path, projectRoot, grants) =>
104
115
  });
105
116
  if (!candidate.success)
106
117
  return err(new ProjectPermissionStoreError("invalid", { cause: candidate.error }));
118
+ if (process.getuid === undefined)
119
+ return err(new ProjectPermissionStoreError("not_owner_only"));
107
120
  try {
108
121
  await mkdir(dirname(path), { recursive: true, mode: 0o700 });
109
122
  await writeFileAtomic(path, `${JSON.stringify(candidate.data, null, 2)}\n`, {
110
123
  encoding: "utf8",
111
124
  mode: 0o600,
112
125
  });
113
- const metadata = await stat(path);
114
- if ((metadata.mode & 0o077) !== 0) {
115
- return err(new ProjectPermissionStoreError("not_owner_only"));
116
- }
126
+ const verified = await readProjectPermissionStore(path, projectRoot);
127
+ if (!verified.ok)
128
+ return verified;
129
+ if (verified.value === null ||
130
+ JSON.stringify(verified.value) !== JSON.stringify(candidate.data))
131
+ return err(new ProjectPermissionStoreError("invalid"));
117
132
  return ok(candidate.data);
118
133
  }
119
134
  catch (cause) {
@@ -209,7 +224,31 @@ const releasePermissionStoreLock = async (lock) => {
209
224
  await unlink(lock.path).catch(() => undefined);
210
225
  };
211
226
  const isNotFound = (cause) => errorCode(cause) === "ENOENT";
227
+ const openPrivateStore = async (path) => {
228
+ let handle;
229
+ try {
230
+ handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
231
+ const metadata = await handle.stat();
232
+ if (!privateRegularFile(metadata)) {
233
+ await handle.close();
234
+ return err(new ProjectPermissionStoreError("not_owner_only"));
235
+ }
236
+ return ok(handle);
237
+ }
238
+ catch (cause) {
239
+ await handle?.close().catch(() => undefined);
240
+ if (isNotFound(cause))
241
+ return ok(null);
242
+ return err(new ProjectPermissionStoreError(isSymlinkRefusal(cause) ? "not_owner_only" : "io", { cause }));
243
+ }
244
+ };
245
+ const privateRegularFile = (metadata) => metadata.isFile() &&
246
+ !metadata.isSymbolicLink() &&
247
+ (Number(metadata.mode) & 0o077) === 0 &&
248
+ process.getuid !== undefined &&
249
+ metadata.uid === process.getuid();
212
250
  const isAlreadyExists = (cause) => errorCode(cause) === "EEXIST";
213
251
  const errorCode = (cause) => typeof cause === "object" && cause !== null && "code" in cause
214
252
  ? cause.code
215
253
  : undefined;
254
+ const isSymlinkRefusal = (cause) => errorCode(cause) === "ELOOP" || errorCode(cause) === "EMLINK";