* fix(agent): give file tools the agent workspace as root when no workspace is set (#6679)
With no workspace bound, bash/python run in data/agent_workspace, but
RequestAuthority sealed no filesystem root, so every native file tool
failed with "requires a sealed resource root", and an approved
write_file failed with "no sealed resource identity". Seal the agent
workspace instead, and resolve relative paths in _resolve_tool_path
against it rather than the server's cwd.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: re-seal resource roots when a test swaps the authority owner (#6679)
A no-workspace authority now seals the agent workspace for its owner, so
replace(authority, owner="bob") copied alice's root and was rejected
before the approval-binding check the tests are about.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(agent): only seal a vetted agent workspace as the file-tool root (#6679)
Apply the agent_cwd() check before sealing data/agent_workspace: it must
be a real directory in the readable data subdirs, otherwise no root is
sealed. A symlinked workspace no longer redirects the file tools or the
bash/python launch scope. Tighten the dispatch test assertions to the
exact 'outside the sealed roots' message.
* refactor(agent): vet the no-workspace file root with default_agent_workspace (#6679)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(security): keep agent file tools out of the app state directory
The agent's read tools (read_file, grep, glob, ls) resolved model-supplied
paths against a root list whose first entry was the whole data directory.
That directory holds the session store, the auth database, the app
encryption key and the settings file, so prompt-injected content could ask
for any of them. No approval prompt stood in the way: reads are classified
read_workspace and pass the untrusted-context gate untouched, which is
correct for reading a workspace and wrong for reading the app's own state.
The agent gets data/agent_workspace/ instead, and the subprocess cwd and
HOME move with it so bash and read_file agree on where scratch files live.
The deny itself is a property of the path, not of the root it arrived
through, because three routes reach the same bytes and closing only the
first leaves the other two working:
- the default root list
- a workspace bound at or above the data directory, which vet_workspace
accepted and chat_routes auto-binds from a path named in the message
- a tool_path_extra_roots setting covering the data directory
_resolve_search_root also returned the workspace root unchecked when the
path was empty, so a bare ls enumerated the directory whatever the deny
list said. It now resolves that case through the same guards.
A containment rule rather than a filename deny list, so state files added
later are covered without anyone remembering to list them, and so a user's
own settings.json or app.db inside a real workspace is not caught.
Four directories of user content stay readable, because the application
hands their paths to the model and tells it to open them: the chat upload
manifest, downloaded mail attachments, personal docs (which covers the
runbook) and personal uploads.
* fix: enforce state deny during recursive file search
* fix: bound protected filesystem searches
* fix(security): reject inode aliases and workspace redirects
* fix(security): harden partitioned agent searches
* fix(security): report fallback worker exits promptly
* fix(security): clean up search readers and retain relative data roots
---------
Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
Rework read_file / write_file confinement after review feedback:
- Remove $HOME from default allow roots. Only project data/ and system
temp dirs are allowed out of the box.
- Add a sensitive-subpath deny list (.ssh, .gnupg, shell rc files,
.env, .netrc, SSH key filenames). Checked BEFORE allowlist so it
blocks even when a broader root is configured.
- Add "tool_path_extra_roots" setting for opt-in broader access.
- Sensitive subpaths remain blocked regardless of configured roots.
Tests: 24 cases covering /etc/shadow, ~/.ssh/authorized_keys,
symlink into .ssh, traversal, shell rc files, key filenames,
extra roots, and dispatch-level end-to-end.