"""Safety policy for generic command execution surfaces. Dedicated first-party CLI/SDK methods keep their normal behavior. This module only gates raw surfaces where a single string can trigger arbitrary browser capabilities: ``browser-cli command``, ``browser-cli script``, and the HTTP ``/command`` endpoint. """ from __future__ import annotations from dataclasses import dataclass SAFE_COMMANDS = { "browser-cli.targets", "clients.list", "extension.capabilities", "extension.info", "group.list", "group.query", "group.tabs", "page.info", "perf.status", "tabs.active_in_window", "tabs.count", "tabs.filter", "tabs.list", "tabs.query", "tabs.status", "windows.list", } READ_PAGE_COMMANDS = { "dom.attr", "dom.exists", "dom.query", "dom.text", "extract.html", "extract.images", "extract.json", "extract.links", "extract.markdown", "extract.text", "tabs.html", } CONTROL_PREFIXES = ( "navigate.", "nav.", "group.", "session.", "tabs.", "windows.", ) CONTROL_COMMANDS = { "dom.check", "dom.clear", "dom.click", "dom.focus", "dom.hover", "dom.key", "dom.poll", "dom.scroll", "dom.select", "dom.submit", "dom.type", "dom.uncheck", "dom.wait_for", "extension.reload", } DANGEROUS_COMMANDS = { "dom.eval", "tabs.screenshot", } DANGEROUS_PREFIXES = ( "storage.", ) # Server-side key-management control commands. Gated separately so a key can be # trusted for browser use without also being able to list or add trusted keys. KEY_COMMANDS = { "browser-cli.auth.keys", "browser-cli.auth.trust", "browser-cli.auth.policy", } @dataclass(frozen=True) class CommandPolicy: allow_read_page: bool = False allow_control: bool = False allow_dangerous: bool = False allow_keys: bool = False @classmethod def unrestricted(cls) -> "CommandPolicy": return cls(allow_read_page=True, allow_control=True, allow_dangerous=True, allow_keys=True) def _is_control(command: str) -> bool: if command in CONTROL_COMMANDS: return True if any(command.startswith(prefix) for prefix in CONTROL_PREFIXES): return command not in SAFE_COMMANDS and command not in READ_PAGE_COMMANDS and command not in DANGEROUS_COMMANDS return False def command_category(command: str) -> str: name = str(command or "") if name in KEY_COMMANDS: return "keys" if name in DANGEROUS_COMMANDS or any(name.startswith(prefix) for prefix in DANGEROUS_PREFIXES): return "dangerous" if name in READ_PAGE_COMMANDS: return "read-page" if name in SAFE_COMMANDS: return "safe" if _is_control(name): return "control" return "unknown" def assert_command_allowed(command: str, policy: CommandPolicy) -> None: category = command_category(command) if category == "safe": return if category == "read-page" and policy.allow_read_page: return if category == "control" and policy.allow_control: return if category == "dangerous" and policy.allow_dangerous: return if category == "keys" and policy.allow_keys: return raise PermissionError( f"Raw command '{command}' is {category} and blocked by default; " "use --allow-read-page, --allow-control, --allow-dangerous, or --allow-keys explicitly" )