Repository navigation
Expand file tree
/
Copy pathsecurity.mjs
More file actions
52 lines (44 loc) · 1.57 KB
/
Copy pathsecurity.mjs
File metadata and controls
52 lines (44 loc) · 1.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { timingSafeEqual } from "node:crypto";
const OBSERVE_TOOLS = new Set([
"tabs_context_mcp",
"tabs_create_mcp",
"navigate",
"read_page",
"get_page_text",
"find",
"read_console_messages",
"read_network_requests",
"resize_window",
"update_plan",
]);
const INTERACT_TOOLS = new Set([...OBSERVE_TOOLS, "computer", "form_input", "upload_image"]);
const FULL_TOOLS = new Set([...INTERACT_TOOLS, "javascript_tool"]);
export function loadSecurityConfig(env = process.env) {
const token = String(env.OPERATOR_TOKEN ?? "").trim();
if (token.length < 32 || token.length > 512) {
throw new Error("OPERATOR_TOKEN must be an explicit 32-512 character secret");
}
const host = String(env.OPERATOR_WS_HOST ?? "127.0.0.1").trim();
if (!new Set(["127.0.0.1", "::1", "localhost"]).has(host)) {
throw new Error("OPERATOR_WS_HOST must remain loopback-only");
}
const profile = String(env.OPERATOR_TOOL_PROFILE ?? "observe")
.trim()
.toLowerCase();
const profiles = { observe: OBSERVE_TOOLS, interact: INTERACT_TOOLS, full: FULL_TOOLS };
if (!(profile in profiles)) {
throw new Error("OPERATOR_TOOL_PROFILE must be observe, interact, or full");
}
return {
token,
host,
profile,
allowedTools: profiles[profile],
pairingEnabled: String(env.OPERATOR_PAIRING_ENABLED ?? "false").toLowerCase() === "true",
};
}
export function tokenMatches(actual, expected) {
const left = Buffer.from(String(actual ?? ""));
const right = Buffer.from(String(expected ?? ""));
return left.length === right.length && timingSafeEqual(left, right);
}