rea-agents 1.7.0 → 2.0.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 (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -1,11 +1,16 @@
1
1
  import { z } from "zod";
2
- import { evidenceEnvelopeSchema } from "../domain/evidence.js";
3
2
  import { processCaptureComparisonSchema, processCaptureSchema, } from "../domain/processCapture.js";
4
3
  import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
5
4
  import { residualUnknownSchema } from "../domain/residualUnknown.js";
6
5
  import { functionDossierSchema, localVariableSchema, procedureClassificationSchema, procedureIdentitySchema, referenceKindSchema, } from "../domain/hopperValues.js";
7
6
  import { demangleSwiftSchema, inspectMachoSchema, inspectPlistSchema, inspectSignatureSchema, listArchitecturesSchema, } from "../domain/nativeInspection.js";
8
7
  import { artifactExtractionResultSchema, artifactInventoryResultSchema, } from "../domain/artifactGraph.js";
8
+ import { managedArtifactInspectionSchema, managedMemberInspectionSchema, managedNativeBoundaryInspectionSchema, } from "../domain/managedArtifact.js";
9
+ import { managedMemberComparisonResultSchema } from "../domain/managedMemberComparison.js";
10
+ import { managedNativeVerificationResultSchema } from "../domain/managedNativeVerification.js";
11
+ import { managedReconstructionImportResultSchema } from "../domain/managedReconstruction.js";
12
+ import { managedRuntimeCorrelationResultSchema } from "../domain/managedRuntimeCorrelation.js";
13
+ import { managedApplicationGraphResultSchema } from "../domain/managedApplicationGraph.js";
9
14
  import { artifactComparisonResultSchema } from "../domain/artifactComparison.js";
10
15
  import { functionComparisonResultSchema } from "../domain/functionComparison.js";
11
16
  import { bundleComparisonResultSchema } from "../domain/bundleComparison.js";
@@ -16,9 +21,13 @@ import { reconstructionVerificationResultSchema } from "../domain/reconstruction
16
21
  import { analysisErrorProjectionSchema } from "./errorSchemas.js";
17
22
  import { analysisProfileSchema } from "../domain/analysisProfile.js";
18
23
  import { PROVIDER_REJECTION_CODES, } from "./providerSelection.js";
19
- const resultOf = (schema) => evidenceEnvelopeSchema
20
- .omit({ normalized_result: true })
21
- .extend({ normalized_result: schema });
24
+ /** Compact MCP result with an immutable link to complete session Evidence. */
25
+ export const evidenceResultOf = (schema) => z.strictObject({
26
+ result: schema,
27
+ evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
28
+ evidence_uri: z.string().regex(/^rea:\/\/evidence\/ev_[a-f0-9]{64}$/u),
29
+ });
30
+ const resultOf = evidenceResultOf;
22
31
  const lifecycleResultOf = (schema) => z.object({ result: schema });
23
32
  /** Resolve a required named output schema or reject contract drift. */
24
33
  export const requireOutputSchema = (schemas, name) => {
@@ -154,6 +163,16 @@ const sessionProvider = z.object({
154
163
  "policy_disabled",
155
164
  ]),
156
165
  remediation: z.string().nullable(),
166
+ effects: z.strictObject({
167
+ mutatesTarget: z.boolean(),
168
+ mutatesSession: z.boolean(),
169
+ writesFilesystem: z.boolean(),
170
+ launchesProcess: z.boolean(),
171
+ accessesNetwork: z.boolean(),
172
+ changesUiState: z.boolean(),
173
+ mayDiscardData: z.boolean(),
174
+ idempotent: z.boolean(),
175
+ }),
157
176
  annotations: z.object({
158
177
  read_only: z.boolean(),
159
178
  destructive: z.boolean(),
@@ -456,6 +475,20 @@ export const artifactOutputSchemas = {
456
475
  inventory_artifact: resultOf(artifactInventoryResultSchema),
457
476
  extract_artifact: resultOf(artifactExtractionResultSchema),
458
477
  };
478
+ /** Exact Evidence v2 schema for execution-free managed static analysis. */
479
+ export const managedOutputSchemas = {
480
+ inspect_managed_artifact: resultOf(managedArtifactInspectionSchema),
481
+ inspect_managed_members: resultOf(managedMemberInspectionSchema),
482
+ inspect_managed_native_boundaries: resultOf(managedNativeBoundaryInspectionSchema),
483
+ };
484
+ /** Exact Evidence v2 schema for provider-neutral managed workflows. */
485
+ export const managedWorkflowOutputSchemas = {
486
+ compare_managed_members: resultOf(managedMemberComparisonResultSchema),
487
+ verify_managed_native_boundaries: resultOf(managedNativeVerificationResultSchema),
488
+ import_managed_reconstruction: resultOf(managedReconstructionImportResultSchema),
489
+ plan_managed_runtime_correlation: resultOf(managedRuntimeCorrelationResultSchema),
490
+ project_managed_application_graph: resultOf(managedApplicationGraphResultSchema),
491
+ };
459
492
  /** Exact structured-content schemas for target lifecycle operations. */
460
493
  export const sessionOutputSchemas = {
461
494
  open_binary: lifecycleResultOf(z.object({
@@ -497,33 +530,15 @@ export const sessionOutputSchemas = {
497
530
  imported: z.number().int().min(0),
498
531
  total: z.number().int().min(0),
499
532
  })),
500
- capture_process_scenario: lifecycleResultOf(evidenceEnvelopeSchema
501
- .omit({ normalized_result: true })
502
- .extend({ normalized_result: processCaptureSchema })),
503
- compare_process_captures: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
504
- normalized_result: processCaptureComparisonSchema,
505
- })),
506
- compare_artifacts: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
507
- normalized_result: artifactComparisonResultSchema,
508
- })),
509
- compare_functions: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
510
- normalized_result: functionComparisonResultSchema,
511
- })),
512
- compare_bundles: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
513
- normalized_result: bundleComparisonResultSchema,
514
- })),
515
- find_changed_behavior: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
516
- normalized_result: changedBehaviorResultSchema,
517
- })),
518
- build_call_path: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
519
- normalized_result: callPathResultSchema,
520
- })),
521
- correlate_static_and_runtime: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
522
- normalized_result: staticRuntimeCorrelationResultSchema,
523
- })),
524
- verify_reconstruction: lifecycleResultOf(evidenceEnvelopeSchema.omit({ normalized_result: true }).extend({
525
- normalized_result: reconstructionVerificationResultSchema,
526
- })),
533
+ capture_process_scenario: resultOf(processCaptureSchema),
534
+ compare_process_captures: resultOf(processCaptureComparisonSchema),
535
+ compare_artifacts: resultOf(artifactComparisonResultSchema),
536
+ compare_functions: resultOf(functionComparisonResultSchema),
537
+ compare_bundles: resultOf(bundleComparisonResultSchema),
538
+ find_changed_behavior: resultOf(changedBehaviorResultSchema),
539
+ build_call_path: resultOf(callPathResultSchema),
540
+ correlate_static_and_runtime: resultOf(staticRuntimeCorrelationResultSchema),
541
+ verify_reconstruction: resultOf(reconstructionVerificationResultSchema),
527
542
  list_unknowns: lifecycleResultOf(z.array(residualUnknownSchema)),
