# How the ProjectTrustDialog in pi-web Validates and Manages Trusted Project Directories

> Discover how pi-web's ProjectTrustDialog validates trusted project directories using server-side checks and persistent storage. Learn how it secures your code.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-07-31

---

**The ProjectTrustDialog validates directories through server-side path resolution and allowed-root checks, persists trust decisions via `ProjectTrustStore`, and gates project-local resources behind a trust resolver to prevent automatic execution of untrusted code.**

The **pi-web** repository implements a security boundary to protect users from automatically executing code found inside untrusted repositories. The **ProjectTrustDialog** surfaces this protection layer in the UI, ensuring that users must explicitly validate and manage trusted project directories before project-local extensions, settings, or skills can load. This system operates across three tightly coupled layers: a React dialog component, a Next.js API route for validation, and a core trust management library that integrates with the Pi SDK.

## The Three-Layer Architecture

The trust validation system spans distinct architectural layers:

- **UI Layer** ([`components/ProjectTrustDialog.tsx`](https://github.com/agegr/pi-web/blob/main/components/ProjectTrustDialog.tsx)): Renders the modal dialog and forwards user decisions to the API.
- **API Layer** ([`app/api/project-trust/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/project-trust/route.ts)): Validates directory paths and coordinates trust persistence.
- **Core Layer** ([`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)): Detects trust requirements, reads and writes trust flags via `ProjectTrustStore`, and supplies reload options to the Pi SDK.

## Path Validation in the API Layer

The [`app/api/project-trust/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/project-trust/route.ts) file exports a `validateCwd` function that enforces strict rules before any trust decision is processed. The validation flow requires the `cwd` parameter to be a non‑empty string, resolves the path, confirms it exists as a directory via `stat`, and verifies it resides within an allowed file root returned by `getAllowedFileRoots()` (which includes the current worktree and paths matching `~/pi‑cwd‑*`).

```ts
// src/app/api/project-trust/route.ts
async function validateCwd(value: unknown) {
  if (typeof value !== "string" || !value.trim()) {
    return { response: NextResponse.json({ error: "cwd required" }, { status: 400 }) };
  }
  const cwd = resolve(value);
  if (!(await stat(cwd)).isDirectory()) {
    return { response: NextResponse.json({ error: "cwd must be a directory" }, { status: 400 }) };
  }
  const allowedRoots = await getAllowedFileRoots();
  if (!isExistingFilePathAllowed(cwd, allowedRoots)) {
    return { response: NextResponse.json({ error: "cwd not allowed" }, { status: 403 }) };
  }
  return { cwd };
}

```

### GET Endpoint: Retrieving Trust Status

When the API receives a GET request, it validates the `cwd` and returns the current status via `getProjectTrustStatus`, passing the `cwd` and `agentDir` from `getAgentDir()`.

```ts
// src/app/api/project-trust/route.ts
return NextResponse.json(getProjectTrustStatus(result.cwd, getAgentDir()));

```

### POST Endpoint: Recording Trust Decisions

The POST handler performs additional safety checks before persisting trust. After validation, it ensures the project actually requires trust, verifies no busy RPC session exists for that directory using `hasBusyRpcSessionForCwd`, persists the decision via `trustProject`, and clears related caches.

```ts
// src/app/api/project-trust/route.ts (POST handler)
const status = trustProject(result.cwd, agentDir);
invalidateModelsCache();
destroyRpcSessionsForCwd(result.cwd);
return NextResponse.json(status);

```

## Core Trust Management in [`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)

This module centralizes the determination of whether a project requires explicit trust and manages the persistent store shared with the `pi` CLI.

### Detecting Trust Requirements

The `getProjectTrustStatus` function checks if the directory contains trust‑requiring resources using `hasTrustRequiringProjectResources`. If the project contains no such resources, it automatically returns `{ requiresTrust: false, trusted: true }`. Otherwise, it reads the trust flag from `ProjectTrustStore`.

```ts
// src/lib/project-trust.ts
const requiresTrust = Boolean(cwd) && hasTrustRequiringProjectResources(cwd);
if (!requiresTrust) return { requiresTrust: false, trusted: true };
const trustStore = new ProjectTrustStore(agentDir);
return { requiresTrust: true, trusted: trustStore.get(cwd) === true };

```

### Persisting Trust Decisions

When a user confirms trust, the `trustProject` function writes `true` to the store for that directory path and returns the updated status.

```ts
// src/lib/project-trust.ts
new ProjectTrustStore(agentDir).set(cwd, true);

```

### Resource Gating via Reload Options

To prevent the Pi SDK from loading untrusted code, `projectTrustReloadOptions` supplies a resolver function used by the `DefaultResourceLoader`. This defers loading of project‑local extensions, [`.pi/settings.json`](https://github.com/agegr/pi-web/blob/main/.pi/settings.json) configurations, and `.agents/skills` until trust is granted.

```ts
// src/lib/project-trust.ts
return { resolveProjectTrust: async () => trustStore.get(cwd) === true };

```

## The React Dialog Component

The [`components/ProjectTrustDialog.tsx`](https://github.com/agegr/pi-web/blob/main/components/ProjectTrustDialog.tsx) file implements the presentation layer, receiving the `cwd`, a `busy` state, and callback handlers. When the user clicks **Trust**, the `onConfirm` callback triggers a POST request to `/api/project-trust`. The component displays a loading state via the translation key `t("trust.trusting")` while the request is in flight.

```tsx
// src/components/ProjectTrustDialog.tsx
<button onClick={onConfirm} disabled={busy}>
  {busy ? t("trust.trusting") : t("trust.trustProject")}
</button>

```

The surrounding application manages dialog visibility and fetches the initial trust status via GET to determine whether to prompt the user.

## Integration with RPC and Skills Services

The trust system integrates with session management to ensure running services respect the trust boundary.

### RPC Session Management

When starting a new session, [`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts) injects the trust reload options into the resource loader configuration. This ensures services spawned for that working directory check trust status before accessing local resources.

```ts
// src/lib/rpc-manager.ts
const trustReloadOptions = projectTrustReloadOptions(cwd, agentDir);
const sessionConfig = {
  ...(trustReloadOptions ? { resourceLoaderReloadOptions: trustReloadOptions } : {})
};

```

### Skills Service Gating

The [`lib/skills-service.ts`](https://github.com/agegr/pi-web/blob/main/lib/skills-service.ts) file reads the trust status to determine whether project‑local skills can be loaded. It passes `projectResourcesLoaded` to downstream consumers, which evaluates to `true` only when the directory is trusted.

```ts
// src/lib/skills-service.ts
projectResourcesLoaded: getProjectTrustStatus(cwd, agentDir).trusted,

```

## Summary

- **Validation**: The API layer validates directory paths using `validateCwd` in [`app/api/project-trust/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/project-trust/route.ts), ensuring directories exist and reside within allowed roots before processing trust requests.
- **Detection**: The core layer in [`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts) uses `hasTrustRequiringProjectResources` to determine if a project requires explicit trust, automatically trusting projects without executable resources.
- **Persistence**: Trust decisions are stored in `ProjectTrustStore`, shared with the `pi` CLI, and survive across application restarts.
- **Gating**: The `projectTrustReloadOptions` resolver prevents the Pi SDK from loading local extensions, settings, and skills until trust is explicitly granted via the dialog.
- **Integration**: The system invalidates model caches with `invalidateModelsCache()` and destroys existing RPC sessions via `destroyRpcSessionsForCwd()` when trust status changes, ensuring a clean security boundary.

## Frequently Asked Questions

### How does the ProjectTrustDialog prevent automatic execution of untrusted code?

The dialog acts as the UI entry point for a security gate that blocks the Pi SDK's `DefaultResourceLoader`. Until the user confirms trust and `trustProject` writes `true` to the `ProjectTrustStore`, the `resolveProjectTrust` resolver returns `false`, preventing the loader from accessing project-local extensions, [`.pi/settings.json`](https://github.com/agegr/pi-web/blob/main/.pi/settings.json), or skills directories. This ensures no code executes until explicit validation occurs.

### What validation rules apply when trusting a project directory?

The `validateCwd` function enforces three strict rules: the input must be a non-empty string, the resolved path must exist as a directory (verified via `fs/promises` `stat`), and the directory must be contained within one of the allowed file roots returned by `getAllowedFileRoots()`. Failure at any step results in a 400 Bad Request or 403 Forbidden response.

### What happens to existing RPC sessions when trust is granted?

When the POST endpoint records a new trust decision, it immediately calls `destroyRpcSessionsForCwd` to terminate any active RPC sessions for that directory, then invalidates the models cache with `invalidateModelsCache()`. This forces a clean restart of services with the new trust permissions, preventing stale untrusted sessions from continuing to run.

### Can a project be trusted if it doesn't contain trust-requiring resources?

No manual trust is required. The `getProjectTrustStatus` function automatically returns `{ trusted: true }` for projects where `hasTrustRequiringProjectResources` returns `false`. The `ProjectTrustDialog` will not appear for these directories, and attempting to POST a trust decision for such projects will fail validation because the system recognizes that trust is not required.