rea-agents 2.0.0 → 2.2.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 (273) hide show
  1. package/README.md +103 -38
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/AndroidApplicationService.js +17 -0
  4. package/dist/application/AppleApplicationService.js +17 -0
  5. package/dist/application/ArtifactExtraction.js +1 -1
  6. package/dist/application/ArtifactInventory/classify.js +42 -0
  7. package/dist/application/ArtifactInventory/hash.js +43 -0
  8. package/dist/application/ArtifactInventory/reader.js +41 -0
  9. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  10. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  11. package/dist/application/ArtifactInventory/types.js +10 -0
  12. package/dist/application/ArtifactInventory.js +9 -351
  13. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  14. package/dist/application/BinarySession.js +47 -270
  15. package/dist/application/BinarySessionExecution.js +28 -0
  16. package/dist/application/BinarySessionOpen.js +39 -0
  17. package/dist/application/BinarySessionRecords.js +149 -0
  18. package/dist/application/BinarySessionStatus.js +75 -0
  19. package/dist/application/CapabilityInventory.js +24 -2
  20. package/dist/application/ClientRegistrationStatus.js +11 -4
  21. package/dist/application/CrossVersionInventory.js +8 -2
  22. package/dist/application/CrossVersionInvestigation.js +1 -23
  23. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  24. package/dist/application/DirectAnalysis.js +47 -44
  25. package/dist/application/Doctor.js +156 -238
  26. package/dist/application/DoctorDiagnostics.js +180 -0
  27. package/dist/application/ElectronBoundaryAnalysis.js +23 -7
  28. package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
  29. package/dist/application/EnhancedToolPagination.js +22 -0
  30. package/dist/application/EnhancedToolTypes.js +4 -0
  31. package/dist/application/EnhancedTools.js +20 -37
  32. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  33. package/dist/application/InventoryProjectionEvidence.js +41 -0
  34. package/dist/application/InvestigationProviders.js +12 -0
  35. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  36. package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
  37. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  38. package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
  39. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  40. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  41. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  42. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  43. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  44. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  45. package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
  46. package/dist/application/JavaScriptJsonModules.js +39 -0
  47. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  48. package/dist/application/JavaScriptReplayPermission.js +16 -0
  49. package/dist/application/JavaScriptReplayService.js +182 -156
  50. package/dist/application/LinuxHopper.js +10 -0
  51. package/dist/application/MacHopper.js +22 -11
  52. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  53. package/dist/application/PermissionAuthority.js +106 -63
  54. package/dist/application/ProcessCaptureCapability.js +1 -1
  55. package/dist/application/ProcessCapturePermission.js +13 -0
  56. package/dist/application/ProcessCli.js +2 -12
  57. package/dist/application/ProcessHarness.js +1 -1
  58. package/dist/application/ProcessPairedExperiment.js +72 -0
  59. package/dist/application/ProjectPermissionStore.js +48 -9
  60. package/dist/application/RealHopperAssertions.js +76 -0
  61. package/dist/application/ReconstructionCoverageService.js +69 -0
  62. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  63. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  64. package/dist/application/Setup.js +148 -260
  65. package/dist/application/SetupClientConfiguration.js +224 -0
  66. package/dist/application/SetupClients.js +24 -2
  67. package/dist/application/SetupPlan.js +124 -12
  68. package/dist/application/SetupSkill.js +54 -36
  69. package/dist/application/SupportedClients.js +1 -0
  70. package/dist/application/Uninstall.js +12 -3
  71. package/dist/application/Upgrade.js +16 -1
  72. package/dist/application/WindowsCapabilities.js +86 -0
  73. package/dist/artifacts/AsarArtifactReader.js +11 -0
  74. package/dist/browser/BrowserProcessStartup.js +88 -0
  75. package/dist/browser/CdpCaptureDocuments.js +12 -8
  76. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  77. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  78. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  79. package/dist/browser/CdpCaptureEventState.js +1 -0
  80. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  81. package/dist/browser/CdpCaptureEvents.js +29 -496
  82. package/dist/browser/CdpObservationSession.js +51 -8
  83. package/dist/browser/CdpPageCapture.js +12 -133
  84. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  85. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  86. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  87. package/dist/browser/PngVisualDiff.js +9 -2
  88. package/dist/catalogIdentity.js +4 -0
  89. package/dist/cli/artifactCommands.js +82 -0
  90. package/dist/cli/coreAnalysisCommands.js +232 -0
  91. package/dist/cli/managedCommands.js +397 -0
  92. package/dist/cli/managedProjectGraphCommand.js +24 -0
  93. package/dist/cli/options.js +9 -0
  94. package/dist/cli/setupCommands.js +113 -0
  95. package/dist/cli/types.js +1 -0
  96. package/dist/cli/utilityCommands.js +122 -0
  97. package/dist/cli.js +10 -768
  98. package/dist/cliApplicationCommands.js +118 -4
  99. package/dist/cliBrowserAdvancedCommands.js +73 -16
  100. package/dist/cliBrowserCommands.js +39 -115
  101. package/dist/cliCommandNames.js +4 -0
  102. package/dist/cliElectronCommands.js +33 -62
  103. package/dist/cliEvidenceCommands.js +13 -2
  104. package/dist/cliInvestigationCommands.js +53 -10
  105. package/dist/cliObservationOptions.js +137 -0
  106. package/dist/cliPolicyCommands.js +80 -46
  107. package/dist/cliSetup.js +306 -0
  108. package/dist/config/environment.js +107 -0
  109. package/dist/config/parseConfig.js +228 -0
  110. package/dist/config/parsers.js +68 -0
  111. package/dist/config/permissions.js +30 -0
  112. package/dist/config/types.js +1 -0
  113. package/dist/config.js +1 -375
  114. package/dist/contracts/applicationToolContracts.js +134 -0
  115. package/dist/contracts/electronToolContracts.js +51 -3
  116. package/dist/contracts/managedToolContracts.js +8 -3
  117. package/dist/contracts/managedWorkflowToolContracts.js +3 -3
  118. package/dist/contracts/providerSelection.js +2 -0
  119. package/dist/contracts/sessionStatusContract.js +26 -0
  120. package/dist/contracts/toolContractTypes.js +12 -1
  121. package/dist/contracts/toolContracts.js +5 -12
  122. package/dist/contracts/toolEffects.js +14 -0
  123. package/dist/contracts/toolOutputSchemaGroups.js +274 -0
  124. package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
  125. package/dist/contracts/toolOutputSchemas.js +2 -550
  126. package/dist/domain/analysisErrorPresentation.js +234 -0
  127. package/dist/domain/analysisErrorProjection.js +284 -0
  128. package/dist/domain/androidApplication.js +206 -0
  129. package/dist/domain/appleApplication.js +221 -0
  130. package/dist/domain/artifactComparison.js +34 -15
  131. package/dist/domain/binaryTarget.js +87 -37
  132. package/dist/domain/boundedCartesianProjection.js +15 -0
  133. package/dist/domain/browserObservation.js +1 -285
  134. package/dist/domain/browserObservationSchemas.js +286 -0
  135. package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
  136. package/dist/domain/errors.js +4 -486
  137. package/dist/domain/functionComparison.js +4 -165
  138. package/dist/domain/functionComparisonDimensions.js +164 -0
  139. package/dist/domain/javascriptApplicationVersionItems.js +18 -6
  140. package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
  141. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  142. package/dist/domain/javascriptFeatureTraversal.js +19 -7
  143. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  145. package/dist/domain/javascriptSemanticIr.js +30 -0
  146. package/dist/domain/javascriptSemanticProjection.js +366 -0
  147. package/dist/domain/javascriptSemanticState.js +40 -0
  148. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  149. package/dist/domain/javascriptSemanticValues.js +352 -0
  150. package/dist/domain/javascriptStaticAnalysis.js +5 -221
  151. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  152. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  153. package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
  154. package/dist/domain/knownPageCoverage.js +21 -0
  155. package/dist/domain/managedApplicationGraph.js +14 -364
  156. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  157. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  158. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  159. package/dist/domain/managedArtifact.js +16 -8
  160. package/dist/domain/managedMemberComparison.js +37 -391
  161. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  162. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  163. package/dist/domain/managedNativeVerification.js +27 -389
  164. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  165. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  166. package/dist/domain/managedReconstruction.js +82 -69
  167. package/dist/domain/managedRuntimeCorrelation.js +12 -3
  168. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  169. package/dist/domain/permissionPolicy.js +2 -0
  170. package/dist/domain/processComparison.js +19 -0
  171. package/dist/domain/processPairedExperiment.js +128 -0
  172. package/dist/domain/reconstructionCoverage.js +387 -0
  173. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  174. package/dist/domain/reconstructionVerificationSchemas.js +8 -0
  175. package/dist/domain/replayMachine.js +316 -0
  176. package/dist/domain/replayMachineRuntime.js +215 -0
  177. package/dist/domain/runtimeCharacterization.js +85 -0
  178. package/dist/domain/runtimeIdentification.js +210 -0
  179. package/dist/domain/webBundleAnalyzer.js +3 -409
  180. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  181. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  182. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  183. package/dist/dotnet/ManagedArtifactInspector.js +50 -29
  184. package/dist/dotnet/ManagedMemberInspector.js +51 -785
  185. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  186. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  187. package/dist/dotnet/ManagedMemberRows.js +120 -0
  188. package/dist/dotnet/ManagedMetadataInventory.js +42 -306
  189. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  190. package/dist/dotnet/ManagedMetadataLayout.js +48 -111
  191. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  192. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  193. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  194. package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
  195. package/dist/dotnet/ManagedPeReader.js +59 -28
  196. package/dist/evaluation/CodexAgentEval.js +132 -0
  197. package/dist/generatedPackageMetadata.js +2 -2
  198. package/dist/ghidra/GhidraClient.js +76 -91
  199. package/dist/ghidra/GhidraClientStartup.js +29 -0
  200. package/dist/ghidra/GhidraClientWire.js +34 -0
  201. package/dist/ghidra/GhidraDefaults.js +1 -1
  202. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  203. package/dist/ghidra/GhidraInstallation.js +20 -15
  204. package/dist/ghidra/GhidraLauncher.js +93 -29
  205. package/dist/ghidra/GhidraProvider.js +61 -161
  206. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  207. package/dist/ghidra/GhidraSessionValues.js +2 -0
  208. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  209. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  210. package/dist/ghidra/GhidraTransport.js +71 -0
  211. package/dist/identity.js +4 -2
  212. package/dist/main/messages.js +5 -0
  213. package/dist/main/reload.js +49 -0
  214. package/dist/main/shutdown.js +25 -0
  215. package/dist/main/startup.js +14 -0
  216. package/dist/main/state.js +26 -0
  217. package/dist/main/transport.js +61 -0
  218. package/dist/main/types.js +1 -0
  219. package/dist/main.js +35 -146
  220. package/dist/process/PrivateRuntimeRoot.js +3 -2
  221. package/dist/process/ProcessOwnership.js +35 -0
  222. package/dist/process/ProviderProcess.js +2 -0
  223. package/dist/replay/JavaScriptReplayWorker.js +64 -50
  224. package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
  225. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  226. package/dist/replay/ReplayOutcome.js +95 -0
  227. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  228. package/dist/server/ProcessCaptureElicitation.js +157 -0
  229. package/dist/server/createServer.js +124 -56
  230. package/dist/server/javascriptApplicationResult.js +85 -0
  231. package/dist/server/registerApplicationTools/characterization.js +57 -0
  232. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  233. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  234. package/dist/server/registerApplicationTools/coverage.js +84 -0
  235. package/dist/server/registerApplicationTools/helpers.js +42 -0
  236. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  237. package/dist/server/registerApplicationTools/types.js +1 -0
  238. package/dist/server/registerApplicationTools.js +10 -112
  239. package/dist/server/registerBrowserTools.js +59 -83
  240. package/dist/server/registerElectronTools.js +48 -49
  241. package/dist/server/registerEnhancedTools.js +34 -42
  242. package/dist/server/registerEvidenceResources.js +56 -0
  243. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  244. package/dist/server/registerInvestigationTools/types.js +1 -0
  245. package/dist/server/registerInvestigationTools.js +83 -116
  246. package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
  247. package/dist/server/registerManagedTools.js +18 -1
  248. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  249. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  250. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  251. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  252. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  253. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  254. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  255. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  256. package/dist/server/registerManagedWorkflowTools.js +10 -274
  257. package/dist/server/registerOfficialTools.js +73 -59
  258. package/dist/server/registerSessionRecordTools.js +137 -0
  259. package/dist/server/registerSessionStatusTool.js +97 -26
  260. package/dist/server/registerSessionTools.js +60 -164
  261. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  262. package/dist/server/toolRegistrationOptions.js +101 -3
  263. package/dist/server/toolResult.js +15 -4
  264. package/package.json +11 -11
  265. package/skills/reverse-engineer-anything/SKILL.md +76 -0
  266. package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
  267. package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
  268. package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
  269. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
  270. package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
  271. package/scripts/prepare-node-pty.mjs +0 -35
  272. package/scripts/rebuild-native.mjs +0 -22
  273. package/skills/rea-analysis/SKILL.md +0 -222