528
543
  record_unknown: lifecycleResultOf(residualUnknownSchema),
529
544
  update_unknown: lifecycleResultOf(residualUnknownSchema),
@@ -1,6 +1,47 @@
1
- import { systemDoctorHost } from "./application/Doctor.js";
1
+ import { systemDoctorHost, } from "./application/Doctor.js";
2
2
  import { inspectSystemGhidraProvider } from "./ghidra/GhidraDoctor.js";
3
+ import { parseConfig } from "./config.js";
4
+ import { SystemJavaScriptReplayHost } from "./replay/SystemJavaScriptReplayHost.js";
3
5
  /** Compose provider diagnostics at the outer CLI adapter boundary. */
4
6
  export const createSystemDoctorHost = () => systemDoctorHost({
5
7
  providerInspections: async () => [await inspectSystemGhidraProvider()],
8
+ javascriptReplayCheck: inspectJavaScriptReplay,
6
9
  });
10
+ const inspectJavaScriptReplay = async () => {
11
+ const config = parseConfig(process.env);
12
+ if (!config.ok)
13
+ return {
14
+ name: "javascript-replay",
15
+ ok: false,
16
+ classification: "config_drift",
17
+ detail: config.error.message,
18
+ remediation: "Fix the reported REA_JAVASCRIPT_REPLAY_* configuration.",
19
+ };
20
+ if (!config.value.javascriptReplayPolicy.enabled)
21
+ return {
22
+ name: "javascript-replay",
23
+ ok: true,
24
+ classification: "healthy",
25
+ detail: "disabled by default; no extracted-module execution path is admitted",
26
+ };
27
+ try {
28
+ await new SystemJavaScriptReplayHost().probe(config.value.javascriptReplayPolicy);
29
+ return {
30
+ name: "javascript-replay",
31
+ ok: true,
32
+ classification: "healthy",
33
+ detail: "Linux namespace, seccomp, and delegated cgroup probes passed",
34
+ };
35
+ }
36
+ catch (cause) {
37
+ return {
38
+ name: "javascript-replay",
39
+ ok: false,
40
+ classification: process.platform === "linux"
41
+ ? "missing_dependency"
42
+ : "unsupported_host",
43
+ detail: cause instanceof Error ? cause.message : "sandbox probe failed",
44
+ remediation: "Install and configure compatible Bubblewrap and systemd user cgroup delegation, or disable controlled replay.",
45
+ };
46
+ }
47
+ };
@@ -1,7 +1,6 @@
1
1
  import canonicalize from "canonicalize";
