Workspace Boundary

Gate shell and file access outside your project root with a distinct external_dir approval

Set a workspace root and any tool call touching a path outside it asks for extra approval β€” a single broad allow no longer lets the agent reach your whole machine.

The PermissionManager boundary described here is complementary to the EditTools fallback root introduced in PR #2981 β€” see File Editing β†’ Constructor Parameters.

Diagram

Workspace Boundary

Inside workspace

Outside workspace

Rule: allow

Rule: deny

πŸ€– Agent

πŸ”§ Tool Call

Path Check

βœ… Allow

❓ external_dir: ask

βœ… Pre-authorised

🚫 Blocked

Quick Start

  1. Agent β€” boundary active out of the box

    Construct an Agent with permissions= β€” workspace_root defaults to os.getcwd(), so the boundary gate is on with no extra wiring:

    python
    from praisonaiagents import Agent
    
    # workspace_root defaults to os.getcwd() β€” no extra wiring
    agent = Agent(
        name="researcher",
        instructions="Read files inside my project and summarise them.",
        permissions={
            "read:*": "allow",
            "external_dir:*": "deny",  # hard-block everything outside CWD
        },
    )
    
    agent.start("Summarise the READMEs in this repo")
  2. Enable workspace boundary directly

    Create a PermissionManager with workspace_root and the boundary gate activates automatically:

    python
    from praisonaiagents.permissions import PermissionManager
    
    manager = PermissionManager(workspace_root="/path/to/project")
    
    result = manager.check("bash:cat /etc/passwd")
    print(result.needs_approval)  # True β€” /etc/passwd is outside workspace
    print(result.target)          # bash:cat /etc/passwd

    When the manager checks cat /etc/passwd, it returns needs_approval=True β€” even if a broad bash:cat * β†’ allow rule is set.

  3. Pre-authorise an external directory

    Add a rule to allow a specific external directory without prompting:

    python
    from praisonaiagents.permissions import PermissionManager, PermissionRule, PermissionAction
    
    manager = PermissionManager(workspace_root="/proj")
    manager.add_rule(PermissionRule(
        pattern="external_dir:/data/*",
        action=PermissionAction.ALLOW,
        description="Allow shared data volume",
    ))
    
    result = manager.check("bash:cat /data/input.csv")
    print(result.action)  # allow β€” pre-authorised

How It Works

Diagram
path_safetyCommandParserPermissionManagerToolAgentUserpath_safetyCommandParserPermissionManagerToolAgentUserRun taskbash:cat /etc/passwdcheck(“bash:cat /etc/passwd”)parse_command(“cat /etc/passwd”)[ShellOp(cat, /etc/passwd)]is_within_root(“/etc/passwd”, “/proj”)False (outside workspace)ask (external_dir:/etc/*)Approval requiredPrompt for external_dir:/etc/*
StepAction
1Tool call arrives as bash:<cmd> or file tool target
2CommandParser extracts path-like arguments from the command
3Each path is checked with path_safety.is_within_root(path, workspace_root)
4Out-of-workspace paths emit an external_dir:<parent>/* sub-target
5Sub-target evaluated β€” defaults to ask unless a rule pre-authorises or denies it

The boundary check is dialect-agnostic: it fires on Remove-Item C:\outside\x the same way it fires on rm /outside/x. See Command-Aware Permissions β†’ Windows / PowerShell dialect.


What Triggers the Gate

Any path that resolves outside workspace_root triggers an external_dir: approval request.

Path formExampleOutcome
Absolute path/etc/hostsexternal_dir:/etc/* β†’ ask
Home-relative~/.bashrcexternal_dir:<home>/* β†’ ask
Explicit relative../../etc/passwdexternal_dir:/etc/* β†’ ask
Env-prefixed$HOME/.config/xexternal_dir:<home>/* β†’ ask
Bare traversalsub/../../etc/passwdexternal_dir:/etc/* β†’ ask
Joined flag value--config=/etc/xexternal_dir:/etc/* β†’ ask
Short-getopt attachedcurl -o/tmp/fileexternal_dir:/tmp/* β†’ ask
Redirect write-targetecho x > ~/.bashrcexternal_dir:<home>/* β†’ ask
External executable/tmp/tool.shexternal_dir:/tmp/* β†’ ask

Aggregation with Existing Rules

The workspace boundary fits into the same deny→ask→allow aggregation as all other permission checks.

Rule stateCommand touches external pathOutcome
bash:cat * β†’ allowcat /etc/passwdask (boundary gate fires)
external_dir:/data/* β†’ allowcat /data/x.csvallow (pre-authorised)
external_dir:* β†’ denycat /etc/passwddeny (hard block)
bash:rm * β†’ denyrm -rf /etc/otherdeny (explicit deny wins first)

Deny always wins β€” an explicit deny on bash:rm or external_dir: beats the boundary ask, and an explicit deny cannot be overridden by a boundary allow.


Backward Compatibility

When workspace_root is None (the default), no boundary check runs and behaviour is 100% unchanged. Only opting in to workspace_root activates the gate β€” no existing code breaks.

python
from praisonaiagents.permissions import PermissionManager

# No workspace_root β†’ old behaviour, no boundary check
manager = PermissionManager()

# With workspace_root β†’ boundary gate active
manager_bounded = PermissionManager(workspace_root="/proj")

Fail-Closed Behaviour


Pre-Authorising External Directories

CLI

bash
praisonai permissions allow "external_dir:/data/*" --description "Shared data volume"
praisonai permissions deny "external_dir:*" --description "Block all external paths"

YAML

yaml
# agents.yaml
permissions:
  "external_dir:/data/*": allow
  "external_dir:*": deny
  "read:*": allow
  "bash:rm *": deny

Python

python
from praisonaiagents.permissions import PermissionManager, PermissionRule, PermissionAction

manager = PermissionManager(workspace_root="/proj")
manager.add_rule(PermissionRule(
    pattern="external_dir:/data/*",
    action=PermissionAction.ALLOW,
    description="Allow shared data volume",
))
manager.add_rule(PermissionRule(
    pattern="external_dir:*",
    action=PermissionAction.DENY,
    description="Block all other external paths",
))
manager.add_rule(PermissionRule(
    pattern="read:*",
    action=PermissionAction.ALLOW,
))

Best Practices

Set workspace_root to your project root in CI runners

Point workspace_root at your checked-out repository path. The agent can freely read and write within the project; anything outside requires explicit pre-authorisation.

Pre-authorise shared data directories

If your workflow legitimately reads from /data/ or /shared/, add external_dir:/data/* β†’ allow. This is safer than using --approve-all-tools which bypasses the gate entirely.

Combine with external_dir:* β†’ deny for maximum safety

Set external_dir:* β†’ deny to hard-block all out-of-workspace access. No ask prompt appears β€” the operation is immediately rejected. Use this in high-security CI pipelines.

Deny still wins over the boundary gate

A bash:rm * β†’ deny rule fires before the boundary check. You can still hard-block destructive commands regardless of whether the path is inside or outside the workspace.


Related