Declarative Permissions

Pre-declare allow/deny rules in YAML, CLI, or Python for CI-safe agent runs

python
from praisonaiagents import Agent

agent = Agent(name="ci-agent", instructions="Run safely with pre-declared tool permissions.")
agent.start("Run tests and deploy to staging.")

Declarative permissions let you pre-declare which tool calls to allow, deny, or ask about — so unattended CLI/CI runs stay safe without interactive prompts.

The user starts an unattended run; declarative rules resolve each tool call to allow, deny, or ask.

Diagram

📄 agents.yaml

🛡️ PermissionManager

📄 .praisonai/config.yaml

⌨️ CLI flags

🐍 ApprovalConfig

allow / deny / ask

Quick Start

  1. YAML
    yaml
    # agents.yaml
    permissions:
      "read:*": allow
      "bash:rm *": deny
      "*": ask
    
    agents:
      assistant:
        role: Assistant
        instructions: Help safely
    bash
    praisonai run agents.yaml
  2. CLI flags
    bash
    praisonai run "deploy the app" \
      --allow 'read:*' \
      --allow 'bash:git *' \
      --deny 'bash:rm *' \
      --permission-default ask
  3. Python
    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="CI Worker",
        instructions="Run safely in CI",
        approval={
            "permissions": {
                "read:*": "allow",
                "bash:rm *": "deny",
            },
        },
    )
  4. In project config

    Declare rules directly in .praisonai/config.yaml — automatically inherited by every praisonai run in the directory:

    yaml
    # .praisonai/config.yaml
    permissions:
      default: ask
      rules:
        - { pattern: "bash:git *", action: allow }
        - { pattern: "bash:rm *",  action: deny }

    Or use the flat mapping form:

    yaml
    # .praisonai/config.yaml
    permissions:
      "read:*": allow
      "bash:rm *": deny
      "*": ask

How It Works

Diagram
PermissionManagerCLI Approval BackendAgentCI RunnerPermissionManagerCLI Approval BackendAgentCI Runnerpraisonai run –no-ttytool call (bash:git push)check(“bash:git push”)allowapproved

On each tool call, the backend builds a target string (tool_name:arg) and PermissionManager.check() returns allow, deny, or ask. Non-interactive runs honour declared rules; ask without a human present falls back to deny.


Pattern Syntax

Patterns use <tool_name>:<arg-glob>:

PatternMeaning
read:*Any read tool call (does not authorise secret files — see below)
read:.env / read:.pem / read:id_rsaSecret-specific reads — required to opt in to reading credential files
bash:git *Git shell commands
write:/etc/*Writes under /etc/ (including truncating redirections)
tool:delete_*MCP-namespaced tool names — matched against the bare or tool:<name> form
external_dir:/data/*Allow access to /data/ when workspace_root is set — automatic on Agent-built managers since PR #5305
external_dir:~/*Allow access to home directory when workspace_root is set — automatic on Agent-built managers since PR #5305
external_dir:*Deny all out-of-workspace access (hard block, no ask prompt)

external_dir: targets

external_dir:<parent>/* is emitted for any path that resolves outside workspace_root. Use these patterns to pre-authorise or hard-block external filesystem access without interactive prompts. Since PR #5305, the Agent auto-sets workspace_root=os.getcwd() when it builds a manager from permissions=, so these rules are active out of the box. See Workspace Boundary.

Compound shell commands

bash: and shell: targets are decomposed into their constituent operations — &&, ;, |, subshells, $(...), backticks, and truncating redirections. A single deny rule fires when the blocked executable appears anywhere inside the compound command. For example, deny: bash:rm * blocks cd /tmp && rm -rf x and echo $(rm -rf x), not just a bare rm call.

Command-Aware Permissions

Full behaviour details, evasion table, and aggregation rules

Detailed dict form supports is_regex, priority, agent_name, and description:

yaml
permissions:
  "read:*": allow
  "bash:rm *":
    action: deny
    description: Block destructive shell ops
    priority: 100

Configuration Surfaces

SurfaceWhereExample
Per-agent definitionmode: / permission: in .praisonai/agents/*.{md,yaml}mode: read-only
Project config.praisonai/config.yaml permissions:"bash:rm *": deny
YAMLTop-level or per-agent approval.permissions: in agents.yaml"bash:rm *": deny
CLI--allow, --deny, --permissions <file>, --permission-default--deny 'bash:rm *'
PythonApprovalConfig(permissions={...}){"read:*": "allow"}

Precedence: agent definition mode: / permission: → agent-level approval.permissions → top-level agents.yaml permissions: → .praisonai/config.yaml permissions: → CLI --allow/--deny (override file) → --permission-default → built-in defaults. CLI flags override config-yaml rules per-pattern. See Agent Presets & Modes for the full per-agent permission reference.

Load from file:

bash
praisonai run agents.yaml --permissions permissions.yaml --deny 'bash:curl *'

Common Patterns

Read-only CI runner — --permission-default deny with --allow 'read:*'.

Git-only worker — allow bash:git *, deny everything else.

Deny destructive commands — baseline "bash:rm ": deny and "write:/etc/": deny.

Lock down external filesystem access — add "external_dir:*": deny when workspace_root is set for a hard block on everything outside the project.

Opt in to reading .env in a trusted CI — add a secret-specific allow rule, not a broad read:*:

yaml
permissions:
  "read:*": allow            # ordinary files auto-read
  "read:*.env": allow        # explicit opt-in for .env — the broad rule alone won't

Best Practices

Set permission-default deny in CI

Use --permission-default deny or "*": deny so undeclared tools are blocked.

Prefer specific patterns first

Higher-priority rules win — declare bash:git push before broad bash:.

Version-control policy files

Keep permissions.yaml next to agents.yaml in git.

Test with –no-tty locally

Verify rules before deploying to CI.


Related