rea-agents 1.4.0 → 1.5.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 +34 -12
- 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 +12 -7
- package/dist/application/BoundedJsonFiles.js +6 -29
- package/dist/application/BrowserEvidence.js +35 -0
- package/dist/application/BrowserObservationPort.js +1 -0
- package/dist/application/BrowserObservationService.js +52 -0
- package/dist/application/CapabilityInventory.js +7 -0
- package/dist/application/ConfiguredRoots.js +19 -0
- package/dist/application/CrossVersionInvestigation.js +6 -0
- package/dist/application/DeferredFileAuthorization.js +61 -0
- package/dist/application/DirectAnalysis.js +38 -18
- package/dist/application/EvidenceLedger.js +6 -5
- package/dist/application/InvestigationWorkspaceStore.js +2 -26
- package/dist/application/ProcessCaptureAuthority.js +3 -2
- package/dist/application/ProcessCaptureLifecycle.js +18 -8
- package/dist/application/ProcessCheckpoints.js +1 -1
- package/dist/application/ProcessCli.js +17 -5
- package/dist/application/ProcessHarness.js +6 -19
- package/dist/application/ProcessNormalization.js +17 -2
- package/dist/application/ProcessOwnership.js +4 -3
- package/dist/application/ProcessSampling.js +52 -17
- package/dist/application/ProjectPermissionStore.js +106 -3
- package/dist/application/ReferenceSourceImportPolicy.js +3 -2
- package/dist/application/TerminalRenderer.js +1 -1
- 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 +158 -0
- package/dist/browser/CdpCaptureDocuments.js +140 -0
- package/dist/browser/CdpCaptureEvents.js +248 -0
- package/dist/browser/CdpCaptureStorage.js +53 -0
- package/dist/browser/CdpCaptureValues.js +70 -0
- package/dist/browser/CdpConnection.js +218 -0
- package/dist/browser/CdpEndpoint.js +114 -0
- package/dist/browser/CdpOptionalCommand.js +15 -0
- package/dist/browser/CdpPageCapture.js +273 -0
- package/dist/catalogIdentity.js +2 -0
- package/dist/cli.js +2 -0
- package/dist/cliBrowserCommands.js +136 -0
- package/dist/cliInvestigationCommands.js +19 -34
- package/dist/cliPolicyCommands.js +13 -10
- package/dist/config.js +44 -0
- package/dist/contracts/browserToolContracts.js +80 -0
- package/dist/contracts/errorSchemas.js +2 -0
- package/dist/contracts/toolContracts.js +3 -5
- package/dist/domain/browserObservation.js +361 -0
- package/dist/domain/changedBehavior.js +4 -15
- package/dist/domain/errors.js +41 -0
- package/dist/domain/jsonLimits.js +26 -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/staticRuntimeCorrelation.js +2 -0
- package/dist/domain/symbolAnalysis.js +1 -1
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/main.js +6 -0
- package/dist/native/CommandRunner.js +11 -4
- package/dist/server/createServer.js +7 -0
- package/dist/server/registerBrowserTools.js +36 -0
- package/dist/server/registerInvestigationTools.js +30 -35
- package/dist/server/registerProcessComparisonTool.js +2 -0
- package/dist/server/registerSessionTools.js +15 -2
- package/dist/server/runDerivedOperation.js +10 -2
- package/package.json +2 -1
- package/scripts/rea.mjs +1 -1
- package/skills/rea-analysis/SKILL.md +18 -3
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Cli, z } from "incur";
|
|
2
|
+
import { inspectWebPage, listBrowserTargets, } from "./application/BrowserObservationService.js";
|
|
3
|
+
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
4
|
+
import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js";
|
|
5
|
+
import { logCliCommand } from "./cliLogging.js";
|
|
6
|
+
import { parseConfig } from "./config.js";
|
|
7
|
+
import { inspectWebPageInputSchema, listBrowserTargetsInputSchema, } from "./domain/browserObservation.js";
|
|
8
|
+
import { AnalysisInputError, projectAnalysisError } from "./domain/errors.js";
|
|
9
|
+
const browserScopeOptions = {
|
|
10
|
+
allowedOrigins: z
|
|
11
|
+
.array(z.string().min(1))
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Exact origins to observe; defaults to REA_BROWSER_ALLOWED_ORIGINS_JSON"),
|
|
14
|
+
approved: z.boolean().default(false).describe("Approve passive observation"),
|
|
15
|
+
};
|
|
16
|
+
/** Register CLI equivalents of the passive browser MCP tools. */
|
|
17
|
+
export const registerBrowserCommands = (cli, logger) => {
|
|
18
|
+
registerTargetList(cli, logger);
|
|
19
|
+
registerPageInspection(cli, logger);
|
|
20
|
+
};
|
|
21
|
+
const registerTargetList = (cli, logger) => {
|
|
22
|
+
cli.command("list-browser-targets", {
|
|
23
|
+
description: "List approved pages from a user-owned loopback CDP browser",
|
|
24
|
+
args: z.object({
|
|
25
|
+
endpoint: z.string().describe("Configured loopback CDP HTTP endpoint"),
|
|
26
|
+
}),
|
|
27
|
+
options: z.object({
|
|
28
|
+
...browserScopeOptions,
|
|
29
|
+
offset: z.number().int().min(0).default(0),
|
|
30
|
+
limit: z.number().int().min(1).max(200).default(100),
|
|
31
|
+
}),
|
|
32
|
+
run: ({ args, options }) => logCliCommand(logger, "list-browser-targets", async () => {
|
|
33
|
+
const context = await browserContext();
|
|
34
|
+
if (!context.ok)
|
|
35
|
+
return context.error;
|
|
36
|
+
const parsed = listBrowserTargetsInputSchema.safeParse({
|
|
37
|
+
cdp_endpoint: args.endpoint,
|
|
38
|
+
allowed_origins: options.allowedOrigins ?? context.allowedBrowserOrigins,
|
|
39
|
+
approved: options.approved,
|
|
40
|
+
offset: options.offset,
|
|
41
|
+
limit: options.limit,
|
|
42
|
+
});
|
|
43
|
+
if (!parsed.success)
|
|
44
|
+
return cliError(new AnalysisInputError("list_browser_targets"));
|
|
45
|
+
const result = await listBrowserTargets(context.provider, context.authority, parsed.data);
|
|
46
|
+
return result.ok ? result.value : cliError(result.error);
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const registerPageInspection = (cli, logger) => {
|
|
51
|
+
cli.command("inspect-web-page", {
|
|
52
|
+
description: "Passively inspect one approved page through CDP",
|
|
53
|
+
args: z.object({
|
|
54
|
+
endpoint: z.string().describe("Configured loopback CDP HTTP endpoint"),
|
|
55
|
+
targetId: z.string().describe("Target ID from list-browser-targets"),
|
|
56
|
+
}),
|
|
57
|
+
options: z.object({
|
|
58
|
+
...browserScopeOptions,
|
|
59
|
+
observationMs: z.number().int().min(0).max(10_000).default(500),
|
|
60
|
+
includeScriptSources: z.boolean().default(false),
|
|
61
|
+
includeStorageKeys: z.boolean().default(false),
|
|
62
|
+
maxFrames: z.number().int().min(1).max(1_000).default(200),
|
|
63
|
+
maxDomNodes: z.number().int().min(1).max(10_000).default(2_000),
|
|
64
|
+
maxAxNodes: z.number().int().min(1).max(10_000).default(2_000),
|
|
65
|
+
maxScripts: z.number().int().min(1).max(1_000).default(200),
|
|
66
|
+
maxResources: z.number().int().min(1).max(10_000).default(2_000),
|
|
67
|
+
maxWorkers: z.number().int().min(1).max(5_000).default(500),
|
|
68
|
+
maxStorageKeys: z.number().int().min(1).max(10_000).default(1_000),
|
|
69
|
+
maxScriptSourceBytes: z
|
|
70
|
+
.number()
|
|
71
|
+
.int()
|
|
72
|
+
.min(1)
|
|
73
|
+
.max(4 * 1_024 * 1_024)
|
|
74
|
+
.default(1_024 * 1_024),
|
|
75
|
+
maxTotalScriptSourceBytes: z
|
|
76
|
+
.number()
|
|
77
|
+
.int()
|
|
78
|
+
.min(1)
|
|
79
|
+
.max(16 * 1_024 * 1_024)
|
|
80
|
+
.default(4 * 1_024 * 1_024),
|
|
81
|
+
maxNetworkEvents: z.number().int().min(1).max(10_000).default(1_000),
|
|
82
|
+
maxConsoleEvents: z.number().int().min(1).max(2_000).default(200),
|
|
83
|
+
maxWebsocketEvents: z.number().int().min(1).max(5_000).default(500),
|
|
84
|
+
}),
|
|
85
|
+
run: ({ args, options }) => logCliCommand(logger, "inspect-web-page", async () => {
|
|
86
|
+
const context = await browserContext();
|
|
87
|
+
if (!context.ok)
|
|
88
|
+
return context.error;
|
|
89
|
+
const parsed = inspectWebPageInputSchema.safeParse({
|
|
90
|
+
cdp_endpoint: args.endpoint,
|
|
91
|
+
allowed_origins: options.allowedOrigins ?? context.allowedBrowserOrigins,
|
|
92
|
+
approved: options.approved,
|
|
93
|
+
target_id: args.targetId,
|
|
94
|
+
observation_ms: options.observationMs,
|
|
95
|
+
include_script_sources: options.includeScriptSources,
|
|
96
|
+
include_storage_keys: options.includeStorageKeys,
|
|
97
|
+
limits: {
|
|
98
|
+
max_frames: options.maxFrames,
|
|
99
|
+
max_dom_nodes: options.maxDomNodes,
|
|
100
|
+
max_ax_nodes: options.maxAxNodes,
|
|
101
|
+
max_scripts: options.maxScripts,
|
|
102
|
+
max_resources: options.maxResources,
|
|
103
|
+
max_workers: options.maxWorkers,
|
|
104
|
+
max_storage_keys: options.maxStorageKeys,
|
|
105
|
+
max_script_source_bytes: options.maxScriptSourceBytes,
|
|
106
|
+
max_total_script_source_bytes: options.maxTotalScriptSourceBytes,
|
|
107
|
+
max_network_events: options.maxNetworkEvents,
|
|
108
|
+
max_console_events: options.maxConsoleEvents,
|
|
109
|
+
max_websocket_events: options.maxWebsocketEvents,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
if (!parsed.success)
|
|
113
|
+
return cliError(new AnalysisInputError("inspect_web_page"));
|
|
114
|
+
const result = await inspectWebPage(context.provider, context.authority, parsed.data);
|
|
115
|
+
return result.ok ? result.value : cliError(result.error);
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const browserContext = async () => {
|
|
120
|
+
const config = parseConfig(process.env);
|
|
121
|
+
if (!config.ok)
|
|
122
|
+
return { ok: false, error: cliError(config.error) };
|
|
123
|
+
const authority = await loadConfiguredPermissionAuthority(config.value);
|
|
124
|
+
if (!authority.ok)
|
|
125
|
+
return { ok: false, error: cliError(authority.error) };
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
authority: authority.value,
|
|
129
|
+
provider: new CdpBrowserProvider(),
|
|
130
|
+
allowedBrowserOrigins: config.value.browserAllowedOrigins,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
const cliError = (error) => ({
|
|
134
|
+
error: "Browser observation failed",
|
|
135
|
+
...projectAnalysisError(error),
|
|
136
|
+
});
|
|
@@ -2,8 +2,9 @@ import { Cli, z } from "incur";
|
|
|
2
2
|
import { runCrossVersionInvestigation } from "./application/CrossVersionInvestigation.js";
|
|
3
3
|
import { logCliCommand } from "./cliLogging.js";
|
|
4
4
|
import { parseConfig } from "./config.js";
|
|
5
|
-
import {
|
|
5
|
+
import { projectAnalysisError } from "./domain/errors.js";
|
|
6
6
|
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
7
|
+
import { authorizeFileReadWithDeferredWrite, authorizeRootPermission, } from "./application/DeferredFileAuthorization.js";
|
|
7
8
|
const investigationOptionsSchema = z.object({
|
|
8
9
|
yes: z
|
|
9
10
|
.boolean()
|
|
@@ -68,39 +69,22 @@ const runInvestigation = async (args, options) => {
|
|
|
68
69
|
const authority = await loadConfiguredPermissionAuthority(config.value);
|
|
69
70
|
if (!authority.ok)
|
|
70
71
|
return cliError(authority.error);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
]) {
|
|
88
|
-
const authorized = await authority.value.authorize({
|
|
89
|
-
capability: scope.capability,
|
|
90
|
-
roots: scope.roots,
|
|
91
|
-
executables: [],
|
|
92
|
-
environment_names: [],
|
|
93
|
-
network: "none",
|
|
94
|
-
mount: false,
|
|
95
|
-
operation_identity: `investigate_versions:${scope.capability}:${scope.roots.join(":")}`,
|
|
96
|
-
}, scope.access);
|
|
97
|
-
if (!authorized.ok)
|
|
98
|
-
return cliError(authorized.error instanceof PermissionRequiredError
|
|
99
|
-
? authorized.error
|
|
100
|
-
: new AnalysisProtocolError(authorized.error.message, {
|
|
101
|
-
cause: authorized.error,
|
|
102
|
-
}));
|
|
103
|
-
}
|
|
72
|
+
const workspaceAuthorization = await authorizeFileReadWithDeferredWrite(authority.value, {
|
|
73
|
+
path: args.workspacePath,
|
|
74
|
+
readCapability: "investigation_workspace_read",
|
|
75
|
+
writeCapability: "investigation_workspace_write",
|
|
76
|
+
operation: "investigate_versions",
|
|
77
|
+
});
|
|
78
|
+
if (!workspaceAuthorization.ok)
|
|
79
|
+
return cliError(workspaceAuthorization.error);
|
|
80
|
+
const inputAuthorization = await authorizeRootPermission(authority.value, {
|
|
81
|
+
capability: "investigation_input",
|
|
82
|
+
roots: [args.leftPath, args.rightPath],
|
|
83
|
+
access: "read",
|
|
84
|
+
operation: "investigate_versions",
|
|
85
|
+
});
|
|
86
|
+
if (!inputAuthorization.ok)
|
|
87
|
+
return cliError(inputAuthorization.error);
|
|
104
88
|
const investigated = await runCrossVersionInvestigation({
|
|
105
89
|
approved: true,
|
|
106
90
|
workspace_path: args.workspacePath,
|
|
@@ -126,6 +110,7 @@ const runInvestigation = async (args, options) => {
|
|
|
126
110
|
}, config.value.evidenceFilePolicy, {
|
|
127
111
|
inputRoots: config.value.investigationInputRoots,
|
|
128
112
|
integrityContinueEnabled: config.value.artifactIntegrityContinueEnabled,
|
|
113
|
+
authorizeWorkspaceWrite: workspaceAuthorization.value.authorizeWrite,
|
|
129
114
|
});
|
|
130
115
|
return investigated.ok
|
|
131
116
|
? investigated.value.evidence
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Cli, z } from "incur";
|
|
2
2
|
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
3
|
-
import { readProjectPermissionStore,
|
|
3
|
+
import { readProjectPermissionStore, revokeProjectPermissionGrant, } from "./application/ProjectPermissionStore.js";
|
|
4
4
|
import { logCliCommand } from "./cliLogging.js";
|
|
5
5
|
import { parseConfig } from "./config.js";
|
|
6
6
|
import { PermissionRequiredError, projectAnalysisError, } from "./domain/errors.js";
|
|
7
7
|
const capabilitySchema = z.enum([
|
|
8
8
|
"process_capture",
|
|
9
|
+
"browser_observe",
|
|
9
10
|
"evidence_read",
|
|
10
11
|
"evidence_write",
|
|
11
12
|
"investigation_input",
|
|
@@ -29,6 +30,7 @@ export const registerPolicyCommands = (cli, logger) => {
|
|
|
29
30
|
root: z.string().optional(),
|
|
30
31
|
executable: z.string().optional(),
|
|
31
32
|
environmentNames: z.array(z.string()).default([]),
|
|
33
|
+
origins: z.array(z.string()).default([]),
|
|
32
34
|
network: z.enum(["none", "loopback", "external"]).default("none"),
|
|
33
35
|
mount: z.boolean().default(false),
|
|
34
36
|
write: z.boolean().default(false),
|
|
@@ -50,19 +52,19 @@ export const registerPolicyCommands = (cli, logger) => {
|
|
|
50
52
|
if (args.action === "revoke") {
|
|
51
53
|
if (args.value === undefined)
|
|
52
54
|
return { error: "Grant ID is required for policy revoke" };
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const written = await writeProjectPermissionStore(result.path, result.root, retained);
|
|
60
|
-
return written.ok
|
|
55
|
+
const { permissionProjectRoot, permissionProjectStore } = config.value;
|
|
56
|
+
if (permissionProjectRoot === undefined ||
|
|
57
|
+
permissionProjectStore === undefined)
|
|
58
|
+
return { error: "Project policy is not configured" };
|
|
59
|
+
const revoked = await revokeProjectPermissionGrant(permissionProjectStore, permissionProjectRoot, args.value);
|
|
60
|
+
return revoked.ok && revoked.value
|
|
61
61
|
? {
|
|
62
62
|
revoked: args.value,
|
|
63
63
|
reload: "Send SIGHUP to the REA MCP process",
|
|
64
64
|
}
|
|
65
|
-
:
|
|
65
|
+
: revoked.ok
|
|
66
|
+
? { error: "Grant was not found", grant_id: args.value }
|
|
67
|
+
: { error: revoked.error.message };
|
|
66
68
|
}
|
|
67
69
|
const capability = capabilitySchema.safeParse(args.value);
|
|
68
70
|
if (!capability.success)
|
|
@@ -82,6 +84,7 @@ const explain = async (config, capability, options) => {
|
|
|
82
84
|
roots: options.root === undefined ? [] : [options.root],
|
|
83
85
|
executables: options.executable === undefined ? [] : [options.executable],
|
|
84
86
|
environment_names: options.environmentNames,
|
|
87
|
+
origins: options.origins,
|
|
85
88
|
network: options.network,
|
|
86
89
|
mount: options.mount,
|
|
87
90
|
operation_identity: `policy_explain:${capability}`,
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ConfigurationError } from "./domain/errors.js";
|
|
3
3
|
import { err, ok } from "./domain/result.js";
|
|
4
|
+
import { browserEndpointSchema, browserOriginSchema, } from "./domain/browserObservation.js";
|
|
4
5
|
const DEFAULT_HOPPER_LAUNCHER_PATH = "/Applications/Hopper Disassembler.app/Contents/MacOS/hopper";
|
|
5
6
|
const defaultHopperLauncherPath = () => process.platform === "linux"
|
|
6
7
|
? "/opt/hopper/bin/Hopper"
|
|
@@ -34,6 +35,9 @@ const environmentSchema = z
|
|
|
34
35
|
REA_ANALYSIS_SNAPSHOT_ROOTS_JSON: z.string().default("[]"),
|
|
35
36
|
REA_REFERENCE_ROOTS_JSON: z.string().default("[]"),
|
|
36
37
|
REA_REFERENCE_SECRET_PATTERNS_JSON: z.string().default("[]"),
|
|
38
|
+
REA_BROWSER_OBSERVE_ENABLED: z.enum(["true", "false"]).default("false"),
|
|
39
|
+
REA_BROWSER_CDP_ENDPOINTS_JSON: z.string().default("[]"),
|
|
40
|
+
REA_BROWSER_ALLOWED_ORIGINS_JSON: z.string().default("[]"),
|
|
37
41
|
REA_PERMISSION_PROJECT_ROOT: z.string().min(1).optional(),
|
|
38
42
|
REA_PERMISSION_PROJECT_STORE: z.string().min(1).optional(),
|
|
39
43
|
})
|
|
@@ -57,6 +61,22 @@ const parseStringArray = (encoded, name) => {
|
|
|
57
61
|
return err(new ConfigurationError(`${name} must be valid JSON`, { cause }));
|
|
58
62
|
}
|
|
59
63
|
};
|
|
64
|
+
const parseBrowserArray = (encoded, name, itemSchema, maximum) => {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = z
|
|
67
|
+
.array(itemSchema)
|
|
68
|
+
.max(maximum)
|
|
69
|
+
.safeParse(JSON.parse(encoded));
|
|
70
|
+
return parsed.success
|
|
71
|
+
? ok([...new Set(parsed.data)].sort())
|
|
72
|
+
: err(new ConfigurationError(`${name} must encode valid browser scopes`, {
|
|
73
|
+
cause: parsed.error,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
return err(new ConfigurationError(`${name} must be valid JSON`, { cause }));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
60
80
|
const filePolicy = (roots) => ({
|
|
61
81
|
roots,
|
|
62
82
|
maxBytes: 64 * 1024 * 1024,
|
|
@@ -69,6 +89,7 @@ const permissionScope = (capability, roots, options = {}) => ({
|
|
|
69
89
|
roots,
|
|
70
90
|
executables: options.executables ?? [],
|
|
71
91
|
environment_names: options.environment_names ?? [],
|
|
92
|
+
...(options.origins === undefined ? {} : { origins: options.origins }),
|
|
72
93
|
network: options.network ?? "none",
|
|
73
94
|
mount: options.mount ?? false,
|
|
74
95
|
});
|
|
@@ -136,6 +157,12 @@ export const parseConfig = (environment) => {
|
|
|
136
157
|
const secretPatterns = parseStringArray(parsedEnvironment.data.REA_REFERENCE_SECRET_PATTERNS_JSON, "REA_REFERENCE_SECRET_PATTERNS_JSON");
|
|
137
158
|
if (!secretPatterns.ok)
|
|
138
159
|
return secretPatterns;
|
|
160
|
+
const browserEndpoints = parseBrowserArray(parsedEnvironment.data.REA_BROWSER_CDP_ENDPOINTS_JSON, "REA_BROWSER_CDP_ENDPOINTS_JSON", browserEndpointSchema, 16);
|
|
161
|
+
if (!browserEndpoints.ok)
|
|
162
|
+
return browserEndpoints;
|
|
163
|
+
const browserOrigins = parseBrowserArray(parsedEnvironment.data.REA_BROWSER_ALLOWED_ORIGINS_JSON, "REA_BROWSER_ALLOWED_ORIGINS_JSON", browserOriginSchema, 32);
|
|
164
|
+
if (!browserOrigins.ok)
|
|
165
|
+
return browserOrigins;
|
|
139
166
|
const permissionCeilings = [
|
|
140
167
|
...(parsedEnvironment.data.REA_PROCESS_CAPTURE_ENABLED === "true"
|
|
141
168
|
? [
|
|
@@ -158,6 +185,14 @@ export const parseConfig = (environment) => {
|
|
|
158
185
|
permissionScope("snapshot_write", analysisSnapshotRoots.value),
|
|
159
186
|
permissionScope("artifact_extract", ["/"]),
|
|
160
187
|
permissionScope("reference_read", referenceRoots.value),
|
|
188
|
+
...(parsedEnvironment.data.REA_BROWSER_OBSERVE_ENABLED === "true"
|
|
189
|
+
? [
|
|
190
|
+
permissionScope("browser_observe", [], {
|
|
191
|
+
origins: [...browserEndpoints.value, ...browserOrigins.value],
|
|
192
|
+
network: browserNetworkScope(browserOrigins.value),
|
|
193
|
+
}),
|
|
194
|
+
]
|
|
195
|
+
: []),
|
|
161
196
|
...(parsedEnvironment.data.REA_ARTIFACT_NATIVE_MOUNT_ENABLED === "true"
|
|
162
197
|
? [permissionScope("native_mount", [], { mount: true })]
|
|
163
198
|
: []),
|
|
@@ -189,9 +224,18 @@ export const parseConfig = (environment) => {
|
|
|
189
224
|
maxDepth: 32,
|
|
190
225
|
maxPathBytes: 4_096,
|
|
191
226
|
},
|
|
227
|
+
browserObservationEnabled: parsedEnvironment.data.REA_BROWSER_OBSERVE_ENABLED === "true",
|
|
228
|
+
browserCdpEndpoints: browserEndpoints.value,
|
|
229
|
+
browserAllowedOrigins: browserOrigins.value,
|
|
192
230
|
permissionCeilings,
|
|
193
231
|
administratorPermissionGrants: administratorGrants(permissionCeilings),
|
|
194
232
|
permissionProjectRoot: parsedEnvironment.data.REA_PERMISSION_PROJECT_ROOT,
|
|
195
233
|
permissionProjectStore: parsedEnvironment.data.REA_PERMISSION_PROJECT_STORE,
|
|
196
234
|
});
|
|
197
235
|
};
|
|
236
|
+
const browserNetworkScope = (origins) => origins.every((origin) => {
|
|
237
|
+
const hostname = new URL(origin).hostname;
|
|
238
|
+
return hostname === "127.0.0.1" || hostname === "[::1]";
|
|
239
|
+
})
|
|
240
|
+
? "loopback"
|
|
241
|
+
: "external";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { evidenceSchema } from "../domain/evidence.js";
|
|
2
|
+
import { browserTargetListSchema, inspectWebPageInputSchema, listBrowserTargetsInputSchema, webPageInspectionSchema, } from "../domain/browserObservation.js";
|
|
3
|
+
const evidenceResult = (schema) => evidenceSchema
|
|
4
|
+
.omit({ normalized_result: true })
|
|
5
|
+
.extend({ normalized_result: schema });
|
|
6
|
+
const listOutputSchema = evidenceResult(browserTargetListSchema);
|
|
7
|
+
const inspectionOutputSchema = evidenceSchema
|
|
8
|
+
.omit({ normalized_result: true })
|
|
9
|
+
.extend({ normalized_result: webPageInspectionSchema });
|
|
10
|
+
const endpoint = "http://127.0.0.1:9222";
|
|
11
|
+
const origin = "https://app.example.test";
|
|
12
|
+
/** Origin-scoped, passive browser reverse-engineering contracts. */
|
|
13
|
+
export const BROWSER_TOOL_CONTRACTS = [
|
|
14
|
+
{
|
|
15
|
+
name: "list_browser_targets",
|
|
16
|
+
description: "List bounded page targets from an approved user-owned loopback Chrome DevTools Protocol endpoint. Only targets whose current URL matches an approved exact origin are returned; URL credentials, query values, and fragments are redacted.",
|
|
17
|
+
kind: "browser-provider",
|
|
18
|
+
inputSchema: listBrowserTargetsInputSchema,
|
|
19
|
+
outputSchema: listOutputSchema,
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: true,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
idempotentHint: true,
|
|
24
|
+
openWorldHint: true,
|
|
25
|
+
},
|
|
26
|
+
examples: [
|
|
27
|
+
{
|
|
28
|
+
title: "List approved browser page targets",
|
|
29
|
+
input: {
|
|
30
|
+
cdp_endpoint: endpoint,
|
|
31
|
+
allowed_origins: [origin],
|
|
32
|
+
approved: true,
|
|
33
|
+
offset: 0,
|
|
34
|
+
limit: 100,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "inspect_web_page",
|
|
41
|
+
description: "Passively inspect one approved page target through CDP without evaluating JavaScript, navigating, clicking, closing, or mutating the page. Returns bounded DOM structure, accessibility, scripts, resources, attach-window network and console metadata, workers, and redacted storage inventory as Evidence v2.",
|
|
42
|
+
kind: "browser-provider",
|
|
43
|
+
inputSchema: inspectWebPageInputSchema,
|
|
44
|
+
outputSchema: inspectionOutputSchema,
|
|
45
|
+
annotations: {
|
|
46
|
+
readOnlyHint: true,
|
|
47
|
+
destructiveHint: false,
|
|
48
|
+
idempotentHint: true,
|
|
49
|
+
openWorldHint: true,
|
|
50
|
+
},
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
title: "Inspect one approved browser page",
|
|
54
|
+
input: {
|
|
55
|
+
cdp_endpoint: endpoint,
|
|
56
|
+
allowed_origins: [origin],
|
|
57
|
+
approved: true,
|
|
58
|
+
target_id: "TARGET_ID_FROM_LIST_BROWSER_TARGETS",
|
|
59
|
+
observation_ms: 500,
|
|
60
|
+
include_script_sources: false,
|
|
61
|
+
include_storage_keys: false,
|
|
62
|
+
limits: {
|
|
63
|
+
max_frames: 200,
|
|
64
|
+
max_dom_nodes: 2_000,
|
|
65
|
+
max_ax_nodes: 2_000,
|
|
66
|
+
max_scripts: 200,
|
|
67
|
+
max_resources: 2_000,
|
|
68
|
+
max_workers: 500,
|
|
69
|
+
max_storage_keys: 1_000,
|
|
70
|
+
max_script_source_bytes: 1_048_576,
|
|
71
|
+
max_total_script_source_bytes: 4_194_304,
|
|
72
|
+
max_network_events: 1_000,
|
|
73
|
+
max_console_events: 200,
|
|
74
|
+
max_websocket_events: 500,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
];
|
|
@@ -33,6 +33,7 @@ const scopeSchema = z
|
|
|
33
33
|
roots: z.array(z.string()),
|
|
34
34
|
executables: z.array(z.string()),
|
|
35
35
|
environment_names: z.array(z.string()),
|
|
36
|
+
origins: z.array(z.string()).optional(),
|
|
36
37
|
network: z.enum(["none", "loopback", "external"]),
|
|
37
38
|
mount: z.boolean(),
|
|
38
39
|
})
|
|
@@ -42,6 +43,7 @@ const missingScopeSchema = z
|
|
|
42
43
|
roots: z.array(z.string()).optional(),
|
|
43
44
|
executables: z.array(z.string()).optional(),
|
|
44
45
|
environment_names: z.array(z.string()).optional(),
|
|
46
|
+
origins: z.array(z.string()).optional(),
|
|
45
47
|
network: z.enum(["none", "loopback", "external"]).optional(),
|
|
46
48
|
mount: z.literal(true).optional(),
|
|
47
49
|
})
|
|
@@ -14,6 +14,7 @@ import { enhancedOutputSchemas, officialOutputSchemas, requireOutputSchema, sess
|
|
|
14
14
|
import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js";
|
|
15
15
|
import { NATIVE_TOOL_CONTRACTS } from "./nativeToolContracts.js";
|
|
16
16
|
import { ARTIFACT_TOOL_CONTRACTS } from "./artifactToolContracts.js";
|
|
17
|
+
import { BROWSER_TOOL_CONTRACTS } from "./browserToolContracts.js";
|
|
17
18
|
import { closeBinaryInputSchema, openBinaryInputSchema, } from "./sessionLifecycleInputs.js";
|
|
18
19
|
const document = z.string().optional().describe("The document name");
|
|
19
20
|
const address = z.string().describe("A Hopper address");
|
|
@@ -207,15 +208,12 @@ export const SESSION_TOOL_CONTRACTS = [
|
|
|
207
208
|
session("update_unknown", "Append one immutable full-state revision and mutation evidence. Requires approved: true and exact expected_revision; stale concurrent writers fail instead of overwriting newer analysis.", updateUnknownInputSchema),
|
|
208
209
|
session("verify_unknown_resolution", "Revalidate the current residual-unknown head against live bundled evidence, exact authority/confidence/environment requirements, and revision integrity. Withdrawn and out-of-scope dispositions are not truth claims.", z.object({ unknown_id: z.string().regex(/^unk_[a-f0-9]{64}$/u) })),
|
|
209
210
|
];
|
|
210
|
-
/**
|
|
211
|
-
* Complete ordered public inventory used by registration and verification.
|
|
212
|
-
* Keep this collection at 68 tools unless a deliberate contract change updates
|
|
213
|
-
* snapshots, package verification, and real-Hopper verification together.
|
|
214
|
-
*/
|
|
211
|
+
/** Complete ordered public inventory used by registration and verification. */
|
|
215
212
|
export const TOOL_CONTRACTS = [
|
|
216
213
|
...OFFICIAL_TOOL_CONTRACTS,
|
|
217
214
|
...ENHANCED_TOOL_CONTRACTS,
|
|
218
215
|
...NATIVE_TOOL_CONTRACTS,
|
|
219
216
|
...ARTIFACT_TOOL_CONTRACTS,
|
|
217
|
+
...BROWSER_TOOL_CONTRACTS,
|
|
220
218
|
...SESSION_TOOL_CONTRACTS,
|
|
221
219
|
];
|