# How PI-Desktop Plugin Permissions and Isolation Work: A Complete Technical Guide

> Explore how PI-Desktop plugin permissions and isolation work. Learn about sandboxed Node processes, manifest-driven permissions, and runtime validation via a Rust gateway.

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

---

**PI-Desktop runs every plugin in a sandboxed Node process and enforces a manifest-driven permission model that validates file system, network, and UI access at runtime through a Rust-based permission gateway.**

The PI-Desktop plugin system (vastsa/PI-Desktop) combines process-level isolation with declarative security to prevent untrusted code from accessing host resources. Unlike simple allow-list approaches, the platform uses a **closed-by-default** architecture where every capability—from reading files to opening UI panels—must be explicitly declared in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) and validated by the Rust host core before execution.

## Architectural Isolation Mechanisms

PI-Desktop employs a multi-layer isolation strategy that separates plugin logic, UI rendering, and host privileges into distinct trust boundaries.

### Process Isolation for Plugin Entry Code

When a plugin loads, the `PluginManager` (located in `plugins/registry`) spawns the plugin’s entry code in a **dedicated Node process** via `apps/desktop/electron/main/plugin-runtime.mjs`. This process has no direct access to the host’s SQLite database or system APIs. Instead, all calls to `pi.*` host APIs route through a **permission gateway** that intercepts requests and checks them against the plugin’s declared permissions.

### Sandboxed UI Rendering

Panels and views run inside **sandboxed Electron windows** with **Node integration disabled** ([`apps/desktop/electron/main/plugin-panel-host.ts`](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-panel-host.ts)). The UI context cannot access Node.js APIs or Electron internals directly. Communication with the plugin backend occurs exclusively through the `window.pluginBridge` channel, which filters all messages through the same permission checks applied to the main process.

### Host Core Authority

The Rust crate `crates/host-core` maintains ultimate control over sensitive operations. It owns the SQLite database, file system access, and network stack. Before delegating any request to the plugin process, the host core validates file system scopes, network allow-lists, and high-risk capabilities like shell execution or clipboard access.

## Declaring PI-Desktop Plugin Permissions in manifest.json

Every plugin must declare its required capabilities in a static [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) file. The system evaluates these declarations using **closed-by-default** semantics—an absent or empty permission list grants zero access.

### Permission Structure

```json
{
  "permissions": [
    "ui.panel",
    "fs.read",
    "net.fetch"
  ],
  "fs": {
    "read": { "scope": ["docs/**", "*.md"] }
  },
  "net": {
    "domains": ["api.example.com", "*.githubusercontent.com"]
  }
}

```

The **`permissions`** array enumerates high-level capabilities such as `ui.panel`, `fs.read`, or `agent.tool.register`. For permissions supporting **range constraints** (like `fs.*` or `net.*`), the manifest must include corresponding scope or domain lists. The host rejects any request outside these declared boundaries, including path traversal attempts (`..`) or symlinks escaping the root directory.

