noracu gives an MCP client control over your real machine — mouse, keyboard, clipboard, browser, and (via iPhone Mirroring) your phone. Treat every tool call as privileged.
noracu acts inside your real, logged-in session. It does not sandbox what it touches, and it does not sanitize, filter or rate what it observes: the MCP client and its model decide what to do with every result. The threat table below has the per-tool detail.
| Asset | How noracu touches it | Who receives it | What noracu does and does not do |
|---|---|---|---|
| Text returned to the model — AX values and titles, ARIA snapshots, page text, iPhone OCR, screenshots | Every observation tool returns it as read | The MCP client and its model provider | Not sanitized or filtered. A web page, document, message or notification can carry instructions aimed at the model; treat every result as untrusted data (threat 11) |
| Clipboard | mac_paste, phone_type and phone_open put the text on the Mac clipboard and paste it |
Apps on the Mac; the iPhone, which fetches the paste from the Mac clipboard through iPhone Mirroring | mac_paste sets every item aside and puts it back once the paste shows, but not while the app does not answer — then the pasted text stays on the clipboard (threat 10). The iPhone fetches a paste late, from whatever the Mac clipboard holds at that moment, so phone_type puts the clipboard back only on screen evidence and otherwise leaves the typed text on it; phone_open never puts it back (threat 18). Items a password manager marks concealed are never copied |
| macOS privacy-consent prompts and administrator authorization dialogs | A synthesized click or keystroke can be aimed at them like any other window | — | noracu does not recognize these dialogs or refuse input to them, and whether macOS accepts a synthesized click on each of them has not been measured. Keep a person present for any run that may raise one |
| Secrets an app displays (Keychain items an app shows, passwords shown in clear, one-time codes) | AX reads, OCR and screenshots read what is on screen | The model provider | Reading is not blocked. mac_set_value never echoes a value in a secure text field, and concealed clipboard items are never returned (threat 16) |
| Browser cookies | The browser layer keeps one persistent Playwright profile at ~/.noracu/browser-profile |
Every site the browser opens; page text goes to the model | Not isolated between runs; clear the profile between untrusted runs |
| iPhone Mirroring session | Phone tools see and act through the iPhone Mirroring window | The model sees the phone screen, including notifications, messages and codes | Biometric authentication requests fail during iPhone Mirroring by default (Apple TN3210), but a Mac set to authenticate automatically gives anyone at the unlocked Mac the phone (threats 6, 12). Input reaches iOS as indirect pointer events, not touches (TN3210), so a touch-only system gesture such as swiping in from the left edge to go back does nothing through the mirror — that is the mirror's behaviour, not a noracu fault |
Boundaries:
- macOS grants Accessibility and Screen Recording to the host app that starts
noracu (Claude Desktop, Cursor, a terminal), not to noracu. Whether macOS
attributes events from helper binaries such as
cliclickandpython3separately is unverified. - noracu does not constrain the host's own shell or file tools. Blocking an app inside noracu would not stop a host that can run commands, so noracu keeps no such list.
- Tool annotations (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint) are hints for the client and restrict nothing.NORACU_READ_ONLY=1,NORACU_DISABLE_EVAL=1and the opt-inNORACU_ALLOW_*variables are what the server enforces. - No telemetry and no network use at run time, except the one-time Playwright
Chromium download, which
NORACU_DISABLE_AUTO_INSTALL=1turns off.
| # | Threat | Vector | Status | Mitigation / notes |
|---|---|---|---|---|
| 1 | Prompt injection → arbitrary code execution | Observed content (web page text, AX tree titles, OCR output) contains instructions that trick the agent into calling tools with malicious arguments | Mitigated | The Mac accessibility engine src/mac-ax.js is a static script that takes its whole request as JSON on stdin — parameters are data, never script source (regression-tested in test/e2e.mjs); the remaining inline JXA (scroll wheel) takes clamped numbers through argv |
| 2 | Arbitrary local control | mac_click / mac_type / mac_paste can operate password managers, banking pages, terminals |
Inherent | Use client-side confirmation gates for high-stakes targets; see recommendations below. Optional app focus guard on mac_paste / mac_key (and mac_type keystrokes) refuses input unless that app — compared by process, not by name — is frontmost; mac_type keystrokes after a click on an element_index go out only if the element's app is then frontmost. An element_index is re-identified inside the window it came from (by CGWindowID) before any action, and a physical click or drag (both ends) is sent only when a hit test shows the point reaches the element, or — when the hit test is inconclusive — when the topmost on-screen window at the point is that same window (by CGWindowID, not covered by another window of the same app; occluded otherwise). Right before a physical click or drag is sent, the window on top at each point is checked again (by CGWindowID); afterwards a physical click counts as delivered_verified only if the element is still there, changed, and its own window is still on top at the click point. An element read with surface: "app" is bound to its window's CGWindowID, and an element in a window without a CGWindowID (other than Finder's desktop) is refused with policy_denied. A mac_click at raw x,y is sent only when the topmost on-screen window at the point belongs to app (default: the frontmost app; the menu bar counts as the frontmost app's, and the strip a display keeps for the Dock counts as the Dock's; Notification Center's full-display overlay counts only over a banner or alert it draws, or everywhere when their frames cannot be read), unless force: true; a short gap remains between that check and the click. mac_key refuses quit / log-out / lock / force-quit / delete-immediately / empty-Trash chords without force; mac_menu_command applies the same chord list, refuses Apple-menu items without force, and never quits the MCP host or a session process. |
| 3 | Arbitrary JS in browser | browser_evaluate executes any JavaScript in the page context |
Partially mitigated | Set NORACU_DISABLE_EVAL=1; browser_evaluate then returns an error on every call (it stays listed in tools/list) |
| 4 | Input validation gaps | Malformed arguments (NaN coordinates, oversized strings, javascript: URLs) |
Mitigated | validateArgs validates every tool's arguments (types, ranges, enums, string lengths, URL schemes) before any subprocess runs; tools/list emits required arrays derived from non-optional arguments |
| 5 | Screenshot privacy | mac_screenshot / phone_screenshot capture the full screen / phone display |
Inherent | Run in a dedicated macOS user account or virtual display for untrusted automation. NORACU_READ_ONLY=1 prevents side effects (and skips enableWebAccessibility on apps), not screen secrecy: mac_screenshot still shows visible notification banners on screen, and Notification Center accessibility reads via mac_ax_state / mac_find are not blocked by bundle id |
| 6 | iPhone operations | phone_tap / phone_type can interact with payment and SMS-verification UIs; taps go to OCR coordinates with no element re-check |
Inherent | Keep Mirroring off for untrusted sessions; NORACU_READ_ONLY=1 refuses all phone input; all phone input (including phone_home / phone_app_switcher, via require_enabled on the menu item or imctl's _require_live) fails closed with mirror_not_live when not live — without stealing focus when paused or closed — or mirror_state_unknown when the state cannot be read, checks that iPhone Mirroring is frontmost before synthesized events, and never falls back to a second gesture after an uncertain menu timeout (menu_unknown); optional expect OCR anchor gates coordinate/key/type actions; "Authenticate automatically" lets anyone at the unlocked Mac start mirroring |
| 12 | Phone screen as model input | phone_screenshot / phone_ocr / phone_find return notifications, messages, mail and one-time codes visible on the phone |
Inherent | Use a Focus mode and a neutral app; the MCP client sends tool results to its model; iPhone notifications also reach the Mac once Mirroring is set up |
| 7 | Supply chain | malicious PRs / dependency poisoning after open-sourcing | Partially mitigated | Review all PRs touching src/server.mjs argument handling; lockfile committed; no postinstall scripts; the only runtime download is the one-time Playwright chromium install triggered by browser_launch, which can be disabled with NORACU_DISABLE_AUTO_INSTALL=1 |
| 8 | Local file read via browser | browser_navigate to a file:// URL, then browser_read exfiltrates local files |
Mitigated | file:// blocked at the browser_navigate argument unless NORACU_ALLOW_FILE_URL=1 (initial URL only; redirects fall under threat 9) |
| 9 | HTTP GET side effects | navigation triggers logout links, one-time tokens, private-network / cloud-metadata endpoints | Not mitigated | No URL blocklist (redirects and DNS rebinding defeat pre-navigation checks); client-side policy |
| 10 | Clipboard clobbering by mac_paste | mac_paste overwrites the clipboard with the pasted text |
Partially mitigated | One engine call copies every clipboard item and type aside (inside that process), writes the text marked org.nspasteboard.TransientType, sends ⌘V only if the clipboard and the frontmost app are unchanged, and puts the user's items back after the paste shows in the same focused element (held at least 0.3 s; else after a 1 s hold) and only if nothing else wrote to the clipboard meanwhile; when the keystroke may not have completed it is not put back. An item marked concealed (on any clipboard item) is never copied: the clipboard is cleared after the paste. Universal Clipboard content and clipboards over 64 MB are refused unless force. Remaining: for up to about a second the clipboard holds the pasted text (a clipboard manager that ignores the transient marker can record it); an app that reads the clipboard after the hold receives the restored content; when the app stops answering the pasted text is left on the clipboard |
| 13 | Actions on windows the user cannot see | Semantic Mac actions (press, set value, insert text) work on background windows without moving the pointer or taking focus | Inherent | Results report delivery and retry; review the client's tool log; close high-stakes apps during unattended runs |
| 14 | Quitting apps | mac_app quit can close apps; force-quit loses unsaved work |
Mitigated | Graceful quit by default and verified by the process ending; force: true required to force-quit; session processes (Finder, Dock, loginwindow, SystemUIServer, Control Center, Notification Center, …) and the MCP host process are never quit |
| 15 | DevTools port exposure | mac_app launch with cdp_port opens a Chromium/Electron app's remote-debugging port on 127.0.0.1 (no per-user isolation); any local process can then control that app |
Partially mitigated | Off unless NORACU_ALLOW_CDP=1 (or =any); browser_launch with cdp_port is refused under NORACU_READ_ONLY=1 even with NORACU_ALLOW_CDP set; bound to 127.0.0.1; only for a fresh launch after canonicalizing .app paths (realpath, trailing slash stripped — a running app is refused, never quit for you); with NORACU_ALLOW_CDP=1, browser_launch attaches only when the port was recorded by this noracu process, its PID is still alive, and /json/version's webSocketDebuggerUrl matches (attaching to any other loopback port requires NORACU_ALLOW_CDP=any); ~/.noracu and cdp-profiles are created 0700; Chrome-family browsers get a separate profile under ~/.noracu/cdp-profiles; standalone browser_launch is refused while a CDP session is open; the port closes when the app quits |
| 16 | Clipboard and notification content | mac_clipboard_read / mac_notifications return clipboard text, images, file paths and notification text to the model |
Partially mitigated | Items marked org.nspasteboard.ConcealedType (password managers) are never returned; mac_clipboard_read, mac_notifications, and mac_wait with condition: "notification" are refused under NORACU_READ_ONLY=1 (NORACU_READ_ONLY=1 prevents side effects, not screen secrecy: mac_screenshot still shows visible notification banners and Notification Center AX reads are not blocked by bundle id); notification actions reject empty id / expected_app / expected_title, return notification_not_found when absent, and require an id or an index plus a re-checked app/title fingerprint; mac_set_value on AXSecureTextField never echoes the value. Remaining: mac_clipboard_read on Universal Clipboard content (copied on another device) makes macOS fetch it from that device; the result marks it remote: true |
| 17 | Text in process arguments | Text sent as keystrokes (mac_type physical path: cliclick t:…) is visible in the process list while it runs |
Partially mitigated | The accessibility path (mac_type default, mac_set_value), mac_paste, and phone_type / phone_find / phone_open (imctl reads the text from stdin) pass text on stdin, and mac_set_value on AXSecureTextField and imctl type never echo typed text; only the keystroke fallback uses argv. A phone call's expect text is still an argument |
| 18 | Clipboard on the phone path | phone_type / phone_open paste through the Mac clipboard; iPhone Mirroring fetches the paste asynchronously, sometimes more than 10 s after ⌘V, from whatever the clipboard holds then, so putting the user's clipboard back too early could paste it into the phone |
Partially mitigated | phone_type and phone_open wait at least 1.5 s after writing the clipboard before ⌘V, and call ⌘V off (focus_lost) rather than bring iPhone Mirroring back if another app came to the front meanwhile. phone_type copies every clipboard item aside and puts them back only on screen evidence read within its 2.5 s check while no "Pasting from" progress box shows: OCR sees the text at a new spot below the status-bar strip in two reads, counted against a read taken right before ⌘V. A text of one to three characters (spaces and case aside) never licenses it, and neither does a paste seen only later. An item marked concealed or transient is never copied (the clipboard is cleared instead). Without that evidence the typed text stays on the clipboard and the result says so. An error before ⌘V is sent restores the clipboard; an error once ⌘V may be on its way never does. phone_open never restores it once ⌘V may have been sent (before ⌘V — an error, or focus_lost — it goes back under the same rule). Remaining: the user's previous clipboard is lost whenever the evidence is missing; Universal Clipboard content and clipboards over 64 MB are not copied, so they are replaced by the typed text; a ⌘V that reaches the phone before Universal Clipboard has announced the new text can paste the phone's own older clipboard instead — possibly something once pasted on the phone, such as a password — and the wait — 1.5 s, a judgement rather than a measured delay — is meant to let the new text reach the phone first but does not close that window; the OCR evidence matches the typed text inside a line, so an older clipboard that contains the typed text can pass for the paste; the "Pasting from" box is recognized only in English |
| 11 | Untrusted observation content | ARIA snapshots, AX trees, OCR, page text contain attacker-controlled text | Not mitigated | The MCP client must treat tool results as data, never as instructions |
- Set
NORACU_DISABLE_EVAL=1unless you actively needbrowser_evaluate. - Leave
NORACU_ALLOW_CDPunset unless you drive an Electron app's web contents; quit the app when done so its DevTools port closes. NORACU_TRACEwrites only{ ts, tool, args, ms, error, code }— argument names and sizes, error code prefix, never argument text, result text, or error messages — and opens the file0600withO_NOFOLLOW(refusing symlinks).- Before an agent touches the iPhone layer: turn on a Focus, open a neutral app,
and start with
NORACU_READ_ONLY=1. Prefer "Ask every time" in iPhone Mirroring settings unless you need unattended sessions. - Gate
mac_type/mac_pastebehind human confirmation when the frontmost app is a password manager or banking site (client-side policy). - Run untrusted automation in a dedicated macOS user account with Accessibility + Screen Recording granted only to that account's client.
- Audit tool calls in your MCP client's log — every action is visible there.
- The persistent browser profile at
~/.noracu/browser-profilekeeps cookies across sessions — clear it between untrusted runs. NORACU_READ_ONLY=1does not block the one-timeplaywright install chromiumdownload triggered bybrowser_launch; setNORACU_DISABLE_AUTO_INSTALL=1to require a manual install instead.
Please report security issues privately through a GitHub Security Advisory (repo → Security → Advisories → Report a vulnerability). Do not open public issues for exploitable bugs. We aim to respond within 72 hours.