@@ -0,0 +1,86 @@
1
+ import { constants } from "node:fs";
2
+ import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { probeProcessCaptureCapability, } from "./ProcessCaptureCapability.js";
6
+ /** Probe named prerequisites without converting missing controls into support. */
7
+ export const probeWindowsCapabilities = async (dependencies = systemDependencies()) => {
8
+ const [symlinkCreation, pty] = await Promise.all([
9
+ dependencies.probeSymlinkCreation(),
10
+ dependencies.probePty(),
11
+ ]);
12
+ return {
13
+ platform: dependencies.platform,
14
+ architecture: dependencies.architecture,
15
+ capabilities: {
16
+ symlink_creation: symlinkCreation,
17
+ no_follow_open: dependencies.probeNoFollowOpen(),
18
+ private_acl: dependencies.probePrivateAcl(),
19
+ unix_domain_socket: dependencies.probeUnixDomainSocket(),
20
+ pty: {
21
+ available: pty.available,
22
+ reason: pty.available ? null : pty.reason,
23
+ },
24
+ },
25
+ };
26
+ };
27
+ const systemDependencies = () => ({
28
+ platform: process.platform,
29
+ architecture: process.arch,
30
+ probeSymlinkCreation,
31
+ probeNoFollowOpen: () => systemNoFollowOpenCapability(process.platform, constants.O_NOFOLLOW),
32
+ probePrivateAcl: () => ({
33
+ available: false,
34
+ reason: "Windows ACL enforcement is not implemented by this REA build",
35
+ }),
36
+ probeUnixDomainSocket: () => process.platform === "win32"
37
+ ? {
38
+ available: false,
39
+ reason: "Node path-based IPC uses named pipes on Windows",
40
+ }
41
+ : { available: true, reason: null },
42
+ probePty: probeProcessCaptureCapability,
43
+ });
44
+ /** Report pathname no-follow support without treating it as Windows authority. */
45
+ export const systemNoFollowOpenCapability = (platform, noFollow) => {
46
+ if (platform === "win32")
47
+ return {
48
+ available: false,
49
+ reason: "Windows reparse-safe handle admission is not implemented",
50
+ };
51
+ return typeof noFollow === "number"
52
+ ? { available: true, reason: null }
53
+ : {
54
+ available: false,
55
+ reason: "O_NOFOLLOW is unavailable in this Node runtime",
56
+ };
57
+ };
58
+ const probeSymlinkCreation = async () => {
59
+ const root = await mkdtemp(join(tmpdir(), "rea-symlink-probe-"));
60
+ const target = join(root, "target");
61
+ const link = join(root, "link");
62
+ try {
63
+ await writeFile(target, "probe");
64
+ await symlink(target, link, "file");
65
+ return { available: true, reason: null };
66
+ }
67
+ catch (cause) {
68
+ return {
69
+ available: false,
70
+ reason: cause instanceof Error &&
71
+ "code" in cause &&
72
+ typeof cause.code === "string"
73
+ ? cause.code
74
+ : "symlink creation failed",
75
+ };
76
+ }
77
+ finally {
78
+ try {
79
+ await rm(root, { recursive: true, force: true });
80
+ }
81
+ catch {
82
+ // Cleanup failure must not replace the machine-readable probe outcome.
83
+ // The probe root lives beneath the OS temporary directory and is unique.
84
+ }
85
+ }
86
+ };
@@ -70,6 +70,13 @@ export class AsarArtifactReader {
70
70
  return Promise.resolve(Readable.from(extractFile(this.path, entry.adapterKey, false)));
71
71
  }
