Safeguard
Supply Chain Security•

Computer-Use Agents Explained: How AI Runs a Screen and How to Sandbox It

A computer-use agent looks at screenshots and sends clicks and keystrokes. How the loop works, what can go wrong, and a container sandbox you can build.

Safeguard Security Team
44 min read

A computer-use agent is an AI model that operates a desktop or browser like a person: it receives a screenshot, decides what to do, and returns mouse and keyboard actions that your code carries out. Because it acts on whatever appears on screen, run it in a disposable VM or container with no credentials, an egress allow-list and human approval.

This post goes one level deeper than our earlier agent pieces on keeping PII out of agent web searches, self-improving agents, guardrails and rollback for self-modifying agents and hardening Hermes Agent. Here we stay on the screen: the loop, how Anthropic and OpenAI describe their tools in their own docs, the risks, and a sandbox you can build step by step.

Key takeaways

  • The loop: your application sends a task and a screenshot, the model returns actions (click at x, y; type; press a key), your code performs them, and you send back a new screenshot until the model answers in text.
  • The model never touches the machine. Both vendors document the tool as client side, so your executor is where security controls belong.
  • Anthropic's current tool is the computer_toolset_20260801 client toolset with 17 member tools. OpenAI's is the computer tool in the Responses API, which returns a computer_call with an ordered actions array.
  • Both return several actions per turn. Check each action before it runs, not just the first.
  • The main risks are prompt injection from on-screen content, credential exposure and irreversible actions. Both vendors recommend a dedicated VM or container, no sensitive data, a domain allow-list and human confirmation of consequential actions.
  • OWASP LLM01 and LLM06, the OWASP agentic guidance and NIST AI 600-1 map closely onto computer use.
  • Screenshots are sensitive data. Decide retention, redaction and access before the first run.
  • Prefer an API or MCP tool whenever one exists. Use computer use when the only interface is a screen.

What a computer-use agent is and how the loop works

A computer-use agent combines a model that reads images and plans, a tool definition that lets it express screen actions, and an environment (a real or virtual desktop, or a browser) where those actions run. The model has no mouse. It writes structured requests such as "left click at 640, 60" and your program turns them into input events.

Anthropic's documentation describes the environment in five parts: a virtual X11 display served by Xvfb, a lightweight desktop (the Mutter window manager and Tint2 panel), applications such as Firefox and LibreOffice, tool implementations that translate requests into real operations, and the agent loop. It says Claude "doesn't directly connect to this environment"; your application receives the requests, performs them and returns the results.

The loop step by step

  1. Send the task with the computer-use tool enabled. OpenAI's guide notes the first call may contain only a screenshot action, which you answer without changing anything.
  2. The model returns actions. With Anthropic's toolset these are tool_use blocks whose name is the member (for example left_click) and which carry "toolset_name": "computer". With OpenAI's tool it is a computer_call whose actions array lists the steps in order.
  3. Your executor runs them: an xdotool command on a Linux display, a Playwright call in a browser, or an OS input API on a VM. This is where you check policy, ask a human, or refuse.
  4. Return the observation. Anthropic requires one tool_result per tool_use block, matched by ID, with an image for screenshot and zoom and a short text such as OK for the rest. OpenAI expects a computer_call_output holding a computer_screenshot, linked with previous_response_id.
  5. Repeat or finish. Anthropic's docs call the repetition of steps 3 and 4 without user input the "agent loop".

Two things follow. Anything visible on screen becomes model input, including text an attacker placed there. And the model decides, but your code acts: every control in this post lives in step 3.

Batches change how approval works

Both vendors let the model return a short plan in one turn: click the search box, type, press Enter, take a screenshot. Anthropic's docs say to run blocks "in order and stop at the first failure", answer the failed block with is_error: true, and answer every later block with Not executed: an earlier computer action in this turn failed. A request that leaves a block unanswered is rejected.

Both spell out the approval consequence. Anthropic: make a human check "before each block runs, because a batch can complete a multistep action within one turn." OpenAI: "For an action batch, stop before the first action that needs confirmation." An approval check on only the first action lets one turn type a recipient, type a message and press Send unseen.

How Anthropic's computer use tool describes its actions

This section comes from Anthropic's computer use tool documentation and its quickstart repository.

The toolset and its versions

One {"type": "computer_toolset_20260801"} entry in tools gives Claude 17 member tools, and "your application runs every call in an environment you control." The docs list it as generally available on the Claude API and on Claude models on Google Cloud (Google managed). Two earlier versions stay in beta behind headers: computer_20251124 (header computer-use-2025-11-24) and computer_20250124 (header computer-use-2025-01-24); on some platforms only these are available. The tool is not currently available in Claude Managed Agents.

The older versions used one tool named computer with an action field. The toolset gives each action its own member name, adds toolset_name to calls and results, and drops the display size parameters because "coordinates are always in the pixel space of the screenshots you return."

The 17 member tools

MemberInputWhat it does
screenshotnoneCapture the full display
zoomregion as x0, y0, x1, y1Capture one region at full resolution
left_clickoptional coordinate, optional modifier keys in textLeft click at a point or the current cursor
right_click, middle_click, double_click, triple_clicksame as left_clickOther buttons and multiple clicks
left_click_dragstart_coordinate, coordinatePress, drag and release
mouse_movecoordinateMove without clicking
left_mouse_down, left_mouse_upnonePress or release at the cursor
cursor_positionnoneReport the cursor position as text
scrollscroll_direction, scroll_amount, optional coordinateScroll by wheel clicks
typetextType literal text at the focus
keytext such as Return or ctrl+s, optional repeat 1 to 100Press a key or combination
hold_keytext, duration up to 300 secondsHold a key down
waitduration up to 300 secondsPause

