Files
figcraft/crates/engine/clippy.toml
T
BFlat ac4b98dbd7 M8: confine MCP file access with --automation-read-root/--automation-write-root (#715) (#746)
- `figcraft-cli mcp --automation-read-root DIR --automation-write-root DIR` (each repeatable, `--flag=DIR` works too). When either is given, every file an agent's tool call reads must lie inside a read root or a write root, and every file it writes inside a write root. Anything else is an ordinary tool error (`automation path rejected: … is outside the folders that may be written: …`); nothing is written and the server keeps serving. Without the flags nothing changes.
- The desktop app takes the same flags (`figcraft --control PORT --automation-read-root DIR …`) and confines every control request and every frame of every window to them, so `figcraft-cli mcp --attach` drives a confined app. `--attach` together with the flags is a usage error, because the app runs the commands and only the app can confine them.
- New engine module `crates/engine/src/file_access.rs`: `Roots` (resolution and comparison), a thread-scoped `confined(roots, f)`, and the file primitives (`read`, `read_capped`, `open`, `metadata`, `read_dir`, `write` (atomic), `create_dir_all`, `exists`, `is_dir`). Every engine file access now goes through it. `crates/engine/clippy.toml` denies `std::fs::*`, `File::open/create`, `OpenOptions::open`, `Path::exists/is_file/is_dir/metadata/read_dir/canonicalize` and `figcraft_format::{write_file_atomic, save_file, load_file}` everywhere else in the engine, so a command added later is confined without a check of its own.
- `ui.screenshot` and `ui.render` return the PNG as base64 `data` when no `path` is given (before, `ui.screenshot` wrote `figcraft-screenshot.png` in the working directory and `ui.render` required a path). The attached MCP backend uses that, so it no longer writes and deletes temporary PNGs, and a confined app needs no exemption for them.
- `initialize`'s `instructions` name the folders the agent may read and write.
- `figcraft-cli` options may be given more than once (`Args::values`); single-valued options keep the last value, as before.
- Docs: `docs/mcp.md` › Confining file access (options, semantics, coverage, resolution, limits), `docs/control-protocol.md` › Confining file access and the optional `path` of `ui.screenshot` / `ui.render`, `docs/cli.md`, `figcraft-cli --help`, and the Automation line plus a revision row in `docs/target-app-parity.md`.

Fixes #715, fixes #481.
2026-10-17 19:56:20 +07:00

40 lines
3.2 KiB
TOML

# The workspace settings (../../clippy.toml; clippy reads only the nearest file), plus the engine's
# own rule: shipped engine code reaches the file system only through `file_access`, which enforces
# the automation roots (#715), so every command is confined without a check of its own.
allow-unwrap-in-tests = true
allow-expect-in-tests = true
allow-panic-in-tests = true
too-many-arguments-threshold = 10
disallowed-methods = [
{ path = "std::fs::read", reason = "use file_access::read (automation roots)" },
{ path = "std::fs::read_to_string", reason = "use file_access::read (automation roots)" },
{ path = "std::fs::write", reason = "use file_access::write (automation roots)" },
{ path = "std::fs::metadata", reason = "use file_access::metadata (automation roots)" },
{ path = "std::fs::symlink_metadata", reason = "use file_access (automation roots)" },
{ path = "std::fs::read_dir", reason = "use file_access::read_dir (automation roots)" },
{ path = "std::fs::canonicalize", reason = "use file_access (automation roots)" },
{ path = "std::fs::copy", reason = "use file_access (automation roots)" },
{ path = "std::fs::rename", reason = "use file_access (automation roots)" },
{ path = "std::fs::create_dir", reason = "use file_access::create_dir_all (automation roots)" },
{ path = "std::fs::create_dir_all", reason = "use file_access::create_dir_all (automation roots)" },
{ path = "std::fs::remove_file", reason = "use file_access (automation roots)" },
{ path = "std::fs::remove_dir", reason = "use file_access (automation roots)" },
{ path = "std::fs::remove_dir_all", reason = "use file_access (automation roots)" },
{ path = "std::fs::hard_link", reason = "use file_access (automation roots)" },
{ path = "std::fs::File::open", reason = "use file_access::open (automation roots)" },
{ path = "std::fs::File::create", reason = "use file_access::write (automation roots)" },
{ path = "std::fs::File::create_new", reason = "use file_access::write (automation roots)" },
{ path = "std::fs::OpenOptions::open", reason = "use file_access (automation roots)" },
{ path = "std::path::Path::exists", reason = "use file_access::exists (automation roots)" },
{ path = "std::path::Path::try_exists", reason = "use file_access::exists (automation roots)" },
{ path = "std::path::Path::is_file", reason = "use file_access::metadata (automation roots)" },
{ path = "std::path::Path::is_dir", reason = "use file_access::is_dir (automation roots)" },
{ path = "std::path::Path::metadata", reason = "use file_access::metadata (automation roots)" },
{ path = "std::path::Path::symlink_metadata", reason = "use file_access (automation roots)" },
{ path = "std::path::Path::read_dir", reason = "use file_access::read_dir (automation roots)" },
{ path = "std::path::Path::canonicalize", reason = "use file_access (automation roots)" },
{ path = "figcraft_format::write_file_atomic", reason = "use file_access::write (automation roots)" },
{ path = "figcraft_format::save_file", reason = "use file_access::write (automation roots)" },
{ path = "figcraft_format::load_file", reason = "use file_access::read (automation roots)" },
]