fix: confine MCP agents' files to allowed folders; recovery discard deletes only recovery entries (#205) (#578)
Agent tool calls (MCP, and bridged control requests carrying allow_paths) reach files only in the open design's folder (strictly inside home or the temporary folder, not in a hidden folder, and never its hidden files), the app's recovery folder, the server's private scratch folder and each --allow-path; --allow-any-path lifts the limit. Paths are resolved first, so .. and symlinks that lead out (including a save through a link, #845) are refused. On Windows, UNC, device, NT, verbatim non-drive, drive-relative, alternate-stream, reserved-device and 8.3 short-name paths are refused before any file system call, and the resolved path must be on a local drive. doc.recovery_discard and doc.recover {discard} delete only real recovery entries. Docs: mcp.md, control-protocol.md, README.
This commit is contained in:
1 parent
3d3f9b626f
commit
96f6424aec
23 files changed
+1000
-37
No files matched your search
@@ -14,6 +14,15 @@ run per frame so a flood cannot starve drawing, and a request that the UI thread
|
||||
answered within 60 seconds is replied to with `{"ok": false, "error": "timeout"}`. The same
|
||||
16 MiB line limit applies to the MCP server.
|
||||
|
||||
Any request's `params` may carry `allow_paths: ["/abs/folder", …]`: every file that request
|
||||
reads, writes or deletes (command paths such as `file.export`'s `path` or `doc.recovery_*`'s
|
||||
`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.
|
||||
|
||||
## Engine
|
||||
|
||||
| Method | Params | Result |
|
||||
|
||||
+45
@@ -22,6 +22,51 @@ solidcraft-cli mcp --connect 127.0.0.1:7878
|
||||
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.
|
||||
|
||||
### Which files the agent can reach
|
||||
|
||||
Tool calls read, write and delete files only inside the server's folders:
|
||||
|
||||
- the folder of the open design (the `--in` design, or whatever the agent last opened or saved),
|
||||
but only when it lies strictly inside your home folder (or the temporary folder) and not in a
|
||||
hidden folder. Home itself, a folder above it (`/`, `/home`, `/Users`, a drive's root),
|
||||
hidden folders such as `~/.config` and folders elsewhere don't count. Hidden files and folders
|
||||
inside the design's folder (`.env`, `.git`) are out of reach too;
|
||||
- SolidCraft's own recovery folder, so `doc.recovery_list`, `doc.recover` and
|
||||
`doc.recovery_discard` work out of the box (discarding deletes only real recovery entries);
|
||||
- the server's own scratch folder (a new private `solidcraft-mcp-*` folder in the temporary
|
||||
folder), where `screenshot` without a `path` goes;
|
||||
- every `--allow-path DIR` (repeat it for more). A folder named here is reached in full,
|
||||
hidden files included.
|
||||
|
||||
The working folder the server was started in does not count by itself, so `~/.ssh`, `~/.aws`,
|
||||
`~/.bashrc` and other dotfiles are never in reach by default. stderr lists the folders at
|
||||
start-up and says when the `--in` design's folder was left out; `--allow-path` adds it back.
|
||||
|
||||
Paths are resolved before they are compared, so `..` and symlinks that lead out are refused
|
||||
(saving through a link writes the file it points to, so the link's target is what counts), and
|
||||
a new file must go into a folder that exists. On Windows a path must be on a local drive
|
||||
(`C:\…` or `\\?\C:\…`): UNC, device and NT paths in any spelling (`\\server\share`,
|
||||
`//server/share`, `\\?\UNC\…`, `\??\…`, `\\.\…`), drive-relative paths (`C:file`), a `:` in a
|
||||
file name (alternate data streams), reserved device names with any extension (`NUL`, `CON`,
|
||||
`COM1.txt`, …) and short 8.3 names (`PROGRA~1`) are refused before any file system call, and the
|
||||
resolved path is checked again, so a link or junction that leads to a share is refused too. This
|
||||
includes a `sketch.text` font given as a file path; installed fonts named by family still work.
|
||||
A refused path is a tool error saying why.
|
||||
|
||||
A hard link inside an allowed folder can't be told apart from an ordinary file, so a hard link
|
||||
to a file elsewhere is reachable through it; don't keep such links in a folder you allow.
|
||||
|
||||
```sh
|
||||
solidcraft-cli mcp --allow-path ~/designs --allow-path ~/exports
|
||||
solidcraft-cli mcp --allow-any-path # no limit (the behaviour before this option existed)
|
||||
```
|
||||
|
||||
Bridged, the server sends its folders with every request (`allow_paths`, see
|
||||
[control-protocol.md](control-protocol.md)) and the app adds its own recovery folder and the
|
||||
folder of the design open in it, under the same rules, so give absolute paths: a relative one
|
||||
resolves against the app's working folder, not the server's. The app's own
|
||||
file dialogs are never limited.
|
||||
|
||||
### Client configuration
|
||||
|
||||
Claude Code (project `.mcp.json`, or `claude mcp add solidcraft -- solidcraft-cli mcp`; a setup
|
||||
|
||||
Reference in new issue
Block a user