All members, including zoom, are enabled by default. You can withhold one with configs, for example {"zoom": {"enabled": false}}, and return is_error: true if Claude calls it anyway. Because a custom tool can share a member's name, the docs say to dispatch on the pair of toolset_name and name.

Withholding members is a real security lever. If a task never drags or holds keys, turn off left_click_drag, left_mouse_down, left_mouse_up and hold_key. If your loop can handle only one action per turn, the docs say to set disable_parallel_tool_use to true in tool_choice, which makes per action approval simpler.

Coordinates, screenshot sizes and cost

The API does not downscale toolset images; an oversized screenshot is rejected. If your display exceeds the model's limit, you resize screenshots yourself and scale Claude's coordinates back up. The docs give two tiers: models that support the toolset accept up to 2,576 pixels on the long edge and 4,784 visual tokens (about 3.75 megapixels); earlier models accept 1,568 pixels and about 1.15 megapixels. Above 20 images in one request a stricter per side limit applies, and the docs suggest 2,000 pixels per side or pruning.

A worked example with the docs' scaling formula on the earlier tier: for a 1,512 by 982 display, the long edge scale is 1,568 / 1,512 = 1.037 and the pixel total scale is the square root of 1,150,000 / 1,484,784, about 0.880. The smaller wins, so screenshots go out at about 1,330 by 864, and every returned coordinate is multiplied by about 1.136 before clicking. Skip this and the docs' troubleshooting table names the symptom: "Clicks consistently offset in one direction." On macOS Retina displays, also account for the 2x pixel ratio.

Declaring the toolset adds about 4,500 input tokens per request (disabling zoom saves about 410), and each screenshot costs roughly 1,000 to 1,800 input tokens, so a 40 step run that keeps every screenshot carries tens of thousands of image tokens.

Anthropic's security considerations

The docs say computer use "has unique risks distinct from standard API features," heightened on the internet, and recommend:

  1. A dedicated virtual machine or container with minimal privileges.
  2. No access to sensitive data such as account login information.
  3. Internet access limited to an allow-list of domains.
  4. A human confirming decisions with meaningful real world consequences and anything needing affirmative consent, such as accepting cookies, financial transactions or terms of service.

They add that "Claude follows commands found in content even when they conflict with your instructions" in some circumstances, including text on webpages or in images. Anthropic describes classifiers that scan tool results such as screenshots for potential prompt injections and steer the model to check whether an instruction really came from you; customers can ask support to turn this off, and "the precautions listed earlier remain important even with these classifiers in place." Developers are asked to inform end users and get consent before enabling computer use in their products.

If the model must log in, the prompting guidance allows passing credentials in tags such as <robot_credentials>, and warns in the same breath that login flows increase "the risk of bad outcomes as a result of prompt injection." Use throwaway test accounts.

The reference implementation

The quickstart repository ships a containerized desktop, tool implementations, an agent loop and a web UI, described in its README as "a deliberately minimal, containerized reference." Its docker run examples pass the API key into the container with -e ANTHROPIC_API_KEY, mount $HOME/.anthropic, and publish ports with flags such as -p 5900:5900. Docker's documentation says a port published without an IP is bound on all interfaces (0.0.0.0). On a shared network, prefix each mapping with 127.0.0.1:. Beyond a demo, keep the API key and agent loop outside the box the model controls.

How OpenAI's computer use tool describes its actions

This section comes from OpenAI's computer use guide, integration recipes page and sample app repository.

Integration paths

OpenAI's SDK runs the Responses tool loop with a "driver" you supply: ComputerToolset for screenshots and mouse or keyboard actions, BrowserToolset for page text, controls and tabs. Calling the API directly, you choose code execution, where the model writes a script that drives a library such as PyAutoGUI or Playwright in an isolated environment, or the computer tool, which returns structured actions. The guide recommends code execution for at least one current model and keeps the computer tool supported. A separate path runs a browser hosted by OpenAI.

For code execution, OpenAI says to "enforce permissions in the exposed helpers and runtime; a single script can perform many actions." The script should only reach your wrapped helpers, not raw OS APIs.

The computer tool and its actions

You enable it with tools: [{"type": "computer"}]. The model replies with a computer_call that has a call_id and an actions array, for example a click with button, x and y followed by a type. The guide lists click, double_click, drag, move, scroll, keypress, type, wait and screenshot, and notes that status: "completed" only means the model finished generating the call; you still execute it. You reply with a computer_call_output carrying a computer_screenshot (with detail: "original" to keep resolution) and continue with previous_response_id. The guide warns that continuing a response "does not restore a browser session, login state, or runtime variables."

The earlier preview used a computer_use_preview tool with display_width, display_height and environment, one action per call; the current tool returns a batched actions[] array.

Environment recipes

The recipes suggest a browser for web apps and a VM for native desktop apps. For local browsers: isolate the browser, pass an empty env so it does not inherit host environment variables, and disable extensions and file system access where possible. The Docker recipe uses debian:bookworm-slim with xfce4, xvfb, xdotool, imagemagick and firefox-esr, an ordinary user, Xvfb :99 with -nolisten tcp, and docker exec instead of VNC, run with --network none, --cap-drop ALL, --security-opt no-new-privileges, --pids-limit 256, --memory 2g, --cpus 2 and --shm-size 512m.

