ADR 0339: Chat file references open absolute files outside the managed roots
- Status: Accepted
- Date: 2026-11-03
- Deciders: PI-Desktop maintainers
- Related: ADR 0262 (chat file refs open in the file view), ADR 0172 (fs read containment), ADR 0249 / ADR 0263 (project folder groups),
docs/spec/03-runtime/01-ipc-protocol.md§ fs, issue #1837 - Amends: ADR 0262 §4
Context
The agent is instructed to reference files by their exact absolute path, and it routinely works on files outside the open project: a download, a chat client's folder, the system temporary directory, or any path in a session with no project at all. The main process answered fs/resolveRef, fs/read, fs/reveal, and fs/open by root containment — the project folders, the session scratch store, the whole scratch store, and the attachment store. Every correctly written absolute link to a file outside those roots therefore reported "outside the locations this app can access" and could not be opened (#1722 follow-up, #1837).
The restriction protected nothing. The agent reads and writes those files with its own tools, and a renderer that can issue arbitrary IPC already controls the agent. Opening a file the user clicked shows it to that same user on this machine: the bytes reach neither the model nor the network. A credential deny list was considered to stop a prompt-injected link to ~/.ssh/id_ed25519, but such a click only shows the user their own file — there is no disclosure to defend against. Comparable agent desktops (ZCode 3.16) open any clicked local file, dotfiles included, and hand any file to the OS default application without a deny list.
Decision
- For a user-initiated open of an exact absolute path outside every managed root, the host replaces containment with one shape check: the path is absolute (no
~, no relative shorthand, noattachments/<sha256>) and its real path is an existing regular file. No path is withheld, dotfile folders such as.sshincluded. - The rule lives in one module,
packages/host-runtime/src/external-files.ts(resolveExternalFile,resolveUserOpenablePath,readUserOpenableFile), and is applied byfs/read,fs/reveal, andfs/open. Containment still answers first, unchanged.fs/resolveRefanswers an absolute outside reference withresolveExternalChatRef: a match with the newroot: "external",matchedBy: "exact-absolute", ormatch: nullwhen no regular file is there. - A shorthand never resolves outside the managed roots, and an outside absolute path never falls back to a same-name file inside a root.
- A Markdown link whose destination is an absolute path reaches the opener even without a file extension (
/etc/hosts,.ssh/config); the host, not a renderer filename heuristic, decides. Bare prose tokens keep the extension heuristic that decides which of them become chips. - Unchanged:
fs/list(project only),fs/readImageDataUrl(inline chat images load without a click, so a model-written image reference must not render an arbitrary local image), remote and headless-host file reads (apps/pi-host), thepi.file-managerplugin's own deny list (plugin code receives the bytes), and the renderer routing, which already sends a non-project match to the hostfile:tab. An external.htmlfile opens as source there, never in the side browser.
Consequences
- Clicking a correct absolute link to an ordinary file anywhere on the machine opens it; sessions without a project and temporary directories work.
- An exact absolute path to an existing outside file now verifies as a chip in user messages too.
fs/opencan hand any such file to its OS default application, including a script or application bundle that the OS runs when "opened". That is the same capability the agent already has through its shell and that project files already had, and it still requires the user to choose the default-app destination.FsChatRefRootgains"external". Consumers that switch on the root treat any value other thanworkspaceas an absolute-path file, so no consumer changes.reason: "outside-allowed-roots"keeps its shape and remains only for an outside reference that is not an exact absolute path.
Alternatives considered
- Credential deny list on the host. Rejected: showing a user their own file discloses nothing, the agent can already read it, and the list made correct links to dotfiles fail.
- Ask the user to grant each outside folder. Rejected: the user asked for links that simply open, and
fs/resolveRefalso runs automatically for chip verification, so a grant prompt would fire without a click. - Keep containment and add the temp directory and downloads as roots. Rejected: it fixes only the reported folders and leaves every other location (chat clients, mounted drives) broken.