# How PI-Desktop File Scope Configuration Works: A Complete Guide to Plugin Permissions

> Master PI-Desktop file scope configuration and plugin permissions. Learn how activation and manifest scopes control extension file access for enhanced security and functionality.

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

---

**PI-Desktop enforces fine-grained file access through a dual-layer system combining activation scopes (where extensions run) and manifest-declared permission scopes (what files they can access).**

The **PI-Desktop** project (`vastsa/PI-Desktop`) implements a rigorous permission model that ensures plugins, MCP servers, and user skills operate strictly within defined boundaries. Understanding how **file scope configuration** works is essential for both extension developers and users managing workspace security.

## Understanding Activation Scopes

**Activation scopes** determine the contextual boundaries where an extension is permitted to execute. This mechanism prevents plugins from activating in inappropriate project contexts.

### Global vs. Project-Specific Activation

Each activatable record in the system stores an `enabled` flag alongside an optional `scope` field of type `ActivationScope`, defined in [[`packages/shared/src/activation.ts`](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/activation.ts)](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/activation.ts) (lines 13-22). The default configuration uses `GLOBAL_SCOPE`, meaning the extension remains active across every project without restriction.

When operating in `"projects"` mode, the `projects` array contains absolute project paths that the extension may access. These paths undergo normalization through `normalizeProjectPath` to ensure consistent comparison across different operating systems and path formats.

### Core Helper Functions

The activation system relies on two primary utilities:

- **`resolveScope`** (lines 81-96) normalizes potentially missing scope configurations, ensuring consistent data structures for downstream validation
- **`isActiveInProject`** (lines 101-115) performs the actual membership test, determining whether a specific extension should execute for a given project path

```typescript
import {
  isActiveInProject,
  GLOBAL_SCOPE,
  resolveScope,
} from "./activation";

const plugin = { 
  enabled: true, 
  scope: { 
    mode: "projects", 
    projects: ["/home/user/myproject"] 
  } 
};

const active = isActiveInProject(plugin, "/home/user/myproject/src"); // → true

```

## Declaring File Scope in Plugin Manifests

While activation scopes control *where* plugins run, **permission scopes** (file scopes) govern *what* files the extension may read, write, or delete.

### The fs Block Structure

In a plugin's [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json), the `fs` block contains granular declarations for each filesystem operation. Each permission category—`read`, `write`, and `delete`—accepts a `scope` array containing glob patterns that define accessible paths:

```json
{
  "permissions": ["fs.read", "fs.write"],
  "fs": {
    "read": { 
      "scope": ["src/**/*.ts", "README.md"] 
    },
    "write": { 
      "scope": ["src/generated/**"] 
    },
    "delete": { 
      "own": true, 
      "scope": ["dist/**"] 
    }
  }
}

```

### Validation Rules

