File size: 5,012 Bytes
9dc6089 | 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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import type { OpenClawConfig } from "../config/config.js";
import type { NodeSession } from "./node-registry.js";
const CANVAS_COMMANDS = [
"canvas.present",
"canvas.hide",
"canvas.navigate",
"canvas.eval",
"canvas.snapshot",
"canvas.a2ui.push",
"canvas.a2ui.pushJSONL",
"canvas.a2ui.reset",
];
const CAMERA_COMMANDS = ["camera.list"];
const CAMERA_DANGEROUS_COMMANDS = ["camera.snap", "camera.clip"];
const SCREEN_DANGEROUS_COMMANDS = ["screen.record"];
const LOCATION_COMMANDS = ["location.get"];
const DEVICE_COMMANDS = ["device.info", "device.status"];
const CONTACTS_COMMANDS = ["contacts.search"];
const CONTACTS_DANGEROUS_COMMANDS = ["contacts.add"];
const CALENDAR_COMMANDS = ["calendar.events"];
const CALENDAR_DANGEROUS_COMMANDS = ["calendar.add"];
const REMINDERS_COMMANDS = ["reminders.list"];
const REMINDERS_DANGEROUS_COMMANDS = ["reminders.add"];
const PHOTOS_COMMANDS = ["photos.latest"];
const MOTION_COMMANDS = ["motion.activity", "motion.pedometer"];
const SMS_DANGEROUS_COMMANDS = ["sms.send"];
// iOS nodes don't implement system.run/which, but they do support notifications.
const IOS_SYSTEM_COMMANDS = ["system.notify"];
const SYSTEM_COMMANDS = [
"system.run",
"system.which",
"system.notify",
"system.execApprovals.get",
"system.execApprovals.set",
"browser.proxy",
];
// "High risk" node commands. These can be enabled by explicitly adding them to
// `gateway.nodes.allowCommands` (and ensuring they're not blocked by denyCommands).
export const DEFAULT_DANGEROUS_NODE_COMMANDS = [
...CAMERA_DANGEROUS_COMMANDS,
...SCREEN_DANGEROUS_COMMANDS,
...CONTACTS_DANGEROUS_COMMANDS,
...CALENDAR_DANGEROUS_COMMANDS,
...REMINDERS_DANGEROUS_COMMANDS,
...SMS_DANGEROUS_COMMANDS,
];
const PLATFORM_DEFAULTS: Record<string, string[]> = {
ios: [
...CANVAS_COMMANDS,
...CAMERA_COMMANDS,
...LOCATION_COMMANDS,
...DEVICE_COMMANDS,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
...IOS_SYSTEM_COMMANDS,
],
android: [
...CANVAS_COMMANDS,
...CAMERA_COMMANDS,
...LOCATION_COMMANDS,
...DEVICE_COMMANDS,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
],
macos: [
...CANVAS_COMMANDS,
...CAMERA_COMMANDS,
...LOCATION_COMMANDS,
...DEVICE_COMMANDS,
...CONTACTS_COMMANDS,
...CALENDAR_COMMANDS,
...REMINDERS_COMMANDS,
...PHOTOS_COMMANDS,
...MOTION_COMMANDS,
...SYSTEM_COMMANDS,
],
linux: [...SYSTEM_COMMANDS],
windows: [...SYSTEM_COMMANDS],
unknown: [...CANVAS_COMMANDS, ...CAMERA_COMMANDS, ...LOCATION_COMMANDS, ...SYSTEM_COMMANDS],
};
function normalizePlatformId(platform?: string, deviceFamily?: string): string {
const raw = (platform ?? "").trim().toLowerCase();
if (raw.startsWith("ios")) {
return "ios";
}
if (raw.startsWith("android")) {
return "android";
}
if (raw.startsWith("mac")) {
return "macos";
}
if (raw.startsWith("darwin")) {
return "macos";
}
if (raw.startsWith("win")) {
return "windows";
}
if (raw.startsWith("linux")) {
return "linux";
}
const family = (deviceFamily ?? "").trim().toLowerCase();
if (family.includes("iphone") || family.includes("ipad") || family.includes("ios")) {
return "ios";
}
if (family.includes("android")) {
return "android";
}
if (family.includes("mac")) {
return "macos";
}
if (family.includes("windows")) {
return "windows";
}
if (family.includes("linux")) {
return "linux";
}
return "unknown";
}
export function resolveNodeCommandAllowlist(
cfg: OpenClawConfig,
node?: Pick<NodeSession, "platform" | "deviceFamily">,
): Set<string> {
const platformId = normalizePlatformId(node?.platform, node?.deviceFamily);
const base = PLATFORM_DEFAULTS[platformId] ?? PLATFORM_DEFAULTS.unknown;
const extra = cfg.gateway?.nodes?.allowCommands ?? [];
const deny = new Set(cfg.gateway?.nodes?.denyCommands ?? []);
const allow = new Set([...base, ...extra].map((cmd) => cmd.trim()).filter(Boolean));
for (const blocked of deny) {
const trimmed = blocked.trim();
if (trimmed) {
allow.delete(trimmed);
}
}
return allow;
}
export function isNodeCommandAllowed(params: {
command: string;
declaredCommands?: string[];
allowlist: Set<string>;
}): { ok: true } | { ok: false; reason: string } {
const command = params.command.trim();
if (!command) {
return { ok: false, reason: "command required" };
}
if (!params.allowlist.has(command)) {
return { ok: false, reason: "command not allowlisted" };
}
if (Array.isArray(params.declaredCommands) && params.declaredCommands.length > 0) {
if (!params.declaredCommands.includes(command)) {
return { ok: false, reason: "command not declared by node" };
}
} else {
return { ok: false, reason: "node did not declare commands" };
}
return { ok: true };
}
|