The sample app README is blunt: generated code "runs with your user permissions," the samples "do not provide an operating-system sandbox," screenshots "may include other windows," and "a final answer does not prove the task succeeded."

OpenAI's safety guidance

"Run safely" has four rules: restrict the environment with an allow-list of sites and actions, treat screen content as untrusted, confirm consequential actions, and bound and verify the run with step, time or cost limits. It adds: "Typing sensitive information into a form counts as transmission." The recipes sort actions into three levels:

LevelExamples from OpenAI's guidance
Hand-off requiredThe final step of changing a password; bypassing an HTTPS warning or paywall
Always confirm at action timeDeleting data; changing permissions, sharing or API keys; solving CAPTCHAs; installing or running downloaded software or scripts; sending or posting on the user's behalf; financial transactions; changing VPN or OS security settings
Pre-approval in the first prompt can be enoughLogging in to a site the user asked to visit; uploading, moving or renaming files; transmitting sensitive data the user explicitly approved

The same page defines sensitive data broadly (contact details, medical and legal information, browsing history and logs, identifiers, financial details, passwords, one time codes, API keys, location) and says the model's request to act "is not user permission."

The two tools side by side

AspectAnthropic computer use toolsetOpenAI computer tool
Enable{"type": "computer_toolset_20260801"}{"type": "computer"} (Responses API)
Call shapeOne tool_use block per action with toolset_nameOne computer_call with an actions array
Vocabulary17 members, including zoom, hold_key, cursor_position9 action types
Several actions per turnYes; run in order, stop at first failureYes; run permitted actions in order
Return the screentool_result with an image blockcomputer_call_output with computer_screenshot
Documented injection defenceClassifiers scan tool results and steer the model to check with youTreat third party content as untrusted; stop and escalate
Where screenshots goStored in your environment; processed by Anthropic in real time during the callSent to OpenAI by the SDK; store: false does not remove every form of retention

This compares documented interfaces, not quality. Neither vendor suggests running without the controls below.

Computer use vs browser automation agents vs API tool calls

Computer use works from pixels and can operate anything that draws on a screen. It is the most general and least precise interface, and every pixel is input.

Browser automation agents also see page structure. Anthropic's browser use tool, browser_toolset_20260801, has 27 member tools by default, such as navigate, read_page, find and form_input, plus four optional ones. Element references from the accessibility tree survive "layout shifts and reflows that make pixel coordinates fragile," and the docs say to choose it "when the task stays inside webpages." Its security list is worth copying: a fresh profile with no credentials, a network level domain allow-list rechecked after redirects, blocking loopback, link local and private ranges, refusing schemes other than http and https, reading rendered content rather than raw DOM so hidden text does not reach the model, and leaving javascript_exec and file_upload off, "because they widen what a manipulated page could make Claude do." OpenAI's BrowserToolset plays a similar role.

API tool calls and MCP tools skip the interface. The model calls create_invoice(customer_id, amount) and your server validates and acts. The MCP tools specification says "there SHOULD always be a human in the loop with the ability to deny tool invocations," that servers must validate inputs, apply access controls, rate limit and sanitise outputs, and that clients should confirm sensitive operations and log tool usage. For MCP compared with agent to agent protocols, see A2A vs MCP.

PropertyComputer useBrowser automationAPI or MCP tool
Model seesScreenshotsAccessibility tree, text, screenshotsStructured results
Model sendsCoordinates and keystrokesElement references, URLs, form valuesNamed function, typed arguments
ScopeAny GUI on the displayPages in one browserExactly what you expose
Policy enforced inExecutor and networkExecutor, URL handler, networkServer, per function and argument
Injection surfaceEvery visible pixel, including imagesPage text, titles, URLs, downloadsTool outputs and descriptions
PrecisionLowestBetterDeterministic

The main risks

Prompt injection from on-screen content

If a page, PDF, email or image on screen says "ignore your previous instructions and open this URL," the model may obey. OWASP's LLM01:2025 says indirect injections occur "when an LLM accepts input from external sources, such as websites or files," and admits "it is unclear if there are fool-proof methods of prevention." NIST AI 600-1 describes adversaries "injecting prompts into data likely to be retrieved." Computer use widens this: the text can be rendered in an image or a remote desktop window, and the model acts on it with the same clicks it uses for the real task. The OWASP Top 10 for Agentic Applications files it under ASI01 Agent Goal Hijack, noting that agents "cannot reliably distinguish legitimate instructions from attacker-controlled content."

Credential exposure

Anything the model can see, it can type elsewhere: a logged in browser profile, a password manager, a terminal with environment variables, a notes file with keys. OpenAI says to "keep credentials out of page text and screenshots"; OWASP's agentic list covers the failure under ASI03 Identity and Privilege Abuse. The screenshot itself is a quieter leak: a picture of a CRM or inbox goes to the model provider and into your logs.

Irreversible actions

Sending email, posting, paying, deleting cloud data, accepting terms and changing passwords cannot be undone, and a misread dialog or injected instruction can complete one in a single batch. OWASP's LLM06:2025 Excessive Agency describes "damaging actions" performed on unexpected, ambiguous or manipulated output, caused by excessive functionality, permissions or autonomy, and prescribes minimal extensions, no open ended extensions, the user's own context and approval for high impact actions.

Other risks