The **Plugin SDK** validates scope consistency during manifest parsing. A plugin requesting `fs.read` must declare a non-empty `read.scope` array; otherwise, the SDK rejects the manifest as invalid. This validation logic appears in [[`packages/plugin-sdk/src/index.test.ts`](https://github.com/vastsa/PI-Desktop/blob/main/packages/plugin-sdk/src/index.test.ts)](https://github.com/vastsa/PI-Desktop/blob/main/packages/plugin-sdk/src/index.test.ts) (lines 363-387), which provides comprehensive test coverage for acceptance and rejection scenarios.

## Runtime Enforcement Mechanisms

File scope configuration transitions from declaration to enforcement when the Electron main process handles IPC calls.

### Permission Assertions

When the host receives an IPC call such as `fs.read`, the runtime first verifies the calling plugin possesses the requisite permission through `assertPermission`. This check occurs before any file system interaction begins.

### Path Validation Against Scope

Following permission verification, the runtime resolves the target path against the plugin's declared file scope. If the requested path falls outside the permitted glob set, the operation terminates with an access denial. This security-critical logic resides in [[`apps/desktop/electron/main/plugin-runtime.ts`](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-runtime.ts)](https://github.com/vastsa/PI-Desktop/blob/main/apps/desktop/electron/main/plugin-runtime.ts):

```typescript
function handleFsRead(request) {
  const { pluginId, path } = request;
  const plugin = getLoadedPlugin(pluginId);
  
  assertPermission(plugin, "fs.read"); // Permission check
  
  if (!matchesScope(plugin.manifest.fs.read.scope, path)) {
    throw new Error("Path outside declared file scope");
  }
  
  return fs.readFile(path, "utf8");
}

```

## Legacy Compatibility and Migration

Older PI-Desktop manifests utilized the legacy permission `fs.read.workspace`. The current SDK maintains backward compatibility by accepting this deprecated format but internally mapping it to the modern `fs.read` model. This migration path enforces identical scope rules to ensure no security degradation during upgrades. Test cases covering these legacy mappings appear around lines 399-410 in [[`packages/plugin-sdk/src/index.test.ts`](https://github.com/vastsa/PI-Desktop/blob/main/packages/plugin-sdk/src/index.test.ts)](https://github.com/vastsa/PI-Desktop/blob/main/packages/plugin-sdk/src/index.test.ts).

## User Interface Integration

The **file scope configuration** surface extends into the user experience layer. According to [[`packages/shared/src/changelog.ts`](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/changelog.ts)](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/changelog.ts) (lines 453-454), the interface displays each plugin's declared file scope adjacent to its permission summary, providing transparency about extension capabilities.

Users adjust activation scopes through the Settings studio, toggling between "global" and "project-specific" modes. Despite UI variations, the underlying data model remains consistent, preserving a single source of truth for both activation and permission enforcement.

## Summary

- **Activation scopes** in [`activation.ts`](https://github.com/vastsa/PI-Desktop/blob/main/activation.ts) determine project-level execution rights using `isActiveInProject` and `resolveScope`
- **File scopes** declared in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) use glob patterns within the `fs` block to restrict read, write, and delete operations
- **Runtime enforcement** in [`plugin-runtime.ts`](https://github.com/vastsa/PI-Desktop/blob/main/plugin-runtime.ts) validates paths against declared scopes before executing IPC calls
- **Manifest validation** requires non-empty scope arrays for requested permissions, rejecting inconsistent configurations
- **Legacy formats** automatically migrate to the current scope model while maintaining security boundaries

## Frequently Asked Questions

### What happens if a plugin tries to access a file outside its declared scope?

The runtime throws an error indicating the path falls outside the declared file scope. This check occurs in the Electron main process after permission verification but before filesystem access, effectively sandboxing the plugin to its permitted directories.

### How do I configure a plugin to work across all projects?

Set the activation scope to `GLOBAL_SCOPE` (the default) in the extension configuration. This eliminates project restrictions, allowing the plugin to activate regardless of the current workspace. You can verify this status using the `isActiveInProject` helper from [[`packages/shared/src/activation.ts`](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/activation.ts)](https://github.com/vastsa/PI-Desktop/blob/main/packages/shared/src/activation.ts).

### Can a plugin have different read and write scopes?

Yes. The `fs` block in [`manifest.json`](https://github.com/vastsa/PI-Desktop/blob/main/manifest.json) supports independent scope arrays for each operation type. For example, you might grant read access to `["src/**/*.ts"]` while restricting write access to `["src/generated/**"]`, ensuring the plugin can read source files but only modify specific output directories.

### What is the difference between activation scope and file scope?

**Activation scope** controls *where* (in which projects) the extension loads and executes, managed through the `ActivationScope` type in [`activation.ts`](https://github.com/vastsa/PI-Desktop/blob/main/activation.ts). **File scope** controls *what* files the extension may access within those projects, declared in the manifest's `fs` block and enforced at runtime during IPC calls.