What Are the Allowed Roots for File Access in Pi Web?

Pi Web restricts the /api/files endpoint to a whitelist comprising the session's current working directory, resolved Git project roots, temporary ~/pi-cwd-* directories, and any paths explicitly added via allowFileRoot() in lib/file-access.ts.

The agegr/pi-web repository implements a strict security model to prevent directory traversal attacks. Instead of allowing arbitrary file system access, the allowed roots for file access in Pi Web are confined to a small, well-defined set of directories that are validated at runtime. This design ensures that the file browser API only serves content from approved locations.

The Three Built-In Allowed Root Sources

Pi Web automatically permits file access from three distinct sources. These are accumulated when a session initializes and when specific API endpoints are invoked.

Session Current Working Directory (CWD)

Every session maintains a cwd property that is automatically whitelisted upon creation or validation. When you set or validate a working directory via the CWD validation API, the system registers that path as an allowed root. This logic is handled in app/api/cwd/validate/route.ts, which calls the underlying registration functions to update the allow-list.

Resolved Project Root via Git Worktree

If the session's CWD resides inside a Git worktree, Pi Web resolves the top-level project directory using the utilities in lib/worktree.ts. This resolved project root is then added to the whitelist alongside the raw CWD. This mechanism allows the file API to serve files from the main repository root even when the session is operating from a specific worktree subdirectory or linked worktree.

Temporary pi-cwd Sandbox Directories

Directories matching the pattern ~/pi-cwd-* are automatically whitelisted. These sandboxes are generated dynamically through the /api/default-cwd endpoint implemented in app/api/default-cwd/route.ts. When created, these folders provide isolated environments for ad-hoc browsing without exposing the broader file system.

Programmatically Extending the Allow-List

Any code path can explicitly extend the whitelist by calling allowFileRoot(path) exported from lib/file-access.ts. This function is utilized internally by the CWD validation, worktree resolution, and default-CWD creation routes to register new locations as they become browsable. Server-side plugins or custom routes can import this function to expose additional directories:

import { allowFileRoot } from '@/lib/file-access';

// Register a custom documentation folder
allowFileRoot('/var/shared/docs');

After this call, the /api/files endpoint will accept requests for any file whose resolved pathname starts with /var/shared/docs.

How the Allow-List Is Enforced

The enforcement logic resides in lib/file-access.ts. The function isPathAllowed(p) performs path resolution and prefix matching against the accumulated whitelist. The API route at app/api/files/[...path]/route.ts invokes this check before reading any file from disk. If the requested path does not fall under one of the registered roots, the request is rejected.

Practical Code Examples

Accessing Files Within the Session CWD

Request a file relative to the session's current working directory. Because src/utils.ts resolves under the whitelisted CWD, the request succeeds:

fetch(`/api/files/src/utils.ts?sessionId=${sessionId}`)
  .then(r => r.text())
  .then(content => console.log(content));

Accessing Files in the Project Root

When the session CWD is inside a worktree, the resolved project root is also accessible. Even if the raw CWD is /home/user/repos/my-project-worktrees/feature-branch, you can access files in /home/user/repos/my-project:

fetch(`/api/files/README.md?sessionId=${sessionId}`)
  .then(r => r.text())
  .then(md => console.log(md));

Using Temporary pi-cwd Directories

Create a fresh sandbox and access files within it:

fetch('/api/default-cwd', { method: 'POST' })
  .then(r => r.json())
  .then(({ cwd }) => {
    // cwd is something like /home/user/pi-cwd-20240730-abc123
    return fetch(`/api/files/${encodeURIComponent(cwd)}/sample.txt`);
  })
  .then(r => r.text())
  .then(txt => console.log(txt));

Adding Custom Roots Dynamically

Expose an additional folder from within a server-side route:

import { allowFileRoot } from '@/lib/file-access';

allowFileRoot('/var/shared/docs');

Summary

  • Session CWDs are automatically whitelisted when validated or created.
  • Git project roots are resolved via lib/worktree.ts and added to the allow-list to support worktree workflows.
  • Temporary directories matching ~/pi-cwd-* are automatically permitted for sandboxed browsing.
  • Custom paths can be registered at runtime using allowFileRoot() from lib/file-access.ts.
  • The isPathAllowed() function in lib/file-access.ts enforces these restrictions for all /api/files requests.

Frequently Asked Questions

Can I access files outside my session's immediate working directory?

Yes. If your CWD is inside a Git worktree, Pi Web automatically resolves and whitelists the top-level project root, allowing access to repository files outside the specific subdirectory. You can also call allowFileRoot() to register any arbitrary path programmatically.

How do I create a temporary sandbox for file browsing?

Send a POST request to /api/default-cwd. This creates a new directory matching the ~/pi-cwd-* pattern and returns the path, which is automatically whitelisted for the file access API.

What function validates paths against the allow-list?

The isPathAllowed(p) function in lib/file-access.ts performs the validation. It checks whether the requested path falls under any of the registered allowed roots before the system attempts to read the file.

Is the allow-list extensible without modifying core code?

Yes. Any server-side code, including plugins or custom API routes, can import allowFileRoot() from lib/file-access.ts to register new accessible directories at runtime without modifying the core Pi Web source files.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →