Skip to main content
Glama

Cut-Copy-Paste Clipboard Server

path-access-control.test.ts•13 kB
import { PathAccessControl } from '../path-access-control.js'; import { existsSync, mkdirSync, writeFileSync, unlinkSync, rmSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; describe('PathAccessControl', () => { let testDir: string; let allowFilePath: string; beforeEach(() => { // Create a unique test directory for each test testDir = join(tmpdir(), `path-access-control-test-${Date.now()}-${Math.random()}`); mkdirSync(testDir, { recursive: true }); allowFilePath = join(testDir, 'paths.allow'); }); afterEach(() => { // Clean up test directory if (existsSync(testDir)) { rmSync(testDir, { recursive: true, force: true }); } }); describe('Default behavior (file does not exist)', () => { it('should allow all paths when allowlist file does not exist', () => { const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/any/path/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/tmp/test.js')).toBe(true); }); it('should default to wildcard pattern when file does not exist', () => { const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].pattern).toBe('**/*'); expect(patterns[0].isNegation).toBe(false); }); }); describe('Simple patterns', () => { it('should allow paths matching a simple glob pattern', () => { writeFileSync(allowFilePath, '/home/user/project/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/other/file.txt')).toBe(false); }); it('should handle multiple allow patterns', () => { writeFileSync( allowFilePath, '/home/user/project1/**\n/home/user/project2/**\n/home/user/project3/**\n' ); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project1/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project2/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project3/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project4/file.txt')).toBe(false); }); it('should handle wildcard pattern', () => { writeFileSync(allowFilePath, '*\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/any/path/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); }); }); describe('Negation patterns', () => { it('should deny paths matching negation pattern', () => { writeFileSync(allowFilePath, '/home/user/project/**\n!**/node_modules/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/node_modules/lib/file.js')).toBe(false); expect(control.isPathAllowed('/home/user/project/src/node_modules/test.js')).toBe(false); }); it('should handle multiple negation patterns', () => { writeFileSync( allowFilePath, '/home/user/project/**\n!**/node_modules/**\n!**/.git/**\n!**/dist/**\n' ); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/node_modules/lib.js')).toBe(false); expect(control.isPathAllowed('/home/user/project/.git/config')).toBe(false); expect(control.isPathAllowed('/home/user/project/dist/bundle.js')).toBe(false); }); it('should evaluate patterns in order (last match wins)', () => { // First allow all, then deny node_modules, then re-allow specific node_modules writeFileSync( allowFilePath, '/home/user/project/**\n!**/node_modules/**\n/home/user/project/node_modules/special/**\n' ); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/node_modules/lib.js')).toBe(false); expect(control.isPathAllowed('/home/user/project/node_modules/special/file.js')).toBe(true); }); }); describe('Relative patterns', () => { it('should handle relative patterns by converting to **/ prefix', () => { writeFileSync(allowFilePath, '*.ts\n*.js\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/file.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/file.js')).toBe(true); expect(control.isPathAllowed('/home/user/project/file.py')).toBe(false); }); it('should handle patterns already starting with **/', () => { writeFileSync(allowFilePath, '**/*.ts\n'); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns[0].pattern).toBe('**/*.ts'); expect(control.isPathAllowed('/home/user/project/file.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/user/project/file.js')).toBe(false); }); }); describe('Comments and empty lines', () => { it('should ignore comment lines', () => { writeFileSync( allowFilePath, '# This is a comment\n/home/user/project/**\n# Another comment\n' ); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].pattern).toBe('/home/user/project/**'); }); it('should ignore empty lines', () => { writeFileSync(allowFilePath, '/home/user/project/**\n\n\n/home/user/other/**\n'); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(2); }); it('should handle mixed comments, empty lines, and patterns', () => { writeFileSync( allowFilePath, `# Allow project directory /home/user/project/** # Deny node_modules !**/node_modules/** # Deny .git !**/.git/** ` ); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(3); expect(patterns[0].isNegation).toBe(false); expect(patterns[1].isNegation).toBe(true); expect(patterns[2].isNegation).toBe(true); }); }); describe('Empty or invalid file', () => { it('should default to allow all when file is empty', () => { writeFileSync(allowFilePath, ''); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/any/path/file.txt')).toBe(true); const patterns = control.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].pattern).toBe('**/*'); }); it('should default to allow all when file contains only comments', () => { writeFileSync(allowFilePath, '# Comment 1\n# Comment 2\n# Comment 3\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/any/path/file.txt')).toBe(true); const patterns = control.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].pattern).toBe('**/*'); }); it('should ignore invalid negation-only lines', () => { writeFileSync(allowFilePath, '!\n/home/user/project/**\n'); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(1); expect(patterns[0].pattern).toBe('/home/user/project/**'); }); }); describe('Reload functionality', () => { it('should reload patterns from file', () => { writeFileSync(allowFilePath, '/home/user/project1/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project1/file.txt')).toBe(true); expect(control.isPathAllowed('/home/user/project2/file.txt')).toBe(false); // Update file writeFileSync(allowFilePath, '/home/user/project2/**\n'); control.reload(); expect(control.isPathAllowed('/home/user/project1/file.txt')).toBe(false); expect(control.isPathAllowed('/home/user/project2/file.txt')).toBe(true); }); it('should handle file deletion during reload', () => { writeFileSync(allowFilePath, '/home/user/project/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/file.txt')).toBe(true); expect(control.isPathAllowed('/other/file.txt')).toBe(false); // Delete file unlinkSync(allowFilePath); control.reload(); // Should default to allow all expect(control.isPathAllowed('/home/user/project/file.txt')).toBe(true); expect(control.isPathAllowed('/other/file.txt')).toBe(true); }); }); describe('Complex scenarios', () => { it('should handle real-world allowlist configuration', () => { writeFileSync( allowFilePath, `/home/user/projects/** !**/node_modules/** !**/.git/** !**/dist/** !**/build/** !**/.DS_Store !**/coverage/** ` ); const control = new PathAccessControl(allowFilePath); // Allowed paths expect(control.isPathAllowed('/home/user/projects/app/src/index.ts')).toBe(true); expect(control.isPathAllowed('/home/user/projects/app/README.md')).toBe(true); expect(control.isPathAllowed('/home/user/projects/lib/package.json')).toBe(true); // Denied paths expect(control.isPathAllowed('/home/user/projects/app/node_modules/lib.js')).toBe(false); expect(control.isPathAllowed('/home/user/projects/app/.git/config')).toBe(false); expect(control.isPathAllowed('/home/user/projects/app/dist/bundle.js')).toBe(false); expect(control.isPathAllowed('/home/user/projects/app/build/output.js')).toBe(false); expect(control.isPathAllowed('/home/user/projects/app/.DS_Store')).toBe(false); expect(control.isPathAllowed('/home/user/projects/app/coverage/index.html')).toBe(false); }); it('should handle workspace-restricted configuration', () => { const workspace = '/home/user/my-current-project'; writeFileSync(allowFilePath, `${workspace}/**\n`); const control = new PathAccessControl(allowFilePath); // Only allow within workspace expect(control.isPathAllowed(`${workspace}/src/index.ts`)).toBe(true); expect(control.isPathAllowed(`${workspace}/deep/nested/file.js`)).toBe(true); expect(control.isPathAllowed('/home/user/other-project/file.ts')).toBe(false); expect(control.isPathAllowed('/etc/passwd')).toBe(false); }); }); describe('Pattern information', () => { it('should return pattern metadata', () => { writeFileSync(allowFilePath, '/home/user/project/**\n!**/node_modules/**\n'); const control = new PathAccessControl(allowFilePath); const patterns = control.getPatterns(); expect(patterns).toHaveLength(2); expect(patterns[0]).toMatchObject({ pattern: '/home/user/project/**', isNegation: false, originalLine: '/home/user/project/**', }); expect(patterns[1]).toMatchObject({ pattern: '**/node_modules/**', isNegation: true, originalLine: '!**/node_modules/**', }); }); it('should return allowlist file path', () => { const control = new PathAccessControl(allowFilePath); expect(control.getAllowFilePath()).toBe(allowFilePath); }); }); describe('Edge cases', () => { it('should handle Windows-style paths on Unix systems', () => { // This test ensures path resolution works correctly writeFileSync(allowFilePath, '/home/user/project/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/project/src/file.ts')).toBe(true); }); it('should handle paths with special characters', () => { writeFileSync(allowFilePath, '/home/user/my-project/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed('/home/user/my-project/file.ts')).toBe(true); }); it('should handle very long paths', () => { const longPath = '/home/user/' + 'a/'.repeat(100) + 'file.ts'; writeFileSync(allowFilePath, '/home/user/**\n'); const control = new PathAccessControl(allowFilePath); expect(control.isPathAllowed(longPath)).toBe(true); }); }); });

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/Pr0j3c7t0dd-Ltd/cut-copy-paste-mcp'

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