diff --git a/docs/profiles.md b/docs/profiles.md index 32d87a5..291213e 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -174,7 +174,14 @@ | `peer` | Runs an agent on a neighbouring machine, where `PEER_ASK_PROFILE=server_admin`. | | `reload_tools`, `create_mcp_server` | Execute arbitrary code on the Navi host. | | `test_mcp_tool` | Calls any tool of any server, bypassing the profile's MCP groups — it would undo both the group limits and the BYOK rule. | -| `image_view` | Reads any absolute path; the working-directory convention does not apply to it. | + +One tool goes the other way: **`image_view` is granted to all three**, so the 3D designer +can inspect the renders `mcp__navi-3d__render_stl` produces instead of shipping geometry it +has never looked at. It reads any absolute path and fetches any http(s) URL, subject to a +raster-only extension filter (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`, `.bmp`) — it cannot +read `.env` or any other text file, but it can read any image on the host. See the residual +risk below; the boundary instruction in the three system prompts ("pass only session paths +and the paths the 3D server returned") is a prompt rule, not a code check. MCP groups are all read-only or local: @@ -208,11 +215,17 @@ - **The working directory is not a sandbox.** `terminal` and `code_exec` run as the Navi process; `python3 script.py` from a restricted profile can read `/home/ubuntu/navi-1/.env`. The only real defence is the model obeying the prompt. -- **Six dangerous tools stay unguarded in code.** `ssh_exec`, `peer`, `reload_tools`, - `create_mcp_server`, `test_mcp_tool` and `image_view` still check no role; they are - withheld from the restricted profiles by *composition* alone. A model that talks itself - into "remembering" one, or finds its description through `tool_manual`, still cannot call - it — it is not in the list. +- **Five dangerous tools stay unguarded in code.** `ssh_exec`, `peer`, `reload_tools`, + `create_mcp_server` and `test_mcp_tool` still check no role; they are withheld from the + restricted profiles by *composition* alone. A model that talks itself into "remembering" + one, or finds its description through `tool_manual`, still cannot call it — it is not in + the list. +- **`image_view` is granted deliberately, and is the one file reader with no path check.** + It accepts any absolute path or URL whose extension is a raster image type, so it cannot + read `.env`, source or logs, but it *can* read any image file on the host. It is in all + three sets because the 3D designer needs to see its own renders, and because + `switch_profile` would hand it to the other two regardless. The prompt asks for session + paths only; nothing enforces that. - **`gmail` is the exception, and is not covered by composition.** The global file `tools/enabled.json` is merged into *every* profile's tool list by `build_tool_list` (`navi/core/tool_utils.py`), so `gmail` — Navi's own mailbox, not a personal one — is diff --git a/navi/profiles/assistant/config.json b/navi/profiles/assistant/config.json index 8679768..3bc06c7 100644 --- a/navi/profiles/assistant/config.json +++ b/navi/profiles/assistant/config.json @@ -51,6 +51,7 @@ "spawn_agent", "share_file", "content_publish", + "image_view", "schedule_recall", "manage_recall", "weather", diff --git a/navi/profiles/coder/config.json b/navi/profiles/coder/config.json index 4f68c71..73f4018 100644 --- a/navi/profiles/coder/config.json +++ b/navi/profiles/coder/config.json @@ -51,6 +51,7 @@ "spawn_agent", "share_file", "content_publish", + "image_view", "schedule_recall", "manage_recall", "weather", diff --git a/navi/profiles/designer_3d/config.json b/navi/profiles/designer_3d/config.json index 7ae365b..06af59b 100644 --- a/navi/profiles/designer_3d/config.json +++ b/navi/profiles/designer_3d/config.json @@ -6,7 +6,7 @@ "full_description": { "specialization": "Building physically coherent 3D geometry and exporting it as STL. Models are written as OpenSCAD source, then checked by compiling and by looking at a rendered preview before the file is handed over.", "when_to_use": "When the user wants a physical object described as 3D geometry: a replacement part, a bracket, an enclosure, a jig, a decorative item, or a quick prototype.", - "key_tools": "mcp__navi-3d__lint_scad, mcp__navi-3d__compile_scad, mcp__navi-3d__render_stl, filesystem, code_exec, content_publish, spawn_agent" + "key_tools": "mcp__navi-3d__lint_scad, mcp__navi-3d__compile_scad, mcp__navi-3d__render_stl, image_view, filesystem, code_exec, content_publish, spawn_agent" }, "llm_backend": "ollama", "model": [ @@ -51,6 +51,7 @@ "spawn_agent", "share_file", "content_publish", + "image_view", "schedule_recall", "manage_recall", "weather", diff --git a/navi/profiles/designer_3d/system_prompt.txt b/navi/profiles/designer_3d/system_prompt.txt index 4c1cc64..0d372d8 100644 --- a/navi/profiles/designer_3d/system_prompt.txt +++ b/navi/profiles/designer_3d/system_prompt.txt @@ -45,23 +45,31 @@ `tool_manual("lint_scad")`. 3. **`mcp__navi-3d__compile_scad`** — compile to a binary STL; `tool_manual("compile_scad")`. 4. **`mcp__navi-3d__render_stl`** — render PNG previews; `tool_manual("render_stl")`. -5. **`content_publish`** — hand the STL to the user, and publish a preview render +5. **`image_view`** — look at every preview `render_stl` returned, before publishing. +6. **`content_publish`** — hand the STL to the user, and publish a preview render alongside it. -### You cannot look at the render yourself +### Inspect every render yourself -There is no image-reading tool in this profile. The PNG from `render_stl` is for the -**user**, not for you, and your own verification has to come from the other side: +The PNG from `render_stl` is your own check on the geometry, not just a bonus for the +user. Call `image_view` on **every** path the 3D server returned — `iso`, `front`, `top`, +`side` and the rest; one angle is not enough — and fix what you see before anything is +published. The image appears in your next turn; call the tool instead of announcing that +you are about to. -- `lint_scad` for source-level mistakes; -- `compile_scad` — a non-zero exit or a warning is your strongest geometric signal, so - never publish over a failed or noisy compile; -- the numeric parameter sanity check below, run before every compile; -- the bounding-box and volume figures the 3D server reports, if the tool returns them. +Pass only the paths `render_stl` produced, or paths inside the session directory. +`image_view` will read any image path on the machine and fetch any image URL, and that is +outside this profile's boundary — the boundary is yours to keep, not the tool's. -Never claim you have visually checked a model. Say what you verified and how, and tell the -user the preview is there for them to look at. Ask them if the shape is what they wanted -when a feature is hard to confirm numerically. +The visual check does not replace the mechanical ones — `lint_scad` for source-level +mistakes, `compile_scad` (a non-zero exit or a warning is your strongest geometric signal, +so never publish over a failed or noisy compile) and the numeric parameter sanity check +below, run before every compile. + +Say what you verified and how. If a render contradicts the specification — wrong +orientation, a floating part, a wall too thin, a hole the wrong size — fix the model and +re-render; never publish and then describe the difference. When something cannot be +settled from the renders, ask the user whether the shape is what they wanted. Use `spawn_agent` only to gather missing facts from the web or local files — never to design geometry, write or review OpenSCAD, compile, render, publish, or make a modelling