rea-agents 1.4.0 → 1.6.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 +37 -13
- package/dist/application/AnalysisSnapshotCache.js +8 -6
- package/dist/application/ArtifactGraphConstruction.js +11 -2
- package/dist/application/ArtifactInventory.js +3 -2
- package/dist/application/AuthorizedArtifactInventory.js +2 -1
- package/dist/application/BinarySession.js +26 -11
- package/dist/application/BoundedJsonFiles.js +6 -29
- package/dist/application/BrowserEvidence.js +107 -0
- package/dist/application/BrowserObservationPort.js +1 -0
- package/dist/application/BrowserObservationService.js +116 -0
- package/dist/application/CapabilityInventory.js +14 -0
- package/dist/application/ClientConfigPath.js +33 -0
- package/dist/application/ConfiguredRoots.js +19 -0
- package/dist/application/CrossVersionInventory.js +16 -3
- package/dist/application/CrossVersionInvestigation.js +23 -62
- package/dist/application/CrossVersionInvestigationEvidence.js +64 -0
- package/dist/application/CrossVersionInvestigationReplay.js +170 -0
- package/dist/application/DeferredFileAuthorization.js +61 -0
- package/dist/application/DirectAnalysis.js +38 -18
- package/dist/application/Doctor.js +16 -11
- package/dist/application/ElectronEvidence.js +35 -0
- package/dist/application/ElectronObservationPort.js +1 -0
- package/dist/application/ElectronObservationService.js +46 -0
- package/dist/application/EvidenceLedger.js +11 -7
- package/dist/application/InvestigationWorkspaceStore.js +2 -26
- package/dist/application/PermissionAuthority.js +30 -0
- package/dist/application/ProcessCaptureAuthority.js +3 -2
- package/dist/application/ProcessCaptureLifecycle.js +19 -13
- package/dist/application/ProcessCheckpoints.js +1 -1
- package/dist/application/ProcessCli.js +17 -5
- package/dist/application/ProcessHarness.js +19 -26
- package/dist/application/ProcessNormalization.js +17 -2
- package/dist/application/ProcessOwnership.js +44 -23
- package/dist/application/ProcessSampling.js +111 -25
- package/dist/application/ProjectPermissionStore.js +107 -3
- package/dist/application/ReferenceSourceImport.js +13 -16
- package/dist/application/ReferenceSourceImportPolicy.js +3 -2
- package/dist/application/Setup.js +89 -30
- package/dist/application/SetupClients.js +7 -4
- package/dist/application/SetupInstallFailure.js +24 -0
- package/dist/application/SetupPlan.js +11 -7
- package/dist/application/SetupSkill.js +20 -3
- package/dist/application/TerminalRenderer.js +1 -1
- package/dist/application/Uninstall.js +20 -7
- package/dist/application/Upgrade.js +14 -3
- package/dist/artifacts/MachOSliceArtifactReader.js +1 -1
- package/dist/artifacts/NativeDmgArtifactReader.js +1 -1
- package/dist/artifacts/SafeOutputTree.js +3 -3
- package/dist/browser/CdpBrowserProvider.js +261 -0
- package/dist/browser/CdpCaptureCompleteness.js +90 -0
- package/dist/browser/CdpCaptureDocuments.js +299 -0
- package/dist/browser/CdpCaptureEvents.js +547 -0
- package/dist/browser/CdpCaptureStorage.js +53 -0
- package/dist/browser/CdpCaptureValues.js +68 -0
- package/dist/browser/CdpConnection.js +241 -0
- package/dist/browser/CdpElectronInspection.js +330 -0
- package/dist/browser/CdpElectronProvider.js +134 -0
- package/dist/browser/CdpEndpoint.js +173 -0
- package/dist/browser/CdpObservationSession.js +301 -0
- package/dist/browser/CdpOptionalCommand.js +15 -0
- package/dist/browser/CdpPageCapture.js +376 -0
- package/dist/browser/CdpSafeMetadata.js +203 -0
- package/dist/browser/CdpScreenshot.js +101 -0
- package/dist/browser/CdpTargetSession.js +47 -0
- package/dist/browser/CdpWebMcpDiscovery.js +242 -0
- package/dist/browser/ElectronFileScope.js +50 -0
- package/dist/browser/PngVisualDiff.js +177 -0
- package/dist/browser/SensitiveTextCapture.js +25 -0
- package/dist/browser/WebSourceMapFetcher.js +357 -0
- package/dist/catalogIdentity.js +10 -0
- package/dist/cli.js +6 -0
- package/dist/cliBrowserAdvancedCommands.js +169 -0
- package/dist/cliBrowserCommands.js +312 -0
- package/dist/cliElectronCommands.js +114 -0
- package/dist/cliInvestigationCommands.js +26 -34
- package/dist/cliPolicyCommands.js +60 -12
- package/dist/config.js +80 -1
- package/dist/contracts/browserToolContracts.js +381 -0
- package/dist/contracts/electronToolContracts.js +73 -0
- package/dist/contracts/errorSchemas.js +2 -0
- package/dist/contracts/toolContracts.js +5 -5
- package/dist/domain/browserCompleteness.js +101 -0
- package/dist/domain/browserObservation.js +587 -0
- package/dist/domain/browserSession.js +63 -0
- package/dist/domain/changedBehavior.js +4 -15
- package/dist/domain/electronObservation.js +162 -0
- package/dist/domain/errors.js +41 -0
- package/dist/domain/investigationWorkspace.js +4 -0
- package/dist/domain/jsonLimits.js +26 -0
- package/dist/domain/jsonShape.js +125 -0
- package/dist/domain/permissionPolicy.js +6 -2
- package/dist/domain/processCapture.js +3 -1
- package/dist/domain/processComparison.js +22 -34
- package/dist/domain/reconstructionVerification.js +2 -15
- package/dist/domain/reconstructionVerificationSchemas.js +2 -0
- package/dist/domain/referenceSourceGraph.js +3 -1
- package/dist/domain/staticRuntimeCorrelation.js +2 -0
- package/dist/domain/symbolAnalysis.js +1 -1
- package/dist/domain/webBundleAnalysis.js +195 -0
- package/dist/domain/webBundleAnalyzer.js +423 -0
- package/dist/domain/webCaptureDiff.js +184 -0
- package/dist/domain/webContentArtifact.js +47 -0
- package/dist/domain/webInventory.js +61 -0
- package/dist/domain/webMcpDiscovery.js +68 -0
- package/dist/domain/webScreenshot.js +114 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/hopper/BridgeLauncher.js +2 -3
- package/dist/hopper/HopperClient.js +51 -11
- package/dist/hopper/HopperProvider.js +6 -1
- package/dist/main.js +42 -31
- package/dist/native/CommandRunner.js +11 -4
- package/dist/native/NativeMacOSProvider.js +3 -2
- package/dist/native/NativeMachoInspection.js +21 -6
- package/dist/server/createServer.js +14 -0
- package/dist/server/registerBrowserTools.js +95 -0
- package/dist/server/registerElectronTools.js +36 -0
- package/dist/server/registerInvestigationTools.js +31 -37
- package/dist/server/registerProcessComparisonTool.js +2 -0
- package/dist/server/registerSessionTools.js +16 -2
- package/dist/server/runDerivedOperation.js +10 -2
- package/package.json +6 -1
- package/scripts/rea.mjs +2 -2
- package/skills/rea-analysis/SKILL.md +28 -3
package/README.md
CHANGED
|
@@ -10,23 +10,27 @@
|
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/rea-agents)
|
|
12
12
|
[](https://github.com/morluto/rea/actions/workflows/ci.yml)
|
|
13
|
-
[](#78-tools-for-investigation)
|
|
14
14
|
[](https://nodejs.org/)
|
|
15
15
|
[](LICENSE)
|
|
16
16
|
|
|
17
|
-
[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [
|
|
17
|
+
[Quick start](#quick-start) · [Current status](#current-status) · [Investigation model](#the-investigation-model) · [78 tools](#78-tools-for-investigation) · [Roadmap](#roadmap) · [How it works](#how-it-works)
|
|
18
18
|
|
|
19
19
|
<br />
|
|
20
20
|
|
|
21
21
|
<code>npm install --global rea-agents && rea setup</code>
|
|
22
22
|
|
|
23
|
+
<br />
|
|
24
|
+
|
|
25
|
+
<img src="docs/assets/rea-hopper-analysis.png" alt="REA launching its analysis bridge inside Hopper while inspecting a native binary" width="1200" />
|
|
26
|
+
|
|
23
27
|
</div>
|
|
24
28
|
|
|
25
29
|
---
|
|
26
30
|
|
|
27
31
|
See a feature in an app that you want in your own product? Give the app to your agent—even without its source code. With REA, the agent can investigate the feature, explain how it works, show its evidence, and build a version adapted to your stack and requirements.
|
|
28
32
|
|
|
29
|
-
REA gives agents one consistent way to investigate software. Today that includes deep native analysis through Hopper, complete function dossiers, reproducible Evidence v2 records,
|
|
33
|
+
REA gives agents one consistent way to investigate software. Today that includes deep native analysis through Hopper, complete function dossiers, reproducible Evidence v2 records, controlled process capture, passive website and Electron observation, and bounded JavaScript/source-map reconstruction. The longer-term toolkit extends the same agent workflow to APIs, protocols, mobile artifacts, firmware, richer runtime behavior, and differences between versions.
|
|
30
34
|
|
|
31
35
|
Reverse engineering normally makes the operator choose a tool, learn its API, move evidence between programs, and decide what to inspect next. REA gives that work to the agent through commands, skills, structured results, and repeatable investigation workflows.
|
|
32
36
|
|
|
@@ -290,15 +294,17 @@ REA handles the app analysis in steps 1–5. The agent performs step 6 with its
|
|
|
290
294
|
- Analyze Swift and Objective-C metadata without manually untangling every mangled symbol.
|
|
291
295
|
- Leave names, comments, and bookmarks in Hopper so human and agent analysis reinforce each other.
|
|
292
296
|
|
|
293
|
-
##
|
|
297
|
+
## 78 tools for investigation
|
|
294
298
|
|
|
295
|
-
| Tool family | Count | Examples
|
|
296
|
-
| ------------------------- | ----: |
|
|
297
|
-
| Native inspection | 33 | procedures, pseudocode, assembly, strings, names, segments, callers, callees, xrefs, annotations
|
|
298
|
-
| Investigation workflows | 10 | `binary_overview`, `analyze_function`, `batch_decompile`, `trace_feature`, call graphs, Swift and Objective-C discovery
|
|
299
|
-
| Native macOS utilities | 5 | Mach-O metadata, code signatures, plists, architectures, Swift demangling; Hopper-free and provenance-bearing
|
|
300
|
-
| Artifact graph | 2 | deterministic directory, ZIP/APK/IPA, and ASAR inventory; explicitly selected extraction into an absent owned tree
|
|
301
|
-
|
|
|
299
|
+
| Tool family | Count | Examples |
|
|
300
|
+
| ------------------------- | ----: | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
301
|
+
| Native inspection | 33 | procedures, pseudocode, assembly, strings, names, segments, callers, callees, xrefs, annotations |
|
|
302
|
+
| Investigation workflows | 10 | `binary_overview`, `analyze_function`, `batch_decompile`, `trace_feature`, call graphs, Swift and Objective-C discovery |
|
|
303
|
+
| Native macOS utilities | 5 | Mach-O metadata, code signatures, plists, architectures, Swift demangling; Hopper-free and provenance-bearing |
|
|
304
|
+
| Artifact graph | 2 | deterministic directory, ZIP/APK/IPA, and ASAR inventory; explicitly selected extraction into an absent owned tree |
|
|
305
|
+
| Browser observation | 8 | exact-origin CDP capture, bundle and source-map analysis, WebMCP discovery, session timelines, capture diff, and visual evidence |
|
|
306
|
+
| Electron observation | 2 | canonical-root-confined file-page discovery and passive DOM, resource, and optionally approved script-source inspection |
|
|
307
|
+
| Workspace and observation | 18 | target lifecycle, Evidence v2 bundles, process/artifact/function comparison, evidence-linked residual-unknown lifecycle |
|
|
302
308
|
|
|
303
309
|
The public interface describes what the agent is trying to learn. Providers decide how to answer. macOS utilities handle common semantic inspection without launching Hopper; Hopper handles deeper native analysis; the process harness implements controlled behavioral capture.
|
|
304
310
|
|
|
@@ -307,6 +313,8 @@ The public interface describes what the agent is trying to learn. Providers deci
|
|
|
307
313
|
REA is already useful for native application investigation on macOS:
|
|
308
314
|
|
|
309
315
|
- Open Mach-O, ELF, PE, `.app`, ZIP, APK, IPA, ASAR, plist, JavaScript, source-map, and Hopper database targets.
|
|
316
|
+
- Attach to a user-owned Chrome-family browser over a configured loopback CDP endpoint; capture exact-origin web structure, safe metadata, approved value-free payload shapes, bundle/source-map evidence, WebMCP declarations, user-action timelines, capture diffs, and explicitly approved screenshots without navigation or JavaScript evaluation.
|
|
317
|
+
- Inspect Electron `file://` renderer pages through a separate canonical-root permission boundary without invoking Electron APIs; script contents remain separately approved and byte bounded.
|
|
310
318
|
- Traverse content-addressed artifact graphs without extraction; on macOS, read-only DMG traversal additionally requires `native_mount_approved: true` and `REA_ARTIFACT_NATIVE_MOUNT_ENABLED=true`. Materialize only approved occurrences into absent output roots.
|
|
311
319
|
- Build bounded function dossiers with pseudocode, assembly, CFG edges, comments, calls, references, strings, and names.
|
|
312
320
|
- Search and trace features across symbols, strings, metadata, references, and call paths.
|
|
@@ -327,12 +335,27 @@ REA is already useful for native application investigation on macOS:
|
|
|
327
335
|
|
|
328
336
|
Hopper is the first provider, not the boundary of the project. Some current workflows still require Hopper and macOS; every evidence record identifies the provider and limitations behind its result.
|
|
329
337
|
|
|
338
|
+
### Website observation with CDP
|
|
339
|
+
|
|
340
|
+
REA can inspect an already-running Chrome-family browser that you own. Browser observation is disabled by default and requires a literal loopback CDP endpoint plus exact approved page origins:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
export REA_BROWSER_OBSERVE_ENABLED=true
|
|
344
|
+
export REA_BROWSER_CDP_ENDPOINTS_JSON='["http://127.0.0.1:9222"]'
|
|
345
|
+
export REA_BROWSER_ALLOWED_ORIGINS_JSON='["http://127.0.0.1:3000"]'
|
|
346
|
+
|
|
347
|
+
rea list-browser-targets http://127.0.0.1:9222 --approved --json
|
|
348
|
+
rea inspect-web-page http://127.0.0.1:9222 TARGET_ID --approved --json
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
All eight browser tools expose the same Evidence v2 contracts over CLI and MCP. Inspection is passive: REA does not evaluate page JavaScript, navigate, click, close the page, or close the browser. Query values, credentials, cookies, authorization headers, storage values, and raw JSON or WebSocket values are never retained. Separately approved captures can retain bounded redacted console primitives, value-free JSON/WebSocket shapes, script sources, accessibility text, or screenshot pixels. Existing activity before attach is explicitly unavailable. See [Website observation with CDP](docs/browser-observation.md) for browser startup, schemas, limits, and the threat model.
|
|
352
|
+
|
|
330
353
|
## Roadmap
|
|
331
354
|
|
|
332
355
|
REA is growing into a toolkit for understanding software across static artifacts and observed behavior. The next capability families are:
|
|
333
356
|
|
|
334
357
|
1. **Artifact decomposition** — DMG, ASAR, ZIP, packages, universal-binary slices, application resources, embedded frameworks, mobile packages, and artifact graphs.
|
|
335
|
-
2. **Web and Electron investigation** —
|
|
358
|
+
2. **Web and Electron investigation** — extend the shipped passive CDP, bundle, visual-diff, and file-page observation with approved interaction, controlled replay, Electron IPC observation, semantic UI differences, and deeper JavaScript reconstruction.
|
|
336
359
|
3. **Deterministic behavior harnesses** — stronger process-tree ownership, protocol fixtures, network policy, filesystem tracing, signals, reconnects, and cross-version comparison.
|
|
337
360
|
4. **JavaScript and source recovery** — bundle indexing, AST/module reconstruction, source-map discovery, historical-source matching, and CodeDB-backed cross-references.
|
|
338
361
|
5. **Runtime observation** — approval-gated LLDB, Frida, system logs, process and filesystem observers, and native API tracing.
|
|
@@ -377,8 +400,9 @@ flowchart LR
|
|
|
377
400
|
Router --> Hopper["Hopper provider"]
|
|
378
401
|
Router --> Native["Native macOS provider"]
|
|
379
402
|
Router --> Artifact["Artifact graph provider"]
|
|
403
|
+
Router --> Browser["Browser CDP provider"]
|
|
380
404
|
Router --> Process["Process capture provider"]
|
|
381
|
-
Router -. roadmap .-> More["
|
|
405
|
+
Router -. roadmap .-> More["Dynamic and additional<br/>static providers"]
|
|
382
406
|
Hopper --> Target["Target software"]
|
|
383
407
|
Process --> Target
|
|
384
408
|
Native --> Target
|
|
@@ -52,12 +52,12 @@ export class AnalysisSnapshotCache {
|
|
|
52
52
|
this.#target.format !== snapshot.target.format ||
|
|
53
53
|
this.#target.architecture !== snapshot.target.architecture))
|
|
54
54
|
this.#entries.clear();
|
|
55
|
-
this.#target = snapshot.target;
|
|
55
|
+
this.#target = structuredClone(snapshot.target);
|
|
56
56
|
let imported = 0;
|
|
57
57
|
for (const entry of snapshot.entries) {
|
|
58
58
|
if (!this.#entries.has(entry.query_id))
|
|
59
59
|
imported += 1;
|
|
60
|
-
this.#entries.set(entry.query_id, entry);
|
|
60
|
+
this.#entries.set(entry.query_id, structuredClone(entry));
|
|
61
61
|
}
|
|
62
62
|
return imported;
|
|
63
63
|
}
|
|
@@ -69,7 +69,7 @@ export class AnalysisSnapshotCache {
|
|
|
69
69
|
snapshot_version: 1,
|
|
70
70
|
target: snapshotTarget(target),
|
|
71
71
|
entries: this.entries(),
|
|
72
|
-
evidence_bundle: evidenceBundle,
|
|
72
|
+
evidence_bundle: structuredClone(evidenceBundle),
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
/** Validate target identity, merge evidence atomically, then stage entries. */
|
|
@@ -81,7 +81,9 @@ export class AnalysisSnapshotCache {
|
|
|
81
81
|
}
|
|
82
82
|
/** Return canonical entries for persistence. */
|
|
83
83
|
entries() {
|
|
84
|
-
return [...this.#entries.values()]
|
|
84
|
+
return [...this.#entries.values()]
|
|
85
|
+
.sort((left, right) => left.query_id.localeCompare(right.query_id))
|
|
86
|
+
.map((entry) => structuredClone(entry));
|
|
85
87
|
}
|
|
86
88
|
/** Replay an exact provider-specific query, marking its cached provenance. */
|
|
87
89
|
lookup(target, operation, parameters, provider) {
|
|
@@ -90,7 +92,7 @@ export class AnalysisSnapshotCache {
|
|
|
90
92
|
if (cached === undefined)
|
|
91
93
|
return undefined;
|
|
92
94
|
const subject = cached.execution.subject;
|
|
93
|
-
return {
|
|
95
|
+
return structuredClone({
|
|
94
96
|
result: cached.execution.result,
|
|
95
97
|
rawResult: cached.execution.raw_result,
|
|
96
98
|
provider: cached.execution.provider,
|
|
@@ -113,7 +115,7 @@ export class AnalysisSnapshotCache {
|
|
|
113
115
|
format: subject.format,
|
|
114
116
|
architecture: subject.architecture,
|
|
115
117
|
},
|
|
116
|
-
};
|
|
118
|
+
});
|
|
117
119
|
}
|
|
118
120
|
/** Record one successful immutable call unless the cache is full. */
|
|
119
121
|
record(target, operation, parameters, execution) {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { extname } from "node:path";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
|
+
/** Index the first occurrence for each canonical logical path. */
|
|
5
|
+
export const indexOccurrencesByPath = (occurrences) => {
|
|
6
|
+
const byPath = new Map();
|
|
7
|
+
for (const occurrence of occurrences)
|
|
8
|
+
if (!byPath.has(occurrence.logical_path))
|
|
9
|
+
byPath.set(occurrence.logical_path, occurrence);
|
|
10
|
+
return byPath;
|
|
11
|
+
};
|
|
4
12
|
export const createOccurrence = (entry, path, parent) => ({
|
|
5
13
|
occurrence_id: `occ_${digestCanonical({ path, kind: entry.kind })}`,
|
|
6
14
|
artifact_id: null,
|
|
@@ -118,13 +126,14 @@ export const rekeyOccurrences = (rootArtifactId, occurrences) => {
|
|
|
118
126
|
};
|
|
119
127
|
export const createArtifactEdges = (rootArtifactId, occurrences, producer) => {
|
|
120
128
|
const byId = new Map(occurrences.map((item) => [item.occurrence_id, item]));
|
|
129
|
+
const byPath = indexOccurrencesByPath(occurrences);
|
|
121
130
|
const edges = [];
|
|
122
131
|
for (const occurrence of occurrences) {
|
|
123
132
|
if (occurrence.parent_occurrence_id === null ||
|
|
124
133
|
occurrence.artifact_id === null)
|
|
125
134
|
continue;
|
|
126
|
-
const mappedSource = occurrence.logical_path.endsWith(".map")
|
|
127
|
-
?
|
|
135
|
+
const mappedSource = occurrence.logical_path.toLowerCase().endsWith(".map")
|
|
136
|
+
? byPath.get(occurrence.logical_path.slice(0, -".map".length))
|
|
128
137
|
: undefined;
|
|
129
138
|
const parentArtifactId = mappedSource?.artifact_id ??
|
|
130
139
|
byId.get(occurrence.parent_occurrence_id)?.artifact_id ??
|
|
@@ -10,7 +10,7 @@ import { MachOSliceArtifactReader } from "../artifacts/MachOSliceArtifactReader.
|
|
|
10
10
|
import { NativeDmgArtifactReader } from "../artifacts/NativeDmgArtifactReader.js";
|
|
11
11
|
import { streamChunkToBuffer } from "../artifacts/StreamBytes.js";
|
|
12
12
|
import { artifactInventoryResultSchema, } from "../domain/artifactGraph.js";
|
|
13
|
-
import { classifyArtifactPath, classifyArtifactContent, createArtifactEdges, createArtifactNode, createOccurrence, createRootNode, digestCanonical, materializeDirectoryNodes, nearestParent, pageOf, rekeyOccurrences, rootOccurrenceFor, toOutputLimits, } from "./ArtifactGraphConstruction.js";
|
|
13
|
+
import { classifyArtifactPath, classifyArtifactContent, createArtifactEdges, createArtifactNode, createOccurrence, createRootNode, digestCanonical, indexOccurrencesByPath, materializeDirectoryNodes, nearestParent, pageOf, rekeyOccurrences, rootOccurrenceFor, toOutputLimits, } from "./ArtifactGraphConstruction.js";
|
|
14
14
|
const NATIVE_MOUNT_DISABLED = {
|
|
15
15
|
nativeMountApproved: false,
|
|
16
16
|
nativeMountEnabled: false,
|
|
@@ -61,8 +61,9 @@ export const scanCanonicalArtifactInventory = async (path, limits, signal, nativ
|
|
|
61
61
|
occurrence.parent_occurrence_id = rootOccurrence.occurrence_id;
|
|
62
62
|
occurrences.unshift(rootOccurrence);
|
|
63
63
|
const occurrenceById = new Map(occurrences.map((occurrence) => [occurrence.occurrence_id, occurrence]));
|
|
64
|
+
const occurrenceByPath = indexOccurrencesByPath(occurrences);
|
|
64
65
|
const integrityContradictions = pendingContradictions.map((contradiction) => {
|
|
65
|
-
const occurrence =
|
|
66
|
+
const occurrence = occurrenceByPath.get(contradiction.logicalPath);
|
|
66
67
|
if (occurrence === undefined)
|
|
67
68
|
throw new ArtifactReaderFailure("integrity", "Integrity contradiction lost its graph occurrence");
|
|
68
69
|
const parent = occurrence.parent_occurrence_id === null
|
|
@@ -2,13 +2,14 @@ import { realpath } from "node:fs/promises";
|
|
|
2
2
|
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
|
3
3
|
import { withinRoot } from "../reference/ReferenceSourceReaderPaths.js";
|
|
4
4
|
import { scanCanonicalArtifactInventory, } from "./ArtifactInventory.js";
|
|
5
|
+
import { canonicalizeConfiguredRoots } from "./ConfiguredRoots.js";
|
|
5
6
|
/** Resolve, authorize, and scan one artifact without a second path resolution. */
|
|
6
7
|
export const scanAuthorizedArtifactInventory = async (inputPath, roots, limits, signal, integrity) => {
|
|
7
8
|
if (roots.length === 0)
|
|
8
9
|
throw new ArtifactReaderFailure("path", "Artifact input roots are disabled");
|
|
9
10
|
const [path, approved] = await Promise.all([
|
|
10
11
|
realpath(inputPath),
|
|
11
|
-
|
|
12
|
+
canonicalizeConfiguredRoots(roots),
|
|
12
13
|
]);
|
|
13
14
|
if (!approved.some((root) => withinRoot(root, path)))
|
|
14
15
|
throw new ArtifactReaderFailure("path", "Artifact path is outside approved input roots");
|
|
@@ -7,7 +7,7 @@ import { EvidenceLedger } from "./EvidenceLedger.js";
|
|
|
7
7
|
import { UnknownRegistryError } from "../domain/errors.js";
|
|
8
8
|
import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
|
|
9
9
|
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
|
|
10
|
-
import { snapshotMatchesTarget } from "../domain/analysisSnapshot.js";
|
|
10
|
+
import { snapshotMatchesTarget, snapshotTarget, } from "../domain/analysisSnapshot.js";
|
|
11
11
|
import { AnalysisSnapshotCache, isSnapshotCacheable, } from "./AnalysisSnapshotCache.js";
|
|
12
12
|
import { UNKNOWN_REGISTRY_PROVIDER, unknownEvidenceLinks, unknownMutationEvidence, } from "./UnknownEvidence.js";
|
|
13
13
|
import { parseInvestigationWorkspace, } from "../domain/investigationWorkspace.js";
|
|
@@ -61,7 +61,7 @@ export class BinarySession {
|
|
|
61
61
|
if (operation !== undefined) {
|
|
62
62
|
const exact = this.#capabilities?.get(operation)?.provider;
|
|
63
63
|
if (exact !== undefined)
|
|
64
|
-
return exact;
|
|
64
|
+
return structuredClone(exact);
|
|
65
65
|
if (enhancedToolNameSchema.safeParse(operation).success) {
|
|
66
66
|
const providers = new Map();
|
|
67
67
|
for (const descriptor of this.#capabilities?.values() ?? [])
|
|
@@ -71,11 +71,11 @@ export class BinarySession {
|
|
|
71
71
|
if (providers.size === 1) {
|
|
72
72
|
const provider = providers.values().next().value;
|
|
73
73
|
if (provider !== undefined)
|
|
74
|
-
return provider;
|
|
74
|
+
return structuredClone(provider);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
return this.#providerIdentity;
|
|
78
|
+
return structuredClone(this.#providerIdentity);
|
|
79
79
|
}
|
|
80
80
|
/** Observe runtime provider-health changes that affect discovery metadata. */
|
|
81
81
|
onAvailabilityChanged(listener) {
|
|
@@ -130,12 +130,15 @@ export class BinarySession {
|
|
|
130
130
|
}
|
|
131
131
|
/** Read one session-retained immutable workspace revision. */
|
|
132
132
|
investigationWorkspace(workspaceId, revision) {
|
|
133
|
-
|
|
133
|
+
const workspace = this.#investigationWorkspaces.get(`${workspaceId}:${String(revision)}`);
|
|
134
|
+
return workspace === undefined ? undefined : structuredClone(workspace);
|
|
134
135
|
}
|
|
135
136
|
/** List retained workspace revisions in canonical identity order. */
|
|
136
137
|
investigationWorkspaces() {
|
|
137
|
-
return [...this.#investigationWorkspaces.values()]
|
|
138
|
-
left
|
|
138
|
+
return [...this.#investigationWorkspaces.values()]
|
|
139
|
+
.sort((left, right) => left.workspace_id.localeCompare(right.workspace_id) ||
|
|
140
|
+
left.revision - right.revision)
|
|
141
|
+
.map((workspace) => structuredClone(workspace));
|
|
139
142
|
}
|
|
140
143
|
/** Create an approved residual unknown and immutable mutation evidence. */
|
|
141
144
|
recordUnknown(input) {
|
|
@@ -191,7 +194,8 @@ export class BinarySession {
|
|
|
191
194
|
return err(new EvidenceIntegrityError("Analysis snapshot target does not match the requested binary"));
|
|
192
195
|
if (isAborted(options.signal))
|
|
193
196
|
return err(new AnalysisCancelledError("open_binary"));
|
|
194
|
-
if (this.#active?.target.path === parsed.value.path
|
|
197
|
+
if (this.#active?.target.path === parsed.value.path &&
|
|
198
|
+
snapshotMatchesTarget(snapshotTarget(this.#active.target), parsed.value)) {
|
|
195
199
|
if (options.snapshot !== undefined) {
|
|
196
200
|
const imported = this.importAnalysisSnapshot(options.snapshot);
|
|
197
201
|
if (!imported.ok)
|
|
@@ -310,7 +314,9 @@ export class BinarySession {
|
|
|
310
314
|
}
|
|
311
315
|
/** Return the immutable artifact identity captured before Hopper launched. */
|
|
312
316
|
activeTarget() {
|
|
313
|
-
return this.#active
|
|
317
|
+
return this.#active === undefined
|
|
318
|
+
? undefined
|
|
319
|
+
: structuredClone(this.#active.target);
|
|
314
320
|
}
|
|
315
321
|
/**
|
|
316
322
|
* Invoke a Hopper tool against the active target.
|
|
@@ -396,8 +402,17 @@ export class BinarySession {
|
|
|
396
402
|
this.#emitAvailabilityChanged();
|
|
397
403
|
}
|
|
398
404
|
#emitAvailabilityChanged() {
|
|
399
|
-
for (const listener of this.#availabilityListeners)
|
|
400
|
-
|
|
405
|
+
for (const listener of this.#availabilityListeners) {
|
|
406
|
+
try {
|
|
407
|
+
const notification = listener();
|
|
408
|
+
if (notification !== undefined)
|
|
409
|
+
void notification.catch(() => undefined);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
// External observers are best-effort notifications. Contain only the
|
|
413
|
+
// callback failure so state transitions and other listeners continue.
|
|
414
|
+
}
|
|
415
|
+
}
|
|
401
416
|
}
|
|
402
417
|
#serialize(operation) {
|
|
403
418
|
const result = this.#transition.then(operation, operation);
|
|
@@ -2,7 +2,9 @@ import { lstat, readFile, realpath } from "node:fs/promises";
|
|
|
2
2
|
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import writeFileAtomic from "write-file-atomic";
|
|
4
4
|
import { EvidenceFileError } from "../domain/errors.js";
|
|
5
|
+
import { isJsonWithinLimits } from "../domain/jsonLimits.js";
|
|
5
6
|
import { err, ok } from "../domain/result.js";
|
|
7
|
+
import { canonicalizeConfiguredRoots } from "./ConfiguredRoots.js";
|
|
6
8
|
/** Read bounded JSON data from a regular file beneath an approved root. */
|
|
7
9
|
export const readBoundedJson = async (path, policy) => {
|
|
8
10
|
if (policy.roots.length === 0)
|
|
@@ -26,8 +28,9 @@ export const readBoundedJson = async (path, policy) => {
|
|
|
26
28
|
catch (cause) {
|
|
27
29
|
return err(new EvidenceFileError("read", "invalid-json", { cause }));
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
return isJsonWithinLimits(decoded, policy)
|
|
32
|
+
? ok(decoded)
|
|
33
|
+
: err(new EvidenceFileError("read", "too-large"));
|
|
31
34
|
}
|
|
32
35
|
catch (cause) {
|
|
33
36
|
return err(new EvidenceFileError("read", "io", { cause }));
|
|
@@ -69,8 +72,7 @@ export const writeBoundedText = async (encoded, path, overwrite, policy) => {
|
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
74
|
const isApproved = async (candidate, roots) => {
|
|
72
|
-
for (const
|
|
73
|
-
const root = await realpath(resolve(configuredRoot));
|
|
75
|
+
for (const root of await canonicalizeConfiguredRoots(roots.map((configuredRoot) => resolve(configuredRoot)))) {
|
|
74
76
|
const relation = relative(root, candidate);
|
|
75
77
|
if (relation === "" ||
|
|
76
78
|
(!relation.startsWith("..") && !isAbsolute(relation)))
|
|
@@ -78,31 +80,6 @@ const isApproved = async (candidate, roots) => {
|
|
|
78
80
|
}
|
|
79
81
|
return false;
|
|
80
82
|
};
|
|
81
|
-
const validateJsonLimits = (root, policy) => {
|
|
82
|
-
const pending = [
|
|
83
|
-
{ value: root, depth: 0 },
|
|
84
|
-
];
|
|
85
|
-
let nodes = 0;
|
|
86
|
-
while (pending.length > 0) {
|
|
87
|
-
const current = pending.pop();
|
|
88
|
-
if (current === undefined)
|
|
89
|
-
break;
|
|
90
|
-
nodes += 1;
|
|
91
|
-
if (nodes > policy.maxNodes || current.depth > policy.maxDepth)
|
|
92
|
-
return err(new EvidenceFileError("read", "too-large"));
|
|
93
|
-
if (typeof current.value === "string" &&
|
|
94
|
-
current.value.length > policy.maxStringLength)
|
|
95
|
-
return err(new EvidenceFileError("read", "too-large"));
|
|
96
|
-
if (typeof current.value !== "object" || current.value === null)
|
|
97
|
-
continue;
|
|
98
|
-
for (const [key, value] of Object.entries(current.value)) {
|
|
99
|
-
if (key.length > policy.maxStringLength)
|
|
100
|
-
return err(new EvidenceFileError("read", "too-large"));
|
|
101
|
-
pending.push({ value, depth: current.depth + 1 });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return ok(null);
|
|
105
|
-
};
|
|
106
83
|
const isFileNotFound = (cause) => typeof cause === "object" &&
|
|
107
84
|
cause !== null &&
|
|
108
85
|
"code" in cause &&
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createEvidence, } from "../domain/evidence.js";
|
|
2
|
+
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
3
|
+
/** Create Evidence v2 for a policy-scoped external browser observation. */
|
|
4
|
+
export const createBrowserEvidence = (operation, input, result, provider) => createEvidence(undefined, provider, {
|
|
5
|
+
predicateType: browserPredicate(operation),
|
|
6
|
+
operation,
|
|
7
|
+
parameters: browserParameters(input),
|
|
8
|
+
result: jsonValueSchema.parse(result),
|
|
9
|
+
confidence: "observed",
|
|
10
|
+
authority: "external-service",
|
|
11
|
+
environment: "browser" in result
|
|
12
|
+
? {
|
|
13
|
+
id: `${result.browser.product}@${result.browser.revision}`,
|
|
14
|
+
platform: process.platform,
|
|
15
|
+
architecture: process.arch,
|
|
16
|
+
isolation: "none",
|
|
17
|
+
}
|
|
18
|
+
: null,
|
|
19
|
+
limitations: result.limitations,
|
|
20
|
+
});
|
|
21
|
+
const browserParameters = (input) => {
|
|
22
|
+
if (!("cdp_endpoint" in input)) {
|
|
23
|
+
if ("max_changes" in input)
|
|
24
|
+
return {
|
|
25
|
+
before_target_id: input.before.inspection.target.target_id,
|
|
26
|
+
before_capture_ended_at: input.before.inspection.capture_window.ended_at,
|
|
27
|
+
after_target_id: input.after.inspection.target.target_id,
|
|
28
|
+
after_capture_ended_at: input.after.inspection.capture_window.ended_at,
|
|
29
|
+
max_changes: input.max_changes,
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
before_artifact_uri: input.before.uri,
|
|
33
|
+
after_artifact_uri: input.after.uri,
|
|
34
|
+
channel_threshold: input.channel_threshold,
|
|
35
|
+
maximum_pixels: input.maximum_pixels,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const scope = {
|
|
39
|
+
cdp_endpoint: input.cdp_endpoint,
|
|
40
|
+
allowed_origins: input.allowed_origins,
|
|
41
|
+
};
|
|
42
|
+
if (!("target_id" in input))
|
|
43
|
+
return { ...scope, offset: input.offset, limit: input.limit };
|
|
44
|
+
if ("screenshot_approved" in input)
|
|
45
|
+
return {
|
|
46
|
+
...scope,
|
|
47
|
+
target_id: input.target_id,
|
|
48
|
+
maximum_image_bytes: input.maximum_image_bytes,
|
|
49
|
+
};
|
|
50
|
+
if ("max_tools" in input)
|
|
51
|
+
return {
|
|
52
|
+
...scope,
|
|
53
|
+
target_id: input.target_id,
|
|
54
|
+
observation_ms: input.observation_ms,
|
|
55
|
+
max_tools: input.max_tools,
|
|
56
|
+
max_schema_bytes: input.max_schema_bytes,
|
|
57
|
+
max_schema_nodes: input.max_schema_nodes,
|
|
58
|
+
max_schema_depth: input.max_schema_depth,
|
|
59
|
+
};
|
|
60
|
+
if (!("include_accessibility_text" in input))
|
|
61
|
+
return {
|
|
62
|
+
...scope,
|
|
63
|
+
target_id: input.target_id,
|
|
64
|
+
observation_ms: input.observation_ms,
|
|
65
|
+
max_timeline_events: input.max_timeline_events,
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
...scope,
|
|
69
|
+
target_id: input.target_id,
|
|
70
|
+
observation_ms: input.observation_ms,
|
|
71
|
+
include_accessibility_text: input.include_accessibility_text,
|
|
72
|
+
include_console_text: input.include_console_text,
|
|
73
|
+
include_json_body_shapes: input.include_json_body_shapes,
|
|
74
|
+
include_websocket_shapes: input.include_websocket_shapes,
|
|
75
|
+
include_script_sources: input.include_script_sources,
|
|
76
|
+
include_storage_keys: input.include_storage_keys,
|
|
77
|
+
limits: input.limits,
|
|
78
|
+
...(input.include_script_sources && "source_capture_approved" in input
|
|
79
|
+
? {
|
|
80
|
+
source_capture_approved: input.source_capture_approved,
|
|
81
|
+
fetch_source_maps: input.fetch_source_maps,
|
|
82
|
+
source_map_fetch_approved: input.source_map_fetch_approved,
|
|
83
|
+
analysis_limits: input.analysis_limits,
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const browserPredicate = (operation) => {
|
|
89
|
+
switch (operation) {
|
|
90
|
+
case "list_browser_targets":
|
|
91
|
+
return "rea.browser-target-list/v1";
|
|
92
|
+
case "inspect_web_page":
|
|
93
|
+
return "rea.web-page-inspection/v2";
|
|
94
|
+
case "analyze_web_bundle":
|
|
95
|
+
return "rea.web-bundle-analysis/v1";
|
|
96
|
+
case "observe_web_session":
|
|
97
|
+
return "rea.web-observation-session/v1";
|
|
98
|
+
case "discover_webmcp_tools":
|
|
99
|
+
return "rea.webmcp-discovery/v1";
|
|
100
|
+
case "compare_web_captures":
|
|
101
|
+
return "rea.web-capture-diff/v1";
|
|
102
|
+
case "capture_web_screenshot":
|
|
103
|
+
return "rea.web-screenshot/v1";
|
|
104
|
+
case "compare_web_screenshots":
|
|
105
|
+
return "rea.web-screenshot-diff/v1";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isLiteralLoopbackHostname, } from "../domain/browserObservation.js";
|
|
2
|
+
import { AnalysisCapabilityUnavailableError, AnalysisProtocolError, PermissionRequiredError, } from "../domain/errors.js";
|
|
3
|
+
import { err, ok } from "../domain/result.js";
|
|
4
|
+
import { createBrowserEvidence } from "./BrowserEvidence.js";
|
|
5
|
+
/** Authorize and execute one policy-scoped target discovery observation. */
|
|
6
|
+
export const listBrowserTargets = async (browser, authority, input, options = {}) => {
|
|
7
|
+
const ready = await prepare(browser, authority, input, "list_browser_targets");
|
|
8
|
+
if (!ready.ok)
|
|
9
|
+
return ready;
|
|
10
|
+
const result = await ready.value.listTargets(input, options);
|
|
11
|
+
return result.ok
|
|
12
|
+
? ok(createBrowserEvidence("list_browser_targets", input, result.value, ready.value.identity()))
|
|
13
|
+
: result;
|
|
14
|
+
};
|
|
15
|
+
/** Authorize and execute one policy-scoped passive page inspection. */
|
|
16
|
+
export const inspectWebPage = async (browser, authority, input, options = {}) => {
|
|
17
|
+
const ready = await prepare(browser, authority, input, "inspect_web_page");
|
|
18
|
+
if (!ready.ok)
|
|
19
|
+
return ready;
|
|
20
|
+
const result = await ready.value.inspectPage(input, options);
|
|
21
|
+
return result.ok
|
|
22
|
+
? ok(createBrowserEvidence("inspect_web_page", input, result.value, ready.value.identity()))
|
|
23
|
+
: result;
|
|
24
|
+
};
|
|
25
|
+
/** Capture approved sources and derive bounded static web-bundle evidence. */
|
|
26
|
+
export const analyzeWebBundle = async (browser, authority, input, options = {}) => {
|
|
27
|
+
const ready = await prepare(browser, authority, input, "analyze_web_bundle");
|
|
28
|
+
if (!ready.ok)
|
|
29
|
+
return ready;
|
|
30
|
+
const analyzed = await ready.value.analyzeBundle(input, options);
|
|
31
|
+
if (!analyzed.ok)
|
|
32
|
+
return analyzed;
|
|
33
|
+
return ok(createBrowserEvidence("analyze_web_bundle", input, analyzed.value, ready.value.identity()));
|
|
34
|
+
};
|
|
35
|
+
/** Observe navigation caused by external user actions during one armed window. */
|
|
36
|
+
export const observeWebSession = async (browser, authority, input, options = {}) => {
|
|
37
|
+
const ready = await prepare(browser, authority, input, "observe_web_session");
|
|
38
|
+
if (!ready.ok)
|
|
39
|
+
return ready;
|
|
40
|
+
const observed = await ready.value.observeSession(input, options);
|
|
41
|
+
return observed.ok
|
|
42
|
+
? ok(createBrowserEvidence("observe_web_session", input, observed.value, ready.value.identity()))
|
|
43
|
+
: observed;
|
|
44
|
+
};
|
|
45
|
+
/** Discover WebMCP declarations without exposing an invocation surface. */
|
|
46
|
+
export const discoverWebMcpTools = async (browser, authority, input, options = {}) => {
|
|
47
|
+
const ready = await prepare(browser, authority, input, "discover_webmcp_tools");
|
|
48
|
+
if (!ready.ok)
|
|
49
|
+
return ready;
|
|
50
|
+
const discovered = await ready.value.discoverWebMcpTools(input, options);
|
|
51
|
+
return discovered.ok
|
|
52
|
+
? ok(createBrowserEvidence("discover_webmcp_tools", input, discovered.value, ready.value.identity()))
|
|
53
|
+
: discovered;
|
|
54
|
+
};
|
|
55
|
+
/** Compare two already-normalized web captures without external access. */
|
|
56
|
+
export const compareWebCaptureEvidence = async (browser, input) => {
|
|
57
|
+
const ready = requireBrowser(browser, "compare_web_captures");
|
|
58
|
+
if (!ready.ok)
|
|
59
|
+
return ready;
|
|
60
|
+
const compared = await ready.value.compareCaptures(input);
|
|
61
|
+
return compared.ok
|
|
62
|
+
? ok(createBrowserEvidence("compare_web_captures", input, compared.value, ready.value.identity()))
|
|
63
|
+
: compared;
|
|
64
|
+
};
|
|
65
|
+
/** Capture one explicitly approved visible-viewport screenshot. */
|
|
66
|
+
export const captureWebScreenshot = async (browser, authority, input, options = {}) => {
|
|
67
|
+
const ready = await prepare(browser, authority, input, "capture_web_screenshot");
|
|
68
|
+
if (!ready.ok)
|
|
69
|
+
return ready;
|
|
70
|
+
const captured = await ready.value.captureScreenshot(input, options);
|
|
71
|
+
return captured.ok
|
|
72
|
+
? ok(createBrowserEvidence("capture_web_screenshot", input, captured.value, ready.value.identity()))
|
|
73
|
+
: captured;
|
|
74
|
+
};
|
|
75
|
+
/** Compare two self-verifying PNG artifacts without external access. */
|
|
76
|
+
export const compareWebScreenshotEvidence = async (browser, input) => {
|
|
77
|
+
const ready = requireBrowser(browser, "compare_web_screenshots");
|
|
78
|
+
if (!ready.ok)
|
|
79
|
+
return ready;
|
|
80
|
+
const compared = await ready.value.compareScreenshots(input);
|
|
81
|
+
return compared.ok
|
|
82
|
+
? ok(createBrowserEvidence("compare_web_screenshots", input, compared.value, ready.value.identity()))
|
|
83
|
+
: compared;
|
|
84
|
+
};
|
|
85
|
+
const prepare = async (browser, authority, input, operation) => {
|
|
86
|
+
if (authority === undefined)
|
|
87
|
+
return err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser observation permission policy is not configured"));
|
|
88
|
+
const authorized = await authority.authorize({
|
|
89
|
+
capability: "browser_observe",
|
|
90
|
+
roots: [],
|
|
91
|
+
executables: [],
|
|
92
|
+
environment_names: [],
|
|
93
|
+
origins: [input.cdp_endpoint, ...input.allowed_origins],
|
|
94
|
+
network: browserNetworkScope(input.allowed_origins),
|
|
95
|
+
mount: false,
|
|
96
|
+
operation_identity: `${operation}:${input.target_id ?? input.cdp_endpoint}`,
|
|
97
|
+
}, "read");
|
|
98
|
+
if (!authorized.ok)
|
|
99
|
+
return err(authorized.error instanceof PermissionRequiredError
|
|
100
|
+
? authorized.error
|
|
101
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
102
|
+
cause: authorized.error,
|
|
103
|
+
}));
|
|
104
|
+
return browser === undefined
|
|
105
|
+
? err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser observation provider is not configured"))
|
|
106
|
+
: ok(browser);
|
|
107
|
+
};
|
|
108
|
+
const requireBrowser = (browser, operation) => browser === undefined
|
|
109
|
+
? err(new AnalysisCapabilityUnavailableError("rea-cdp-browser", operation, "browser comparison provider is not configured"))
|
|
110
|
+
: ok(browser);
|
|
111
|
+
const browserNetworkScope = (allowedOrigins) => allowedOrigins.every((origin) => {
|
|
112
|
+
const hostname = new URL(origin).hostname;
|
|
113
|
+
return isLiteralLoopbackHostname(hostname);
|
|
114
|
+
})
|
|
115
|
+
? "loopback"
|
|
116
|
+
: "external";
|
|
@@ -64,6 +64,20 @@ const availabilityFor = (name, kind, targetOpen, targetKind, descriptors, policy
|
|
|
64
64
|
reason: "policy_disabled",
|
|
65
65
|
remediation: "Configure or grant an evidence_read root.",
|
|
66
66
|
};
|
|
67
|
+
if (kind === "browser-provider")
|
|
68
|
+
return policy.browserObservationEnabled === true
|
|
69
|
+
? { reason: "available", remediation: null }
|
|
70
|
+
: {
|
|
71
|
+
reason: "policy_disabled",
|
|
72
|
+
remediation: "Enable browser observation and configure exact CDP endpoint and page origins.",
|
|
73
|
+
};
|
|
74
|
+
if (kind === "electron-provider")
|
|
75
|
+
return policy.electronObservationEnabled === true
|
|
76
|
+
? { reason: "available", remediation: null }
|
|
77
|
+
: {
|
|
78
|
+
reason: "policy_disabled",
|
|
79
|
+
remediation: "Enable Electron observation and configure a loopback CDP endpoint and canonical file roots.",
|
|
80
|
+
};
|
|
67
81
|
if (kind === "session")
|
|
68
82
|
return { reason: "available", remediation: null };
|
|
69
83
|
if (!targetOpen)
|