rea-agents 2.5.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -12
- package/bridge/ghidra/ReaGhidraBridge.java +477 -4
- package/bridge/hopper_bridge.py +54 -4
- package/dist/application/AnalysisContextQueries.js +142 -0
- package/dist/application/AnalysisSnapshotCache.js +4 -2
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +19 -11
- package/dist/application/ArtifactInventory/policy.js +17 -0
- package/dist/application/ArtifactInventory/reader.js +1 -4
- package/dist/application/ArtifactInventory/scanCanonical.js +0 -3
- package/dist/application/ArtifactInventory/scanReader.js +7 -7
- package/dist/application/ArtifactInventory/types.js +1 -5
- package/dist/application/BinarySession.js +19 -20
- package/dist/application/BinarySessionComposition.js +21 -0
- package/dist/application/BinarySessionRecords.js +106 -10
- package/dist/application/BinarySessionStatus.js +11 -5
- package/dist/application/BrowserEvidence.js +3 -5
- package/dist/application/CapabilityClientRequirements.js +26 -0
- package/dist/application/CapabilityInventory.js +140 -55
- package/dist/application/ClientRegistrationStatus.js +4 -1
- package/dist/application/CrossVersionInventory.js +7 -6
- package/dist/application/DirectAnalysis.js +6 -2
- package/dist/application/ElectronActiveEvidence.js +55 -0
- package/dist/application/ElectronActiveObservationPort.js +1 -0
- package/dist/application/ElectronActiveObservationService.js +81 -0
- package/dist/application/EnhancedTools.js +49 -17
- package/dist/application/EvidenceBundleCommands.js +5 -1
- package/dist/application/EvidenceLedger.js +53 -17
- package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
- package/dist/application/JavaScriptReplayPermission.js +1 -1
- package/dist/application/JavaScriptReplayService.js +23 -15
- package/dist/application/LazyAnalysisProvider.js +77 -0
- package/dist/application/LoopbackReplay.js +1 -1
- package/dist/application/ManagedRuntimeCorrelationService.js +9 -5
- package/dist/application/NativeApiInspection.js +80 -0
- package/dist/application/NodeRuntimeCharacterizationService.js +8 -9
- package/dist/application/ProcessCaptureLifecycle.js +5 -0
- package/dist/application/ProcessCli.js +2 -1
- package/dist/application/ProcessHarness.js +8 -3
- package/dist/application/ProcessNormalization.js +2 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReferenceSourceImport.js +2 -2
- package/dist/application/SessionProviderRouter.js +6 -6
- package/dist/application/SetupClientConfiguration.js +10 -3
- package/dist/application/WindowsCapabilities.js +5 -4
- package/dist/application/runtime.js +26 -9
- package/dist/artifacts/ArtifactProvider.js +8 -10
- package/dist/artifacts/SafeOutputTree.js +7 -8
- package/dist/browser/CdpCaptureStorageFingerprints.js +10 -7
- package/dist/browser/CdpElectronInspection.js +3 -1
- package/dist/browser/JavaScriptRuntimeScope.js +5 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +8 -2
- package/dist/browser/PlaywrightElectronActiveActions.js +324 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +294 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +4 -4
- package/dist/browser/PlaywrightScenarioSession.js +4 -4
- package/dist/browser/SensitiveTextCapture.js +1 -1
- package/dist/browser/V8InspectorCaptureProjection.js +1 -0
- package/dist/browser/V8InspectorProvider.js +3 -3
- package/dist/catalogIdentity.js +15 -9
- package/dist/cli/artifactCommands.js +5 -3
- package/dist/cli/coreAnalysisCommands.js +41 -0
- package/dist/cli/managedCommands.js +1 -1
- package/dist/cliApplicationCommands.js +2 -2
- package/dist/cliBrowserAdvancedCommands.js +11 -5
- package/dist/cliBrowserCommands.js +14 -7
- package/dist/cliCommandNames.js +2 -0
- package/dist/cliElectronCommands.js +46 -4
- package/dist/cliJavaScriptRuntimeCommands.js +14 -6
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/browserScenario.js +77 -0
- package/dist/config/electronAutomation.js +35 -0
- package/dist/config/environment.js +6 -0
- package/dist/config/javascriptReplay.js +41 -0
- package/dist/config/managedRuntime.js +25 -0
- package/dist/config/parseConfig.js +56 -200
- package/dist/config/parsers.js +10 -1
- package/dist/config/passiveObservation.js +157 -0
- package/dist/config/processCapture.js +47 -0
- package/dist/contracts/applicationToolContracts.js +2 -5
- package/dist/contracts/applicationWorkflowInputContracts.js +86 -96
- package/dist/contracts/artifactToolContracts.js +35 -37
- package/dist/contracts/browserToolContracts.js +2 -2
- package/dist/contracts/electronToolContracts.js +30 -1
- package/dist/contracts/enhancedInputs.js +21 -27
- package/dist/contracts/functionWorkflowToolContracts.js +29 -0
- package/dist/contracts/managedToolContracts.js +4 -9
- package/dist/contracts/managedWorkflowExamples.js +12 -9
- package/dist/contracts/managedWorkflowToolContracts.js +30 -27
- package/dist/contracts/nativeToolContracts.js +2 -3
- package/dist/contracts/promptContracts.js +14 -15
- package/dist/contracts/sessionToolSchemas.js +61 -0
- package/dist/contracts/toolContractExamples.js +4 -0
- package/dist/contracts/toolContractHelpers.js +23 -0
- package/dist/contracts/toolContracts.js +12 -67
- package/dist/contracts/toolEffects.js +19 -0
- package/dist/contracts/toolOutputSchemaGroups.js +66 -11
- package/dist/contracts/toolOutputSchemaPrimitives.js +76 -26
- package/dist/contracts/toolSchemaMetadata.js +128 -0
- package/dist/doctorRuntime.js +1 -1
- package/dist/domain/analysisErrorPresentation.js +8 -2
- package/dist/domain/analysisErrorProjection.js +1 -0
- package/dist/domain/androidApplication.js +2 -0
- package/dist/domain/appleApplication.js +2 -0
- package/dist/domain/binaryTarget.js +7 -5
- package/dist/domain/binaryTargetTypes.js +1 -0
- package/dist/domain/browserCaptureComparison.js +21 -69
- package/dist/domain/browserObservation.js +22 -9
- package/dist/domain/bytecodeProvider.js +177 -0
- package/dist/domain/callPath.js +3 -2
- package/dist/domain/conformancePackage.js +169 -0
- package/dist/domain/conformanceReplay.js +104 -0
- package/dist/domain/conformanceTrustGate.js +328 -0
- package/dist/domain/customProtocolCapture.js +137 -0
- package/dist/domain/electronActiveObservation.js +277 -0
- package/dist/domain/electronObservation.js +15 -11
- package/dist/domain/electronStaticAnalysisValues.js +4 -1
- package/dist/domain/eventProcessTree.js +217 -0
- package/dist/domain/evidenceBundle.js +47 -0
- package/dist/domain/functionComparisonNormalization.js +15 -8
- package/dist/domain/functionDossierEvidence.js +9 -4
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +68 -68
- package/dist/domain/inputIssueProjection.js +11 -2
- package/dist/domain/investigationWorkspace.js +16 -15
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +2 -2
- package/dist/domain/javascriptReplay.js +79 -63
- package/dist/domain/javascriptRuntimeObservation.js +1 -0
- package/dist/domain/javascriptRuntimeReconciliationActive.js +89 -0
- package/dist/domain/javascriptRuntimeReconciliationGraph.js +11 -10
- package/dist/domain/javascriptRuntimeReconciliationMatching.js +57 -49
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +5 -1
- package/dist/domain/javascriptRuntimeReconciliationResult.js +21 -14
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +8 -12
- package/dist/domain/javascriptSourceParser.js +15 -0
- package/dist/domain/javascriptStaticAnalysis.js +8 -12
- package/dist/domain/knownPageCoverage.js +15 -7
- package/dist/domain/mobileApplicationInvestigation.js +159 -0
- package/dist/domain/nativeApiBoundary.js +138 -0
- package/dist/domain/nodeRuntimeCharacterization.js +14 -13
- package/dist/domain/objcSwiftMetadata.js +150 -0
- package/dist/domain/packageAnalysis.js +123 -0
- package/dist/domain/peInspection.js +196 -0
- package/dist/domain/processCaptureReactiveValidation.js +8 -3
- package/dist/domain/processCaptureValidation.js +7 -3
- package/dist/domain/processComparison.js +12 -7
- package/dist/domain/processObservation.js +5 -6
- package/dist/domain/processReactiveMatching.js +14 -7
- package/dist/domain/processReactiveTransition.js +9 -3
- package/dist/domain/processScenario.js +6 -3
- package/dist/domain/processTraceComparison.js +3 -2
- package/dist/domain/protocolCapture.js +245 -0
- package/dist/domain/reconstructionReadinessSchemas.js +30 -2
- package/dist/domain/referenceSourceImportParsing.js +2 -2
- package/dist/domain/replayMachineRuntime.js +5 -3
- package/dist/domain/sourceToBundleComparison.js +2 -2
- package/dist/domain/sourceToBundleComparisonSchemas.js +8 -8
- package/dist/domain/sourceToBundleSignals.js +7 -1
- package/dist/domain/staticRuntimeCorrelation.js +7 -5
- package/dist/domain/webBundleAnalysis.js +14 -14
- package/dist/domain/webCaptureDiff.js +15 -12
- package/dist/dotnet/ManagedNativeBoundaryInspector.js +2 -3
- package/dist/dotnet/ManagedStaticProvider.js +34 -5
- package/dist/generatedMcpToolCatalog.js +37 -0
- package/dist/generatedPackageMetadata.js +4 -4
- package/dist/ghidra/GhidraClient.js +1 -1
- package/dist/ghidra/GhidraClientStartup.js +1 -1
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +44 -16
- package/dist/ghidra/GhidraInventoryValues.js +57 -47
- package/dist/ghidra/GhidraProvider.js +18 -10
- package/dist/ghidra/GhidraProviderCapabilities.js +45 -39
- package/dist/ghidra/GhidraSocketConnection.js +1 -1
- package/dist/ghidra/protocol.js +19 -42
- package/dist/hopper/BridgeLauncher.js +3 -1
- package/dist/hopper/HopperAnalysisProfile.js +2 -4
- package/dist/hopper/HopperCleanup.js +5 -6
- package/dist/hopper/HopperClient.js +27 -3
- package/dist/hopper/HopperProvider.js +42 -44
- package/dist/hopper/HopperRequestQueue.js +32 -4
- package/dist/hopper/HopperResponseStream.js +2 -2
- package/dist/hopper/LinuxPrivateDisplayProbe.js +16 -10
- package/dist/hopper/protocol.js +63 -21
- package/dist/main/reload.js +1 -6
- package/dist/main/state.js +0 -14
- package/dist/main/transport.js +31 -41
- package/dist/main.js +17 -13
- package/dist/mcpDoctor.js +4 -26
- package/dist/mcpStartupPolicy.js +14 -0
- package/dist/process/PrivateRuntimeRoot.js +17 -0
- package/dist/process/ProcessOwnership.js +64 -128
- package/dist/process/ProcessOwnershipIdentity.js +13 -0
- package/dist/process/ProcessOwnershipObservation.js +88 -0
- package/dist/process/ProcessOwnershipProcessTree.js +22 -0
- package/dist/process/ProviderDeadline.js +8 -12
- package/dist/process/WindowsAuthority.js +32 -0
- package/dist/reference/ReferenceSourceReader.js +3 -3
- package/dist/reference/ReferenceSourceReaderEntries.js +2 -2
- package/dist/replay/JavaScriptReplayWorker.js +95 -11
- package/dist/replay/JavaScriptReplayWorkerTypes.js +1 -0
- package/dist/replay/LinuxJavaScriptReplayRunner.js +10 -15
- package/dist/replay/ReplayOutcome.js +26 -15
- package/dist/replay/ReplayWorkerProtocol.js +34 -29
- package/dist/server/createServer.js +101 -144
- package/dist/server/mcpClientMetadata.js +32 -0
- package/dist/server/registerApplicationTools/characterization.js +6 -13
- package/dist/server/registerApplicationTools/compareExportShapes.js +2 -7
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +2 -7
- package/dist/server/registerApplicationTools/compareVersions.js +3 -8
- package/dist/server/registerApplicationTools/controlledReplay.js +6 -11
- package/dist/server/registerApplicationTools/coverage.js +13 -19
- package/dist/server/registerApplicationTools/obligationLedger.js +1 -6
- package/dist/server/registerApplicationTools/readiness.js +1 -6
- package/dist/server/registerApplicationTools/traceFeature.js +3 -8
- package/dist/server/registerApplicationTools/traceSemantics.js +1 -6
- package/dist/server/registerArtifactComparisonTool.js +12 -16
- package/dist/server/registerBrowserScenarioTool.js +1 -6
- package/dist/server/registerBrowserTools.js +37 -79
- package/dist/server/registerBundleComparisonTool.js +10 -14
- package/dist/server/registerCloseLifecycleTool.js +6 -14
- package/dist/server/registerElectronTools.js +30 -47
- package/dist/server/registerEnhancedTools.js +124 -122
- package/dist/server/registerEvidenceResources.js +50 -101
- package/dist/server/registerEvidenceTools.js +5 -9
- package/dist/server/registerFunctionComparisonTool.js +11 -15
- package/dist/server/registerInvestigationTools.js +42 -59
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +13 -27
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +4 -9
- package/dist/server/registerManagedWorkflowTools/contract.js +2 -3
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
- package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +8 -13
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +4 -9
- package/dist/server/registerOfficialTools.js +8 -9
- package/dist/server/registerProcessComparisonTool.js +19 -24
- package/dist/server/registerReconstructionObligationLedgerResource.js +0 -7
- package/dist/server/registerReconstructionReadinessResource.js +0 -7
- package/dist/server/registerReplayMachineTool.js +4 -10
- package/dist/server/registerSessionRecordTools.js +146 -47
- package/dist/server/registerSessionStatusTool.js +14 -29
- package/dist/server/registerSessionTools.js +45 -41
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/sessionToolPolicies.js +1 -5
- package/dist/server/toolRegistrationOptions.js +4 -177
- package/dist/server/toolResult.js +14 -18
- package/package.json +35 -16
- package/scripts/electron-active-hook-boundaries.cjs +394 -0
- package/scripts/electron-active-hook.cjs +501 -0
- package/skills/reverse-engineer-anything/SKILL.md +10 -3
- package/dist/server/DynamicToolAvailability.js +0 -70
- package/dist/server/toolInputValidation.js +0 -77
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BrowserObservationError } from "../domain/errors.js";
|
|
3
|
+
import { redactSensitiveText } from "./SensitiveTextCapture.js";
|
|
4
|
+
const OPERATION = "capture_electron_scenario";
|
|
5
|
+
const hookEventSchema = z.strictObject({
|
|
6
|
+
sequence: z.number().int().min(1),
|
|
7
|
+
correlation_id: z.string().max(256).nullable(),
|
|
8
|
+
kind: z.enum([
|
|
9
|
+
"main-handler-invocation",
|
|
10
|
+
"main-event-invocation",
|
|
11
|
+
"utility-process-fork",
|
|
12
|
+
"utility-process-message",
|
|
13
|
+
"ipc-main-to-renderer",
|
|
14
|
+
"ipc-utility-to-main",
|
|
15
|
+
"ipc-renderer-send",
|
|
16
|
+
"ipc-renderer-invoke",
|
|
17
|
+
"ipc-renderer-post-message",
|
|
18
|
+
"app-lifecycle",
|
|
19
|
+
"window-lifecycle",
|
|
20
|
+
"web-contents-lifecycle",
|
|
21
|
+
"navigation",
|
|
22
|
+
"shell-attempt",
|
|
23
|
+
"process-lifecycle",
|
|
24
|
+
"permission",
|
|
25
|
+
"popup-attempt",
|
|
26
|
+
"download",
|
|
27
|
+
"protocol",
|
|
28
|
+
"preload",
|
|
29
|
+
"native-addon",
|
|
30
|
+
"updater",
|
|
31
|
+
"error",
|
|
32
|
+
]),
|
|
33
|
+
event: z.string().max(128).nullable(),
|
|
34
|
+
phase: z.enum(["attempted", "completed", "blocked", "failed", "observed"]),
|
|
35
|
+
channel: z.string().max(1_024).nullable(),
|
|
36
|
+
channel_truncated: z.boolean().default(false),
|
|
37
|
+
direction: z
|
|
38
|
+
.enum([
|
|
39
|
+
"renderer-to-main",
|
|
40
|
+
"main-to-renderer",
|
|
41
|
+
"main-to-utility",
|
|
42
|
+
"utility-to-main",
|
|
43
|
+
])
|
|
44
|
+
.nullable(),
|
|
45
|
+
sender: z.string().max(256).nullable(),
|
|
46
|
+
receiver: z.string().max(256).nullable(),
|
|
47
|
+
frame: z.string().max(256).nullable(),
|
|
48
|
+
target: z.string().max(256).nullable(),
|
|
49
|
+
argument_shapes: z.array(z.string().max(64)).max(32),
|
|
50
|
+
argument_shapes_truncated: z.boolean().default(false),
|
|
51
|
+
result_shape: z.string().max(64).nullable(),
|
|
52
|
+
process_type: z.string().max(128).nullable(),
|
|
53
|
+
source: z.string().max(64),
|
|
54
|
+
capture_method: z.enum(["api-wrapper", "event-emitter", "process-hook"]),
|
|
55
|
+
artifact_path: z.string().max(16_384).nullable(),
|
|
56
|
+
artifact_sha256: z
|
|
57
|
+
.string()
|
|
58
|
+
.regex(/^[a-f0-9]{64}$/u)
|
|
59
|
+
.nullable(),
|
|
60
|
+
error: z.boolean(),
|
|
61
|
+
});
|
|
62
|
+
const hookSnapshotSchema = z.strictObject({
|
|
63
|
+
events: z.array(hookEventSchema).max(20_000),
|
|
64
|
+
observed: z.number().int().min(0),
|
|
65
|
+
observed_ipc: z.number().int().min(0),
|
|
66
|
+
observed_runtime: z.number().int().min(0),
|
|
67
|
+
truncated: z.boolean(),
|
|
68
|
+
hook_error: z.boolean(),
|
|
69
|
+
});
|
|
70
|
+
const metricSchema = z.strictObject({
|
|
71
|
+
pid: z.number().int().min(1),
|
|
72
|
+
type: z.string().min(1).max(128),
|
|
73
|
+
name: z.string().max(256).nullable(),
|
|
74
|
+
service_name: z.string().max(256).nullable(),
|
|
75
|
+
});
|
|
76
|
+
const windowMetadataSchema = z.strictObject({
|
|
77
|
+
window_id: z.string().max(256),
|
|
78
|
+
web_contents_id: z.string().max(256),
|
|
79
|
+
visible: z.boolean().nullable(),
|
|
80
|
+
destroyed: z.boolean(),
|
|
81
|
+
});
|
|
82
|
+
/** Run bounded, explicit actions against provider-owned Electron windows. */
|
|
83
|
+
export const runElectronActions = async (application, input, options, deadline) => {
|
|
84
|
+
const actions = [];
|
|
85
|
+
for (const action of input.actions) {
|
|
86
|
+
if (options.signal?.aborted === true)
|
|
87
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
88
|
+
const actionStartedAt = Date.now();
|
|
89
|
+
const windowIndex = actionWindowIndex(action);
|
|
90
|
+
let selectedWindow;
|
|
91
|
+
try {
|
|
92
|
+
const remaining = deadline - Date.now();
|
|
93
|
+
if (remaining <= 0)
|
|
94
|
+
throw new BrowserObservationError(OPERATION, "timeout");
|
|
95
|
+
selectedWindow =
|
|
96
|
+
windowIndex === null
|
|
97
|
+
? undefined
|
|
98
|
+
: await waitForWindow({
|
|
99
|
+
application,
|
|
100
|
+
windowIndex,
|
|
101
|
+
timeout: input.limits.action_timeout_ms,
|
|
102
|
+
signal: options.signal,
|
|
103
|
+
deadline,
|
|
104
|
+
});
|
|
105
|
+
if ((action.kind === "click" ||
|
|
106
|
+
action.kind === "renderer-reload" ||
|
|
107
|
+
action.kind === "renderer-crash") &&
|
|
108
|
+
selectedWindow === undefined)
|
|
109
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
110
|
+
await runAction({
|
|
111
|
+
application,
|
|
112
|
+
action,
|
|
113
|
+
selectedWindow,
|
|
114
|
+
remaining,
|
|
115
|
+
actionTimeoutMs: input.limits.action_timeout_ms,
|
|
116
|
+
options,
|
|
117
|
+
deadline,
|
|
118
|
+
});
|
|
119
|
+
actions.push({
|
|
120
|
+
step_id: action.step_id,
|
|
121
|
+
kind: action.kind,
|
|
122
|
+
window_index: windowIndex,
|
|
123
|
+
target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
|
|
124
|
+
status: "completed",
|
|
125
|
+
elapsed_ms: Date.now() - actionStartedAt,
|
|
126
|
+
error: null,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
actions.push({
|
|
131
|
+
step_id: action.step_id,
|
|
132
|
+
kind: action.kind,
|
|
133
|
+
window_index: windowIndex,
|
|
134
|
+
target: selectedWindow === undefined ? null : `window:${String(windowIndex)}`,
|
|
135
|
+
status: options.signal?.aborted ? "cancelled" : "failed",
|
|
136
|
+
elapsed_ms: Date.now() - actionStartedAt,
|
|
137
|
+
error: safeActionErrorMessage(cause, action),
|
|
138
|
+
});
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return actions;
|
|
143
|
+
};
|
|
144
|
+
const runAction = async ({ application, action, selectedWindow, remaining, actionTimeoutMs, options, deadline, }) => {
|
|
145
|
+
switch (action.kind) {
|
|
146
|
+
case "click":
|
|
147
|
+
if (selectedWindow === undefined)
|
|
148
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
149
|
+
await runWithExecutionLimits(selectedWindow.locator(action.selector).click({
|
|
150
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
151
|
+
}), options.signal, deadline);
|
|
152
|
+
return;
|
|
153
|
+
case "wait": {
|
|
154
|
+
const page = selectedWindow ??
|
|
155
|
+
(await application.firstWindow({
|
|
156
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
157
|
+
}));
|
|
158
|
+
await runWithExecutionLimits(page.waitForTimeout(Math.min(action.duration_ms, remaining)), options.signal, deadline);
|
|
159
|
+
if (action.duration_ms > remaining)
|
|
160
|
+
throw new BrowserObservationError(OPERATION, "timeout");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
case "renderer-reload":
|
|
164
|
+
if (selectedWindow === undefined)
|
|
165
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
166
|
+
await runWithExecutionLimits(selectedWindow.reload({
|
|
167
|
+
timeout: Math.min(actionTimeoutMs, remaining),
|
|
168
|
+
}), options.signal, deadline);
|
|
169
|
+
return;
|
|
170
|
+
case "renderer-crash": {
|
|
171
|
+
const crashed = await runWithExecutionLimits(application.evaluate(({ BrowserWindow }, index) => {
|
|
172
|
+
const candidate = BrowserWindow.getAllWindows()[index];
|
|
173
|
+
if (candidate === undefined || candidate.isDestroyed())
|
|
174
|
+
return false;
|
|
175
|
+
candidate.webContents.forcefullyCrashRenderer();
|
|
176
|
+
return true;
|
|
177
|
+
}, action.window_index), options.signal, deadline);
|
|
178
|
+
if (!crashed)
|
|
179
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
case "deep-link":
|
|
183
|
+
await runWithExecutionLimits(emitDeepLink(application, action), options.signal, deadline);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const actionWindowIndex = (action) => {
|
|
188
|
+
switch (action.kind) {
|
|
189
|
+
case "click":
|
|
190
|
+
case "renderer-reload":
|
|
191
|
+
case "renderer-crash":
|
|
192
|
+
return action.window_index;
|
|
193
|
+
case "wait":
|
|
194
|
+
return action.window_index ?? null;
|
|
195
|
+
case "deep-link":
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const waitForWindow = async ({ application, windowIndex, timeout, signal, deadline, }) => {
|
|
200
|
+
const stopAt = Math.min(deadline, Date.now() + timeout);
|
|
201
|
+
while (Date.now() < stopAt) {
|
|
202
|
+
if (signal?.aborted === true)
|
|
203
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
204
|
+
const window = application.windows()[windowIndex];
|
|
205
|
+
if (window !== undefined)
|
|
206
|
+
return window;
|
|
207
|
+
await runWithExecutionLimits(new Promise((resolve) => setTimeout(resolve, 25)), signal, stopAt);
|
|
208
|
+
}
|
|
209
|
+
throw new BrowserObservationError(OPERATION, "window_not_found");
|
|
210
|
+
};
|
|
211
|
+
const emitDeepLink = (application, action) => application.evaluate(({ app }, payload) => {
|
|
212
|
+
if (payload.delivery === "open-url")
|
|
213
|
+
return app.emit("open-url", { preventDefault: () => undefined }, payload.url);
|
|
214
|
+
return app.emit("second-instance", {}, [payload.url], "", {});
|
|
215
|
+
}, { delivery: action.delivery, url: action.url });
|
|
216
|
+
/** Read the current Electron windows, metrics, and bounded hook snapshot. */
|
|
217
|
+
// oxlint-disable-next-line max-lines-per-function -- one state read must share one capture boundary.
|
|
218
|
+
export const readApplicationState = async (application, maxWindows) => {
|
|
219
|
+
const pages = application.windows();
|
|
220
|
+
const pageSnapshotsPromise = Promise.all(pages.slice(0, maxWindows).map(async (page, index) => ({
|
|
221
|
+
index,
|
|
222
|
+
url: page.url().slice(0, 65_536),
|
|
223
|
+
title: (await page.title()).slice(0, 16_384),
|
|
224
|
+
})));
|
|
225
|
+
const windowMetadataPromise = application
|
|
226
|
+
.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows().map((window) => ({
|
|
227
|
+
window_id: `window:${String(window.id)}`,
|
|
228
|
+
web_contents_id: `webContents:${String(window.webContents.id)}`,
|
|
229
|
+
visible: window.isDestroyed() ? null : window.isVisible(),
|
|
230
|
+
destroyed: window.isDestroyed(),
|
|
231
|
+
})))
|
|
232
|
+
.then((value) => z.array(windowMetadataSchema).parse(value));
|
|
233
|
+
const processMetricsPromise = application
|
|
234
|
+
.evaluate(({ app }) => app
|
|
235
|
+
.getAppMetrics()
|
|
236
|
+
.map((metric) => ({
|
|
237
|
+
pid: metric.pid,
|
|
238
|
+
type: metric.type,
|
|
239
|
+
name: metric.name ?? null,
|
|
240
|
+
service_name: metric.serviceName ?? null,
|
|
241
|
+
})))
|
|
242
|
+
.then((value) => z.array(metricSchema).parse(value));
|
|
243
|
+
const electronVersionPromise = application
|
|
244
|
+
.evaluate(() => process.versions.electron)
|
|
245
|
+
.then((value) => z.string().parse(value));
|
|
246
|
+
const hookSnapshotPromise = application
|
|
247
|
+
.evaluate(() => {
|
|
248
|
+
const candidate = Reflect.get(globalThis, "__reaElectronActiveSnapshot");
|
|
249
|
+
return typeof candidate === "function"
|
|
250
|
+
? candidate()
|
|
251
|
+
: {
|
|
252
|
+
events: [],
|
|
253
|
+
observed: 0,
|
|
254
|
+
observed_ipc: 0,
|
|
255
|
+
observed_runtime: 0,
|
|
256
|
+
truncated: false,
|
|
257
|
+
hook_error: true,
|
|
258
|
+
};
|
|
259
|
+
})
|
|
260
|
+
.then((value) => hookSnapshotSchema.parse(value));
|
|
261
|
+
const [pageSnapshots, windowMetadata, processMetrics, electronVersion, hookSnapshot,] = await Promise.all([
|
|
262
|
+
pageSnapshotsPromise,
|
|
263
|
+
windowMetadataPromise,
|
|
264
|
+
processMetricsPromise,
|
|
265
|
+
electronVersionPromise,
|
|
266
|
+
hookSnapshotPromise,
|
|
267
|
+
]);
|
|
268
|
+
return {
|
|
269
|
+
windows: pageSnapshots.map((page) => {
|
|
270
|
+
const metadata = windowMetadata[page.index];
|
|
271
|
+
if (metadata === undefined)
|
|
272
|
+
throw new BrowserObservationError(OPERATION, "window_metadata_missing");
|
|
273
|
+
return {
|
|
274
|
+
window_id: metadata.window_id,
|
|
275
|
+
web_contents_id: metadata.web_contents_id,
|
|
276
|
+
url: page.url,
|
|
277
|
+
title: page.title,
|
|
278
|
+
visible: metadata.visible,
|
|
279
|
+
destroyed: metadata.destroyed,
|
|
280
|
+
};
|
|
281
|
+
}),
|
|
282
|
+
windowsTruncated: pages.length > maxWindows,
|
|
283
|
+
metrics: processMetrics,
|
|
284
|
+
electronVersion,
|
|
285
|
+
hookSnapshot,
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
/** Apply a deadline and caller cancellation to one Playwright operation. */
|
|
289
|
+
export const runWithExecutionLimits = async (operation, signal, deadline) => {
|
|
290
|
+
if (signal?.aborted === true)
|
|
291
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
292
|
+
let timer;
|
|
293
|
+
let abortListener;
|
|
294
|
+
const timeout = new Promise((_, reject) => {
|
|
295
|
+
timer = setTimeout(() => reject(new BrowserObservationError(OPERATION, "timeout")), Math.max(1, deadline - Date.now()));
|
|
296
|
+
});
|
|
297
|
+
const cancellation = signal === undefined
|
|
298
|
+
? timeout
|
|
299
|
+
: Promise.race([
|
|
300
|
+
timeout,
|
|
301
|
+
new Promise((_, reject) => {
|
|
302
|
+
abortListener = () => reject(new BrowserObservationError(OPERATION, "cancelled"));
|
|
303
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
304
|
+
}),
|
|
305
|
+
]);
|
|
306
|
+
try {
|
|
307
|
+
return await Promise.race([operation, cancellation]);
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
if (timer !== undefined)
|
|
311
|
+
clearTimeout(timer);
|
|
312
|
+
if (abortListener !== undefined)
|
|
313
|
+
signal?.removeEventListener("abort", abortListener);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const safeActionErrorMessage = (cause, action) => {
|
|
317
|
+
let message = cause instanceof Error ? cause.message : "Electron action failed";
|
|
318
|
+
const inputValues = Object.values(action)
|
|
319
|
+
.filter((value) => typeof value === "string" && value.length > 0)
|
|
320
|
+
.sort((left, right) => right.length - left.length);
|
|
321
|
+
for (const value of inputValues)
|
|
322
|
+
message = message.split(value).join("<redacted-input>");
|
|
323
|
+
return redactSensitiveText(message).slice(0, 1_024);
|
|
324
|
+
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { _electron as electron } from "playwright-core";
|
|
5
|
+
import { electronActiveObservationResultSchema, } from "../domain/electronActiveObservation.js";
|
|
6
|
+
import { AnalysisError, BrowserObservationError, ProviderAdapterError, } from "../domain/errors.js";
|
|
7
|
+
import { isPathContained } from "../domain/permissionPolicy.js";
|
|
8
|
+
import { err, ok } from "../domain/result.js";
|
|
9
|
+
import { cleanupOwnedProcessGroup, cleanupWindowsProcessTree, observeOwnedProcessLineage, selectCapturedProcessGroupIds, verifyNoTokenOwnedProcesses, } from "../process/ProcessOwnership.js";
|
|
10
|
+
import { runElectronActions, readApplicationState, runWithExecutionLimits, } from "./PlaywrightElectronActiveActions.js";
|
|
11
|
+
const OPERATION = "capture_electron_scenario";
|
|
12
|
+
/** Public identity for provider-owned Electron runtime experiments. */
|
|
13
|
+
export const PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY = Object.freeze({
|
|
14
|
+
id: "rea-playwright-electron-active",
|
|
15
|
+
name: "REA Playwright active Electron observation provider",
|
|
16
|
+
version: "1",
|
|
17
|
+
});
|
|
18
|
+
const hookPath = fileURLToPath(new URL("../../scripts/electron-active-hook.cjs", import.meta.url));
|
|
19
|
+
const observableEventFamilies = [
|
|
20
|
+
"app-lifecycle",
|
|
21
|
+
"window-lifecycle",
|
|
22
|
+
"web-contents-lifecycle",
|
|
23
|
+
"navigation",
|
|
24
|
+
"shell-attempt",
|
|
25
|
+
"process-lifecycle",
|
|
26
|
+
"permission",
|
|
27
|
+
"popup-attempt",
|
|
28
|
+
"download",
|
|
29
|
+
"protocol",
|
|
30
|
+
"preload",
|
|
31
|
+
"preload-configuration",
|
|
32
|
+
"renderer-ipc",
|
|
33
|
+
"native-addon",
|
|
34
|
+
"updater",
|
|
35
|
+
"error",
|
|
36
|
+
"ipc",
|
|
37
|
+
];
|
|
38
|
+
const ipcEventKinds = new Set([
|
|
39
|
+
"main-handler-invocation",
|
|
40
|
+
"main-event-invocation",
|
|
41
|
+
"utility-process-fork",
|
|
42
|
+
"utility-process-message",
|
|
43
|
+
"ipc-main-to-renderer",
|
|
44
|
+
"ipc-utility-to-main",
|
|
45
|
+
"ipc-renderer-send",
|
|
46
|
+
"ipc-renderer-invoke",
|
|
47
|
+
"ipc-renderer-post-message",
|
|
48
|
+
]);
|
|
49
|
+
const createCoverage = (hookSnapshot) => {
|
|
50
|
+
const observedEventFamilies = [
|
|
51
|
+
...new Set(hookSnapshot.events.map(({ kind, process_type }) => {
|
|
52
|
+
if (kind === "preload" && process_type === "main")
|
|
53
|
+
return "preload-configuration";
|
|
54
|
+
if (kind.startsWith("ipc-renderer") && process_type !== "renderer")
|
|
55
|
+
return "ipc";
|
|
56
|
+
return ipcEventKinds.has(kind) ? "ipc" : kind;
|
|
57
|
+
})),
|
|
58
|
+
].sort();
|
|
59
|
+
const unavailableEventFamilies = new Set(observableEventFamilies.filter((family) => !observedEventFamilies.includes(family)));
|
|
60
|
+
const observedRoles = [
|
|
61
|
+
...new Set(hookSnapshot.events.flatMap(({ process_type, kind }) => [
|
|
62
|
+
...(process_type === null ? [] : [process_type]),
|
|
63
|
+
...(kind === "window-lifecycle" ? ["window"] : []),
|
|
64
|
+
...(kind === "web-contents-lifecycle" || kind === "navigation"
|
|
65
|
+
? ["web_contents"]
|
|
66
|
+
: []),
|
|
67
|
+
...(kind === "preload" && process_type === "preload"
|
|
68
|
+
? ["preload"]
|
|
69
|
+
: []),
|
|
70
|
+
...(kind.startsWith("ipc-renderer") && process_type === "renderer"
|
|
71
|
+
? ["renderer"]
|
|
72
|
+
: []),
|
|
73
|
+
])),
|
|
74
|
+
].sort();
|
|
75
|
+
const rendererContextObserved = hookSnapshot.events.some(({ process_type }) => process_type === "renderer" || process_type === "preload");
|
|
76
|
+
if (!rendererContextObserved) {
|
|
77
|
+
unavailableEventFamilies.add("preload");
|
|
78
|
+
unavailableEventFamilies.add("renderer-ipc");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
status: hookSnapshot.hook_error
|
|
82
|
+
? "hook_conflict"
|
|
83
|
+
: hookSnapshot.truncated
|
|
84
|
+
? "capture_truncated"
|
|
85
|
+
: "partial_attach",
|
|
86
|
+
observed_event_families: observedEventFamilies,
|
|
87
|
+
unavailable_event_families: [...unavailableEventFamilies].sort(),
|
|
88
|
+
observed_roles: observedRoles,
|
|
89
|
+
pre_capture_activity: "unavailable",
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/** Launch an owned Electron application through the official Playwright API. */
|
|
93
|
+
export class PlaywrightElectronActiveProvider {
|
|
94
|
+
identity() {
|
|
95
|
+
return PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY;
|
|
96
|
+
}
|
|
97
|
+
async capture(input, options = {}) {
|
|
98
|
+
let application;
|
|
99
|
+
let ownership;
|
|
100
|
+
let lineage;
|
|
101
|
+
let outcome;
|
|
102
|
+
const runId = randomUUID();
|
|
103
|
+
try {
|
|
104
|
+
if (options.signal?.aborted === true)
|
|
105
|
+
throw new BrowserObservationError(OPERATION, "cancelled");
|
|
106
|
+
const deadline = Date.now() + input.limits.max_duration_ms;
|
|
107
|
+
const paths = await canonicalPaths(input);
|
|
108
|
+
application = await electron.launch({
|
|
109
|
+
executablePath: paths.executable,
|
|
110
|
+
cwd: paths.root,
|
|
111
|
+
env: safeElectronEnvironment(input.limits.max_ipc_events, input.limits.max_runtime_events, runId),
|
|
112
|
+
args: ["-r", hookPath, paths.application, ...input.args],
|
|
113
|
+
timeout: Math.max(1, deadline - Date.now()),
|
|
114
|
+
});
|
|
115
|
+
const leaderPid = application.process().pid;
|
|
116
|
+
if (typeof leaderPid !== "number" ||
|
|
117
|
+
!Number.isSafeInteger(leaderPid) ||
|
|
118
|
+
leaderPid <= 0)
|
|
119
|
+
throw new BrowserObservationError(OPERATION, "process_ownership_unavailable");
|
|
120
|
+
ownership = {
|
|
121
|
+
runId,
|
|
122
|
+
leaderPid,
|
|
123
|
+
processGroupId: leaderPid,
|
|
124
|
+
expectedParentPid: process.pid,
|
|
125
|
+
expectedCommand: paths.executable,
|
|
126
|
+
sweepTokenOwnedProcesses: true,
|
|
127
|
+
};
|
|
128
|
+
const actions = await runElectronActions(application, input, options, deadline);
|
|
129
|
+
const state = await runWithExecutionLimits(readApplicationState(application, input.limits.max_windows), options.signal, deadline);
|
|
130
|
+
outcome = ok(createResult(paths, input, actions, state));
|
|
131
|
+
}
|
|
132
|
+
catch (cause) {
|
|
133
|
+
outcome = err(providerError(cause));
|
|
134
|
+
}
|
|
135
|
+
if (application !== undefined) {
|
|
136
|
+
if (ownership !== undefined)
|
|
137
|
+
lineage = await observeOwnedProcessLineage(ownership);
|
|
138
|
+
let closeError;
|
|
139
|
+
try {
|
|
140
|
+
await runWithExecutionLimits(application.close(), undefined, Date.now() + 5_000);
|
|
141
|
+
}
|
|
142
|
+
catch (cause) {
|
|
143
|
+
closeError = cause;
|
|
144
|
+
}
|
|
145
|
+
const cleanup = ownership === undefined
|
|
146
|
+
? {
|
|
147
|
+
cleaned: false,
|
|
148
|
+
reason: "owned Electron process identity was unavailable",
|
|
149
|
+
}
|
|
150
|
+
: await cleanupElectronProcesses(ownership, lineage);
|
|
151
|
+
if (!cleanup.cleaned)
|
|
152
|
+
return err(providerError(new BrowserObservationError(OPERATION, "cleanup_failed", {
|
|
153
|
+
cause: new Error(cleanup.reason),
|
|
154
|
+
})));
|
|
155
|
+
if (closeError !== undefined)
|
|
156
|
+
return err(providerError(closeError));
|
|
157
|
+
}
|
|
158
|
+
return outcome;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const cleanupElectronProcesses = async (ownership, lineage) => {
|
|
162
|
+
if (process.platform === "win32") {
|
|
163
|
+
const root = await cleanupWindowsProcessTree(ownership.leaderPid);
|
|
164
|
+
if (!root.cleaned)
|
|
165
|
+
return root;
|
|
166
|
+
if (lineage?.status !== "verified")
|
|
167
|
+
return {
|
|
168
|
+
cleaned: false,
|
|
169
|
+
reason: "owned Electron lineage was unavailable; helper cleanup was not proven",
|
|
170
|
+
};
|
|
171
|
+
for (const descendant of lineage.lineage.descendants) {
|
|
172
|
+
const result = await cleanupWindowsProcessTree(descendant.pid);
|
|
173
|
+
if (!result.cleaned)
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
return root;
|
|
177
|
+
}
|
|
178
|
+
if (lineage?.status !== "verified") {
|
|
179
|
+
const root = await cleanupOwnedProcessGroup(ownership);
|
|
180
|
+
return root.cleaned
|
|
181
|
+
? {
|
|
182
|
+
cleaned: false,
|
|
183
|
+
reason: "owned Electron lineage was unavailable; helper cleanup was not proven",
|
|
184
|
+
}
|
|
185
|
+
: root;
|
|
186
|
+
}
|
|
187
|
+
const groupIds = selectCapturedProcessGroupIds(ownership.leaderPid, lineage.lineage.descendants.map(({ pid, processGroupId }) => ({
|
|
188
|
+
pid,
|
|
189
|
+
process_group_id: processGroupId,
|
|
190
|
+
})));
|
|
191
|
+
let signaled = false;
|
|
192
|
+
for (const processGroupId of groupIds) {
|
|
193
|
+
const cleanupOwnership = processGroupId === ownership.processGroupId
|
|
194
|
+
? { ...ownership, leaderPid: processGroupId, processGroupId }
|
|
195
|
+
: {
|
|
196
|
+
runId: ownership.runId,
|
|
197
|
+
leaderPid: processGroupId,
|
|
198
|
+
processGroupId,
|
|
199
|
+
};
|
|
200
|
+
const result = await cleanupOwnedProcessGroup(cleanupOwnership);
|
|
201
|
+
if (!result.cleaned)
|
|
202
|
+
return result;
|
|
203
|
+
signaled ||= result.signaled;
|
|
204
|
+
}
|
|
205
|
+
const remaining = await verifyNoTokenOwnedProcesses(ownership.runId);
|
|
206
|
+
if (!remaining.cleaned)
|
|
207
|
+
return remaining;
|
|
208
|
+
return { cleaned: true, signaled };
|
|
209
|
+
};
|
|
210
|
+
const createResult = (paths, input, actions, state) => {
|
|
211
|
+
const { hookSnapshot } = state;
|
|
212
|
+
const ipcEvents = hookSnapshot.events.filter(({ kind }) => ipcEventKinds.has(kind));
|
|
213
|
+
return electronActiveObservationResultSchema.parse({
|
|
214
|
+
schema_version: 1,
|
|
215
|
+
application: {
|
|
216
|
+
executable_path: paths.executable,
|
|
217
|
+
application_path: paths.application,
|
|
218
|
+
electron_version: state.electronVersion,
|
|
219
|
+
process_ownership: "provider-owned",
|
|
220
|
+
cleanup: "terminated-owned-process",
|
|
221
|
+
},
|
|
222
|
+
actions,
|
|
223
|
+
windows: state.windows.slice(0, input.limits.max_windows),
|
|
224
|
+
windows_truncated: state.windowsTruncated,
|
|
225
|
+
processes: {
|
|
226
|
+
items: state.metrics.slice(0, input.limits.max_processes),
|
|
227
|
+
truncated: state.metrics.length > input.limits.max_processes,
|
|
228
|
+
},
|
|
229
|
+
ipc: {
|
|
230
|
+
events: ipcEvents.slice(0, input.limits.max_ipc_events),
|
|
231
|
+
observed: hookSnapshot.observed_ipc,
|
|
232
|
+
retained: Math.min(ipcEvents.length, input.limits.max_ipc_events),
|
|
233
|
+
truncated: hookSnapshot.truncated ||
|
|
234
|
+
ipcEvents.length > input.limits.max_ipc_events,
|
|
235
|
+
},
|
|
236
|
+
timeline: {
|
|
237
|
+
events: hookSnapshot.events.slice(0, input.limits.max_runtime_events),
|
|
238
|
+
observed: hookSnapshot.observed,
|
|
239
|
+
retained: Math.min(hookSnapshot.events.length, input.limits.max_runtime_events),
|
|
240
|
+
truncated: hookSnapshot.truncated ||
|
|
241
|
+
hookSnapshot.events.length > input.limits.max_runtime_events,
|
|
242
|
+
},
|
|
243
|
+
coverage: createCoverage(hookSnapshot),
|
|
244
|
+
limitations: [
|
|
245
|
+
"IPC payloads are represented only by bounded value shapes; values are never retained, channels are capped at 1,024 characters, and argument-shape arrays at 32 entries.",
|
|
246
|
+
"IPC direction and sender/receiver identifiers are observed only where Electron exposes them at the hooked boundary.",
|
|
247
|
+
"The runtime timeline records bounded lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.",
|
|
248
|
+
"The preload and renderer process contexts are not instrumented by the main-process -r hook; preload configuration and contextBridge API-shape events are main-boundary observations, not proof of renderer-side execution.",
|
|
249
|
+
"Process metrics are an Electron API snapshot and do not prove hostile-local-user isolation.",
|
|
250
|
+
"External shell opens, external navigation, permission grants, downloads, popup windows, updater relaunches, and OS integration are blocked and recorded by the active hook; other application filesystem and network behavior is not sandboxed by this provider.",
|
|
251
|
+
...(state.windowsTruncated
|
|
252
|
+
? ["Windows beyond max_windows were not retained."]
|
|
253
|
+
: []),
|
|
254
|
+
...(hookSnapshot.hook_error
|
|
255
|
+
? ["The active IPC hook could not be installed."]
|
|
256
|
+
: []),
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
const canonicalPaths = async (input) => {
|
|
261
|
+
const [executable, application, root] = await Promise.all([
|
|
262
|
+
realpath(input.executable_path),
|
|
263
|
+
realpath(input.application_path),
|
|
264
|
+
realpath(input.application_root),
|
|
265
|
+
]);
|
|
266
|
+
if (!isPathContained(root, application))
|
|
267
|
+
throw new BrowserObservationError(OPERATION, "target_not_allowed");
|
|
268
|
+
return { executable, application, root };
|
|
269
|
+
};
|
|
270
|
+
const safeElectronEnvironment = (maxIpcEvents, maxRuntimeEvents, runId) => Object.fromEntries([
|
|
271
|
+
...[
|
|
272
|
+
"HOME",
|
|
273
|
+
"LANG",
|
|
274
|
+
"PATH",
|
|
275
|
+
"TMP",
|
|
276
|
+
"TMPDIR",
|
|
277
|
+
"USER",
|
|
278
|
+
"DISPLAY",
|
|
279
|
+
"WAYLAND_DISPLAY",
|
|
280
|
+
]
|
|
281
|
+
.map((name) => [name, process.env[name]])
|
|
282
|
+
.filter((entry) => entry[1] !== undefined),
|
|
283
|
+
["REA_ELECTRON_ACTIVE_MAX_IPC_EVENTS", String(maxIpcEvents)],
|
|
284
|
+
[
|
|
285
|
+
"REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS",
|
|
286
|
+
String(maxRuntimeEvents),
|
|
287
|
+
],
|
|
288
|
+
["REA_PROCESS_RUN_ID", runId],
|
|
289
|
+
]);
|
|
290
|
+
const providerError = (cause) => cause instanceof AnalysisError
|
|
291
|
+
? cause
|
|
292
|
+
: new ProviderAdapterError(PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY.id, OPERATION, {
|
|
293
|
+
cause,
|
|
294
|
+
});
|
|
@@ -89,9 +89,9 @@ export const openPlaywrightScenarioBrowser = async (scenario, environment, maxim
|
|
|
89
89
|
await configureAttachedEnvironment(target.context, target.page, scenario);
|
|
90
90
|
return { ...target, browser, profilePath: undefined };
|
|
91
91
|
}
|
|
92
|
-
catch (
|
|
92
|
+
catch (cause) {
|
|
93
93
|
await browser.close();
|
|
94
|
-
throw
|
|
94
|
+
throw cause;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
const profilePath = await mkdtemp(join(tmpdir(), "rea-browser-scenario-"));
|
|
@@ -125,9 +125,9 @@ export const openPlaywrightScenarioBrowser = async (scenario, environment, maxim
|
|
|
125
125
|
const page = context.pages()[0] ?? (await context.newPage());
|
|
126
126
|
return { context, page, browser, profilePath };
|
|
127
127
|
}
|
|
128
|
-
catch (
|
|
128
|
+
catch (cause) {
|
|
129
129
|
await rm(profilePath, { recursive: true, force: true, maxRetries: 3 });
|
|
130
|
-
throw
|
|
130
|
+
throw cause;
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
133
|
export const closePlaywrightScenarioBrowser = async ({ context, browser, profilePath, }) => {
|
|
@@ -201,11 +201,11 @@ export class PlaywrightScenarioSession {
|
|
|
201
201
|
try {
|
|
202
202
|
opened = await withPlaywrightExecutionBoundary(() => opening, remaining(), options.signal);
|
|
203
203
|
}
|
|
204
|
-
catch (
|
|
204
|
+
catch (cause) {
|
|
205
205
|
void opening
|
|
206
206
|
.then((lateOpened) => closePlaywrightScenarioBrowser(lateOpened))
|
|
207
207
|
.catch(() => undefined);
|
|
208
|
-
throw
|
|
208
|
+
throw cause;
|
|
209
209
|
}
|
|
210
210
|
try {
|
|
211
211
|
await withPlaywrightExecutionBoundary(() => initializePage(opened.context, opened.page, scenario, secrets), remaining(), options.signal);
|
|
@@ -224,9 +224,9 @@ export class PlaywrightScenarioSession {
|
|
|
224
224
|
}), remaining(), options.signal);
|
|
225
225
|
return session;
|
|
226
226
|
}
|
|
227
|
-
catch (
|
|
227
|
+
catch (cause) {
|
|
228
228
|
await closePlaywrightScenarioBrowser(opened);
|
|
229
|
-
throw
|
|
229
|
+
throw cause;
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
currentUrl() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const SECRET_ASSIGNMENT = /\b(authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password|passwd|secret|cookie|session)\b\s*[:=]\s*(?:Bearer\s+[A-Za-z0-9._~+/=-]+|"[^"]*"|'[^']*'|[^\s,;]+)/giu;
|
|
1
|
+
const SECRET_ASSIGNMENT = /\b(authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|token|password|passwd|secret|cookie|session)\b\s*[:=]\s*(?:Bearer\s+[A-Za-z0-9._~+/=-]+|"[^"]*"|'[^']*'|[^\s,;]+)/giu;
|
|
2
2
|
const BEARER = /\bBearer\s+[A-Za-z0-9._~+/=-]+/giu;
|
|
3
3
|
const JWT = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
|
|
4
4
|
/** Redact credential-shaped substrings before approved console text is retained. */
|
|
@@ -11,6 +11,7 @@ export const describeInspectorTargetLimitations = () => [
|
|
|
11
11
|
"REA attaches to an already-running exact target and never launches, resumes, evaluates, pauses, or mutates it.",
|
|
12
12
|
"Only Runtime.enable and Debugger.enable are sent; source text, object values, EventEmitter activity, and Electron IPC are not inspected.",
|
|
13
13
|
"Target IDs and locations are authorized, but the Inspector protocol does not authenticate an operating-system process ID or Electron role.",
|
|
14
|
+
"Some Electron main targets report only file://; for those targets, the first approved file root is recorded as a scope fallback and script locations are authorized independently.",
|
|
14
15
|
"No runtime graph depth is traversed because passive Inspector events do not establish require/import caller edges.",
|
|
15
16
|
];
|
|
16
17
|
/** Canonically authorize, deduplicate, and sort one bounded raw capture. */
|