You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e2ea424
Browse filesBrowse the repository at this point in the historyBrowse files
native2: the signed client and MCP server verify the host's team (#191) (#205)
* native2: the signed client and MCP server verify the host's team before sending (#191)
desktop-tools-client and desktop-tools-mcp check the process at the other
end of the socket by its audit token, as the host checks them, and refuse
with HOST_UNTRUSTED unless it is the same user's and signed by their team.
Peer moves into its own DesktopPeer target so the client binaries can use
it without linking DesktopMac.
Why: native2/Sources/DesktopPeer/Peer.swift | Trust only the code-directory hash the kernel reports for the audit token's process generation, before and after reading the on-disk signature; a pid lookup can be redirected by pid reuse.
Why: native2/Sources/DesktopAppLink/Trust.swift | the host check is team-only, never identifier: embedding apps host under their own bundle identifier; an unsigned client skips it, mirroring the host, so swift build dev/test setups keep working
* lane: resolve notes for the Peer move
* lane: sync fix-host-verify memory
the host check is team-only, never identifier: embedding apps host under their own bundle identifier; an unsigned client skips it, mirroring the host, so swift build dev/test setups keep working
Copy file name to clipboardExpand all lines: .lane/memory/native2/Sources/DesktopHost/Server.swift/01M5CKE5M626ZCCCNEJH5Q4Y73-the-lease-is-released-when-t.md
Trust only the code-directory hash the kernel reports for the audit token's process generation, before and after reading the on-disk signature; a pid lookup can be redirected by pid reuse.
Copy file name to clipboardExpand all lines: .lane/memory/native2/Sources/desktop-tools-client/main.swift/01M5CVGAD88HCF2N16ZA1BM1W2-a-node-server-pinned-before.md
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,6 +14,7 @@ Notable changes to Desktop Tools, newest first. Each release's GitHub release no
14
14
- An open menu's window, such as a SwiftUI `MenuBarExtra` status item's NSMenu or one of its submenus, can be inspected: `desktop_inspect` reads the open `AXMenu` and lists its items with `checked` for a checkmark, so they can be clicked by element. `desktop_menubar_click` on an item whose menu is open closes the menu without choosing an item (`closed_menu`), and `desktop_element_action``AXCancel` on the menu closes it too. A window opened by a status item is `inspectable` only when it has Accessibility elements to read. ([#193](https://github.com/githubnext/desktop-tools/issues/193))
15
15
-`desktop_scroll` scrolls web content it used to refuse with "no supported route". A Chrome or other Chromium page gets arrow keys, one per unit, at the window's keyboard focus when that focus is on the page and not in a field, and the result reads back that the page moved; with focus in a field it is refused with a hint to click the page or send `desktop_key``pagedown`. A WKWebView app that loads WebKit from a library beside its executable, such as an Electrobun app, gets the background wheel Safari gets. ([#197](https://github.com/githubnext/desktop-tools/issues/197), [#201](https://github.com/githubnext/desktop-tools/issues/201))
16
16
-`desktop_type` into a web field reads the field back until it settles, up to 2 seconds, so a busy page such as a React filter is confirmed instead of reported `TYPING_UNVERIFIED`. When the field still doesn't match, the error says what it read back, in `read_back`. Replacing a Chrome field's text no longer appends to it: Chrome ignores a background Command-A, so the field's text is also selected through Accessibility, and typing stops with the field's contents reported when it can't be cleared. ([#203](https://github.com/githubnext/desktop-tools/issues/203))
17
+
- The signed `desktop-tools-client` and `desktop-tools-mcp` check the host again before sending anything: a process at the socket path that isn't the same user's and signed by the same team gets no requests, and the call fails with `HOST_UNTRUSTED`. The Peekaboo-based client did this, and docs/embed.md still said so. ([#191](https://github.com/githubnext/desktop-tools/issues/191))
Copy file name to clipboardExpand all lines: docs/embed.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,6 +21,6 @@
21
21
|`void desktop_tools_log(const char *message)`| Append one line of the app's own to that log, such as when the user pauses input |
22
22
|`void desktop_tools_free(void *)`| Release a status or activity string |
23
23
24
-
macOS grants Accessibility, Screen Recording, and event synthesis to the embedding app. Each embedding app needs its own grants on each machine. Clients must be signed by the host's team with the exact allowlisted identifier, and the client verifies the host's team. The agent cursor and window outline draw click-through panels from the GUI process, so turn them on only from an app with a running AppKit main loop. The outline's label names the agent from `DESKTOP_TOOLS_AGENT` in the requesting client's environment, which `createDesktop` sets from its `agent` option; without it the label says "Agent". For agents outside an embedding app, use the [Desktop Tools app](app.md).
24
+
macOS grants Accessibility, Screen Recording, and event synthesis to the embedding app. Each embedding app needs its own grants on each machine. Clients must be signed by the host's team with the exact allowlisted identifier. In turn, a client signed with a team sends nothing, not even its hello, to a socket served by a process that isn't the same user's and signed by that team, and fails with `HOST_UNTRUSTED`; this covers `desktop-tools-client` and the app's `desktop-tools-mcp`. Both checks read the signature of the exact process at the other end of the socket, by its audit token. An unsigned build, such as `swift build` makes for development, skips them, as the host checks its client only when it is signed itself. The agent cursor and window outline draw click-through panels from the GUI process, so turn them on only from an app with a running AppKit main loop. The outline's label names the agent from `DESKTOP_TOOLS_AGENT` in the requesting client's environment, which `createDesktop` sets from its `agent` option; without it the label says "Agent". For agents outside an embedding app, use the [Desktop Tools app](app.md).
25
25
26
26
The caller is the client's nearest ancestor that isn't `node`, `npx`, `npm`, a shell, `login` or `env`, such as Claude Code or the terminal app an agent runs in. Its key is the team ID and signing identifier of its running code when that signature validates, so an update that moves it keeps its approval; otherwise it is its path, plus its identifier when it has one. Its name is its app's name, else the last part of a team-signed identifier (`claude-code`), else its file name. The process can't choose either one through its arguments or environment.
Copy file name to clipboardExpand all lines: native2/README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,13 +9,14 @@ A Swift MCP server and host with no Peekaboo and no Node. It runs beside the shi
9
9
|`DesktopProtocol`| JSON, wire framing, `ToolError`, and the contract (`ContractText.swift`), which `node scripts/contract.mjs` compiles from the Node server's instructions and tools/list; `--check` fails when they differ. It is compiled in because `Bundle.module` crashes in an embedded dylib |
10
10
|`DesktopCore`| Result bounding, window availability and the approved-callers store, with no macOS calls |
11
11
|`DesktopMac`| Everything that touches macOS: Accessibility reads (`Elements`, `AX`), capture, apps, windows, and input (`Pointer`, `Keyboard`, `Gestures`) |
12
+
|`DesktopPeer`| Who is at the other end of the socket, by the signature of its audit token's process (`Peer.swift`), and this process's own signing |
12
13
|`DesktopHost`| The tools, the snapshot store, the socket server, admission (`Admission.swift`: approved callers, pause, own-UI guard) and the runtime hooks |
13
14
|`desktop-tools-host`| The host, run from a terminal for now; it holds the macOS grants |
14
15
|`desktop-tools-mcp`| The stdio MCP server, which forwards each call to the host |
15
16
|`desktop-tools-client`| One tool call per run, for `createDesktop({ wire: 2 })` and `dist/mcp.js --wire 2` (docs/node.md) |
16
17
|`DesktopTools`|`libDesktopTools.dylib`, the menu bar app's embedding runtime with native/'s C ABI (docs/embed.md): it serves the host's `Server` and draws the agent cursor, window outline and activity list through `DesktopHost/Hooks.swift`|
17
18
18
-
The host serves `~/.local/state/desktop-tools2/desktop.sock`, or `$DESKTOP_TOOLS2_STATE/desktop.sock`, so it runs beside the installed Desktop Tools. It accepts a connection only from the same user, and, when the host is signed, only from an MCP binary signed by the same team with the expected identifier (`DesktopMac/Peer.swift`: the socket's audit token, checked with Security and a CDHash read before and after).
19
+
The host serves `~/.local/state/desktop-tools2/desktop.sock`, or `$DESKTOP_TOOLS2_STATE/desktop.sock`, so it runs beside the installed Desktop Tools. It accepts a connection only from the same user, and, when the host is signed, only from an MCP binary signed by the same team with the expected identifier (`DesktopPeer/Peer.swift`: the socket's audit token, checked with Security and a CDHash read before and after). In turn, a signed client or MCP server sends nothing to a host that isn't the same user's and signed by its team (`DesktopAppLink/Trust.swift`).
0 commit comments