72
72
  catch (cause) {
73
+ if (entry.unpacked && isMissingFile(cause))
74
+ return Promise.reject(new ArtifactReaderFailure("unavailable", `ASAR unpacked entry bytes are unavailable: ${entry.path}`, { cause }, {
75
+ logicalPath: entry.path,
76
+ declaredSha256: entry.declaredSha256,
77
+ calculatedSha256: null,
78
+ unpacked: true,
79
+ }));
73
80
  return Promise.reject(asarFailure(this.path, `read ${entry.path}`, cause));
74
81
  }
75
82
  }
@@ -87,3 +94,7 @@ const abortIfNeeded = (signal) => {
87
94
  const asarFailure = (path, operation, cause) => cause instanceof ArtifactReaderFailure
88
95
  ? cause
89
96
  : new ArtifactReaderFailure("format", `Malformed or unreadable ASAR during ${operation}: ${path}`, { cause });
97
+ const isMissingFile = (cause) => typeof cause === "object" &&
98
+ cause !== null &&
99
+ "code" in cause &&
100
+ cause.code === "ENOENT";
@@ -0,0 +1,88 @@
1
+ import { readFile } from "node:fs/promises";
2
+ /** Safe diagnostics for a browser process that did not publish a CDP port. */
3
+ export class BrowserStartupError extends Error {
4
+ _tag = "BrowserStartupError";
5
+ failure;
6
+ executable;
7
+ elapsedMs;
8
+ exitCode;
9
+ signalCode;
10
+ stderr;
11
+ constructor(diagnostics, options) {
12
+ super(browserStartupMessage(diagnostics.failure, diagnostics), options);
13
+ this.failure = diagnostics.failure;
14
+ this.executable = diagnostics.executable;
15
+ this.elapsedMs = diagnostics.elapsedMs;
16
+ this.exitCode = diagnostics.exitCode;
17
+ this.signalCode = diagnostics.signalCode;
18
+ this.stderr = diagnostics.stderr;
19
+ }
20
+ }
21
+ /** Wait for Chrome's valid DevToolsActivePort or a terminal process outcome. */
22
+ export const waitForBrowserDevtoolsPort = async (options) => {
23
+ const timeoutMs = options.timeoutMs ?? 15_000;
24
+ const pollIntervalMs = options.pollIntervalMs ?? 25;
25
+ const startedAt = performance.now();
26
+ const terminal = childTermination(options, startedAt);
27
+ const deadline = startedAt + timeoutMs;
28
+ while (true) {
29
+ const terminalState = currentTerminalState(options.child);
30
+ if (terminalState !== null)
31
+ throw startupError(options, startedAt, terminalState);
32
+ const port = await readDevtoolsPort(options.activePortPath);
33
+ if (port !== null)
34
+ return port;
35
+ const remainingMs = deadline - performance.now();
36
+ if (remainingMs <= 0)
37
+ throw startupError(options, startedAt, { failure: "timeout" });
38
+ const outcome = await Promise.race([
39
+ terminal,
40
+ delay(Math.min(pollIntervalMs, remainingMs)).then(() => null),
41
+ ]);
42
+ if (outcome !== null)
43
+ throw outcome;
44
+ }
45
+ };
46
+ const childTermination = (options, startedAt) => new Promise((resolve) => {
47
+ options.child.once("error", (cause) => {
48
+ resolve(startupError(options, startedAt, {
49
+ failure: "spawn-error",
50
+ cause,
51
+ }));
52
+ });
53
+ options.child.once("exit", () => {
54
+ resolve(startupError(options, startedAt, options.child.signalCode === null
55
+ ? { failure: "exited" }
56
+ : { failure: "signalled" }));
57
+ });
58
+ });
59
+ const currentTerminalState = (child) => {
60
+ if (child.signalCode !== null)
61
+ return { failure: "signalled" };
62
+ if (child.exitCode !== null)
63
+ return { failure: "exited" };
64
+ return null;
65
+ };
66
+ const startupError = (options, startedAt, state) => new BrowserStartupError({
67
+ failure: state.failure,
68
+ executable: options.executable,
69
+ elapsedMs: Math.max(0, Math.round(performance.now() - startedAt)),
70
+ exitCode: options.child.exitCode,
71
+ signalCode: options.child.signalCode,
72
+ stderr: boundedStderr(options.stderr()),
73
+ }, state.failure === "spawn-error" ? { cause: state.cause } : undefined);
74
+ const readDevtoolsPort = async (path) => {
75
+ try {
76
+ const firstLine = (await readFile(path, "utf8")).trim().split("\n")[0];
77
+ const port = Number(firstLine);
78
+ return Number.isSafeInteger(port) && port > 0 && port <= 65_535
79
+ ? port
80
+ : null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ };
86
+ const boundedStderr = (stderr) => stderr.length <= 65_536 ? stderr : stderr.slice(-65_536);
87
+ const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
88
+ const browserStartupMessage = (failure, diagnostics) => `Chrome CDP startup ${failure}; executable=${diagnostics.executable}; elapsed_ms=${String(diagnostics.elapsedMs)}; exit_code=${diagnostics.exitCode === null ? "null" : String(diagnostics.exitCode)}; signal=${diagnostics.signalCode ?? "null"}; stderr=${diagnostics.stderr.length === 0 ? "<empty>" : diagnostics.stderr}`;
@@ -1,4 +1,5 @@
1
- import { allowedSanitizedUrl, boundedText, isHttpUrl, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
1
+ import { allowedSanitizedUrl, boundedText, numberValue, recordValue, recordsValue, requiredRecord, stringValue, } from "./CdpCaptureValues.js";
2
+ import { exclusionReasonForUrl } from "./CdpCaptureEventHelpers.js";
2
3
  /** Normalize allowed frames while retaining a bounded prefix and total count. */
3
4
  export const captureFrames = (result, allowedOrigins, maximum, completeness) => {
4
5
  const root = recordValue(requiredRecord(result).frameTree);
@@ -121,7 +122,14 @@ export const captureDom = (result, allowedOrigins, input, completeness) => {
121
122
  .map((value) => indexedString(strings, value).slice(0, 256))
122
123
  .slice(0, 200),
123
124
  });
124
- const metadata = domMetadata(strings, attributeIndexes, documentUrl, nodeIndex, nodeName, allowedOrigins);
125
+ const metadata = domMetadata({
126
+ strings,
127
+ attributes: attributeIndexes,
128
+ documentUrl,
129
+ nodeIndex,
130
+ nodeName,
131
+ allowedOrigins,
132
+ });
125
133
  for (const url of metadata.urls) {
126
134
  if (urls.length >= input.limits.max_resources) {
127
135
  completeness?.truncate("metadata");
@@ -218,7 +226,8 @@ const numberArray = (value) => Array.isArray(value)
218
226
  return number === undefined ? [] : [number];
219
227
  })
220
228
  : [];
221
- const domMetadata = (strings, attributes, documentUrl, nodeIndex, nodeName, allowedOrigins) => {
229
+ const domMetadata = (options) => {
230
+ const { strings, attributes, documentUrl, nodeIndex, nodeName, allowedOrigins, } = options;
222
231
  const pairs = new Map();
223
232
  for (let index = 0; index + 1 < attributes.length; index += 2) {
224
233
  const name = indexedString(strings, attributes[index]).toLowerCase();
@@ -284,11 +293,6 @@ const boundedUtf8 = (value, maximumBytes) => {
284
293
  }
285
294
  return { text, bytes, truncated: true };
286
295
  };
287
- const exclusionReasonForUrl = (value) => value === undefined || value === ""
288
- ? "unattributed_origin"
289
- : isHttpUrl(value)
290
- ? "disallowed_origin"
291
- : "unsupported_url";
292
296
  const domUrlAttributes = [
293
297
  "href",
294
298
  "src",
@@ -0,0 +1,75 @@
1
+ import { inferJsonShape } from "../domain/jsonShape.js";
2
+ import { recordValue, stringValue, } from "./CdpCaptureValues.js";
3
+ import { decodeBase64, isJsonContentType } from "./CdpCaptureEventHelpers.js";
4
+ export const ingestResponseBodyShape = (state, requestId, value) => {
5
+ const result = recordValue(value);
6
+ const body = stringValue(result?.body);
7
+ if (body === undefined) {
8
+ invalidResponseBodyShape(state, requestId);
9
+ return;
10
+ }
11
+ const decoded = result?.base64Encoded === true
12
+ ? decodeBase64(body)?.toString("utf8")
13
+ : body;
14
+ if (decoded === undefined) {
15
+ invalidResponseBodyShape(state, requestId);
16
+ return;
17
+ }
18
+ const inferred = inferBodyShape(state, decoded);
19
+ updateResponseBodyShape(state, requestId, inferred.shape, inferred.truncated);
20
+ };
21
+ export const requestBodyShape = (state, request) => {
22
+ if (!state.input.include_json_body_shapes)
23
+ return { status: "not_approved", request: null, response: null };
24
+ const body = stringValue(request.postData);
25
+ if (!isJsonContentType(recordValue(request.headers)) || body === undefined)
26
+ return { status: "unavailable", request: null, response: null };
27
+ const inferred = inferBodyShape(state, body);
28
+ return {
29
+ status: inferred.truncated
30
+ ? "truncated"
31
+ : inferred.shape === null
32
+ ? "unavailable"
33
+ : "included",
34
+ request: inferred.shape,
35
+ response: null,
36
+ };
37
+ };
38
+ export const updateResponseBodyShape = (state, requestId, response, truncated) => {
39
+ const current = state.network.get(requestId);
40
+ if (current === undefined)
41
+ return;
42
+ const request = current.body_shapes.request;
43
+ const status = truncated || current.body_shapes.status === "truncated"
44
+ ? "truncated"
45
+ : response !== null
46
+ ? "included"
47
+ : request !== null
48
+ ? "partial"
49
+ : "unavailable";
50
+ state.network.set(requestId, {
51
+ ...current,
52
+ body_shapes: { status, request, response },
53
+ });
54
+ };
55
+ const inferBodyShape = (state, text) => {
56
+ const bytes = Buffer.byteLength(text);
57
+ const remaining = state.input.limits.max_total_json_body_bytes - state.jsonBodyBytes;
58
+ if (bytes > state.input.limits.max_json_body_bytes || bytes > remaining) {
59
+ state.completeness.truncate("json_body_shapes");
60
+ return { shape: null, truncated: true };
61
+ }
62
+ state.jsonBodyBytes += bytes;
63
+ const shape = inferJsonShape(text, {
64
+ maximumBytes: state.input.limits.max_json_body_bytes,
65
+ maximumNodes: state.input.limits.max_json_shape_nodes,
66
+ maximumDepth: state.input.limits.max_json_shape_depth,
67
+ });
68
+ if (shape?.truncated === true)
69
+ state.completeness.truncate("json_body_shapes");
70
+ return { shape, truncated: shape?.truncated === true };
71
+ };
72
+ const invalidResponseBodyShape = (state, requestId) => {
73
+ updateResponseBodyShape(state, requestId, null, false);
74
+ state.completeness.exclude("json_body_shapes", "invalid_protocol_value");
75
+ };
@@ -0,0 +1,336 @@
1
+ import { inferJsonShape } from "../domain/jsonShape.js";
2
+ import { safeResponseMetadata } from "./CdpSafeMetadata.js";
3
+ import { boundedSensitiveText } from "./SensitiveTextCapture.js";
4
+ import { allowedSanitizedUrl, boundedText, isHttpUrl, numberValue, recordValue, recordsValue, stringValue, } from "./CdpCaptureValues.js";
5
+ import { requestBodyShape } from "./CdpCaptureEventBodyShapes.js";
6
+ import { consolePrimitive, decodeBase64, exclusionReasonForUrl, executionContextKey, firstCallFrame, initiatorLocation, integerOrNull, } from "./CdpCaptureEventHelpers.js";
7
+ export const handleExecutionContextCreated = (state, params) => {
8
+ const context = recordValue(params.context);
9
+ const identifier = numberValue(context?.id);
10
+ const frameId = stringValue(recordValue(context?.auxData)?.frameId);
11
+ if (identifier === undefined ||
12
+ !Number.isSafeInteger(identifier) ||
13
+ frameId === undefined ||
14
+ frameId.length > 256)
15
+ return;
16
+ const key = String(identifier);
17
+ if (state.executionContextFrames.size >= state.input.limits.max_scripts &&
18
+ !state.executionContextFrames.has(key)) {
19
+ state.completeness.truncate("scripts");
20
+ return;
21
+ }
22
+ state.executionContextFrames.set(key, frameId);
23
+ };
24
+ export const handleExecutionContextDestroyed = (state, params) => {
25
+ const key = executionContextKey(params.executionContextId);
26
+ if (key !== null)
27
+ state.executionContextFrames.delete(key);
28
+ };
29
+ export const handleScriptParsed = (state, params) => {
30
+ const scriptId = stringValue(params.scriptId);
31
+ const rawUrl = stringValue(params.url) ?? "";
32
+ const sanitized = allowedSanitizedUrl(rawUrl, state.allowedOrigins);
33
+ if (scriptId === undefined || scriptId.length > 256) {
34
+ state.completeness.exclude("scripts", "invalid_protocol_value");
35
+ return;
36
+ }
37
+ if (sanitized === undefined) {
38
+ state.completeness.exclude("scripts", exclusionReasonForUrl(rawUrl));
39
+ return;
40
+ }
41
+ if (state.scripts.size >= state.input.limits.max_scripts &&
42
+ !state.scripts.has(scriptId)) {
43
+ state.completeness.drop("scripts");
44
+ return;
45
+ }
46
+ const sourceMap = sourceMapForScript(params.sourceMapURL, rawUrl, state);
47
+ const script = {
48
+ scriptId,
49
+ rawUrl,
50
+ url: sanitized.url,
51
+ origin: sanitized.origin,
52
+ hash: (stringValue(params.hash) ?? "").slice(0, 512),
53
+ length: Math.max(0, Math.min(Number.MAX_SAFE_INTEGER, Math.trunc(numberValue(params.length) ?? 0))),
54
+ isModule: params.isModule === true,
55
+ language: boundedText(params.scriptLanguage, 100),
56
+ sourceMapUrl: sourceMap.sanitized,
57
+ sourceMapRawUrl: sourceMap.raw,
58
+ executionContextKey: executionContextKey(params.executionContextId),
59
+ };
60
+ state.scripts.set(scriptId, script);
61
+ };
62
+ const sourceMapForScript = (value, scriptUrl, state) => {
63
+ const declaredUrl = stringValue(value);
64
+ if (declaredUrl === undefined || declaredUrl === "")
65
+ return { sanitized: null, raw: null };
66
+ let rawUrl;
67
+ try {
68
+ rawUrl = new URL(declaredUrl, scriptUrl).href;
69
+ }
70
+ catch {
71
+ state.completeness.exclude("source_maps", "unsupported_url");
72
+ return { sanitized: null, raw: null };
73
+ }
74
+ const sanitized = allowedSanitizedUrl(rawUrl, state.allowedOrigins);
75
+ if (sanitized !== undefined)
76
+ return { sanitized: sanitized.url, raw: rawUrl };
77
+ state.completeness.exclude("source_maps", exclusionReasonForUrl(rawUrl));
78
+ return { sanitized: null, raw: null };
79
+ };
80
+ export const handleRequestWillBeSent = (state, params) => {
81
+ const requestId = stringValue(params.requestId);
82
+ const request = recordValue(params.request);
83
+ if (requestId === undefined || requestId.length > 256) {
84
+ state.completeness.exclude("network_requests", "invalid_protocol_value");
85
+ return;
86
+ }
87
+ if (request === undefined) {
88
+ state.completeness.exclude("network_requests", "invalid_protocol_value");
89
+ state.network.delete(requestId);
90
+ return;
91
+ }
92
+ const sanitized = allowedSanitizedUrl(request.url, state.allowedOrigins);
93
+ if (sanitized === undefined) {
94
+ state.completeness.exclude("network_requests", exclusionReasonForUrl(stringValue(request.url)));
95
+ state.network.delete(requestId);
96
+ return;
97
+ }
98
+ if (state.network.size >= state.input.limits.max_network_events &&
99
+ !state.network.has(requestId)) {
100
+ state.completeness.drop("network_requests");
101
+ return;
102
+ }
103
+ const initiator = recordValue(params.initiator);
104
+ const initiatorFrame = initiatorLocation(initiator);
105
+ const rawInitiatorUrl = stringValue(initiatorFrame?.url);
106
+ const initiatorUrl = allowedSanitizedUrl(rawInitiatorUrl, state.allowedOrigins);
107
+ if (rawInitiatorUrl !== undefined && initiatorUrl === undefined)
108
+ state.completeness.exclude("network_initiators", exclusionReasonForUrl(rawInitiatorUrl));
109
+ state.network.set(requestId, {
110
+ request_id: requestId,
111
+ url: sanitized.url,
112
+ origin: sanitized.origin ?? "",
113
+ method: (stringValue(request.method) ?? "GET").slice(0, 32),
114
+ resource_type: boundedText(params.type, 100),
115
+ status: null,
116
+ mime_type: null,
117
+ encoded_data_length: null,
118
+ initiator: {
119
+ type: (stringValue(initiator?.type) ?? "other").slice(0, 100),
120
+ url: initiatorUrl?.url ?? null,
121
+ line: integerOrNull(initiatorFrame?.lineNumber),
122
+ column: integerOrNull(initiatorFrame?.columnNumber),
123
+ },
124
+ body_shapes: requestBodyShape(state, request),
125
+ });
126
+ };
127
+ export const handleResponseReceived = (state, params) => {
128
+ const requestId = stringValue(params.requestId);
129
+ if (requestId === undefined || requestId.length > 256) {
130
+ state.completeness.exclude("network_requests", "invalid_protocol_value");
131
+ return;
132
+ }
133
+ const current = state.network.get(requestId);
134
+ const response = recordValue(params.response);
135
+ if (current === undefined)
136
+ return;
137
+ const sanitized = allowedSanitizedUrl(response?.url, state.allowedOrigins);
138
+ if (response === undefined || sanitized === undefined) {
139
+ state.completeness.exclude("network_requests", response === undefined
140
+ ? "invalid_protocol_value"
141
+ : exclusionReasonForUrl(stringValue(response.url)));
142
+ state.network.delete(requestId);
143
+ return;
144
+ }
145
+ state.network.set(requestId, {
146
+ ...current,
147
+ status: numberValue(response.status) ?? null,
148
+ mime_type: boundedText(response.mimeType, 256),
149
+ });
150
+ if (state.responseMetadata.length >= state.input.limits.max_network_events) {
151
+ state.completeness.truncate("metadata");
152
+ return;
153
+ }
154
+ const metadata = safeResponseMetadata(requestId, sanitized.url, response, state.allowedOrigins);
155
+ state.responseMetadata.push(metadata.response);
156
+ state.agentHints.push(...metadata.agentHints);
157
+ };
158
+ export const handleLoadingFinished = (state, params) => {
159
+ const requestId = stringValue(params.requestId);
160
+ if (requestId === undefined || requestId.length > 256)
161
+ return;
162
+ const current = state.network.get(requestId);
163
+ if (current === undefined)
164
+ return;
165
+ state.network.set(requestId, {
166
+ ...current,
167
+ encoded_data_length: Math.max(0, numberValue(params.encodedDataLength) ?? 0),
168
+ });
169
+ };
170
+ export const handleConsoleAPICalled = (state, params) => {
171
+ const frame = firstCallFrame(recordValue(params.stackTrace));
172
+ if (frame === undefined) {
173
+ state.completeness.exclude("console_events", "unattributed_origin");
174
+ return;
175
+ }
176
+ const source = allowedSanitizedUrl(frame.url, state.allowedOrigins);
177
+ if (source === undefined) {
178
+ state.completeness.exclude("console_events", exclusionReasonForUrl(stringValue(frame.url)));
179
+ return;
180
+ }
181
+ if (state.console.length >= state.input.limits.max_console_events) {
182
+ state.completeness.drop("console_events");
183
+ return;
184
+ }
185
+ const arguments_ = recordsValue(params.args);
186
+ state.console.push({
187
+ type: (stringValue(params.type) ?? "unknown").slice(0, 100),
188
+ timestamp: numberValue(params.timestamp) ?? 0,
189
+ argument_types: arguments_
190
+ .map((argument) => (stringValue(argument.type) ?? "unknown").slice(0, 100))
191
+ .slice(0, 100),
192
+ url: source.url,
193
+ line: integerOrNull(frame.lineNumber),
194
+ column: integerOrNull(frame.columnNumber),
195
+ text_capture: captureConsoleText(state, arguments_),
196
+ });
197
+ };
198
+ const captureConsoleText = (state, arguments_) => {
199
+ if (!state.input.include_console_text)
200
+ return {
201
+ status: "not_approved",
202
+ values: [],
203
+ retained_bytes: 0,
204
+ truncated_values: 0,
205
+ };
206
+ const values = [];
207
+ let retainedBytes = 0;
208
+ let truncatedValues = 0;
209
+ for (const [argumentIndex, argument] of arguments_.entries()) {
210
+ const primitive = consolePrimitive(argument);
211
+ if (primitive === undefined)
212
+ continue;
213
+ const remaining = state.input.limits.max_total_console_text_bytes - state.consoleTextBytes;
214
+ if (remaining <= 0) {
215
+ truncatedValues += 1;
216
+ continue;
217
+ }
218
+ const bounded = boundedSensitiveText(primitive.text, Math.min(state.input.limits.max_console_text_field_bytes, remaining));
219
+ values.push({
220
+ argument_index: argumentIndex,
221
+ type: primitive.type,
222
+ text: bounded.text,
223
+ });
224
+ retainedBytes += bounded.bytes;
225
+ state.consoleTextBytes += bounded.bytes;
226
+ if (bounded.truncated)
227
+ truncatedValues += 1;
228
+ }
229
+ if (truncatedValues > 0)
230
+ state.completeness.truncate("console_text");
231
+ return {
232
+ status: truncatedValues > 0 ? "truncated" : "included",
233
+ values,
234
+ retained_bytes: retainedBytes,
235
+ truncated_values: truncatedValues,
236
+ };
237
+ };
238
+ export const handleWebSocketFrame = (state, params, direction) => {
239
+ const requestId = stringValue(params.requestId);
240
+ if (requestId === undefined ||
241
+ requestId.length > 256 ||
242
+ (!state.network.has(requestId) && !state.allowedWebSockets.has(requestId)))
243
+ return;
244
+ if (state.websockets.length >= state.input.limits.max_websocket_events) {
245
+ state.completeness.drop("websocket_frames");
246
+ return;
247
+ }
248
+ const response = recordValue(params.response);
249
+ const payload = stringValue(response?.payloadData) ?? "";
250
+ const opcode = Math.max(0, Math.trunc(numberValue(response?.opcode) ?? 0));
251
+ const decoded = opcode === 1 ? undefined : decodeBase64(payload);
252
+ if (opcode !== 1 && decoded === undefined)
253
+ state.completeness.exclude("websocket_frames", "invalid_protocol_value");
254
+ state.websockets.push({
255
+ request_id: requestId,
256
+ direction,
257
+ opcode,
258
+ payload_bytes: opcode === 1 ? Buffer.byteLength(payload) : (decoded?.byteLength ?? 0),
259
+ payload_shape: captureWebSocketShape(state, payload, opcode),
260
+ });
261
+ };
262
+ const captureWebSocketShape = (state, payload, opcode) => {
263
+ if (!state.input.include_websocket_shapes)
264
+ return null;
265
+ if (opcode !== 1)
266
+ return { format: "binary", json_shape: null, truncated: false };
267
+ const bytes = Buffer.byteLength(payload);
268
+ const remaining = state.input.limits.max_total_websocket_shape_bytes -
269
+ state.websocketShapeBytes;
270
+ if (bytes > state.input.limits.max_websocket_shape_bytes ||
271
+ bytes > remaining) {
272
+ state.completeness.truncate("websocket_shapes");
273
+ return { format: "text", json_shape: null, truncated: true };
274
+ }
275
+ state.websocketShapeBytes += bytes;
276
+ const shape = inferJsonShape(payload, {
277
+ maximumBytes: state.input.limits.max_websocket_shape_bytes,
278
+ maximumNodes: state.input.limits.max_json_shape_nodes,
279
+ maximumDepth: state.input.limits.max_json_shape_depth,
280
+ });
281
+ if (shape?.truncated === true)
282
+ state.completeness.truncate("websocket_shapes");
283
+ return {
284
+ format: shape === null ? "text" : "json",
285
+ json_shape: shape,
286
+ truncated: shape?.truncated === true,
287
+ };
288
+ };
289
+ export const handleWebSocketCreated = (state, params) => {
290
+ const requestId = stringValue(params.requestId);
291
+ const rawUrl = stringValue(params.url);
292
+ if (requestId === undefined || requestId.length > 256) {
293
+ state.completeness.exclude("websocket_connections", "invalid_protocol_value");
294
+ return;
295
+ }
296
+ if (rawUrl === undefined) {
297
+ state.completeness.exclude("websocket_connections", "invalid_protocol_value");
298
+ return;
299
+ }
300
+ let parsed;
301
+ try {
302
+ parsed = new URL(rawUrl);
303
+ }
304
+ catch {
305
+ state.completeness.exclude("websocket_connections", "unsupported_url");
306
+ return;
307
+ }
308
+ if (parsed.protocol === "ws:")
309
+ parsed.protocol = "http:";
310
+ else if (parsed.protocol === "wss:")
311
+ parsed.protocol = "https:";
312
+ else {
313
+ state.completeness.exclude("websocket_connections", "unsupported_url");
314
+ return;
315
+ }
316
+ if (!state.allowedOrigins.has(parsed.origin)) {
317
+ state.completeness.exclude("websocket_connections", "disallowed_origin");
318
+ return;
319
+ }
320
+ if (!state.allowedWebSockets.has(requestId) &&
321
+ state.allowedWebSockets.size >= state.input.limits.max_websocket_events) {
322
+ state.completeness.drop("websocket_connections");
323
+ return;
324
+ }
325
+ state.allowedWebSockets.add(requestId);
326
+ };
327
+ export const handleFrameNavigated = (state, params) => {
328
+ const frame = recordValue(params.frame);
329
+ if (stringValue(frame?.id) !== state.mainFrameId)
330
+ return;
331
+ state.navigationDuringCapture = true;
332
+ const rawUrl = stringValue(frame?.url);
333
+ if (isHttpUrl(rawUrl) &&
334
+ allowedSanitizedUrl(rawUrl, state.allowedOrigins) === undefined)
335
+ state.originViolation = true;
336
+ };