How the Rust Host Core Handles Privileged Operations in PI-Desktop

The Rust Host Core in PI-Desktop routes every privileged operation through a centralized PermissionManager that classifies tool risk, evaluates contract-mode allowlists, and either auto-approves safe actions or prompts the user via a request queue with automatic two-minute expiration.

The open-source vastsa/PI-Desktop repository implements a security-first architecture for AI-assisted desktop automation. At the heart of this system, the Rust Host Core manages access to sensitive resources through a fine-grained permission model that governs filesystem access, code execution, and plugin operations.

Risk Classification and Tool Registration

Every tool capable of affecting the host system—whether a built-in command like Write or Bash, or a third-party plugin—must declare its risk level before execution. In crates/host-core/src/permissions.rs, the PermissionManager::tool_risk_with_declared method (lines 16-30) assigns each tool a classification of Low, Medium, or High. Built-in tools carry hard-coded defaults, while dynamic plugins specify risk via their manifest declarations.

Built-in vs. Plugin Risk Assignment

When a plugin registers with the Host Core, it provides a non-empty planSafeActions list to indicate which operations are safe for contract-mode execution. The system uses this metadata to enforce the principle of least privilege, ensuring that high-risk operations cannot execute without explicit user consent or pre-established session grants.

Permission Modes and Auto-Approval Logic

The Host Core supports multiple permission modes—auto, ask, accept-edits, and others—that determine whether a privileged operation proceeds silently or requires user interaction. The evaluate_auto_with_permission_mode_and_risk_and_path method (lines 94-108) implements the core decision logic, checking the current session mode, tool risk level, and existing grants before returning an Allow, Deny, or Ask decision.

Contract Mode Allowlists

In contract modes (plan or goal), the system enforces a strict allowlist via PermissionManager::plan_mode_allows (lines 33-42). Only tools explicitly declared as safe may execute automatically; all others are denied unless the user has granted specific session permissions. This prevents autonomous agents from performing unexpected privileged operations during automated planning sessions.

Session Grant Persistence

Users may grant a tool permission for the entire session, creating a persistent authorization that bypasses future prompts. The permission evaluation logic checks for existing session grants (lines 44-50) and returns AllowSession when a valid grant exists, streamlining workflows for trusted, repetitive operations while maintaining security boundaries.

The Permission Request Lifecycle

When auto-approval is not possible, the Host Core creates a structured permission request that pauses tool execution until the user responds. This lifecycle manages request creation, UI presentation, timeout handling, and final resolution.

Request Creation and Preview Truncation

The create_request_with_risk_and_shell method (lines 70-84) generates a PermissionRequest struct containing a unique ID, risk classification, and truncated argument preview. To prevent UI flooding, the system applies ARGS_PREVIEW_MAX_CHARS = 2000, ensuring that large file contents or command outputs do not overwhelm the permission dialog.

let mut pm = PermissionManager::default();
let args = serde_json::json!({
    "path": "/tmp/notes.txt",
    "content": "Important data..."
});
let (req, rx) = pm.create_request(
    "session-123",
    "call-42",
    "Write",
    args,
    "User-initiated file write"
);
// `req` now contains a truncated `args_preview` and a unique `request_id`.
// `rx` can be awaited for the user's decision.

Pending Queue and Timeout Handling

Requests enter a HashMap<String, Pending> managed by the PermissionManager. Each request expires after PERMISSION_TIMEOUT_MS = 120000 milliseconds (2 minutes) to prevent indefinite blocking. The expire_stale method (lines 109-125) periodically purges outdated requests, automatically sending a Deny decision to unblock waiting tool callers.

Resolution and Cancellation

When the user makes a decision—or if the request times out—the resolve method (lines 128-150) sends the result via a oneshot::Sender channel. The system also supports explicit cancellation through the cancel method, which immediately terminates the pending request and notifies the awaiting tool that permission was denied.

// Evaluating an auto-allow decision based on the current permission mode
let decision = pm.evaluate_auto_with_permission_mode(
    "session-123",
    "Write",
    "agent",            // session mode
    "auto",             // permission mode
    &HashMap::new()     // no explicit session grants
);
// `decision` is Some(PermissionDecision::AllowOnce) for high-risk tools in auto mode.

// Resolving a user's choice
pm.resolve(&req.request_id, PermissionDecision::AllowOnce).unwrap();

External-Path Security Boundaries

Accessing files outside the designated workspace or scratch directory triggers an external-path check. In auto mode, such requests receive AllowOnce approval for a single operation, while other modes require explicit confirmation unless covered by a session grant. This sandboxing mechanism prevents tools from unexpectedly accessing sensitive system files or user data in arbitrary locations.

Summary

  • The PermissionManager in crates/host-core/src/permissions.rs serves as the central gatekeeper for all privileged operations in PI-Desktop.
  • Tools are classified by risk level (Low, Medium, High) with distinct handling for built-in commands versus plugin declarations.
  • Contract mode enforces strict allowlists via plan_mode_allows, limiting autonomous execution to pre-approved safe actions.
  • Permission modes (auto, ask, etc.) combined with session grants determine whether operations proceed automatically or require user confirmation.
  • The request lifecycle includes creation with truncated previews, a 2-minute timeout via PERMISSION_TIMEOUT_MS, and resolution through oneshot channels.
  • External-path access receives special handling to maintain sandbox boundaries while allowing necessary cross-directory operations.

Frequently Asked Questions

What is the PermissionManager in PI-Desktop?

The PermissionManager is a Rust struct defined in crates/host-core/src/permissions.rs that centralizes security decisions for the Host Core. It evaluates tool risk, manages session grants, maintains a pending request queue, and coordinates with the UI to obtain user consent before executing privileged operations.

How does contract mode restrict privileged operations?

Contract mode (plan or goal) restricts execution to a predefined allowlist of safe tools. The plan_mode_allows method checks whether a tool appears in the planSafeActions list; if not, the operation is denied unless the user has explicitly granted session-wide permission. This ensures automated planning agents cannot execute arbitrary high-risk commands.

What happens when a permission request times out?

When a request exceeds the PERMISSION_TIMEOUT_MS limit of 120 seconds, the expire_stale method automatically purges it from the pending queue and sends a Deny decision through the oneshot::Sender channel. This unblocks the awaiting tool and prevents the system from hanging indefinitely on unanswered permission prompts.

Where is the privileged operation logic implemented in the codebase?

The primary implementation resides in crates/host-core/src/permissions.rs, containing the PermissionManager and request lifecycle logic. Supporting components include sessions.rs for session mode definitions, tools/mod.rs for tool registration, and rpc/mod.rs for exposing permission APIs to the renderer. The Host Core initialization occurs in main.rs, which creates the shared AppState and starts the RPC server.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →