Skip to content

Security Policy

Security in PRX is not an afterthought bolted onto the agent loop. It is a structural component — a 5-layer policy pipeline that governs what every agent, tool, and plugin is allowed to do.

The core policy is defined by the SecurityPolicy struct:

pub struct SecurityPolicy {
pub autonomy: AutonomyLevel,
pub workspace_restrictions: Vec<PathBuf>,
pub allowed_commands: Vec<String>,
pub forbidden_paths: Vec<PathBuf>,
pub rate_limits: RateLimits,
pub cost_limits: CostLimits,
}
LevelBehavior
ReadOnlyAgent can read files and call LLMs but cannot execute commands, write files, or modify state
SupervisedAgent can propose actions but must receive human approval before execution
FullAgent can execute actions autonomously within policy bounds

Limits the file system paths an agent can access:

workspace_restrictions = ["/home/user/project", "/tmp/workspace"]

Any file operation outside these paths is denied, regardless of autonomy level.

An explicit allowlist of shell commands the agent may execute:

allowed_commands = ["git", "cargo", "npm", "python", "ls", "cat", "grep"]

Commands not on this list are blocked. Wildcards are not supported — every permitted command must be listed explicitly.

Paths that are always denied, even if they fall within a permitted workspace:

forbidden_paths = ["/home/user/project/.env", "/home/user/project/secrets/"]

Controls request frequency:

[rate_limits]
requests_per_minute = 60
requests_per_hour = 500
max_concurrent = 4

Caps spending on LLM API calls:

[cost_limits]
max_per_request_usd = 1.00
max_per_hour_usd = 10.00
max_per_day_usd = 50.00

When a cost limit is reached, further LLM calls are blocked until the window resets.

Policies are evaluated in a layered hierarchy. Each layer can restrict (but never expand) the permissions granted by the layer above.

┌──────────────────────────────┐
│ Layer 1: Global Policy │ System-wide defaults
├──────────────────────────────┤
│ Layer 2: Profile Policy │ Per-user or per-role overrides
├──────────────────────────────┤
│ Layer 3: Agent Policy │ Per-named-agent restrictions
├──────────────────────────────┤
│ Layer 4: Group Policy │ Per-channel-group restrictions
├──────────────────────────────┤
│ Layer 5: Tool Policy │ Per-tool restrictions
└──────────────────────────────┘
LayerScopeExample
GlobalAll agents, all channelsautonomy = Supervised, max_per_day_usd = 100
ProfileSpecific user or roleAdmin profile gets autonomy = Full; guest profile stays ReadOnly
AgentNamed agent (e.g., “coder”)Coder agent gets allowed_commands = ["git", "cargo"], research agent gets none
GroupChannel group (e.g., a specific Telegram group)Public groups forced to ReadOnly
ToolIndividual tool (e.g., “bash”)Bash tool gets extra forbidden_paths, tighter rate_limits

The effective policy for any action is the intersection of all applicable layers. If any layer denies an action, it is denied.

When autonomy = Supervised, the agent cannot execute actions directly. Instead:

  1. Agent proposes an action (e.g., “run cargo test”)
  2. PRX formats the proposal and sends it to the supervising channel
  3. The supervisor (human) reviews and responds: approve, deny, or modify
  4. If approved, PRX executes the action and returns the result to the agent
  5. If denied, the agent receives a denial message and must find an alternative approach

Approval requests include:

  • The exact command or action proposed
  • The agent name and context
  • The security policy layer that requires approval
  • A timeout (default: 5 minutes) after which the action is auto-denied

PRX supports multiple sandboxing backends for isolating tool execution. The sandbox is selected based on platform availability and configuration.

Full container isolation. Commands run inside a disposable Docker container with:

  • Mounted workspace directory (read-only or read-write per policy)
  • No network access (unless explicitly allowed)
  • Resource limits (CPU, memory, time)
  • Dropped capabilities
[sandbox]
backend = "docker"
image = "prx-sandbox:latest"
network = false
memory_limit = "512m"
cpu_limit = "1.0"
timeout_seconds = 300

Lightweight Linux sandboxing using namespaces and seccomp:

  • File system whitelisting
  • Network filtering
  • Seccomp syscall filtering
  • No root required
[sandbox]
backend = "firejail"
whitelist = ["/home/user/project"]
net = "none"

Minimal unprivileged sandboxing used by Flatpak:

  • Mount namespace isolation
  • PID namespace isolation
  • Bind-mount only specified directories
  • Drop all capabilities
[sandbox]
backend = "bubblewrap"
bind_rw = ["/home/user/project"]
bind_ro = ["/usr", "/lib", "/bin"]
unshare_net = true

Linux Security Module for fine-grained file system access control:

  • Restricts file access at the kernel level
  • No container overhead
  • Works with unprivileged processes
  • Available on Linux 5.13+
[sandbox]
backend = "landlock"
allowed_read = ["/home/user/project", "/usr/lib"]
allowed_write = ["/home/user/project/output"]

PRX auto-detects available backends and selects the strongest available:

Docker > Bubblewrap > Firejail > Landlock > None

If no sandbox backend is available and the policy requires sandboxing, tool execution is denied.

PRX plugins are compiled to WASM and executed in a wasmtime sandbox:

  • Memory isolation: each plugin gets its own linear memory
  • No file system access unless explicitly granted via WASI
  • No network access unless explicitly granted
  • CPU time limits enforced by wasmtime fuel metering
  • Plugins cannot access PRX internals — they communicate through a defined host API
[plugins.my_plugin]
path = "plugins/my_plugin.wasm"
permissions = ["fs:read:/data", "net:https://api.example.com"]
fuel_limit = 1_000_000

This ensures that third-party or user-authored plugins cannot compromise the host system, leak data, or consume unbounded resources.