2
2
  import { z } from "zod";
3
3
  import {} from "./artifactGraph.js";
4
- import { evidenceSchema } from "./evidence.js";
5
4
  import { parseArtifactInventoryEvidence, } from "./artifactInventoryEvidence.js";
6
5
  const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
7
6
  const comparisonStatusSchema = z.enum([
@@ -32,9 +31,9 @@ const comparisonDimensionSchema = z.enum([
32
31
  "integrity",
33
32
  ]);
34
33
  /** Strict Evidence-backed input for deterministic artifact comparison. */
35
- export const artifactComparisonInputSchema = z.object({
36
- left: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
37
- right: z.union([evidenceSchema, z.array(evidenceSchema).min(1).max(100)]),
34
+ export const artifactComparisonInputSchema = z.strictObject({
35
+ left_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
36
+ right_evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
38
37
  offset: z.number().int().min(0).default(0),
39
38
  limit: z.number().int().min(1).max(500).default(100),
40
39
  unknown_registry_approved: z
@@ -382,6 +382,7 @@ const browserResourceReconciliationSchema = z.discriminatedUnion("status", [
382
382
  ]);
383
383
  const browserScriptSchema = z.object({
384
384
  script_key: z.string().regex(/^scr_[a-f0-9]{64}(?:_[0-9]+)?$/u),
385
+ frame_id: z.string().nullable().default(null),
385
386
  url: z.string(),
386
387
  origin: z.string().nullable(),
387
388
  cdp_hash: z.string(),
@@ -464,6 +465,8 @@ const browserWorkerSchema = z.object({
464
465
  url: z.string(),
465
466
  origin: z.string().nullable(),
466
467
  attached: z.boolean(),
468
+ opener_target_id: z.string().nullable().default(null),
469
+ parent_frame_id: z.string().nullable().default(null),
467
470
  });
468
471
  const browserCspSourceSchema = z.object({
469
472
  kind: z.enum([
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { z } from "zod";
3
- import { evidenceBundleSchema, parseEvidenceBundle, serializeEvidenceBundle, } from "./evidenceBundle.js";
3
+ import { parseEvidenceBundle, serializeEvidenceBundle, } from "./evidenceBundle.js";
4
4
  const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
5
5
  const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
6
6
  const classificationSchema = z.enum([
@@ -12,9 +12,9 @@ const classificationSchema = z.enum([
12
12
  "history_diverged",
13
13
  ]);
14
14
  /** Strict bounded input for canonical Evidence bundle comparison. */
15
- export const bundleComparisonInputSchema = z.object({
16
- left: evidenceBundleSchema,
17
- right: evidenceBundleSchema,
15
+ export const bundleComparisonInputSchema = z.strictObject({
16
+ left_bundle_path: z.string().min(1).max(4_096),
17
+ right_bundle_path: z.string().min(1).max(4_096),
18
18
  record_pairs: z
19
19
  .array(z.object({
20
20
  left_evidence_id: evidenceIdSchema,
@@ -38,6 +38,7 @@ export const inspectElectronPageInputSchema = z
38
38
  max_dom_nodes: z.number().int().min(1).max(10_000).default(2_000),
39
39
  max_scripts: z.number().int().min(1).max(2_000).default(500),
40
40
  max_resources: z.number().int().min(1).max(10_000).default(2_000),
41
+ max_workers: z.number().int().min(1).max(5_000).default(500),
41
42
  max_script_source_bytes: z
42
43
  .number()
43
44
  .int()
@@ -56,6 +57,7 @@ export const inspectElectronPageInputSchema = z
56
57
  max_dom_nodes: 2_000,
57
58
  max_scripts: 500,
58
59
  max_resources: 2_000,
60
+ max_workers: 500,
59
61
  max_script_source_bytes: 1_024 * 1_024,
60
62
  max_total_script_source_bytes: 4 * 1_024 * 1_024,
61
63
  }),
@@ -143,6 +145,7 @@ export const electronPageInspectionSchema = z.object({
143
145
  total: z.number().int().min(0),
144
146
  items: z.array(z.object({
145
147
  script_key: z.string().regex(/^electron_script_[a-f0-9]{64}$/u),
148
+ frame_id: z.string().nullable().default(null),
146
149
  file_path: z.string(),
147
150
  cdp_hash: z.string(),
148
151
  length: z.number().int().min(0),
@@ -158,5 +161,15 @@ export const electronPageInspectionSchema = z.object({
158
161
  mime_type: z.string(),
159
162
  content_size: z.number().min(0).nullable(),
160
163
  })),
164
+ workers: z
165
+ .array(z.object({
166
+ target_id: z.string().min(1).max(256),
167
+ type: z.string().min(1).max(100),
168
+ file_path: z.string(),
169
+ attached: z.boolean(),
170
+ opener_target_id: z.string().min(1).max(256).nullable(),
171
+ parent_frame_id: z.string().min(1).max(256).nullable(),
172
+ }))
173
+ .default([]),
161
174
  limitations: z.array(z.string()),
162
175
  });
@@ -0,0 +1,9 @@
1
+ import { inspectElectronBrowserNode } from "./electronStaticAnalysisBrowser.js";
2
+ import { inspectElectronIpcNode } from "./electronStaticAnalysisIpc.js";
3
+ import { inspectElectronNativeNode } from "./electronStaticAnalysisNative.js";
4
+ /** Collect Electron-specific syntax during the shared inert AST traversal. */
5
+ export const inspectElectronStaticNode = (node, context) => {
6
+ inspectElectronBrowserNode(node, context);
7
+ inspectElectronIpcNode(node, context);
8
+ inspectElectronNativeNode(node, context);
9
+ };
@@ -0,0 +1,194 @@
1
+ import * as t from "@babel/types";
2
+ import { boundedExpression, collectContextBridgeMembers, electronStaticValue, objectProperty, } from "./electronStaticAnalysisValues.js";
3
+ import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
4
+ import { argumentNode, calleeName, compareCodePoints, propertyName, range, staticPath, } from "./javascriptStaticAnalysisHelpers.js";
5
+ const MAX_WEB_PREFERENCES = 64;
6
+ /** Inspect BrowserWindow, contextBridge, and utility-process syntax. */
7
+ export const inspectElectronBrowserNode = (node, context) => {
8
+ if (t.isNewExpression(node))
9
+ inspectBrowserWindow(node, context);
10
+ if (!t.isCallExpression(node))
11
+ return;
12
+ inspectContextBridge(node, context);
13
+ inspectUtilityProcess(node, context);
14
+ };
15
+ const inspectBrowserWindow = (node, context) => {
16
+ const name = calleeName(node.callee);
17
+ if (name !== "BrowserWindow" && !name.endsWith(".BrowserWindow"))
18
+ return;
19
+ const options = argumentNode(node.arguments[0]);
20
+ const collected = collectWindowOptions(context.source, options);
21
+ accountForStructure(context, collected.unknown, collected.omitted);
22
+ const finding = {
23
+ options_status: options === undefined
24
+ ? "missing"
25
+ : t.isObjectExpression(options)
26
+ ? "object-literal"
27
+ : "dynamic",
28
+ web_preferences_status: collected.status,
29
+ web_preferences: collected.preferences,
30
+ omitted_web_preferences: collected.omitted,
31
+ preload_path: collected.preloadPath,
32
+ module_key: null,
33
+ location: range(node),
34
+ };
35
+ addLocatedFinding(context, {
36
+ collection: context.accumulator.browserWindows,
37
+ key: `electron-window\0${name}`,
38
+ node,
39
+ value: finding,
40
+ });
41
+ };
42
+ const collectWindowOptions = (source, options) => {
43
+ if (!t.isObjectExpression(options))
44
+ return {
45
+ status: options === undefined ? "missing" : "dynamic",
46
+ preferences: [],
47
+ preloadPath: null,
48
+ unknown: options === undefined ? 0 : 1,
49
+ omitted: 0,
50
+ };
51
+ const property = objectProperty(options, "webPreferences");
52
+ if (property === undefined)
53
+ return {
54
+ status: "missing",
55
+ preferences: [],
56
+ preloadPath: null,
57
+ unknown: 0,
58
+ omitted: 0,
59
+ };
60
+ if (!t.isObjectExpression(property.value))
61
+ return {
62
+ status: "dynamic",
63
+ preferences: [],
64
+ preloadPath: null,
65
+ unknown: 1,
66
+ omitted: 0,
67
+ };
68
+ return collectWebPreferences(source, property.value);
69
+ };
70
+ const collectWebPreferences = (source, object) => {
71
+ const preferences = [];
72
+ let unknown = 0;
73
+ let preloadPath = null;
74
+ for (const property of object.properties) {
75
+ if (t.isSpreadElement(property)) {
76
+ unknown += 1;
77
+ preferences.push({
78
+ name: `[spread@${String(property.start ?? -1)}]`,
79
+ value: electronStaticValue(source, property.argument),
80
+ });
81
+ continue;
82
+ }
83
+ const name = propertyName(property.key);
84
+ if (name === "" || property.computed) {
85
+ unknown += 1;
86
+ preferences.push({
87
+ name: `[dynamic@${String(property.start ?? -1)}]`,
88
+ value: electronStaticValue(source, property),
89
+ });
90
+ continue;
91
+ }
92
+ if (t.isObjectMethod(property)) {
93
+ unknown += 1;
94
+ preferences.push({
95
+ name,
96
+ value: electronStaticValue(source, property),
97
+ });
98
+ continue;
99
+ }
100
+ const path = name === "preload" ? staticPath(property.value) : undefined;
101
+ if (path !== undefined)
102
+ preloadPath = path;
103
+ const value = path === undefined
104
+ ? electronStaticValue(source, property.value)
105
+ : { status: "literal", value: path, expression: null };
106
+ if (value.status === "dynamic")
107
+ unknown += 1;
108
+ preferences.push({ name, value });
109
+ }
110
+ preferences.sort((left, right) => compareCodePoints(left.name, right.name));
111
+ return {
112
+ status: "object-literal",
113
+ preferences: preferences.slice(0, MAX_WEB_PREFERENCES),
114
+ preloadPath,
115
+ unknown,
116
+ omitted: Math.max(0, preferences.length - MAX_WEB_PREFERENCES),
117
+ };
118
+ };
119
+ const inspectContextBridge = (node, context) => {
120
+ const name = calleeName(node.callee);
121
+ const main = name === "contextBridge.exposeInMainWorld" ||
122
+ name.endsWith(".contextBridge.exposeInMainWorld");
123
+ const isolated = name === "contextBridge.exposeInIsolatedWorld" ||
124
+ name.endsWith(".contextBridge.exposeInIsolatedWorld");
125
+ if (!main && !isolated)
126
+ return;
127
+ const keyNode = argumentNode(node.arguments[isolated ? 1 : 0]);
128
+ const apiNode = argumentNode(node.arguments[isolated ? 2 : 1]);
129
+ const key = electronStaticValue(context.source, keyNode);
130
+ const api = collectContextBridgeMembers(apiNode);
131
+ const worldId = isolated
132
+ ? electronStaticValue(context.source, argumentNode(node.arguments[0]))
133
+ : null;
134
+ const unknown = (key.status === "dynamic" ? 1 : 0) +
135
+ (worldId?.status === "dynamic" ? 1 : 0) +
136
+ api.unknown;
137
+ accountForStructure(context, unknown, api.omitted);
138
+ const finding = {
139
+ world: isolated ? "isolated" : "main",
140
+ world_id: worldId,
141
+ api_key: key.status === "literal" && typeof key.value === "string"
142
+ ? key.value
143
+ : null,
144
+ api_key_expression: key.status === "dynamic" ? key.expression : null,
145
+ api_status: api.status,
146
+ members: api.members,
147
+ unknown_members: api.unknown,
148
+ omitted_members: api.omitted,
149
+ module_key: null,
150
+ location: range(node),
151
+ };
152
+ addLocatedFinding(context, {
153
+ collection: context.accumulator.contextBridgeApis,
154
+ key: `context-bridge\0${isolated ? "isolated" : "main"}\0${finding.api_key ?? finding.api_key_expression ?? "[missing]"}`,
155
+ node,
156
+ value: finding,
157
+ });
158
+ };
159
+ const inspectUtilityProcess = (node, context) => {
160
+ const name = calleeName(node.callee);
161
+ if (name !== "utilityProcess.fork" && !name.endsWith(".utilityProcess.fork"))
162
+ return;
163
+ const moduleNode = argumentNode(node.arguments[0]);
164
+ const modulePath = moduleNode === undefined ? undefined : staticPath(moduleNode);
165
+ const options = argumentNode(node.arguments[2]);
166
+ const serviceName = t.isObjectExpression(options) &&
167
+ objectProperty(options, "serviceName") !== undefined
168
+ ? staticPath(objectProperty(options, "serviceName")?.value ?? options)
169
+ : undefined;
170
+ if (modulePath === undefined)
171
+ context.accumulator.unknownFindings += 1;
172
+ const finding = {
173
+ module_path: modulePath ?? null,
174
+ module_expression: modulePath === undefined
175
+ ? boundedExpression(context.source, moduleNode)
176
+ : null,
177
+ service_name: serviceName ?? null,
178
+ module_key: null,
179
+ location: range(node),
180
+ };
181
+ addLocatedFinding(context, {
182
+ collection: context.accumulator.utilityProcesses,
183
+ key: `electron-utility\0${modulePath ?? finding.module_expression ?? "[missing]"}`,
184
+ node,
185
+ value: finding,
186
+ });
187
+ };
188
+ const accountForStructure = (context, unknown, omitted) => {
189
+ context.accumulator.unknownFindings += unknown;
190
+ if (omitted === 0)
191
+ return;
192
+ context.accumulator.structuralTruncation = true;
193
+ context.accumulator.droppedFindings += omitted;
194
+ };
@@ -0,0 +1,171 @@
1
+ import * as t from "@babel/types";
2
+ import { boundedExpression, electronStaticValue, handlerKind, } from "./electronStaticAnalysisValues.js";
3
+ import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
4
+ import { argumentNode, calleeName, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
5
+ /** Inspect Electron IPC operations and validation candidates. */
6
+ export const inspectElectronIpcNode = (node, context) => {
7
+ if (t.isCallExpression(node)) {
8
+ inspectIpcCall(node, context);
9
+ inspectValidationCall(node, context);
10
+ }
11
+ if (t.isBinaryExpression(node))
12
+ inspectValidationComparison(node, context);
13
+ };
14
+ const inspectIpcCall = (node, context) => {
15
+ const name = calleeName(node.callee);
16
+ const descriptor = ipcDescriptor(name);
17
+ if (descriptor === undefined)
18
+ return;
19
+ const channelNode = argumentNode(node.arguments[0]);
20
+ const channelValue = electronStaticValue(context.source, channelNode);
21
+ const channel = channelValue.status === "literal" && typeof channelValue.value === "string"
22
+ ? channelValue.value
23
+ : null;
24
+ const channelExpression = channel === null
25
+ ? channelValue.status === "dynamic"
26
+ ? channelValue.expression
27
+ : boundedExpression(context.source, channelNode)
28
+ : null;
29
+ const hasHandler = descriptor.mode === "listen" || descriptor.mode === "handle";
30
+ const handlerNode = hasHandler ? argumentNode(node.arguments[1]) : undefined;
31
+ if (channel === null)
32
+ context.accumulator.unknownFindings += 1;
33
+ if (hasHandler && handlerNode === undefined)
34
+ context.accumulator.unknownFindings += 1;
35
+ const finding = {
36
+ ...descriptor,
37
+ channel,
38
+ channel_expression: channelExpression,
39
+ handler_kind: hasHandler ? handlerKind(handlerNode) : null,
40
+ handler_location: handlerNode === undefined ? null : range(handlerNode),
41
+ module_key: null,
42
+ location: range(node),
43
+ };
44
+ addLocatedFinding(context, {
45
+ collection: context.accumulator.ipc,
46
+ key: `electron-ipc\0${descriptor.side}\0${descriptor.operation}\0${channel ?? channelExpression ?? "[missing]"}`,
47
+ node,
48
+ value: finding,
49
+ });
50
+ };
51
+ const inspectValidationComparison = (node, context) => {
52
+ if (!["==", "===", "!=", "!==", "in"].includes(node.operator))
53
+ return;
54
+ const left = senderSubject(node.left);
55
+ const right = senderSubject(node.right);
56
+ if (left === undefined && right === undefined)
57
+ return;
58
+ const expected = left === undefined ? node.left : node.right;
59
+ addValidation(context, node, {
60
+ subject: left ?? right ?? "sender-frame",
61
+ mechanism: `comparison:${node.operator}`,
62
+ expected: electronStaticValue(context.source, expected),
63
+ });
64
+ };
65
+ const inspectValidationCall = (node, context) => {
66
+ if (!t.isMemberExpression(node.callee) &&
67
+ !t.isOptionalMemberExpression(node.callee))
68
+ return;
69
+ const method = propertyName(node.callee.property);
70
+ if (!["startsWith", "endsWith", "includes"].includes(method))
71
+ return;
72
+ const subject = t.isNode(node.callee.object)
73
+ ? senderSubject(node.callee.object)
74
+ : undefined;
75
+ if (subject === undefined)
76
+ return;
77
+ addValidation(context, node, {
78
+ subject,
79
+ mechanism: `call:${method}`,
80
+ expected: electronStaticValue(context.source, argumentNode(node.arguments[0])),
81
+ });
82
+ };
83
+ const addValidation = (context, node, input) => {
84
+ if (input.expected.status === "dynamic")
85
+ context.accumulator.unknownFindings += 1;
86
+ const finding = {
87
+ ...input,
88
+ enforcement: "unknown",
89
+ module_key: null,
90
+ location: range(node),
91
+ };
92
+ addLocatedFinding(context, {
93
+ collection: context.accumulator.senderValidations,
94
+ key: `electron-validation\0${input.subject}\0${input.mechanism}\0${input.expected.status === "literal" ? String(input.expected.value) : input.expected.expression}`,
95
+ node,
96
+ value: finding,
97
+ });
98
+ };
99
+ const ipcDescriptor = (name) => {
100
+ const renderer = suffixOperation(name, "ipcRenderer");
101
+ if (renderer !== undefined) {
102
+ switch (renderer) {
103
+ case "send":
104
+ return { side: "renderer", operation: "send", mode: "send" };
105
+ case "sendSync":
106
+ return { side: "renderer", operation: "send-sync", mode: "send" };
107
+ case "invoke":
108
+ return { side: "renderer", operation: "invoke", mode: "invoke" };
109
+ case "postMessage":
110
+ return {
111
+ side: "renderer",
112
+ operation: "post-message",
113
+ mode: "send",
114
+ };
115
+ case "sendToHost":
116
+ return {
117
+ side: "renderer",
118
+ operation: "send-to-host",
119
+ mode: "send",
120
+ };
121
+ case "on":
122
+ return { side: "renderer", operation: "on", mode: "listen" };
123
+ case "once":
124
+ return { side: "renderer", operation: "once", mode: "listen" };
125
+ }
126
+ }
127
+ const main = suffixOperation(name, "ipcMain");
128
+ switch (main) {
129
+ case "on":
130
+ return { side: "main", operation: "on", mode: "listen" };
131
+ case "once":
132
+ return { side: "main", operation: "once", mode: "listen" };
133
+ case "handle":
134
+ return { side: "main", operation: "handle", mode: "handle" };
135
+ case "handleOnce":
136
+ return { side: "main", operation: "handle-once", mode: "handle" };
137
+ default:
138
+ return undefined;
139
+ }
140
+ };
141
+ const suffixOperation = (name, object) => {
142
+ const marker = `${object}.`;
143
+ const index = name.lastIndexOf(marker);
144
+ if (index < 0)
145
+ return undefined;
146
+ const prefix = name.slice(0, index);
147
+ if (prefix !== "" && !prefix.endsWith("."))
148
+ return undefined;
149
+ const operation = name.slice(index + marker.length);
150
+ return operation.includes(".") ? undefined : operation;
151
+ };
152
+ const senderSubject = (node) => {
153
+ const name = t.isCallExpression(node)
154
+ ? calleeName(node.callee)
155
+ : calleeName(node);
156
+ if (/(?:^|\.)sender\.getURL$/u.test(name))
157
+ return "sender-url";
158
+ if (/(?:^|\.)senderFrame\.origin$/u.test(name))
159
+ return "sender-origin";
160
+ if (/(?:^|\.)senderFrame\.url$/u.test(name))
161
+ return "sender-url";
162
+ if (/(?:^|\.)senderFrame$/u.test(name))
163
+ return "sender-frame";
164
+ if (/(?:^|\.)sender\.id$/u.test(name))
165
+ return "sender-id";
166
+ if (/(?:^|\.)frameId$/u.test(name))
167
+ return "frame-id";
168
+ if (/(?:^|\.)processId$/u.test(name))
169
+ return "process-id";
170
+ return undefined;
171
+ };