Summary
The built-in file tools (read/write/edit/delete) resolve every path through the shared workspace-containment resolver in praisonaiagents/tools/path_safety.py (resolve_within_root/is_within_root) and receive path-scoped permission targets (edit:<path>, write:<path>) via approval/utils.py:build_permission_target. The built-in shell/exec tool does neither. ShellTool.execute (praisonaiagents/tools/shell_tools.py) hands the command straight to a subprocess behind a single blanket @require_approval(risk_level="critical") gate, never consults path_safety, and its cwd can even fall back to the user's home directory.
The result is an inconsistent security posture: an agent can read, mutate or exfiltrate files anywhere on the host (~/.ssh, /etc, sibling repositories) through the shell tool, even though the SDK guarantees workspace containment for its file tools. For CLI-first, production agent runs this is a surprising containment bypass.
Current behaviour
- File tools — path resolved and constrained to the workspace root (
path_safety.py:25 resolve_within_root); per-path approval target built via approval/utils.py:67 build_permission_target → edit:<path> / write:<path> (see _FILE_TOOL_PREFIXES).
- Shell tool —
tools/shell_tools.py:47 @require_approval(risk_level="critical") is the only gate; no import of path_safety; the command body and any file paths inside it are never extracted or checked; cwd accepts arbitrary/home-directory locations (shell_tools.py:107 home-dir fallback). Once a broad shell approval (or a command-prefix allow) is granted, subsequent commands are not re-evaluated against which paths they touch.
Desired behaviour
- Before executing a shell command, extract the filesystem paths it will touch and classify each as in-workspace or out-of-workspace using the existing
path_safety resolver.
- Commands touching only in-workspace paths follow the normal approval flow; commands touching paths outside the resolved workspace root raise a distinct approval target (e.g.
shell:external-path) that names the offending path(s), so a broad "allow shell" grant does not silently authorise operations outside the project.
- Honour a configurable workspace root (default
cwd/git root, consistent with the resolver), remain fail-closed, and stay backward-compatible with an explicit opt-out for sandboxed/CI runs.
Layer placement
- Primary layer: core (
src/praisonai-agents/praisonaiagents/)
- Why not core: this is core — the shell tool,
path_safety, the approval registry and build_permission_target all live in praisonaiagents.
- Why not wrapper: the wrapper only surfaces flags; fixing it there would leave direct SDK users (who never touch the CLI) unprotected, and the containment primitive being bypassed is a core one.
- Why not tools (
praisonai-tools): the affected shell tool is a built-in in praisonaiagents.tools, not the optional integrations package; duplicating a built-in there is explicitly disallowed.
- Why not plugins: a policy plugin could add an extra check, but the defect is that a built-in tool ignores the SDK's own containment guarantee; the guarantee must live with the tool + approval core, not an optional plugin a user may never install.
- Secondary touch (optional): a small command-argument path-extraction helper alongside
path_safety.py.
- 3-way surface (CLI + YAML + Python): yes — Python (
Agent(...) tool/exec config), YAML (agent/tool policy), CLI (praisonai run).
Proposed approach
Add a command-argument analyser that identifies filesystem paths in a shell command (starting with the common file-consuming commands and redirections), resolve each with path_safety.resolve_path, and split into in-root vs escaping sets. Extend approval/utils.build_permission_target to emit a shell path-scoped target, and have ShellTool.execute request an out-of-workspace approval whenever any path escapes the root. Gate the whole path behind a config flag defaulting to on (fail-closed), with an explicit opt-out for trusted/sandboxed environments.
Resolution sketch
tools/shell_tools.py: before subprocess launch, run the analyser; if escaping paths exist, request approval with a distinct target and the path list; otherwise proceed as today.
tools/path_safety.py (or a sibling): extract_command_paths(command) -> list[str], reusing resolve_within_root.
approval/utils.py: add a shell → path-scoped prefix so shell commands earn the same path-scoped grants as file tools.
- Config: an
ExecutionConfig/tool-config flag (e.g. shell_workspace_boundary: bool = True) with env/CLI opt-out.
Severity
High — closes a workspace-containment bypass that contradicts the SDK's own guarantee for file tools, and directly affects the production safety of CLI-first agent runs.
Validation
tools/path_safety.py:25 resolve_within_root, :37 is_within_root — the shared containment resolver used by file tools.
approval/utils.py:67 build_permission_target — file tools get edit:<path>/write:<path>; shell is absent from _FILE_TOOL_PREFIXES.
tools/shell_tools.py:47 blanket @require_approval(risk_level="critical"); no path_safety import; :107 cwd home-directory fallback.
Summary
The built-in file tools (
read/write/edit/delete) resolve every path through the shared workspace-containment resolver inpraisonaiagents/tools/path_safety.py(resolve_within_root/is_within_root) and receive path-scoped permission targets (edit:<path>,write:<path>) viaapproval/utils.py:build_permission_target. The built-in shell/exec tool does neither.ShellTool.execute(praisonaiagents/tools/shell_tools.py) hands the command straight to a subprocess behind a single blanket@require_approval(risk_level="critical")gate, never consultspath_safety, and itscwdcan even fall back to the user's home directory.The result is an inconsistent security posture: an agent can read, mutate or exfiltrate files anywhere on the host (
~/.ssh,/etc, sibling repositories) through the shell tool, even though the SDK guarantees workspace containment for its file tools. For CLI-first, production agent runs this is a surprising containment bypass.Current behaviour
path_safety.py:25resolve_within_root); per-path approval target built viaapproval/utils.py:67build_permission_target→edit:<path>/write:<path>(see_FILE_TOOL_PREFIXES).tools/shell_tools.py:47@require_approval(risk_level="critical")is the only gate; no import ofpath_safety; the command body and any file paths inside it are never extracted or checked;cwdaccepts arbitrary/home-directory locations (shell_tools.py:107home-dir fallback). Once a broad shell approval (or a command-prefix allow) is granted, subsequent commands are not re-evaluated against which paths they touch.Desired behaviour
path_safetyresolver.shell:external-path) that names the offending path(s), so a broad "allow shell" grant does not silently authorise operations outside the project.cwd/git root, consistent with the resolver), remain fail-closed, and stay backward-compatible with an explicit opt-out for sandboxed/CI runs.Layer placement
src/praisonai-agents/praisonaiagents/)path_safety, the approval registry andbuild_permission_targetall live inpraisonaiagents.praisonai-tools): the affected shell tool is a built-in inpraisonaiagents.tools, not the optional integrations package; duplicating a built-in there is explicitly disallowed.path_safety.py.Agent(...)tool/exec config), YAML (agent/tool policy), CLI (praisonai run).Proposed approach
Add a command-argument analyser that identifies filesystem paths in a shell command (starting with the common file-consuming commands and redirections), resolve each with
path_safety.resolve_path, and split into in-root vs escaping sets. Extendapproval/utils.build_permission_targetto emit a shell path-scoped target, and haveShellTool.executerequest an out-of-workspace approval whenever any path escapes the root. Gate the whole path behind a config flag defaulting to on (fail-closed), with an explicit opt-out for trusted/sandboxed environments.Resolution sketch
tools/shell_tools.py: before subprocess launch, run the analyser; if escaping paths exist, request approval with a distinct target and the path list; otherwise proceed as today.tools/path_safety.py(or a sibling):extract_command_paths(command) -> list[str], reusingresolve_within_root.approval/utils.py: add a shell → path-scoped prefix so shell commands earn the same path-scoped grants as file tools.ExecutionConfig/tool-config flag (e.g.shell_workspace_boundary: bool = True) with env/CLI opt-out.Severity
High — closes a workspace-containment bypass that contradicts the SDK's own guarantee for file tools, and directly affects the production safety of CLI-first agent runs.
Validation
tools/path_safety.py:25resolve_within_root,:37is_within_root— the shared containment resolver used by file tools.approval/utils.py:67build_permission_target— file tools getedit:<path>/write:<path>; shell is absent from_FILE_TOOL_PREFIXES.tools/shell_tools.py:47blanket@require_approval(risk_level="critical"); nopath_safetyimport;:107cwdhome-directory fallback.