Path Utils
Examples
Comprehensive usage examples and real-world patterns.
This page provides comprehensive examples of using @ucdjs/path-utils in real-world scenarios.
Basic Virtual Filesystem
Creating a sandboxed file access system with a defined boundary.
import { resolveSafePath } from '@ucdjs/path-utils';
// Define your application's data boundary
const appDataPath = '/home/user/myapp';
// Resolve user-provided paths safely
function getFilePath(userPath: string): string {
return resolveSafePath(appDataPath, userPath);
}
// Usage
getFilePath('config.json');
// → '/home/user/myapp/config.json'
getFilePath('/uploads/document.pdf');
// → '/home/user/myapp/uploads/document.pdf'
getFilePath('data/cache/temp.txt');
// → '/home/user/myapp/data/cache/temp.txt'
// Attempts to escape are blocked
getFilePath('../../etc/passwd');
// ❌ PathTraversalError
Web Server Static File Serving
Safely serve static files from a document root directory.
import { resolveSafePath, PathTraversalError } from '@ucdjs/path-utils';
import { readFile } from 'fs/promises';
const DOCUMENT_ROOT = '/var/www/html';
async function serveStaticFile(requestPath: string): Promise<Buffer> {
try {
// Safely resolve the file path
const filePath = resolveSafePath(DOCUMENT_ROOT, requestPath);
// Read and serve the file
return await readFile(filePath);
} catch (error) {
if (error instanceof PathTraversalError) {
throw new Error('403 Forbidden: Access denied');
}
throw error;
}
}
// Safe requests
await serveStaticFile('/assets/style.css');
await serveStaticFile('/images/logo.png');
await serveStaticFile('/js/app.js');
// Blocked requests
await serveStaticFile('../../etc/passwd');
// → 403 Forbidden
await serveStaticFile('%2e%2e%2f%2e%2e%2fetc%2fpasswd');
// → 403 Forbidden (encoded traversal)
File Upload Handler
Handle user file uploads with path validation.
import { resolveSafePath, PathTraversalError, IllegalCharacterInPathError } from '@ucdjs/path-utils';
import { writeFile } from 'fs/promises';
import { mkdir } from 'fs/promises';
const UPLOAD_DIR = '/var/uploads';
interface UploadResult {
success: boolean;
path?: string;
error?: string;
}
async function handleFileUpload(
userId: string,
filename: string,
content: Buffer
): Promise<UploadResult> {
try {
// Create user-specific subdirectory path
const userDir = `users/${userId}`;
const filePath = `${userDir}/${filename}`;
// Resolve safely within upload boundary
const safePath = resolveSafePath(UPLOAD_DIR, filePath);
// Create directory if needed
const dirPath = resolveSafePath(UPLOAD_DIR, userDir);
await mkdir(dirPath, { recursive: true });
// Write file
await writeFile(safePath, content);
return {
success: true,
path: safePath,
};
} catch (error) {
if (error instanceof PathTraversalError) {
return {
success: false,
error: 'Invalid file path',
};
}
if (error instanceof IllegalCharacterInPathError) {
return {
success: false,
error: 'Invalid characters in filename',
};
}
throw error;
}
}
// Safe uploads
await handleFileUpload('user123', 'document.pdf', buffer);
// → /var/uploads/users/user123/document.pdf
await handleFileUpload('user456', 'photos/vacation.jpg', buffer);
// → /var/uploads/users/user456/photos/vacation.jpg
// Blocked uploads
await handleFileUpload('user789', '../../../etc/passwd', buffer);
// → { success: false, error: 'Invalid file path' }
await handleFileUpload('user789', 'file\0.txt', buffer);
// → { success: false, error: 'Invalid characters in filename' }
Cross-Platform Configuration System
Handle configuration files across different operating systems.
import { resolveSafePath, toUnixFormat, osPlatform } from '@ucdjs/path-utils';
import { readFile, writeFile } from 'fs/promises';
class ConfigManager {
private configDir: string;
constructor(configDir: string) {
// Normalize the config directory path
this.configDir = osPlatform === 'win32'
? configDir
: toUnixFormat(configDir);
}
async getConfigPath(name: string): Promise<string> {
// Ensure config name is safe
return resolveSafePath(this.configDir, `${name}.json`);
}
async loadConfig<T = any>(name: string): Promise<T> {
const path = await this.getConfigPath(name);
const content = await readFile(path, 'utf-8');
return JSON.parse(content);
}
async saveConfig<T = any>(name: string, data: T): Promise<void> {
const path = await this.getConfigPath(name);
await writeFile(path, JSON.stringify(data, null, 2));
}
}
// Works on any platform
const config = new ConfigManager('C:\\Users\\John\\AppData\\myapp');
// or
const config = new ConfigManager('/home/user/.config/myapp');
await config.loadConfig('settings');
await config.saveConfig('preferences', { theme: 'dark' });
// Blocked
await config.loadConfig('../../passwords');
// ❌ PathTraversalError
Plugin Sandbox System
Sandbox plugin file access to specific directories.
import { resolveSafePath, PathTraversalError } from '@ucdjs/path-utils';
import { readFile, writeFile, readdir } from 'fs/promises';
class PluginSandbox {
private pluginDataDir: string;
constructor(pluginId: string) {
this.pluginDataDir = `/var/plugins/${pluginId}/data`;
}
async readFile(path: string): Promise<string> {
const safePath = resolveSafePath(this.pluginDataDir, path);
return readFile(safePath, 'utf-8');
}
async writeFile(path: string, content: string): Promise<void> {
const safePath = resolveSafePath(this.pluginDataDir, path);
await writeFile(safePath, content);
}
async listFiles(dir: string = '/'): Promise<string[]> {
const safePath = resolveSafePath(this.pluginDataDir, dir);
return readdir(safePath);
}
}
// Plugin can only access its own data directory
const sandbox = new PluginSandbox('my-plugin');
await sandbox.writeFile('/config.json', '{}');
// → /var/plugins/my-plugin/data/config.json
await sandbox.readFile('/cache/data.txt');
// → /var/plugins/my-plugin/data/cache/data.txt
await sandbox.listFiles('/');
// → ['config.json', 'cache']
// Plugin cannot escape sandbox
await sandbox.readFile('../../other-plugin/secrets.txt');
// ❌ PathTraversalError
await sandbox.readFile('/etc/passwd');
// ❌ PathTraversalError (treated as /var/plugins/my-plugin/data/etc/passwd)
Windows-Specific Scenarios
Handling Windows paths with drive letters and mixed separators.
import {
resolveSafePath,
isWindowsDrivePath,
getWindowsDriveLetter,
WindowsDriveMismatchError,
} from '@ucdjs/path-utils';
const projectRoot = 'C:\\Users\\John\\Projects\\MyApp';
// Mixed separators work
resolveSafePath(projectRoot, 'src/components\\Button.tsx');
// → 'C:/Users/John/Projects/MyApp/src/components/Button.tsx'
// Backslashes work
resolveSafePath(projectRoot, 'data\\users\\profiles.json');
// → 'C:/Users/John/Projects/MyApp/data/users/profiles.json'
// Absolute Windows paths within boundary
resolveSafePath(
projectRoot,
'C:\\Users\\John\\Projects\\MyApp\\config.json'
);
// → 'C:/Users/John/Projects/MyApp/config.json'
// Different drive letter blocked
try {
resolveSafePath(projectRoot, 'D:\\external\\file.txt');
} catch (error) {
if (error instanceof WindowsDriveMismatchError) {
console.error(
`Cannot access drive ${error.accessedDrive}, ` +
`base is on drive ${error.baseDrive}`
);
}
}
// Check if path is Windows drive path
if (isWindowsDrivePath('C:\\Users')) {
const drive = getWindowsDriveLetter('C:\\Users');
console.log(`Drive letter: ${drive}`); // 'C'
}
Encoding Attack Prevention
Protect against various encoding-based attacks.
import {
resolveSafePath,
decodePathSafely,
PathTraversalError,
MaximumDecodingIterationsExceededError,
} from '@ucdjs/path-utils';
const boundary = '/var/www/uploads';
// Simple URL encoding (safe)
resolveSafePath(boundary, 'user%20files/document.pdf');
// → '/var/www/uploads/user files/document.pdf'
// Encoded path separators (safe)
resolveSafePath(boundary, 'folder%2Ffile.txt');
// → '/var/www/uploads/folder/file.txt'
// Encoded traversal attempts (blocked)
try {
resolveSafePath(boundary, '%2e%2e%2f%2e%2e%2fetc%2fpasswd');
} catch (error) {
console.error('Encoded traversal blocked');
// ❌ PathTraversalError
}
// Double encoding (blocked)
try {
resolveSafePath(boundary, '%252e%252e%252f%252e%252e');
} catch (error) {
console.error('Double-encoded traversal blocked');
// ❌ PathTraversalError
}
// Excessive encoding (blocked)
const malicious = '%25'.repeat(20) + '2e2e2f';
try {
decodePathSafely(malicious);
} catch (error) {
if (error instanceof MaximumDecodingIterationsExceededError) {
console.error('Too many encoding layers');
}
}
API Request Handler
Complete example of handling API requests with path parameters.
import { resolveSafePath, PathUtilsBaseError } from '@ucdjs/path-utils';
import { readFile } from 'fs/promises';
const STORAGE_ROOT = '/var/app/storage';
interface ApiResponse {
status: number;
data?: any;
error?: string;
}
async function handleApiRequest(
userId: string,
resourcePath: string
): Promise<ApiResponse> {
try {
// Build user-specific path
const userPath = `users/${userId}`;
const fullPath = `${userPath}/${resourcePath}`;
// Resolve safely
const safePath = resolveSafePath(STORAGE_ROOT, fullPath);
// Read file (example)
const data = await readFile(safePath, 'utf-8');
return {
status: 200,
data: JSON.parse(data),
};
} catch (error) {
// Handle path-utils errors
if (error instanceof PathUtilsBaseError) {
return {
status: 403,
error: 'Forbidden: Invalid path',
};
}
// Handle file not found
if ((error as any).code === 'ENOENT') {
return {
status: 404,
error: 'Resource not found',
};
}
// Other errors
return {
status: 500,
error: 'Internal server error',
};
}
}
// Valid requests
await handleApiRequest('user123', 'documents/report.json');
// → { status: 200, data: {...} }
await handleApiRequest('user456', 'photos/vacation.jpg');
// → { status: 200, data: {...} }
// Invalid requests
await handleApiRequest('user789', '../../etc/passwd');
// → { status: 403, error: 'Forbidden: Invalid path' }
await handleApiRequest('user789', '%2e%2e%2fadmin%2fsecrets');
// → { status: 403, error: 'Forbidden: Invalid path' }
Database File Storage
Manage database file storage with safe path resolution.
import { resolveSafePath } from '@ucdjs/path-utils';
import { writeFile, readFile } from 'fs/promises';
import { createHash } from 'crypto';
const BLOB_STORAGE = '/var/db/blobs';
class BlobStorage {
async store(userId: string, filename: string, data: Buffer): Promise<string> {
// Create hash-based subdirectories for better distribution
const hash = createHash('sha256').update(data).digest('hex');
const prefix = hash.substring(0, 2);
const subdir = hash.substring(2, 4);
// Safe path: /var/db/blobs/users/{userId}/{prefix}/{subdir}/{hash}
const path = resolveSafePath(
BLOB_STORAGE,
`users/${userId}/${prefix}/${subdir}/${hash}`
);
await writeFile(path, data);
// Return reference path
return `${prefix}/${subdir}/${hash}`;
}
async retrieve(userId: string, reference: string): Promise<Buffer> {
const path = resolveSafePath(
BLOB_STORAGE,
`users/${userId}/${reference}`
);
return readFile(path);
}
}
const storage = new BlobStorage();
// Store blob
const ref = await storage.store('user123', 'avatar.png', buffer);
// → '5f/a3/5fa3...'
// Retrieve blob
const data = await storage.retrieve('user123', ref);
// Cannot access other users' blobs
await storage.retrieve('user123', '../../user456/5f/a3/5fa3...');
// ❌ PathTraversalError
Best Practices Summary
Always Validate Input
Use
resolveSafePath for all user-provided paths before file operations.Define Clear Boundaries
Establish clear base directories for different types of data (uploads, configs, cache).
Handle Errors Gracefully
Catch and handle path-utils errors appropriately, logging security events.
Never Expose Details
Don't expose actual file paths or error details to end users.
Use Type Guards
Use
instanceof checks to handle different error types specifically.Log Security Events
Log path traversal attempts and other security violations for monitoring.