Cua Docs

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 serve daemon (or a process about to be started)
  • For Rego policies: a build with the rego feature 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_execute

Every 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_execute

Each 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_execute

Replace 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.rego

Set the environment variable to the directory path:

export CUA_DRIVER_POLICY_FILE=~/.cua-driver/policies

A 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 serve

The 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 denied

Make 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:

  1. Find the plist file:

    cat ~/Library/LaunchAgents/com.trycua.cua-driver.plist
  2. Add an EnvironmentVariables key:

    <key>EnvironmentVariables</key>
    <dict>
      <key>CUA_DRIVER_POLICY_FILE</key>
      <string>/Users/you/.cua-driver/policy.yaml</string>
    </dict>
  3. 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.