import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { assertPathUnderRoot, clearGrantedPluginReadRoots, grantPluginReadRoot, resolveReadablePath } from './path-jail'; describe('path-jail', () => { let tempRoot = ''; beforeEach(() => { tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'metoyou-path-jail-')); fs.mkdirSync(path.join(tempRoot, 'server', 'room-1'), { recursive: true }); fs.writeFileSync(path.join(tempRoot, 'server', 'room-1', 'file.txt'), 'ok'); }); afterEach(() => { clearGrantedPluginReadRoots(); fs.rmSync(tempRoot, { recursive: true, force: true }); }); it('accepts paths inside allowed subdirectories', async () => { const allowedPath = path.join(tempRoot, 'server', 'room-1', 'file.txt'); await expect(assertPathUnderRoot(tempRoot, allowedPath, ['server'])).resolves.toBe(allowedPath); }); it('accepts cached plugin bundle paths under plugin-bundles', async () => { const bundleDir = path.join(tempRoot, 'plugin-bundles', 'example.plugin', '1.0.0'); fs.mkdirSync(bundleDir, { recursive: true }); const bundlePath = path.join(bundleDir, 'main.js'); fs.writeFileSync(bundlePath, 'export default {}'); await expect(assertPathUnderRoot(tempRoot, bundlePath)).resolves.toBe(bundlePath); }); it('rejects paths outside the user data root', async () => { const outsidePath = path.join(os.tmpdir(), 'outside.txt'); await expect(assertPathUnderRoot(tempRoot, outsidePath, ['server'])).rejects.toThrow('outside allowed app-data paths'); }); it('rejects paths outside allowed subdirectories', async () => { const pluginsPath = path.join(tempRoot, 'plugins', 'evil.txt'); await expect(assertPathUnderRoot(tempRoot, pluginsPath, ['server'])).rejects.toThrow('outside allowed app-data paths'); }); it('allows user-granted plugin source roots outside app data', async () => { const externalRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'metoyou-plugin-source-')); const manifestPath = path.join(externalRoot, 'plugin-source.json'); fs.writeFileSync(manifestPath, '{}'); grantPluginReadRoot(externalRoot); await expect(resolveReadablePath(manifestPath)).resolves.toBe(manifestPath); fs.rmSync(externalRoot, { recursive: true, force: true }); }); });