# How Project-Trust Validation Prevents Unauthorized File Access in Pi Web

> Discover how Pi Web's project-trust validation stops unauthorized file access using trust gating and path-allowlist enforcement. Secure your workspace today.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: security
- Published: 2026-08-18

---

**Pi Web prevents unauthorized file access through project-trust validation by combining two independent safeguards: a trust gating system that requires explicit user approval for projects containing potentially dangerous resources, and a path-allowlist enforcement that restricts all file operations to pre-approved workspace roots.**

Every file-related API in the Pi Web repository enforces security through a layered defense mechanism. This architecture ensures that even if one control is bypassed, a second barrier remains in place. The system is implemented across [`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts), [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts), and individual route handlers in the `app/api/` directory.

## The Two-Layer Security Model

### Layer 1: Project-Trust Gating

Before any operation touches project files, the route evaluates whether the current working directory is trusted. This check is performed by `getProjectTrustStatus(cwd, agentDir)` in [[`lib/project-trust.ts`](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts)](https://github.com/agegr/pi-web/blob/main/lib/project-trust.ts):

```typescript
export function getProjectTrustStatus(cwd: string, agentDir: string): ProjectTrustStatus {
  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,
  };
}

```

- **Trust-requiring resources** include `.pi/extensions`, project-local skills, and plugins — any executable code that could compromise the system.
- Projects without these resources pass automatically (`requiresTrust: false`).
- Projects with these resources return `trusted: false` until the user explicitly trusts them via the UI or `POST /api/project-trust`.
- Routes reject untrusted projects with **HTTP 403 Forbidden**.

### Layer 2: Path-Allowlist Enforcement

Even trusted projects cannot access arbitrary files. The `isFilePathAllowed` function in [[`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts)](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts) performs a pure lexical containment check:

```typescript
export function isFilePathAllowed(target: string, allowedRoots: Set<string>): boolean {
  return isPathWithinRoots(target, allowedRoots);
}

```

The **allowedRoots** set is computed by `getAllowedFileRoots()`, which aggregates:

- All session current working directories
- All project roots
- Automatically created `~/pi-cwd-*` directories

Only paths falling under these roots are reachable. This prevents **path traversal attacks** even against fully trusted projects.

## Step-by-Step Request Flow

Each file API request passes through five verification stages:

1. **Origin validation** — `isApiRequestAllowed` in [[`lib/request-security.ts`](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts)](https://github.com/agegr/pi-web/blob/main/lib/request-security.ts) verifies the request comes from a permitted host/origin, blocking CSRF and cross-site abuse.

2. **Working directory resolution** — The route extracts `cwd` from URL parameters or request body to identify the target project.

3. **Project-trust check** — `getProjectTrustStatus(cwd, agentDir)` is invoked. If `requiresTrust && !trusted`, the request is rejected with **403**.

4. **Path-allowlist validation** — `isFilePathAllowed(targetPath, allowedRoots)` ensures the target path resides within the computed allowed roots. External paths trigger **403**.

5. **File operation execution** — Only after passing both the trust gate and path-allowlist check does the handler perform the read or write.

## Real-World Implementation: Files API

The route at [`app/api/files/[...path]/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/files/%5B...path%5D/route.ts) demonstrates this pattern in practice:

```typescript
// Simplified excerpt showing the security sequence
export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
  // 1. Validate request origin
  if (!isApiRequestAllowed(request)) {
    return NextResponse.json({ error: "Untrusted API request" }, { status: 403 });
  }

  const { path: segments } = await params;
  const cwd = await resolveCwd(segments);
  const trust = getProjectTrustStatus(cwd, getAgentDir());

  // 2. Enforce project-trust validation
  if (!trust.trusted) {
    return NextResponse.json({ error: "Project not trusted" }, { status: 403 });
  }

  // 3. Validate path against allowlist
  const filePath = resolveFilePath(cwd, segments);
  const allowedRoots = await getAllowedFileRoots();
  if (!isFilePathAllowed(filePath, allowedRoots)) {
    return NextResponse.json({ error: "Path not allowed" }, { status: 403 });
  }

  // 4. Execute safe file read
  const content = await readFile(filePath, "utf-8");
  return new NextResponse(content);
}

```

This same pattern appears in:

- **Worktree API** — git worktree operations
- **Git status/diff APIs** — repository inspection
- **Skill-install API** — [[`app/api/skills/install/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/skills/install/route.ts)](https://github.com/agegr/pi-web/blob/main/app/api/skills/install/route.ts) aborts with 403 when the project lacks trust

## Trust Store Persistence

The `ProjectTrustStore` class manages persistent trust decisions. Trust is stored per-project and survives across sessions. The store location is determined by `agentDir`, ensuring isolation between different Pi Web agent instances.

Users grant trust through:

- The web interface trust prompt
- Direct `POST /api/project-trust` API call with the project path

Once granted, trust persists until explicitly revoked.

## Summary

Project-trust validation in Pi Web prevents unauthorized file access through:

- **Explicit trust requirements** for projects containing executable resources (extensions, skills, plugins)
- **Dual-gate verification** — both trust status and path containment must pass
- **Lexical path containment** against computed allowed roots, blocking traversal attacks
- **Consistent enforcement** across all file-touching APIs including files, git operations, and skill installation
- **Request origin validation** as a foundational first line of defense

## Frequently Asked Questions

### What triggers a project to require trust?

A project requires trust when `hasTrustRequiringProjectResources(cwd)` detects `.pi/extensions` directories, project-local skills, or plugin configurations. These resources can execute arbitrary code, so Pi Web treats them as potentially dangerous until explicitly approved.

### Can a trusted project access files outside its workspace?

No. Even fully trusted projects are constrained by the path-allowlist check in [`lib/file-access.ts`](https://github.com/agegr/pi-web/blob/main/lib/file-access.ts). The `isFilePathAllowed` function validates that every target path falls within the pre-computed `allowedRoots` set, which includes only the project's own directories and explicitly managed session roots.

### How does project-trust validation differ from traditional authentication?

Project-trust validation operates **after** origin verification but **before** file operations, serving as an authorization layer specific to filesystem access. It does not replace authentication — rather, it adds a user-consent requirement for projects that could execute code, complementing the origin-based `isApiRequestAllowed` check.

### Where is the trust status stored?

Trust decisions persist in a `ProjectTrustStore` instance initialized with the `agentDir` path. The store provides `get(cwd)` and `set(cwd, trusted)` operations, enabling durable trust state across sessions while maintaining isolation between different agent installations.