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

> Discover how the Rust Host Core in PI-Desktop manages privileged operations. Learn about its PermissionManager for risk classification, allowlists, and user prompts for secure execution.

- Repository: [Lan/PI-Desktop](https://github.com/vastsa/PI-Desktop)
- Tags: internals
- Published: 2026-09-11

---

**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`](https://github.com/vastsa/PI-Desktop/blob/main/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.

```rust
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.

```rust
// 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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/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`](https://github.com/vastsa/PI-Desktop/blob/main/crates/host-core/src/permissions.rs), containing the `PermissionManager` and request lifecycle logic. Supporting components include [`sessions.rs`](https://github.com/vastsa/PI-Desktop/blob/main/sessions.rs) for session mode definitions, [`tools/mod.rs`](https://github.com/vastsa/PI-Desktop/blob/main/tools/mod.rs) for tool registration, and [`rpc/mod.rs`](https://github.com/vastsa/PI-Desktop/blob/main/rpc/mod.rs) for exposing permission APIs to the renderer. The Host Core initialization occurs in [`main.rs`](https://github.com/vastsa/PI-Desktop/blob/main/main.rs), which creates the shared `AppState` and starts the RPC server.