Pi-Web File Access Security: How It Scopes Browsing to Allowed Project Directories
Pi-web restricts every file-listing and preview request to a dynamically constructed whitelist of safe directory roots, enforcing that all target paths must either match or reside within these allowed roots before granting access.
Pi-web implements a defense-in-depth file access security model to ensure that file browsing operations remain confined to legitimate project directories. According to the agegr/pi-web source code, the system aggregates a whitelist of permitted filesystem locations from session metadata, project configurations, and explicit runtime registrations. This whitelist is then enforced by path resolution functions that validate every file request against the allowed set, preventing unauthorized traversal into sensitive system locations.
How the Allowed Roots Whitelist Is Constructed
The security model centers on a whitelist of directory roots that are considered safe for file operations. In lib/file-access.ts, the getAllowedFileRoots() function assembles this list by scanning four distinct sources.
Session Working Directories and Project Roots
The primary sources of allowed roots are the active Pi sessions stored on disk. The system invokes listAllSessions() from lib/session-reader.ts to enumerate all session files. For each session, the code extracts the cwd (current working directory) and projectRoot properties (accessed as s.projectRoot), normalizes them using normalizeSlashes, and adds them to the global whitelist set. This ensures that files within any active session's working directory are immediately accessible.
Temporary CWD Directories
Pi-web automatically includes temporary directories created by the default-cwd endpoint. These folders follow the naming pattern ~/pi-cwd-YYYYMMDD and are discovered by scanning the user's home directory with readdirSync(homedir()). Any directory matching the regex /^pi-cwd-\d{8}$/ is added to the allowed roots, enabling access to transient project workspaces without manual configuration.
Explicit Runtime Registrations
Applications and plugins can expand the whitelist at runtime through the allowFileRoot() function exported from lib/allowed-roots.ts. This helper pushes a normalized path into the global __piAdditionalAllowedRoots set and immediately invalidates the __piAllowedRootsCache, allowing new directories to be accessed without requiring a server restart.
Path Validation and Enforcement Logic
Once the whitelist is established, pi-web enforces access control through two main validation functions defined in lib/file-access.ts.
The core security function isFilePathAllowed(target, allowedRoots) implements the following validation sequence:
- Iterates over every root in the whitelist.
- Detects Windows-style paths using
isWindowsAbsolutePathto determine appropriate path resolution strategy. - Resolves both the target and root to absolute, normalized paths using
path.resolve(orpath.win32.resolveon Windows). - Performs case-insensitive comparison on Windows and case-sensitive comparison on Unix-like systems.
- Grants access only if the target exactly matches the root or starts with the root followed by a path separator (
root/…).
If no roots satisfy these conditions, the function returns false and the API endpoint aborts the request.
Symlink Attack Mitigation
For enhanced security against symbolic link traversal attacks, the isExistingFilePathAllowed wrapper performs additional validation. It calls isExistingPathWithinRoots from lib/path-security.ts, which resolves all symbolic links in the target path before performing the containment check. This guarantees that a malicious symlink cannot escape the allowed directory tree by pointing to sensitive locations outside the whitelist.
Cross-Platform Compatibility and Caching
The implementation handles platform differences and performance optimization through specific architectural decisions.
On Windows systems, the code detects absolute paths (such as C:\… or UNC paths) and uses path.win32 for resolution, ensuring consistent security behavior across operating systems. The path comparison logic automatically adjusts case sensitivity based on the platform, preventing bypasses through case manipulation on Windows file systems.
To maintain performance without sacrificing security, pi-web caches the allowed roots set for 5 seconds (ALLOWED_ROOTS_TTL_MS). The cache resides on globalThis, which persists across Next.js hot-reloads, preventing unnecessary filesystem scans during rapid development iterations while ensuring the whitelist remains current.
Practical Implementation Examples
The following patterns demonstrate how to integrate pi-web's file access security into application code:
// Validating a file read request against allowed roots
import { getAllowedFileRoots, isFilePathAllowed } from "./lib/file-access";
async function canReadFile(requestedPath: string): Promise<boolean> {
const roots = await getAllowedFileRoots(); // builds the whitelist
return isFilePathAllowed(requestedPath, roots); // true → safe, false → reject
}
To dynamically expand access permissions at runtime, use the allowFileRoot API:
// Adding an extra root at runtime (e.g., a plugin requires /var/log)
import { allowFileRoot } from "./lib/allowed-roots";
allowFileRoot("/var/log"); // now any file under /var/log is readable
Summary
- Pi-web constructs a dynamic whitelist of allowed directory roots from session
cwdvalues, project roots, temporarypi-cwd-YYYYMMDDdirectories, and explicit runtime registrations viaallowFileRoot(). - The
isFilePathAllowedfunction inlib/file-access.tsenforces containment by requiring targets to match or reside within whitelisted roots, with platform-specific handling for Windows and Unix paths. - Symbolic link attacks are mitigated by
isExistingPathWithinRootsinlib/path-security.ts, which resolves links before validation to prevent directory traversal. - A 5-second TTL cache stored on
globalThisoptimizes performance while preserving security boundaries across Next.js hot-reloads. - Runtime calls to
allowFileRoot()immediately update the global__piAllowedRootsCache, enabling dynamic security boundary expansion without server restarts.
Frequently Asked Questions
How does pi-web prevent access to files outside project directories?
Pi-web maintains a whitelist of allowed filesystem roots and validates every file request against this list using isFilePathAllowed in lib/file-access.ts. The function resolves both the requested path and each allowed root to absolute, normalized forms using path.resolve, then checks if the target either matches a root exactly or starts with a root followed by a path separator. If no match is found, the request is rejected before any filesystem read occurs.
What protection exists against symbolic links that point outside allowed roots?
The system uses isExistingFilePathAllowed, a security wrapper that calls isExistingPathWithinRoots from lib/path-security.ts. This function resolves all symbolic links in the target path to their ultimate destinations before performing the containment check against the whitelist, ensuring that a symlink cannot bypass security by pointing to locations outside the allowed project directories.
Can new directories be added to the whitelist without restarting the server?
Yes. The allowFileRoot() function exported from lib/allowed-roots.ts enables runtime expansion of the whitelist. When called, it adds the specified path to the global __piAdditionalAllowedRoots set and updates the __piAllowedRootsCache immediately, allowing UI components or plugins to register new accessible directories on demand without requiring a process restart.
How does the caching mechanism affect security enforcement?
The allowed roots are cached for 5 seconds (ALLOWED_ROOTS_TTL_MS) to reduce filesystem overhead from repeated session scans. The cache is stored on globalThis, which survives Next.js hot-reloads. This caching does not compromise security because the whitelist is rebuilt from authoritative sources—session files, project configurations, and explicit registrations—on each cache miss, ensuring that revoked access is enforced within the TTL window.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →