UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

102 lines 16 kB
import path from 'path'; import fs from 'fs/promises'; import { logger } from '../utils/logger.js'; import { RegexValidator } from './regexValidator.js'; export class PathValidator { static ALLOWED_DIRECTORIES = []; static ALLOWED_EXTENSIONS = ['.md', '.markdown', '.txt', '.yml', '.yaml']; static initialize(personasDir, allowedExtensions) { this.ALLOWED_DIRECTORIES = [ path.resolve(personasDir), path.resolve('./personas'), path.resolve('./custom-personas'), path.resolve('./backups'), path.resolve(process.env.PERSONAS_DIR || './personas') ]; if (allowedExtensions) { this.ALLOWED_EXTENSIONS = allowedExtensions; } } static async validatePersonaPath(userPath) { if (!userPath || typeof userPath !== 'string') { throw new Error('Path must be a non-empty string'); } // Remove any null bytes const cleanPath = userPath.replace(/\x00/g, ''); // Normalize and resolve path const normalizedPath = path.normalize(cleanPath); const resolvedPath = path.resolve(normalizedPath); // Check for path traversal attempts if (normalizedPath.includes('..') || cleanPath.includes('..')) { logger.warn('Path traversal attempt detected', { userPath }); throw new Error('Path traversal detected'); } // Check if path is within allowed directories if (this.ALLOWED_DIRECTORIES.length === 0) { // If not initialized, allow paths under current working directory's personas folder const defaultAllowed = [ path.resolve('./personas'), path.resolve(process.env.PERSONAS_DIR || './personas') ]; const isAllowed = defaultAllowed.some(allowedDir => resolvedPath.startsWith(allowedDir + path.sep) || resolvedPath === allowedDir); if (!isAllowed) { // SECURITY FIX #206: Don't expose user paths in error messages logger.error('Path access denied', { path: userPath }); throw new Error('Path access denied'); } } else { const isAllowed = this.ALLOWED_DIRECTORIES.some(allowedDir => resolvedPath.startsWith(allowedDir + path.sep) || resolvedPath === allowedDir); if (!isAllowed) { // SECURITY FIX #206: Don't expose user paths in error messages logger.error('Path access denied', { path: userPath }); throw new Error('Path access denied'); } } // Validate filename if it's a file if (path.extname(resolvedPath)) { const filename = path.basename(resolvedPath); const ext = path.extname(filename).toLowerCase(); // Check if extension is allowed if (!this.ALLOWED_EXTENSIONS.includes(ext)) { throw new Error(`File extension not allowed: ${ext}. Allowed: ${this.ALLOWED_EXTENSIONS.join(', ')}`); } // Validate filename format (alphanumeric, dash, underscore, dot) if (!RegexValidator.validate(filename, /^[a-zA-Z0-9\-_.]+$/i, { maxLength: 255 })) { throw new Error(`Invalid filename format: ${filename}`); } } return resolvedPath; } static async safeReadFile(filePath) { const validatedPath = await this.validatePersonaPath(filePath); // Check file exists and is not a directory const stats = await fs.stat(validatedPath); if (stats.isDirectory()) { throw new Error('Path is a directory, not a file'); } // Size check if (stats.size > 500000) { // 500KB throw new Error('File too large'); } return fs.readFile(validatedPath, 'utf-8'); } static async safeWriteFile(filePath, content) { const validatedPath = await this.validatePersonaPath(filePath); // Content validation if (content.length > 500000) { throw new Error('Content too large'); } // Ensure directory exists before atomic write const dirPath = path.dirname(validatedPath); await fs.mkdir(dirPath, { recursive: true }); // Write to temp file first (atomic write) const tempPath = `${validatedPath}.tmp`; await fs.writeFile(tempPath, content, 'utf-8'); // Rename to final path (atomic on most filesystems) await fs.rename(tempPath, validatedPath); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aFZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZWN1cml0eS9wYXRoVmFsaWRhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUN4QixPQUFPLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUdyRCxNQUFNLE9BQU8sYUFBYTtJQUNoQixNQUFNLENBQUMsbUJBQW1CLEdBQWEsRUFBRSxDQUFDO0lBQzFDLE1BQU0sQ0FBQyxrQkFBa0IsR0FBYSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztJQUU1RixNQUFNLENBQUMsVUFBVSxDQUFDLFdBQW1CLEVBQUUsaUJBQTRCO1FBQ2pFLElBQUksQ0FBQyxtQkFBbUIsR0FBRztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztZQUN6QixJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQztZQUMxQixJQUFJLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDO1lBQ2pDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDO1lBQ3pCLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLElBQUksWUFBWSxDQUFDO1NBQ3ZELENBQUM7UUFFRixJQUFJLGlCQUFpQixFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGlCQUFpQixDQUFDO1FBQzlDLENBQUM7SUFDSCxDQUFDO0lBRUQsTUFBTSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxRQUFnQjtRQUMvQyxJQUFJLENBQUMsUUFBUSxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsRUFBRSxD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TyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUU3QywwQ0FBMEM7UUFDMUMsTUFBTSxRQUFRLEdBQUcsR0FBRyxhQUFhLE1BQU0sQ0FBQztRQUN4QyxNQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztRQUUvQyxvREFBb0Q7UUFDcEQsTUFBTSxFQUFFLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxhQUFhLENBQUMsQ0FBQztJQUMzQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7IFJlZ2V4VmFsaWRhdG9yIH0gZnJvbSAnLi9yZWdleFZhbGlkYXRvci5qcyc7XG5pbXBvcnQgeyBTZWN1cmVFcnJvckhhbmRsZXIgfSBmcm9tICcuL2Vycm9ySGFuZGxlci5qcyc7XG5cbmV4cG9ydCBjbGFzcyBQYXRoVmFsaWRhdG9yIHtcbiAgcHJpdmF0ZSBzdGF0aWMgQUxMT1dFRF9ESVJFQ1RPUklFUzogc3RyaW5nW10gPSBbXTtcbiAgcHJpdmF0ZSBzdGF0aWMgQUxMT1dFRF9FWFRFTlNJT05TOiBzdHJpbmdbXSA9IFsnLm1kJywgJy5tYXJrZG93bicsICcudHh0JywgJy55bWwnLCAnLnlhbWwnXTtcbiAgXG4gIHN0YXRpYyBpbml0aWFsaXplKHBlcnNvbmFzRGlyOiBzdHJpbmcsIGFsbG93ZWRFeHRlbnNpb25zPzogc3RyaW5nW10pOiB2b2lkIHtcbiAgICB0aGlzLkFMTE9XRURfRElSRUNUT1JJRVMgPSBbXG4gICAgICBwYXRoLnJlc29sdmUocGVyc29uYXNEaXIpLFxuICAgICAgcGF0aC5yZXNvbHZlKCcuL3BlcnNvbmFzJyksXG4gICAgICBwYXRoLnJlc29sdmUoJy4vY3VzdG9tLXBlcnNvbmFzJyksXG4gICAgICBwYXRoLnJlc29sdmUoJy4vYmFja3VwcycpLFxuICAgICAgcGF0aC5yZXNvbHZlKHByb2Nlc3MuZW52LlBFUlNPTkFTX0RJUiB8fCAnLi9wZXJzb25hcycpXG4gICAgXTtcbiAgICBcbiAgICBpZiAoYWxsb3dlZEV4dGVuc2lvbnMpIHtcbiAgICAgIHRoaXMuQUxMT1dFRF9FWFRFTlNJT05TID0gYWxsb3dlZEV4dGVuc2lvbnM7XG4gICAgfVxuICB9XG5cbiAgc3RhdGljIGFzeW5jIHZhbGlkYXRlUGVyc29uYVBhdGgodXNlclBhdGg6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgaWYgKCF1c2VyUGF0aCB8fCB0eXBlb2YgdXNlclBhdGggIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggbXVzdCBiZSBhIG5vbi1lbXB0eSBzdHJpbmcnKTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgYW55IG51bGwgYnl0ZXNcbiAgICBjb25zdCBjbGVhblBhdGggPSB1c2VyUGF0aC5yZXBsYWNlKC9cXHgwMC9nLCAnJyk7XG4gICAgXG4gICAgLy8gTm9ybWFsaXplIGFuZCByZXNvbHZlIHBhdGhcbiAgICBjb25zdCBub3JtYWxpemVkUGF0aCA9IHBhdGgubm9ybWFsaXplKGNsZWFuUGF0aCk7XG4gICAgY29uc3QgcmVzb2x2ZWRQYXRoID0gcGF0aC5yZXNvbHZlKG5vcm1hbGl6ZWRQYXRoKTtcbiAgICBcbiAgICAvLyBDaGVjayBmb3IgcGF0aCB0cmF2ZXJzYWwgYXR0ZW1wdHNcbiAgICBpZiAobm9ybWFsaXplZFBhdGguaW5jbHVkZXMoJy4uJykgfHwgY2xlYW5QYXRoLmluY2x1ZGVzKCcuLicpKSB7XG4gICAgICBsb2dnZXIud2FybignUGF0aCB0cmF2ZXJzYWwgYXR0ZW1wdCBkZXRlY3RlZCcsIHsgdXNlclBhdGggfSk7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggdHJhdmVyc2FsIGRldGVjdGVkJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIENoZWNrIGlmIHBhdGggaXMgd2l0aGluIGFsbG93ZWQgZGlyZWN0b3JpZXNcbiAgICBpZiAodGhpcy5BTExPV0VEX0RJUkVDVE9SSUVTLmxlbmd0aCA9PT0gMCkge1xuICAgICAgLy8gSWYgbm90IGluaXRpYWxpemVkLCBhbGxvdyBwYXRocyB1bmRlciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5J3MgcGVyc29uYXMgZm9sZGVyXG4gICAgICBjb25zdCBkZWZhdWx0QWxsb3dlZCA9IFtcbiAgICAgICAgcGF0aC5yZXNvbHZlKCcuL3BlcnNvbmFzJyksXG4gICAgICAgIHBhdGgucmVzb2x2ZShwcm9jZXNzLmVudi5QRVJTT05BU19ESVIgfHwgJy4vcGVyc29uYXMnKVxuICAgICAgXTtcbiAgICAgIGNvbnN0IGlzQWxsb3dlZCA9IGRlZmF1bHRBbGxvd2VkLnNvbWUoYWxsb3dlZERpciA9PiBcbiAgICAgICAgcmVzb2x2ZWRQYXRoLnN0YXJ0c1dpdGgoYWxsb3dlZERpciArIHBhdGguc2VwKSB8fCBcbiAgICAgICAgcmVzb2x2ZWRQYXRoID09PSBhbGxvd2VkRGlyXG4gICAgICApO1xuICAgICAgaWYgKCFpc0FsbG93ZWQpIHtcbiAgICAgICAgLy8gU0VDVVJJVFkgRklYICMyMDY6IERvbid0IGV4cG9zZSB1c2VyIHBhdGhzIGluIGVycm9yIG1lc3NhZ2VzXG4gICAgICAgIGxvZ2dlci5lcnJvcignUGF0aCBhY2Nlc3MgZGVuaWVkJywgeyBwYXRoOiB1c2VyUGF0aCB9KTtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnKTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgaXNBbGxvd2VkID0gdGhpcy5BTExPV0VEX0RJUkVDVE9SSUVTLnNvbWUoYWxsb3dlZERpciA9PiBcbiAgICAgICAgcmVzb2x2ZWRQYXRoLnN0YXJ0c1dpdGgoYWxsb3dlZERpciArIHBhdGguc2VwKSB8fCBcbiAgICAgICAgcmVzb2x2ZWRQYXRoID09PSBhbGxvd2VkRGlyXG4gICAgICApO1xuICAgICAgXG4gICAgICBpZiAoIWlzQWxsb3dlZCkge1xuICAgICAgICAvLyBTRUNVUklUWSBGSVggIzIwNjogRG9uJ3QgZXhwb3NlIHVzZXIgcGF0aHMgaW4gZXJyb3IgbWVzc2FnZXNcbiAgICAgICAgbG9nZ2VyLmVycm9yKCdQYXRoIGFjY2VzcyBkZW5pZWQnLCB7IHBhdGg6IHVzZXJQYXRoIH0pO1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggYWNjZXNzIGRlbmllZCcpO1xuICAgICAgfVxuICAgIH1cbiAgICBcbiAgICAvLyBWYWxpZGF0ZSBmaWxlbmFtZSBpZiBpdCdzIGEgZmlsZVxuICAgIGlmIChwYXRoLmV4dG5hbWUocmVzb2x2ZWRQYXRoKSkge1xuICAgICAgY29uc3QgZmlsZW5hbWUgPSBwYXRoLmJhc2VuYW1lKHJlc29sdmVkUGF0aCk7XG4gICAgICBjb25zdCBleHQgPSBwYXRoLmV4dG5hbWUoZmlsZW5hbWUpLnRvTG93ZXJDYXNlKCk7XG4gICAgICBcbiAgICAgIC8vIENoZWNrIGlmIGV4dGVuc2lvbiBpcyBhbGxvd2VkXG4gICAgICBpZiAoIXRoaXMuQUxMT1dFRF9FWFRFTlNJT05TLmluY2x1ZGVzKGV4dCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGaWxlIGV4dGVuc2lvbiBub3QgYWxsb3dlZDogJHtleHR9LiBBbGxvd2VkOiAke3RoaXMuQUxMT1dFRF9FWFRFTlNJT05TLmpvaW4oJywgJyl9YCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIFZhbGlkYXRlIGZpbGVuYW1lIGZvcm1hdCAoYWxwaGFudW1lcmljLCBkYXNoLCB1bmRlcnNjb3JlLCBkb3QpXG4gICAgICBpZiAoIVJlZ2V4VmFsaWRhdG9yLnZhbGlkYXRlKGZpbGVuYW1lLCAvXlthLXpBLVowLTlcXC1fLl0rJC9pLCB7IG1heExlbmd0aDogMjU1IH0pKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSW52YWxpZCBmaWxlbmFtZSBmb3JtYXQ6ICR7ZmlsZW5hbWV9YCk7XG4gICAgICB9XG4gICAgfVxuICAgIFxuICAgIHJldHVybiByZXNvbHZlZFBhdGg7XG4gIH1cblxuICBzdGF0aWMgYXN5bmMgc2FmZVJlYWRGaWxlKGZpbGVQYXRoOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHZhbGlkYXRlZFBhdGggPSBhd2FpdCB0aGlzLnZhbGlkYXRlUGVyc29uYVBhdGgoZmlsZVBhdGgpO1xuICAgIFxuICAgIC8vIENoZWNrIGZpbGUgZXhpc3RzIGFuZCBpcyBub3QgYSBkaXJlY3RvcnlcbiAgICBjb25zdCBzdGF0cyA9IGF3YWl0IGZzLnN0YXQodmFsaWRhdGVkUGF0aCk7XG4gICAgaWYgKHN0YXRzLmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBpcyBhIGRpcmVjdG9yeSwgbm90IGEgZmlsZScpO1xuICAgIH1cbiAgICBcbiAgICAvLyBTaXplIGNoZWNrXG4gICAgaWYgKHN0YXRzLnNpemUgPiA1MDAwMDApIHsgLy8gNTAwS0JcbiAgICAgIHRocm93IG5ldyBFcnJvcignRmlsZSB0b28gbGFyZ2UnKTtcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGZzLnJlYWRGaWxlKHZhbGlkYXRlZFBhdGgsICd1dGYtOCcpO1xuICB9XG5cbiAgc3RhdGljIGFzeW5jIHNhZmVXcml0ZUZpbGUoZmlsZVBhdGg6IHN0cmluZywgY29udGVudDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgdmFsaWRhdGVkUGF0aCA9IGF3YWl0IHRoaXMudmFsaWRhdGVQZXJzb25hUGF0aChmaWxlUGF0aCk7XG4gICAgXG4gICAgLy8gQ29udGVudCB2YWxpZGF0aW9uXG4gICAgaWYgKGNvbnRlbnQubGVuZ3RoID4gNTAwMDAwKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0NvbnRlbnQgdG9vIGxhcmdlJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIEVuc3VyZSBkaXJlY3RvcnkgZXhpc3RzIGJlZm9yZSBhdG9taWMgd3JpdGVcbiAgICBjb25zdCBkaXJQYXRoID0gcGF0aC5kaXJuYW1lKHZhbGlkYXRlZFBhdGgpO1xuICAgIGF3YWl0IGZzLm1rZGlyKGRpclBhdGgsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgIFxuICAgIC8vIFdyaXRlIHRvIHRlbXAgZmlsZSBmaXJzdCAoYXRvbWljIHdyaXRlKVxuICAgIGNvbnN0IHRlbXBQYXRoID0gYCR7dmFsaWRhdGVkUGF0aH0udG1wYDtcbiAgICBhd2FpdCBmcy53cml0ZUZpbGUodGVtcFBhdGgsIGNvbnRlbnQsICd1dGYtOCcpO1xuICAgIFxuICAgIC8vIFJlbmFtZSB0byBmaW5hbCBwYXRoIChhdG9taWMgb24gbW9zdCBmaWxlc3lzdGVtcylcbiAgICBhd2FpdCBmcy5yZW5hbWUodGVtcFBhdGgsIHZhbGlkYXRlZFBhdGgpO1xuICB9XG59Il19