The canonical reference for available permissions lives in [`spec/07-plugins/13-plugin-permissions-matrix.md`](https://github.com/vastsa/PI-Desktop/blob/main/spec/07-plugins/13-plugin-permissions-matrix.md), which maps every host API to its required permission string and risk level.

## Runtime Permission Enforcement

When a plugin invokes a host API—such as `pi.fs.readText`—the system executes a three-step validation pipeline implemented in [`crates/host-core/src/plugins/permissions.rs`](https://github.com/vastsa/PI-Desktop/blob/main/crates/host-core/src/plugins/permissions.rs):

1. **Lookup**: The host maps the API call to its required permission identifier (e.g., `pi.fs.readText` requires `fs.read`).
2. **Verification**: The system checks that the permission appears in the plugin’s manifest and has been **granted by the user** through an explicit prompt (options: allow once, always, or deny).
3. **Scope Validation**: For scoped permissions, the host validates that the requested file path matches the `manifest.fs.read.scope` globs or that the network hostname appears in `manifest.net.domains`.

If any check fails, the host immediately throws a `PERMISSION_DENIED` error that propagates back to the plugin process. Plugins can catch these errors to implement fallback behavior or user notifications.

## Isolation Guarantees and Threat Mitigation

The PI-Desktop plugin system addresses specific attack vectors through targeted isolation techniques:

| Threat Vector | Isolation Technique |
|--------------|---------------------|
| **File System Access** | Path validation against declared globs; rejection of traversal sequences and restricted paths (`.env*`, `.git/**`). |
| **Network Egress** | `net.fetch` restricted to `manifest.net.domains`; panel-side requests inherit the same allow-list. |
| **Clipboard and Shell** | Explicit opt-in required (`clipboard.read`, `shell.openExternal`); APIs remain unavailable without declaration. |
| **Inter-Plugin Messaging** | Message bus (`bus.publish`, `bus.subscribe`) scopes by topics, but payloads are visible to any subscriber—sensitive data should never traverse this channel. |
| **Agent Extensions** | `agent.extension` runs unsandboxed inside the Agent process and requires high-risk confirmation due to potential system access. |
| **Panel UI Escape** | Node.js disabled in renderer process; only `window.pluginBridge` exposed to prevent arbitrary IPC exploitation. |

All enforcement occurs within the Rust host core before delegation, ensuring that even a compromised Node process cannot bypass scope restrictions.

## Permission Lifecycle and User Consent

The permission system maintains state across the plugin lifecycle through distinct phases:

- **Declaration**: Static definition in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) at install time.
- **User Grant**: Interactive prompts appear on first use; users may grant **once**, **always**, or **deny** future requests.
- **Hot-Reload**: Development mode changes to permissions require a full plugin reload and trigger re-prompting for user consent.
- **Revocation**: Disabling or uninstalling a plugin automatically invalidates all granted permissions and purges associated state from the host core.

## Troubleshooting Common Permission Errors

Developers frequently encounter specific `PERMISSION_DENIED` scenarios that indicate manifest misconfigurations:

| Symptom | Root Cause | Resolution |
|---------|------------|------------|
| `PERMISSION_DENIED` on `pi.fs.readText` | Missing `fs.read` permission or path outside declared scope | Add `"fs.read"` to the permissions array and expand `fs.read.scope` globs to include the target path. |
| Panel fails to render | Absent `ui.panel` permission or invalid entry point | Declare `"ui.panel"` and verify that `manifest.ui.panel` references a valid HTML file. |
| Network request blocked | `net.fetch` granted but hostname not listed | Append the target domain (e.g., `"api.example.com"`) or a wildcard pattern to `manifest.net.domains`. |
| Agent tool invisible | Missing `agent.tool.register` or registration failure | Include `"agent.tool.register"` in permissions and ensure the tool registers during the `onLoad` lifecycle hook. |

## Summary

- PI-Desktop isolates plugins using **separate Node processes** for entry code and **sandboxed Electron windows** for UI rendering.
- The **manifest-driven permission model** requires explicit declaration of all capabilities in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json), evaluated under closed-by-default semantics.
- Runtime enforcement occurs in [`crates/host-core/src/plugins/permissions.rs`](https://github.com/vastsa/PI-Desktop/blob/main/crates/host-core/src/plugins/permissions.rs), validating user grants and scope constraints before executing sensitive operations.
- **File system**, **network**, and **system API** access are strictly bounded by declared scopes, with hard-coded deny-lists preventing access to sensitive paths.
- Users retain control through **interactive grant prompts** that support temporary or permanent authorization, with automatic revocation on plugin removal.

## Frequently Asked Questions

### How does PI-Desktop prevent plugins from accessing files outside their declared scope?

The host core in [`crates/host-core/src/plugins/permissions.rs`](https://github.com/vastsa/PI-Desktop/blob/main/crates/host-core/src/plugins/permissions.rs) sanitizes every file path request against the `fs.read.scope` or `fs.write.scope` globs defined in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json). It explicitly rejects path traversal sequences (`..`), resolves symlinks to ensure they remain within the allowed root, and maintains a hard-coded deny-list protecting files like `.env` and `.git/**`. If the path fails validation, the host returns a `PERMISSION_DENIED` error before the filesystem operation executes.

### Can a plugin access the network if the user grants `net.fetch` but omits domains from the manifest?

No. The PI-Desktop plugin system uses closed-by-default semantics for network permissions. Even with `net.fetch` listed in the `permissions` array, the host checks the `manifest.net.domains` list during every `pi.net.fetch` call. An empty or missing `domains` array results in an immediate `PERMISSION_DENIED` error, preventing any egress communication.

### What is the difference between the plugin Node process and the panel sandbox?

The **plugin Node process** (spawned by `plugin-runtime.mjs`) executes the plugin’s main logic with access to Node.js APIs but isolated from the host core via IPC. The **panel sandbox** (created by [`plugin-panel-host.ts`](https://github.com/vastsa/PI-Desktop/blob/main/plugin-panel-host.ts)) renders HTML/CSS in an Electron window with Node integration disabled, preventing access to `require` or filesystem APIs. The panel communicates with its parent plugin only through the `window.pluginBridge` channel, which inherits the same permission constraints enforced by the Rust host core.

### Why are agent extensions considered high-risk permissions?

Agent extensions (declared via `agent.extension`) execute inside the Agent process **without sandboxing**, granting them the same privileges as the host application. Because this bypasses the Node process isolation and Electron sandbox used by standard plugins, malicious code could potentially access unrestricted system resources. Consequently, PI-Desktop treats `agent.extension` as a high-risk permission requiring **explicit user confirmation** beyond standard capability grants.