Runaway loops and cost (OWASP's threats guide T4 Resource Overload), actions nobody can trace (T8 Repudiation and Untraceability), reviewers who approve everything (NIST AI 600-1's Human-AI Configuration risk, which includes automation bias), and running downloaded code (ASI05 Unexpected Code Execution, which OpenAI puts in its always confirm list).

How to sandbox a computer-use agent

Five layers, each covering a different failure.

Layer 1: an isolation boundary you throw away

BoundaryHow it isolatesGood forWatch out for
Hardened containerNamespaces and cgroups on a shared kernel; Docker's default seccomp profile disables around 44 of 300+ system callsDevelopment, CI, tasks with no secretsA kernel bug can mean escape
Rootless containerDaemon and containers in a user namespace as a non-root userDeveloper laptopsSome features differ; read the limitations
Application kernel (gVisor runsc)System calls go to a per sandbox userspace kernel written in GoUntrusted workloads on shared hostsTest the GUI stack for compatibility
MicroVM (Firecracker)Lightweight VM with its own guest kernelServices starting many sandboxesMore plumbing to build
Full VMHypervisor and full guest OSNative desktop apps, Windows or macOS tasksSlower reset; snapshot discipline

Docker's docs call the default seccomp profile "an allowlist that denies access to system calls by default," and say no-new-privileges stops su or sudo from raising privileges. gVisor calls itself "an application kernel that implements a Linux-like interface," with an OCI runtime, runsc, that works with Docker. Firecracker describes itself as virtualization "purpose-built for creating and managing secure, multi-tenant container and function-based services."

Reset per task. A persistent desktop accumulates cookies, downloads and history, and lets one poisoned session influence the next. If your agent keeps long term memory, read AI agent memory poisoning and memory hardening.

Layer 2: no host credentials, no API key in the box

No SSH keys, cloud credentials, copied browser profiles, mounted home directories or Docker socket. The model API key belongs to the agent loop, which runs outside the sandbox. If a task must log in, use a dedicated low privilege account, rotate its password after each run, or hand the login to a human.

Layer 3: a network allow-list enforced outside the sandbox

"Only visit example.com" in a prompt is advice, not a control. Put the desktop on a network with no route out and give it one exit through a forward proxy that allows named domains only. Docker's docs say containers on an --internal network "may communicate between each other, but not with any other network, as no default route is configured." Recheck the allow-list after redirects, and block loopback, link local, private and cloud metadata addresses.

Layer 4: a policy gate and confirmation in the executor

Refuse action types you have not allowed, count steps against a hard limit, and pause for a human before typing text, pressing Enter or clicking a region you have marked as "submit", unless the first prompt pre-approved it. When a batch reaches an action that needs approval, stop there and tell the model what did and did not run, as OpenAI's guidance requires. If the reviewer declines, do not let the model retry silently.

Layer 5: logs you can trust and afford to keep

Record every action, approval and screenshot hash with a run ID and timestamps, and keep screenshots separately with short retention. The privacy section below covers the details.

Build a minimal reference sandbox, step by step

This builds a Debian desktop on Xvfb with Firefox ESR, no direct internet, a Squid forward proxy with a domain allow-list, and a Python executor on the host that runs actions through docker exec, enforces a policy, asks for confirmation and writes a JSONL audit log. It follows OpenAI's container recipe and Anthropic's environment description and adds egress control and an audit trail. The executor takes simple action dictionaries, so you write a thin adapter from either vendor's format.

Warning: the cleanup commands remove containers and networks by name. Before running them, check docker ps -a --filter name=cua and docker network ls --filter name=cua and confirm the names match only what you created here.

Step 1: lay out the project

mkdir -p cua-sandbox/desktop cua-sandbox/proxy cua-sandbox/runs
cd cua-sandbox

Step 2: the desktop image

desktop/Dockerfile:

FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:99

