Restrict tool access with permission policies
Use YAML or Rego policies to control which MCP tools an agent can call and what arguments it may pass.
Cua Driver's permission policy engine lets you define exactly which tools an agent may call, and — for sensitive tools — restrict the argument values it can supply. The engine is deny-by-default: any tool not explicitly allowed is blocked. Two policy formats are supported — YAML for straightforward allow/deny lists and Rego for programmable logic.
Before you start#
- Cua Driver installed and working (
cua-driver --version) - A running
cua-driver servedaemon (or a process about to be started) - For Rego policies: a build with the
regofeature enabled (included in the default release binary)
Create a YAML policy#
Create a file, for example ~/.cua-driver/policy.yaml, that lists the tools you want to allow:
allow:
tools:
- screenshot
- get_window_state
- list_apps
- list_windows
- click
- type_text
- press_key
- scroll
- launch_app
- wait
deny:
tools:
- shell_executeEvery tool in allow.tools is unconditionally permitted. Every tool in deny.tools is rejected even if it also appears in an allow rule. Any tool not mentioned in either section is denied by the default policy.
Add argument constraints to a YAML policy#
Use allow.rules to permit a tool only when its arguments satisfy bounds, length, pattern, or allowed-value checks:
allow:
tools:
- screenshot
- get_window_state
rules:
- tool: type_text
constraints:
text:
max_length: 500
pattern: "^[\\x20-\\x7E\\n\\t]*$" # printable ASCII only
- tool: scroll
constraints:
amount:
min: -20
max: 20
- tool: launch_app
constraints:
bundle_id:
allowed:
- "com.apple.Safari"
- "com.google.Chrome"
- "com.microsoft.VSCode"
deny:
tools:
- shell_executeEach constraints key matches a JSON argument name. A rule allows the call only when all constraints pass. If you list the same tool in more than one rule, the call is allowed when any rule matches.
The type_text_chars tool name is automatically aliased to type_text before policy evaluation, so a rule on type_text covers both forms.
Restrict a browser-only agent#
For an agent that should work only in a browser Cua Driver has already prepared, allow inspection and ordinary page interaction while denying profile setup, local-file transfer, downloads, and the legacy mutation surface:
allow:
tools:
- list_apps
- list_windows
- start_session
- end_session
- get_browser_state
- browser_click
- browser_type
- browser_pointer
- browser_dialog
- wait
rules:
- tool: browser_navigate
constraints:
url:
pattern: "^https://(docs\\.example\\.com|app\\.example\\.com)(/|$)"
deny:
tools:
- browser_prepare
- browser_set_input_files
- browser_download
- page
- shell_executeReplace the example hosts with the exact destinations required for the task. Run profile preparation separately under maintainer control before starting the restricted agent.
Tool policy cannot predict what a page will do after a click, form submit,
script, or redirect. A host allowlist constrains explicit
browser_navigate calls, not destinations reached through page behavior.
Keep consequential accounts and existing authenticated profiles outside an
untrusted agent's policy boundary.
Create a Rego policy#
For more complex access control — for example, allowing different tool sets based on the active task or runtime conditions — write a Rego policy:
package cua.policy
import rego.v1
# Tools that are always allowed.
safe_tools := {
"screenshot",
"get_window_state",
"list_apps",
"list_windows",
"click",
"press_key",
"scroll",
"wait",
}
allow if {
input.tool in safe_tools
}
# Allow type_text only for short printable strings.
allow if {
input.tool == "type_text"
count(input.arguments.text) <= 500
regex.match(`^[\x20-\x7E\n\t]*$`, input.arguments.text)
}Save this as ~/.cua-driver/policy.rego. The policy engine evaluates data.cua.policy.allow; the rule must produce a boolean. undefined is treated as false (deny).
Load a policy directory (Rego only)#
When you point the environment variable at a directory instead of a single file, the engine loads every .rego file in that directory in lexicographic order:
mkdir -p ~/.cua-driver/policies
# Place policy files in the directory:
# 00_safe_tools.rego
# 10_type_text.regoSet the environment variable to the directory path:
export CUA_DRIVER_POLICY_FILE=~/.cua-driver/policiesA directory path only works with Rego. Pointing it at a directory when the rego feature is disabled returns an error on startup.
Enable the policy#
Set CUA_DRIVER_POLICY_FILE to the path of your policy file before starting the driver:
export CUA_DRIVER_POLICY_FILE=~/.cua-driver/policy.yaml
cua-driver serveThe policy is loaded once when the process starts. Changing the file while the daemon is running has no effect until the daemon restarts. A missing or invalid configured file prevents the daemon from binding its action socket.
Verify the policy is active#
Make a call that should be denied and confirm the error:
cua-driver call shell_execute '{"command":"echo hello"}'
# Error: tool 'shell_execute' is explicitly deniedMake a call that should be allowed:
cua-driver call screenshot '{}'
# ✅ ...Add the policy to autostart (macOS launchd)#
If you use cua-driver autostart enable, add the environment variable to the launchd plist so it is set when the daemon starts automatically:
-
Find the plist file:
cat ~/Library/LaunchAgents/com.trycua.cua-driver.plist -
Add an
EnvironmentVariableskey:<key>EnvironmentVariables</key> <dict> <key>CUA_DRIVER_POLICY_FILE</key> <string>/Users/you/.cua-driver/policy.yaml</string> </dict> -
Reload the agent:
launchctl unload ~/Library/LaunchAgents/com.trycua.cua-driver.plist launchctl load ~/Library/LaunchAgents/com.trycua.cua-driver.plist
Troubleshooting#
All calls are denied even though my policy allows them.
Check that CUA_DRIVER_POLICY_FILE points to the correct path and that the daemon has been restarted after setting the variable. A typo or unreadable file now fails daemon startup instead of disabling enforcement.
The daemon fails to start with a policy parse error.
YAML policies must not contain empty tool names, and Rego policies must define data.cua.policy.allow as a boolean rule. Run cat $CUA_DRIVER_POLICY_FILE to confirm the file is readable and syntactically valid.
A tool I allowed still returns a denial.
The rule might exist but the argument constraints are not satisfied. Check the denial message: "argument 'text' must be at most 500 characters" tells you which constraint failed. For Rego policies, add a print statement temporarily to trace which branch runs.
I want to allow all tools.
Do not set CUA_DRIVER_POLICY_FILE. The user-policy layer is then absent. The built-in risk map, permission mode, managed policy, and active resource grants still apply.
Related#
- Permission policies: full YAML schema and Rego interface reference
- How permission policies work: the engine internals, evaluation order, and trust model
- Permission modes and bounded autonomy: standard, bounded, and unrestricted startup
- MCP tools: complete list of tool names and their arguments