Security model
What this defends against, how each gate actually works, and - the part most pages skip - what it does not protect you from.
The threat we built for
An agent with a screenshot tool and a keyboard on a working developer's Mac. Not a hostile user: a useful agent, on a machine that happens to also hold an SSH key, a password manager, a customer database and a production terminal. The failure modes are mundane and they are why people don't run these tools where the work is:
| Failure | What Computer MCP does |
|---|---|
| A screenshot catches a password field mid-type, or a vault while unlocked. | Secure fields and password-manager windows are painted out in memory, before the file is written. |
| An injected instruction on a web page becomes a keystroke in a terminal. | Terminals are on the always-ask list. Every attempt is logged whether it is allowed or refused. |
| An agent does something irreversible while nobody is watching. | Writes need consent. An unanswered dialog is a refusal, not a default yes. |
| Nobody can say afterwards what the agent actually did. | Append-only log of every call, its target, and the decision. |
Gate 1 - redaction happens before the file exists
The order is the whole point:
capture (in memory)
→ find secure rectangles via the Accessibility API
→ paint them opaque black on the bitmap
→ only then encode and write the PNG
Had we written the file first and redacted after, there would be a window - short, but real - where an unredacted picture of your desktop sat on disk for any other process to read. A security property with a race in it is not a security property.
Two things get painted:
- Secure text fields. Native fields expose the role
AXSecureTextField. Fields in a web page expose the roleAXTextFieldwith the subroleAXSecureTextField. Checking only the role catches native fields and lets every browser password box through - which is the case that actually matters. We check both. - Whole windows of listed apps. Keychain Access, 1Password, Bitwarden, LastPass, Dashlane, Apple Passwords. We do not walk into those windows to decide what to hide; descending into a vault to work out what is secret is the mistake we are avoiding.
The rectangles are painted solid black, not blurred or pixelated. A good enough model can read through pixelation. It cannot read through black.
How we know it works. test/redaction-unit.py builds a synthetic image with a marked block, redacts it, and checks four things: the block is black, the background is untouched, redacting somewhere else leaves the block intact, and a block in the top half is not mirrored to the bottom. That last one catches a flipped y-axis - the bug that would silently black out the wrong part of your screen while leaving the password visible. We verified the test by breaking the code on purpose: with the flip removed, it goes red and names the reason.
Gate 2 - consent, and what it is not
Three modes. ask is the default.
readonly | Write tools are not listed at all. A model that can see a tool will eventually try it; in read-only mode the hands do not exist. |
|---|---|
ask | The first write opens a macOS dialog naming the specific action. Approving grants the rest of the session. |
allow | Writes proceed, still logged. |
Two rules override all three:
- Always-ask apps ask every time - password managers and terminals - including in
allowmode and including after the session was approved. This is not configurable. The difference between “I let the agent work” and “I let the agent into my vault” is worth one dialog. - Silence is refusal. The dialog expires and the call is denied. This is the property most likely to fail quietly, so it has its own test, and that test has been mutation-checked: make the timeout resolve to yes, and the test catches a click that should never have happened.
Consent is not containment. After you approve a session, the agent is driving your actual Mac - that is what you approved. Computer MCP makes the dangerous moments visible and deliberate. It does not make them impossible. If what you need is a boundary rather than a decision, run the agent in a VM, and treat that as the honest answer rather than a feature we are missing.
Gate 3 - the audit log
~/.local/state/computer-mcp/audit.jsonl, mode 0600, appended only. One line per call: the tool, the tier, the scrubbed arguments, the app it targeted, the mode, and whether it was allowed or refused with the reason.
Typed text is never stored in clear. It is recorded as a length and the first twelve hex characters of a salted SHA-256:
{"ts":"2026-09-18T03:01:37.485Z","tool":"computer_type","tier":"write",
"args":{"text":{"length":19,"sha256_12":"9f2b41c0d7ae","salted":true}},
"target":"com.apple.TextEdit","mode":"ask","decision":"allowed"}
That is enough to prove two actions typed the same thing, and never enough to read what it was. A log that captured every keystroke would be the first file worth stealing on the machine - and it would undo gate 1 in a different format.
Ask your agent to call computer_audit and it will read the log back to you.
What is deliberately not here
There is no tool to run a shell command, open an arbitrary file, or fetch a URL. Those would each be one line of code and they are all absent on purpose: a computer-control server with a shell inside it is remote access with a friendlier name. If you want a shell, use a shell MCP server - then you have chosen it, and the choice is visible in your config.
There is no model inside this server and no API key. The agent you already run does the seeing and deciding. Nothing is sent to us, because there is no service of ours in the path.
This website is held to the same rule: no analytics, no cookies, no third-party requests. The only script tag on the site is structured data for search engines, which the browser does not run. Search-engine verification, where we use it, lives in DNS rather than on the page.
The helper binary that receives Accessibility and Screen Recording permission has zero third-party dependencies. Every package in a binary that privileged is another vendor you are trusting without having chosen to.
Known limits
- We redact what macOS marks. A password sitting in a plain text editor, a token in a terminal buffer, or an API key in a config file on screen are not marked secure and will not be hidden. When the screen holds something the system cannot know is sensitive, use
readonlyor close it. - Screenshots capture the active Space only. Windows on other desktops are not in the image.
- Prompt injection remains possible. Content an agent reads can attempt to steer it. Consent dialogs and the log make that visible rather than silent; they do not prevent it.
- macOS permissions are granted to the host app - your terminal or editor - not to this package. Anything else running there has the same reach.
Reporting something
Security reports: hello@agent360.dk, or a private advisory on the GitHub repository. If you find a way past a gate described on this page, that is exactly the thing we want to hear about - please say so before it is public, and we will credit you when it is fixed.