Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
path-traversal.test.tsโ€ข7.35 kB
import { describe, test, expect, beforeAll, afterAll } from '@jest/globals'; import { SecurityTestFramework } from '../setup.js'; import * as path from 'path'; import * as fs from 'fs/promises'; describe('Path Traversal Security Tests', () => { const ALLOWED_DIRS = [ path.resolve('./personas'), path.resolve('./custom-personas'), path.resolve('./backups') ]; describe('Path validation', () => { test('should detect path traversal attempts', () => { const maliciousPaths = [ '../../../etc/passwd', '..\\..\\..\\windows\\system32\\config\\sam', 'personas/../../../sensitive.txt', './././../../../root/.ssh/id_rsa', 'personas/../../custom-personas/../../backups/../../../etc/hosts', 'personas//../..//etc/passwd', 'personas/./../../etc/passwd' ]; for (const malPath of maliciousPaths) { // Check for traversal patterns expect(malPath).toMatch(/\.\./); // Normalized path should not escape allowed directories const normalized = path.normalize(malPath); const resolved = path.resolve(normalized); const isAllowed = ALLOWED_DIRS.some(dir => resolved.startsWith(dir + path.sep) || resolved === dir ); expect(isAllowed).toBe(false); } }); test('should detect URL encoded path traversal', () => { const encodedPaths = [ '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd', '%2e%2e%5c%2e%2e%5c%2e%2e%5cwindows%5csystem32', '..%252f..%252f..%252fetc%252fpasswd', '%c0%ae%c0%ae/%c0%ae%c0%ae/%c0%ae%c0%ae/etc/passwd' ]; for (const encoded of encodedPaths) { try { const decoded = decodeURIComponent(encoded); expect(decoded).toMatch(/\.\./); } catch (e) { // Invalid encoding is also a security issue expect(e).toBeInstanceOf(URIError); } } }); test('should detect null byte injection', () => { const nullBytePaths = [ 'safe.txt\u0000.pdf', 'personas/user.md\u0000.exe', 'file.md\0../../etc/passwd' ]; for (const nullPath of nullBytePaths) { expect(nullPath).toMatch(/\u0000/); // After sanitization, null bytes should be removed const sanitized = nullPath.replaceAll('\u0000', ''); expect(sanitized).not.toMatch(/\u0000/); } }); }); describe('Directory restriction', () => { test('should only allow access to whitelisted directories', () => { const testCases = [ { path: './personas/user.md', allowed: true }, { path: './custom-personas/custom.md', allowed: true }, { path: './backups/backup.md', allowed: true }, { path: '/etc/passwd', allowed: false }, { path: '/home/user/.ssh/id_rsa', allowed: false }, { path: 'C:\\Windows\\System32\\config\\SAM', allowed: false } ]; for (const { path: testPath, allowed } of testCases) { const resolved = path.resolve(testPath); const isInAllowed = ALLOWED_DIRS.some(dir => resolved.startsWith(dir + path.sep) || resolved === dir ); expect(isInAllowed).toBe(allowed); } }); test('should handle symbolic links safely', () => { // Symlinks should be resolved before checking const symlinkPath = './personas/symlink-to-etc-passwd'; const resolved = path.resolve(symlinkPath); // Even if symlink points outside, resolved path should be checked const isAllowed = ALLOWED_DIRS.some(dir => resolved.startsWith(dir + path.sep) ); expect(isAllowed).toBe(true); // Path looks safe // But actual file operation should follow symlink and check real path }); }); describe('Filename validation', () => { test('should validate persona filenames', () => { const validFilenames = [ 'user-persona.md', 'my_persona.md', 'persona123.md', 'test-persona-2.md' ]; const invalidFilenames = [ '../evil.md', 'persona.md.exe', 'persona.md..', '.htaccess', 'persona.php', 'shell.sh', 'persona\u0000.md' ]; const filenameRegex = /^[a-zA-Z0-9\-_.]+\.md$/; for (const filename of validFilenames) { expect(filename).toMatch(filenameRegex); } for (const filename of invalidFilenames) { expect(filename).not.toMatch(filenameRegex); } }); test('should limit filename length', () => { const maxLength = 255; const longFilename = 'a'.repeat(256) + '.md'; expect(longFilename.length).toBeGreaterThan(maxLength); // Should reject overly long filenames expect(() => { if (longFilename.length > maxLength) { throw new Error('Filename too long'); } }).toThrow('Filename too long'); }); }); describe('File size limits', () => { test('should enforce file size limits', async () => { const maxSize = 500000; // 500KB // Mock file stats const mockStats = { size: 1000000, // 1MB isDirectory: () => false }; expect(() => { if (mockStats.size > maxSize) { throw new Error('File too large'); } }).toThrow('File too large'); }); }); describe('Path traversal in different contexts', () => { test('should prevent traversal in ZIP file extraction', () => { const zipEntries = [ { name: 'persona.md', safe: true }, { name: '../../../etc/passwd', safe: false }, { name: 'personas/../../../etc/hosts', safe: false }, { name: '/etc/passwd', safe: false } ]; for (const entry of zipEntries) { // Check for absolute paths first if (path.isAbsolute(entry.name)) { expect(entry.safe).toBe(false); continue; } const extractPath = path.join('./extracted', entry.name); const normalized = path.normalize(extractPath); const resolved = path.resolve(normalized); const baseDir = path.resolve('./extracted'); const isSafe = resolved.startsWith(baseDir + path.sep) || resolved === baseDir + path.sep + 'persona.md'; expect(isSafe).toBe(entry.safe); } }); test('should handle Windows path separators', () => { const windowsPaths = [ { path: 'personas\\..\\..\\..\\windows\\system32', unsafe: true }, { path: 'C:\\Windows\\System32\\cmd.exe', unsafe: true }, { path: '\\\\server\\share\\sensitive.txt', unsafe: true } ]; for (const { path: winPath, unsafe } of windowsPaths) { // Convert to forward slashes for consistent handling const normalized = winPath.replaceAll('\\', '/'); // Check for traversal or absolute paths const hasTraversal = normalized.includes('..'); const isAbsolute = path.isAbsolute(normalized) || path.isAbsolute(winPath) || normalized.startsWith('//') || /^[A-Za-z]:/.test(normalized); expect(hasTraversal || isAbsolute).toBe(unsafe); } }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DollhouseMCP/DollhouseMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server