solidcraft --control PORT writes a random per-launch token to a user-only file (<data>/control/PORT.token; 0700/0600 on Unix, icacls on Windows; SOLIDCRAFT_CONTROL_TOKEN overrides) and refuses any connection whose first request lacks it. mcp --connect, the demo and packaging smoke clients send it. MCP execute/batch/open/save/export/screenshot are annotated destructive and open-world, new_design destructive. Docs: control-protocol.md, mcp.md, AGENTS.md, pending release note in releasing.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9237d004e
commit
fe3717f2e4
19 files changed
+465
-53
No files matched your search
@@ -6,6 +6,42 @@ line `{"id": …, "ok": true, "result": …}` or `{"id": …, "ok": false, "erro
|
||||
handled on the UI thread between frames; pointer and keyboard input is injected as real egui
|
||||
events, one pointer event per frame.
|
||||
|
||||
### Token
|
||||
|
||||
Every connection must authenticate. At start the app makes a random 256-bit token for this
|
||||
launch and writes it, as one line of hex, to a file only the user can read:
|
||||
|
||||
| System | Token file |
|
||||
|---|---|
|
||||
| Linux | `$XDG_DATA_HOME/solidcraft/control/PORT.token` (`~/.local/share/solidcraft/control/PORT.token`) |
|
||||
| macOS | `~/Library/Application Support/SolidCraft/control/PORT.token` |
|
||||
| Windows | `%LOCALAPPDATA%\SolidCraft\control\PORT.token` |
|
||||
|
||||
On Unix the folder is `0700` and the file `0600`; on Windows the file's inherited permissions are
|
||||
replaced by a single entry for the user. `SOLIDCRAFT_CONTROL_DIR` moves the folder. The file is
|
||||
removed when the app exits, and the next launch writes a new token. The token file's path is
|
||||
printed to stderr at start-up.
|
||||
|
||||
The first request on a connection must carry the token as a top-level `"token"` member
|
||||
(`{"id": 1, "method": "ui.inspect", "params": {}, "token": "…"}`); later requests on the same
|
||||
connection may leave it out. A first line without the right token is answered with
|
||||
`{"ok": false, "error": "unauthorized: …"}` and the connection is closed, without running
|
||||
anything. Tokens are compared in constant time.
|
||||
|
||||
`solidcraft-cli mcp --connect 127.0.0.1:PORT` reads the token file for that port by itself
|
||||
(`--token T` or `SOLIDCRAFT_CONTROL_TOKEN` give one explicitly). For a test harness, or a
|
||||
sandboxed package whose data folder the client can't see, start the app with
|
||||
`SOLIDCRAFT_CONTROL_TOKEN` set to at least 32 hex characters (for example
|
||||
`openssl rand -hex 32`) and the app uses that token instead of making one; the file is still
|
||||
written. A one-line client in Python:
|
||||
|
||||
```python
|
||||
token = (Path.home() / ".local/share/solidcraft/control/7878.token").read_text().strip()
|
||||
sock.sendall((json.dumps({"id": 1, "method": "ui.inspect", "params": {}, "token": token}) + "\n").encode())
|
||||
```
|
||||
|
||||
### Limits
|
||||
|
||||
The channel is untrusted input. A request line may be at most 16 MiB; a longer one is answered
|
||||
with `{"ok": false, "error": "request too large"}` and the connection is closed, as are
|
||||
connections that send eight lines that are not JSON. At most 16 connections are served at once
|
||||
@@ -19,9 +55,9 @@ reads, writes or deletes (command paths such as `file.export`'s `path` or `doc.r
|
||||
`dir`, `ui.render`, `ui.screenshot`) must then lie inside one of those folders after `..` and
|
||||
symlinks are resolved, or the request fails with `… outside the folders this agent may use …`.
|
||||
The app adds its own recovery folder and, when it lies safely inside the home folder and not in
|
||||
a hidden folder, the open design's folder (whose hidden files stay out of reach). The bridged MCP server sends its folders this way on every request ([mcp.md](mcp.md)). Without
|
||||
`allow_paths` file paths are not limited, and the channel itself has no authentication beyond
|
||||
listening on loopback only.
|
||||
a hidden folder, the open design's folder (whose hidden files stay out of reach). The bridged
|
||||
MCP server sends its folders this way on every request ([mcp.md](mcp.md)). Without
|
||||
`allow_paths` file paths are not limited: a client holding the token can do what the user can.
|
||||
|
||||
## Engine
|
||||
|
||||
|
||||
+14
@@ -19,6 +19,11 @@ solidcraft --control 7878 &
|
||||
solidcraft-cli mcp --connect 127.0.0.1:7878
|
||||
```
|
||||
|
||||
Bridged, the server finds the app's control token in the token file the app wrote for that port
|
||||
([control-protocol.md](control-protocol.md#token)); `--token T` or `SOLIDCRAFT_CONTROL_TOKEN` give
|
||||
it explicitly when the app runs in a sandbox whose data folder the server can't see (prefer the
|
||||
variable: command-line arguments are visible to other users in the process list).
|
||||
|
||||
stdout carries only protocol messages; errors go to stderr. Bridged screenshots are written to a
|
||||
temporary file by the app and read back by the server, so both must run on the same machine.
|
||||
|
||||
@@ -67,6 +72,15 @@ folder of the design open in it, under the same rules, so give absolute paths: a
|
||||
resolves against the app's working folder, not the server's. The app's own
|
||||
file dialogs are never limited.
|
||||
|
||||
### Tool annotations
|
||||
|
||||
Each tool says what it may do, for clients that ask before running tools (or auto-approve on
|
||||
these hints). `list_commands`, `inspect_design`, `measure`, `body_topology` and `crash_reports`
|
||||
are read-only. `set_parameter`, `undo` and `redo` change the design undoably. `new_design`
|
||||
replaces it (destructive). `execute`, `batch`, `open`, `save`, `export` and `screenshot` (whose
|
||||
`path` writes a file) can read, write, overwrite or delete files: they are not read-only,
|
||||
`destructiveHint` and `openWorldHint` are true.
|
||||
|
||||
### Client configuration
|
||||
|
||||
Claude Code (project `.mcp.json`, or `claude mcp add solidcraft -- solidcraft-cli mcp`; a setup
|
||||
|
||||
@@ -98,6 +98,22 @@ new commits were added. An existing tag or published release is never overwritte
|
||||
version after publication. Do not push the version tag yourself: publishing the draft creates
|
||||
it at the draft's target commit.
|
||||
|
||||
## Notes for the next release
|
||||
|
||||
Items to carry into the next release's notes (step 3 below), then remove from here:
|
||||
|
||||
- **Security (agents and the control channel, [#205](https://github.com/bherbruck/solidcraft/issues/205)).**
|
||||
Upgrading is recommended for anyone who drives SolidCraft from an AI agent or runs it with
|
||||
`--control` on a shared machine. MCP tool calls now reach files only in the open design's
|
||||
folder (inside home, without its hidden files), SolidCraft's recovery folder and folders
|
||||
given with `--allow-path`; `..`, symlinks that lead out and Windows UNC, device, stream and
|
||||
short-name paths are refused ([#578](https://github.com/bherbruck/solidcraft/pull/578), thanks
|
||||
@kravens). `doc.recovery_discard` deletes only real recovery entries. The control port now
|
||||
needs a per-launch token from a file only the user can read, so other local users can't drive
|
||||
the app; `solidcraft-cli mcp --connect` finds it by itself, other clients must send it (see
|
||||
docs/control-protocol.md). MCP tool annotations no longer understate side effects. Reported by
|
||||
@CalvinEthos.
|
||||
|
||||
## Verify and publish
|
||||
|
||||
When the task authorizes shipping a release, agents should complete these steps without
|
||||
|
||||
Reference in new issue
Block a user