RUN apt-get update && apt-get install -y --no-install-recommends \
      xvfb xfce4 dbus-x11 x11-utils xdotool imagemagick \
      firefox-esr ca-certificates curl \
 && rm -rf /var/lib/apt/lists/* \
 && useradd --create-home --shell /bin/sh agent \
 && mkdir -p /etc/firefox/policies

COPY policies.json /etc/firefox/policies/policies.json

USER agent
WORKDIR /home/agent

CMD set -eu; \
    Xvfb :99 -screen 0 1280x800x24 -nolisten tcp & \
    pid=$!; \
    until xdpyinfo -display :99 >/dev/null 2>&1; do kill -0 "$pid"; sleep 0.2; done; \
    exec dbus-run-session -- startxfce4

-nolisten tcp keeps the X server off the network; Xvfb's manual describes it as an X server "that can run on machines with no display hardware and no physical input devices." curl is there only for verification.

desktop/policies.json uses Firefox enterprise policies. Mozilla's policy templates document /etc/firefox/policies as the system wide location on Linux and a Proxy policy with Mode, Locked, HTTPProxy and UseHTTPProxyForAllProtocols:

{
  "policies": {
    "Proxy": {
      "Mode": "manual",
      "HTTPProxy": "egress:3128",
      "UseHTTPProxyForAllProtocols": true,
      "Locked": true
    },
    "PasswordManagerEnabled": false,
    "OfferToSaveLogins": false,
    "DisableTelemetry": true
  }
}

Locking the proxy means no click path through settings can turn it off; disabling the password manager means a credential typed during a run is not saved.

Step 3: the egress proxy

proxy/Dockerfile:

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends squid \
 && rm -rf /var/lib/apt/lists/*
COPY squid.conf /etc/squid/squid.conf
CMD ["squid", "-N", "-f", "/etc/squid/squid.conf"]

proxy/squid.conf, with an allow-list you edit per task:

http_port 3128

acl allowed_sites dstdomain .wikipedia.org .python.org
acl SSL_ports port 443
acl Safe_ports port 80 443
acl CONNECT method CONNECT

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow allowed_sites
http_access deny all

access_log stdio:/dev/stdout
cache deny all

Squid's http_access reference says that when no line matches, the default is the opposite of the last line, so the list ends with an explicit deny all. The access log goes to standard output, so docker logs egress records every allowed and refused request. For HTTPS the proxy sees the host name in the CONNECT request but not the content, so the allow-list works without breaking TLS.

Step 4: networks and containers

docker build -t cua-desktop ./desktop
docker build -t cua-egress ./proxy

# Internal network: no default route out
docker network create --internal cua-internal

# Proxy: outbound on the default bridge, then joined to the internal network
docker run -d --name egress --network bridge \
  --cap-drop ALL --cap-add SETUID --cap-add SETGID \
  --security-opt no-new-privileges --read-only \
  --tmpfs /var/spool/squid --tmpfs /var/log/squid --tmpfs /run \
  cua-egress
docker network connect cua-internal egress

# Desktop: internal network only, nothing published
docker run -d --rm --name cua-desktop --network cua-internal \
  --cap-drop ALL --security-opt no-new-privileges \
  --pids-limit 256 --memory 2g --cpus 2 --shm-size 512m \
  --read-only --tmpfs /tmp --tmpfs /home/agent:uid=1000,gid=1000 \
  cua-desktop

The capability, privilege, PID, memory, CPU and shared memory flags come from OpenAI's recipe. --read-only mounts the root filesystem read only (Docker's run reference), and the tmpfs mounts give the browser somewhere to write that vanishes with the container. If an application fails under a read only root, add a tmpfs for the path it needs rather than dropping the flag. If Squid will not start with these restrictions, read docker logs egress; it may need another capability such as CHOWN, or you can run it as an unprivileged user from the start. No ports are published. If you must watch through VNC, publish it on loopback only, for example -p 127.0.0.1:5900:5900. With gVisor installed and registered, add --runtime=runsc to the desktop and test it.

Step 5: verify the boundary before the model sees it

docker exec cua-desktop id                       # expect uid=1000(agent)
docker inspect --format '{{.HostConfig.CapDrop}} {{.HostConfig.ReadonlyRootfs}}' cua-desktop
docker port cua-desktop                          # expect no output

# Direct internet and metadata should fail
docker exec cua-desktop curl -sS -m 5 https://example.com || echo "blocked as expected"
docker exec cua-desktop curl -sS -m 5 http://169.254.169.254/ || echo "blocked as expected"

# Through the proxy: allowed domain succeeds, anything else is refused
docker exec cua-desktop curl -sS -m 10 -o /dev/null -w '%{http_code}\n' -x http://egress:3128 https://www.wikipedia.org
docker exec cua-desktop curl -sS -m 10 -o /dev/null -w '%{http_code}\n' -x http://egress:3128 https://example.com || true

# Can the desktop resolve outside names on its own? If yes, DNS is a side channel to close
docker exec cua-desktop getent hosts example.com || echo "no outside resolution"

# Test screenshot
docker exec cua-desktop import -window root png:- > runs/check.png && file runs/check.png

If any "blocked" line prints content instead, fix the network before going on. If outside names resolve, the browser still only reaches allowed sites through the proxy, but other processes could leak data in DNS queries, the pattern OWASP's ASI02 examples describe; give the proxy a fixed address and remove the desktop's resolver if your threat model needs it.

Step 6: the executor with a policy gate and an audit log

This Python script runs on the host, outside the sandbox. Map Anthropic's members (left_click, type, key, scroll, screenshot) or OpenAI's actions (click, type, keypress, scroll, screenshot) into its dictionaries.

#!/usr/bin/env python3
"""Minimal computer-use executor: policy gate, confirmation, JSONL audit log."""
import hashlib, json, subprocess, time, uuid
from pathlib import Path

CONTAINER = "cua-desktop"
RUN_ID = uuid.uuid4().hex[:12]
RUN_DIR = Path("runs") / RUN_ID
RUN_DIR.mkdir(parents=True, exist_ok=True)
AUDIT = RUN_DIR / "audit.jsonl"

MAX_STEPS = 60
ALLOWED = {"screenshot", "click", "double_click", "move", "scroll", "type", "key", "wait"}
CONFIRM_KEYS = {"Return", "KP_Enter", "ctrl+Return"}
BLOCKED_KEYS = {"ctrl+alt+Delete", "super", "ctrl+alt+t"}
steps = 0
prev_hash = "0" * 64

def audit(event: dict) -> None:
    """Append-only log; each line carries the hash of the previous line."""
    global prev_hash
    event.update({"run": RUN_ID, "ts": time.time(), "prev": prev_hash})
    line = json.dumps(event, sort_keys=True)
    prev_hash = hashlib.sha256(line.encode()).hexdigest()
    with AUDIT.open("a") as fh:
        fh.write(line + "\n")

def sh(*args: str) -> bytes:
    return subprocess.run(["docker", "exec", CONTAINER, *args],
                          check=True, capture_output=True, timeout=30).stdout

def screenshot() -> bytes:
    png = sh("import", "-window", "root", "png:-")
    digest = hashlib.sha256(png).hexdigest()
    (RUN_DIR / f"{steps:04d}-{digest[:12]}.png").write_bytes(png)
    audit({"kind": "observation", "sha256": digest, "bytes": len(png)})
    return png

def needs_confirmation(a: dict) -> bool:
    if a["type"] == "type":
        return True                                   # typing is transmission
    return a["type"] == "key" and a["text"] in CONFIRM_KEYS   # Enter usually submits

def confirm(a: dict) -> bool:
    if a["type"] == "type":
        print(f"Model wants to type: {a['text']!r}")
    return input(f"Allow {a['type']} action? [y/N] ").strip().lower() == "y"

def run(a: dict) -> str:
    t = a["type"]
    if t == "screenshot":
        screenshot(); return "OK"
    if t in ("click", "double_click"):
        button = {"left": "1", "middle": "2", "right": "3"}[a.get("button", "left")]
        reps = "2" if t == "double_click" else "1"
        sh("xdotool", "mousemove", str(a["x"]), str(a["y"]), "click", "--repeat", reps, button)
    elif t == "move":
        sh("xdotool", "mousemove", str(a["x"]), str(a["y"]))
    elif t == "scroll":
        button = {"up": "4", "down": "5", "left": "6", "right": "7"}[a["direction"]]
        count = str(min(int(a.get("amount", 3)), 10))
        sh("xdotool", "mousemove", str(a["x"]), str(a["y"]), "click", "--repeat", count, button)
    elif t == "type":
        sh("xdotool", "type", "--delay", "12", "--", a["text"])
    elif t == "key":
        sh("xdotool", "key", "--clearmodifiers", a["text"])
    elif t == "wait":
        time.sleep(min(float(a.get("seconds", 1)), 10))
    return "OK"

def execute_batch(actions: list[dict]) -> list[dict]:
    """Run actions in order; stop at the first refusal, decline or failure."""
    global steps
    results, halted = [], False
    for a in actions:
        if halted:
            results.append({"ok": False, "text": "Not executed: an earlier action in this turn did not run."})
            continue
        steps += 1
        rec = {"kind": "action", "step": steps, "type": a.get("type")}
        if a.get("type") == "type":                   # never log typed text itself
            rec.update(text_len=len(a["text"]), text_sha256=hashlib.sha256(a["text"].encode()).hexdigest())
        elif a.get("type") == "key":
            rec["key"] = a.get("text")
        elif "x" in a:
            rec["xy"] = [a["x"], a["y"]]
        blocked_key = a.get("type") == "key" and a.get("text") in BLOCKED_KEYS
        if steps > MAX_STEPS or a.get("type") not in ALLOWED or blocked_key:
            audit({**rec, "decision": "refused"})
            results.append({"ok": False, "text": "Refused by sandbox policy. Stop and report."})
            halted = True; continue
        if needs_confirmation(a) and not confirm(a):
            audit({**rec, "decision": "declined"})
            results.append({"ok": False, "text": "Declined by the user. Do not retry this action."})
            halted = True; continue
        try:
            results.append({"ok": True, "text": run(a)})
            audit({**rec, "decision": "executed"})
        except Exception as exc:
            audit({**rec, "decision": "failed", "error": str(exc)[:200]})
            results.append({"ok": False, "text": f"Failed: {exc}"})
            halted = True
    return results

What it does: arguments go to docker exec as a list, never a shell string, so typed text cannot become a host command, and the -- before typed text stops xdotool reading it as options. Typed text is never logged, only its length and SHA-256, so a password typed during a run does not land in a log; the reviewer sees it at confirmation time. Each log line includes the previous line's hash, so edits or deletions break the chain. A batch halts at the first refusal, decline or failure, mirroring both vendors' rules. Scrolls, waits and total steps are capped. What it does not do is understand screen regions; a production version would also confirm clicks inside areas you have labelled "send", "buy" or "delete".

Step 7: wire it to a model and run one task

Your loop calls the vendor API, converts each call into the dictionaries above, runs execute_batch, and builds the vendor's result format: tool_result blocks with toolset_name for Anthropic, or a computer_call_output for OpenAI. Attach a fresh screenshot at the end of each batch so the model always sees current state, as Anthropic's docs suggest. Start with a harmless task on an allowed domain, such as reading the first sentence of the Wikipedia article on Xvfb, then read the audit log and docker logs egress side by side.

Step 8: tear down after every task

# Check names first: docker ps -a --filter name=cua ; docker network ls --filter name=cua
docker rm -f cua-desktop egress
docker network rm cua-internal

With --rm, a read only root and a tmpfs home, stopping the desktop discards the browser profile, downloads and history. Keep runs/<id> only as long as your retention policy says.

What OWASP and NIST guidance says that applies here

Sources: OWASP's Top 10 for LLM Applications 2025, its Agentic AI Threats and Mitigations guide (17 February 2025), the OWASP Top 10 for Agentic Applications for 2026 (9 December 2025), NIST AI 600-1 (released 26 July 2024) and the NIST AI RMF 1.0 (released 26 January 2023).

Computer use riskOWASP LLM Top 10 2025OWASP agentic guidanceNIST AI 600-1 riskControl above
Injected instructions on screenLLM01 Prompt InjectionASI01 Agent Goal Hijack; T6 Intent Breaking and Goal ManipulationInformation SecurityProxy allow-list, nothing secret in the box, confirm before typing and submitting
Too many verbs, too much accessLLM06 Excessive AgencyASI02 Tool Misuse and Exploitation; T2 Tool MisuseInformation SecurityWithhold members, executor allow-list, step limit
Credentials taken or misusedLLM02 Sensitive Information DisclosureASI03 Identity and Privilege Abuse; T3 Privilege CompromiseData PrivacyNo host credentials, throwaway accounts
Running downloaded or generated codeLLM06 Excessive AgencyASI05 Unexpected Code Execution; T11 Unexpected RCEInformation SecurityRead only root, dropped capabilities, confirm installs
No record of who did what(none)T8 Repudiation and UntraceabilityInformation SecurityHash chained JSONL log, proxy log
Reviewer approves everythingLLM09 Misinformation (over-reliance)ASI09 Human-Agent Trust Exploitation; T10 Overwhelming Human in the LoopHuman-AI ConfigurationConfirm only at the point of risk, show exact text
Runaway loopsLLM10 Unbounded ConsumptionT4 Resource OverloadInformation SecurityStep, time and resource limits

LLM01's mitigations include least privilege, human approval for high risk actions, segregating external content, and adversarial testing "treating the model as an untrusted user." Design the sandbox as if a stranger were at the keyboard.

LLM06 says to avoid open ended extensions such as "run a shell command" in favour of granular ones. For computer use, the screen is the open ended extension, which is the strongest argument for the decision table below.

ASI02's mitigations read like an executor spec: per tool least privilege profiles with scopes, rate limits and egress allow-lists; action level approval with a dry run or preview; sandboxes that "deny all non-approved network destinations"; and a policy enforcement point that treats planner output as untrusted and issues short lived credentials. Its examples include an approved low risk tool abused to leak data through DNS queries, which is why step 5 checks name resolution.

T8 recommends "comprehensive logging, cryptographic verification, enriched metadata, and real-time monitoring," with signed, immutable logs where regulation requires.

NIST AI 600-1 is the generative AI profile of the AI RMF and lists twelve risks; Information Security, Data Privacy, Human-AI Configuration and Value Chain and Component Integration matter most here. Its MANAGE 2.4 actions ask for criteria and procedures to disengage or deactivate a system whose outcomes are inconsistent with intended use; for an agent, that is a kill switch that stops the run and revokes its access. MANAGE 4.3 asks you to track errors, near misses and negative impacts and to run after action assessments. NIST's AI RMF page says the framework is being revised as part of the White House AI Action Plan, so check for a newer version before building a compliance mapping. For EU AI Act mapping, see governing self-improving agents under the EU AI Act and NIST AI RMF.

How to handle audit logs, screenshots and replay for privacy

A replay is your best debugging tool and one of your most sensitive files: screenshots catch names, email contents, account numbers and anything the agent scrolled past.

Know where data goes. Anthropic's docs say that for computer use "all screenshots, mouse actions, keyboard inputs, and any files involved in a session are captured and stored in your environment, not by Anthropic," which processes them in real time during the API call under its retention terms; the docs mark computer use as eligible for zero data retention arrangements. OpenAI's guide says its SDK "sends returned page text or screenshots to OpenAI" and that store: false "does not remove every form of data retention." Read each provider's retention terms for your account before using real data.

ArtifactKeep?How
Action log (types, coordinates, keys, decisions, times)Yes, for your audit periodAppend only JSONL; typed text as length and hash
Approval decisions and reviewer identityYesSame log, same run ID
Proxy access logYesHost names and status codes only
Full screenshotsShort termEncrypted at rest, restricted access, deleted after a set number of days unless tied to an incident
Screenshot hashesYesIn the action log, to prove a stored image is the one the model saw
Prompts and responsesDependsRedact secrets and personal data

Practical rules:

  1. Minimise at capture. Use a small virtual display holding only the needed application, never a real desktop with other windows.
  2. Redact what you store, not what you send. Blurring the model's input breaks the task; keep full images briefly and redacted or hashed versions long term.
  3. Separate duties. Treat replay access like production data access.
  4. Tell people. If the agent works on screens showing customer or employee data, your privacy notice and processing records should say so, and Anthropic asks developers to get end user consent.
  5. Automate deletion. A forgotten folder of screenshots is exactly the leakage NIST AI 600-1's Data Privacy risk describes.

When to use computer use and when to use an API or MCP tool

SituationUseWhy
The system has a documented APIAPI tool callTyped, deterministic, authorised server side
An MCP server exists or you can write oneMCP toolSame benefits plus per tool permissioning
Acting inside web pagesBrowser automation toolReferences beat pixels; narrower scope
Reading public pages onlyWeb fetch or search toolNo interaction, smaller attack surface
Legacy desktop app with no APIComputer use in a VMThe screen is the only interface
GUI testing of your own appComputer use in a containerYou control the app and the data
Payments, account changes, messages to third partiesAPI with approval, or computer use with hand-offIrreversible; keep a human at the point of risk
A real user's logged in desktopAvoid, or hand to the humanCredentials and personal data on screen

If you are writing a long prompt telling an agent which button to press in which order, you have found an API you should build.

Common mistakes

  1. Running the agent on your own desktop. Your sessions and password manager are one screenshot away.
  2. Putting the API key inside the sandbox. A hijacked plan can show it on screen or type it into a form.
  3. Allow-listing in the prompt instead of the network. Only a proxy that refuses everything else is a control.
  4. Confirming only the first action in a batch. Check each action before it runs.
  5. Logging typed text in clear. Logs get copied to tickets and chat. Store a hash and a length.
  6. Publishing VNC on all interfaces. Use 127.0.0.1: in the mapping, or use docker exec and publish nothing.
  7. Reusing one desktop for every task. Cookies, downloads and anything an attacker planted carry over.
  8. Trusting the final answer. Check the result in the target system.
  9. Ignoring coordinate scaling. Unscaled clicks land on the wrong spot, sometimes on the wrong button.
  10. Leaving every member enabled. Withhold what the task does not need.

FAQ

What is a computer-use agent in simple terms?

An AI model connected to a tool that lets it see a screen through screenshots and control it with clicks and keystrokes. Your software performs the actions and returns the new screen, in a loop, until the task is done.

Is computer use the same as RPA?

No. Classic automation follows a script written in advance. A computer-use agent picks its next action from what it sees, which is more flexible and less predictable, so it needs a sandbox and approvals.

Does the AI model connect to my computer directly?

No. In both vendors' designs the model returns action requests and your application executes them in an environment you control.

Can a website hijack a computer-use agent?

It can try. Both vendors document that instructions on a page or in an image can steer the model. Anthropic describes injection classifiers and OpenAI tells developers to treat screen content as untrusted; neither presents this as a complete fix.

Should I give a computer-use agent my passwords?

Avoid it. Use a dedicated low privilege account with a rotated password, or hand the login to a human. Anthropic warns that login flows raise prompt injection risk.

Is a Docker container enough isolation?

For development and low risk tasks without secrets, a hardened container with dropped capabilities, no new privileges, a read only root and no direct network is a reasonable start. Containers share the host kernel, so for untrusted workloads consider gVisor, a microVM or a full VM.

How do I limit which websites the agent can visit?

Put its desktop on a network with no route out, give it one forward proxy that allows named domains, lock the browser's proxy setting, and block private, loopback and metadata addresses.

Where are computer-use screenshots stored?

In your environment, and also with the model provider during the API call. Anthropic's docs say computer use data is stored on your side and processed in real time; OpenAI's say its SDK sends screenshots to OpenAI and that store: false does not remove every form of retention.

When should I use MCP instead of computer use?

Whenever the system has an API or MCP server, or you can build one. Tool calls are typed, authorised server side and keep screenshots of your data out of the model's context.

Where Safeguard fits

Safeguard is not a computer-use sandbox: it does not run or watch agent desktops and does not detect prompt injection in screenshots. The overlap is narrower. The sandbox above is built from open source software (Debian packages, Xvfb, xdotool, ImageMagick, Firefox ESR, Squid) plus the Python libraries your loop imports. Safeguard can scan the images and repositories you build (safeguard scan --image cua-desktop, safeguard scan --dir .), match components against vulnerability databases, apply reachability analysis, show which projects carry a given version, and gate builds with policies. For agents that reach your systems through tools rather than screens, Safeguard's MCP server lets assistants such as Claude query risk with tools you choose. MCP Guardrail, which generates guardrail proxy URLs for MCP servers, and the AI Gateway runtime guardrail proxy are both in early access, so ask before you plan around either. None of this replaces isolation, allow-listing and approvals.

Software supply chain hygiene still matters for computer use because every sandbox is an image built from dozens of open source packages, and the browser and image libraries in it are the first code an attacker's page talks to. The fit is indirect: it covers the images and dependencies you build, not the model's behaviour on screen.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show how Safeguard's own agent surfaces handle tool access and approvals; none shows a computer-use sandbox. You can download Safeguard and follow the CLI docs to try them.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. It is the API side of the decision table: instead of letting an agent click through a dashboard, you hand it a short list of named tools.

Click through it at your own pace, or open it full screen.

This is a real CLI session: asked to fix a lodash CVE, the CLI reads the finding, asks before each command, edits package.json, runs npm install and npm test with approval, and pushes nothing. That "ask before each action" pattern is what your executor should enforce, and /permissions shows how modes from Ask to Strict change what runs without a prompt.

Click through it at your own pace, or open it full screen.

This real Android session sends work to a computer running the CLI and approves the command on the phone with Allow once, Allow for turn or Deny. It is confirmation at the point of risk, with the reviewer away from the machine doing the work.

Click through it at your own pace, or open it full screen.

Quick check

A model returns three actions in one turn: click the "To" field, type an address, press Enter. Your executor asks for approval only on the first action of each turn. What goes wrong, and what should it do instead?

The click is approved, then the typing and Enter run unreviewed, so a message can be sent before anyone sees it. Both vendors' docs say to check each action before it runs: stop before the first action that needs confirmation, run nothing after it until a human approves, and tell the model what did and did not run.

Your display is 1,512 by 982 pixels and you send screenshots scaled to fit a 1,568 pixel long edge and 1.15 megapixel limit. The model asks to click at 600, 400. Where should your executor click?

Total pixels is the limiting factor: the scale is the square root of 1,150,000 divided by 1,484,784, about 0.880, so screenshots are about 1,330 by 864. Divide by 0.880 to map back: about 682, 455 on the real display. Clicking at 600, 400 unscaled lands up and to the left of the target.

Sources

Last reviewed: 23 October 2026. Both vendors' computer-use tools change version often; check the linked documentation for the tool version and models your account supports before you build.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.