Spaces:
Paused
Paused
| /** | |
| * Security utilities for path validation | |
| * Enforces ALLOWED_ROOT_DIRECTORY constraint with appData exception | |
| */ | |
| import path from 'path'; | |
| /** | |
| * Error thrown when a path is not allowed by security policy | |
| */ | |
| export class PathNotAllowedError extends Error { | |
| constructor(filePath: string) { | |
| super(`Path not allowed: ${filePath}. Must be within ALLOWED_ROOT_DIRECTORY or DATA_DIR.`); | |
| this.name = 'PathNotAllowedError'; | |
| } | |
| } | |
| // Allowed root directory - main security boundary | |
| let allowedRootDirectory: string | null = null; | |
| // Data directory - always allowed for settings/credentials | |
| let dataDirectory: string | null = null; | |
| /** | |
| * Initialize security settings from environment variables | |
| * - ALLOWED_ROOT_DIRECTORY: main security boundary | |
| * - DATA_DIR: appData exception, always allowed | |
| */ | |
| export function initAllowedPaths(): void { | |
| // Load ALLOWED_ROOT_DIRECTORY | |
| const rootDir = process.env.ALLOWED_ROOT_DIRECTORY; | |
| if (rootDir) { | |
| allowedRootDirectory = path.resolve(rootDir); | |
| console.log(`[Security] ✓ ALLOWED_ROOT_DIRECTORY configured: ${allowedRootDirectory}`); | |
| } else { | |
| console.log('[Security] ⚠️ ALLOWED_ROOT_DIRECTORY not set - allowing access to all paths'); | |
| } | |
| // Load DATA_DIR (appData exception - always allowed) | |
| const dataDir = process.env.DATA_DIR; | |
| if (dataDir) { | |
| dataDirectory = path.resolve(dataDir); | |
| console.log(`[Security] ✓ DATA_DIR configured: ${dataDirectory}`); | |
| } | |
| } | |
| /** | |
| * Check if a path is allowed based on ALLOWED_ROOT_DIRECTORY | |
| * Returns true if: | |
| * - Path is within ALLOWED_ROOT_DIRECTORY, OR | |
| * - Path is within DATA_DIR (appData exception), OR | |
| * - No restrictions are configured (backward compatibility) | |
| */ | |
| export function isPathAllowed(filePath: string): boolean { | |
| const resolvedPath = path.resolve(filePath); | |
| // Always allow appData directory (settings, credentials) | |
| if (dataDirectory && isPathWithinDirectory(resolvedPath, dataDirectory)) { | |
| return true; | |
| } | |
| // If no ALLOWED_ROOT_DIRECTORY restriction is configured, allow all paths | |
| // Note: DATA_DIR is checked above as an exception, but doesn't restrict other paths | |
| if (!allowedRootDirectory) { | |
| return true; | |
| } | |
| // Allow if within ALLOWED_ROOT_DIRECTORY | |
| if (allowedRootDirectory && isPathWithinDirectory(resolvedPath, allowedRootDirectory)) { | |
| return true; | |
| } | |
| // If restrictions are configured but path doesn't match, deny | |
| return false; | |
| } | |
| /** | |
| * Validate a path - resolves it and checks permissions | |
| * Throws PathNotAllowedError if path is not allowed | |
| */ | |
| export function validatePath(filePath: string): string { | |
| const resolvedPath = path.resolve(filePath); | |
| if (!isPathAllowed(resolvedPath)) { | |
| throw new PathNotAllowedError(filePath); | |
| } | |
| return resolvedPath; | |
| } | |
| /** | |
| * Check if a path is within a directory, with protection against path traversal | |
| * Returns true only if resolvedPath is within directoryPath | |
| */ | |
| export function isPathWithinDirectory(resolvedPath: string, directoryPath: string): boolean { | |
| // Get the relative path from directory to the target | |
| const relativePath = path.relative(directoryPath, resolvedPath); | |
| // If relative path starts with "..", it's outside the directory | |
| // If relative path is absolute, it's outside the directory | |
| // If relative path is empty or ".", it's the directory itself | |
| return !relativePath.startsWith('..') && !path.isAbsolute(relativePath); | |
| } | |
| /** | |
| * Get the configured allowed root directory | |
| */ | |
| export function getAllowedRootDirectory(): string | null { | |
| return allowedRootDirectory; | |
| } | |
| /** | |
| * Get the configured data directory | |
| */ | |
| export function getDataDirectory(): string | null { | |
| return dataDirectory; | |
| } | |
| /** | |
| * Get list of allowed paths (for debugging) | |
| */ | |
| export function getAllowedPaths(): string[] { | |
| const paths: string[] = []; | |
| if (allowedRootDirectory) { | |
| paths.push(allowedRootDirectory); | |
| } | |
| if (dataDirectory) { | |
| paths.push(dataDirectory); | |
| } | |
| return paths; | |
| } | |