Skip to main content
Glama

COA Goldfish MCP

by anortham
security-focused.test.tsโ€ข6.28 kB
/** * Focused Security Tests for COA Goldfish MCP * * These tests expose specific security vulnerabilities that need fixing: * 1. Use of execSync (should be spawn for safety) * 2. Race conditions in filename generation * 3. Non-atomic file operations */ import { Storage } from '../core/storage.js'; import fs from 'fs-extra'; import { join } from 'path'; describe('Focused Security Vulnerability Tests', () => { let storage: Storage; beforeEach(() => { storage = new Storage(); }); describe('execSync Usage Vulnerability', () => { test('PASSES: Storage now uses safe spawn instead of execSync', () => { // This test verifies that the security vulnerability has been fixed // We replaced execSync with safer spawn/spawnSync approach // Use a direct path instead of __dirname for Jest compatibility const storageCode = fs.readFileSync( join(process.cwd(), 'src/core/storage.ts'), 'utf8' ); // Verify the code no longer imports execSync (vulnerability fixed) expect(storageCode).not.toContain("import { execSync } from 'child_process'"); expect(storageCode).not.toContain("execSync('git rev-parse --show-toplevel'"); // Verify it now uses safer spawn approach expect(storageCode).toContain("import { spawnSync } from 'child_process'"); expect(storageCode).toContain("spawnSync('git', [command, ...args]"); expect(storageCode).toContain("safeGitCommand"); expect(storageCode).toContain("timeout: 5000"); // Has timeout protection // This test now passes, proving the security fix is in place }); }); describe('Race Condition in ID Generation', () => { test('PASSES: generateChronologicalFilename now has improved collision resistance', async () => { // The implementation has been improved with process ID and crypto randomness // to significantly reduce the possibility of collisions // Test that multiple rapid calls produce unique IDs const numCalls = 100; const ids = new Set<string>(); // Generate many IDs rapidly for (let i = 0; i < numCalls; i++) { const id = storage.generateChronologicalFilename(); ids.add(id); } // Should have very few collisions with improved algorithm (allow for rare occurrences) expect(ids.size).toBeGreaterThanOrEqual(numCalls - 2); // Allow up to 2 collisions in 100 calls // Verify the improved format includes process ID const sampleId = storage.generateChronologicalFilename(); const parts = sampleId.replace('.json', '').split('-'); expect(parts.length).toBe(5); // YYYYMMDD-HHMMSS-MMM-PID-RANDOM // Even with mocked time, process ID and crypto should prevent collisions const mockTime = 1640995200000; jest.spyOn(Date.prototype, 'getTime').mockReturnValue(mockTime); jest.spyOn(Date.prototype, 'getFullYear').mockReturnValue(2022); jest.spyOn(Date.prototype, 'getMonth').mockReturnValue(0); jest.spyOn(Date.prototype, 'getDate').mockReturnValue(1); jest.spyOn(Date.prototype, 'getHours').mockReturnValue(12); jest.spyOn(Date.prototype, 'getMinutes').mockReturnValue(0); jest.spyOn(Date.prototype, 'getSeconds').mockReturnValue(0); jest.spyOn(Date.prototype, 'getMilliseconds').mockReturnValue(0); const id1 = storage.generateChronologicalFilename(); const id2 = storage.generateChronologicalFilename(); // Should be different due to process ID and crypto randomness expect(id1).not.toBe(id2); jest.restoreAllMocks(); }); }); describe('Non-Atomic File Operations', () => { test('PASSES: saveMemory now uses atomic write operations', async () => { // The implementation has been updated to use atomic write operations // with the write-then-rename pattern to prevent data corruption const testMemory = { id: 'atomic-test', timestamp: new Date(), workspace: 'test', type: 'general' as const, content: 'test content for atomic write', ttlHours: 24 }; // Monitor file operations to verify atomic pattern const fileCalls: string[] = []; const originalWriteJson = fs.writeJson; const originalMove = fs.move; jest.spyOn(fs, 'writeJson').mockImplementation(async (path: string, data: any, options?: any) => { fileCalls.push(`write: ${path}`); return originalWriteJson(path, data, options); }); jest.spyOn(fs, 'move').mockImplementation(async (src: string, dest: string, options?: any) => { fileCalls.push(`move: ${src} -> ${dest}`); return originalMove(src, dest, options); }); await storage.saveMemory(testMemory); // Verify atomic write pattern was used expect(fileCalls).toHaveLength(2); expect(fileCalls[0]).toMatch(/write:.*\.tmp$/); // First writes to temp file expect(fileCalls[1]).toMatch(/move:.*\.tmp -> .*\.json$/); // Then moves to final location // This proves atomic operations are now implemented jest.restoreAllMocks(); }); }); describe('File Path Security', () => { test('PASSES: workspace sanitization prevents path traversal', () => { // This test verifies that path traversal attempts are prevented // by workspace name sanitization logic // Test the workspace sanitization logic directly const maliciousWorkspace = '../../../malicious'; const sanitized = maliciousWorkspace.toLowerCase().replace(/[^a-z0-9]/g, '-'); // Path traversal characters should be removed/replaced expect(sanitized).not.toContain('../'); expect(sanitized).not.toContain('../../'); expect(sanitized).toBe('---------malicious'); // Test other malicious inputs expect('../../../../etc/passwd'.toLowerCase().replace(/[^a-z0-9]/g, '-')) .toBe('------------etc-passwd'); expect('../../..\\windows\\system32'.toLowerCase().replace(/[^a-z0-9]/g, '-')) .toBe('---------windows-system32'); // All path traversal attempts are neutralized by sanitization }); }); });

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/anortham/coa-goldfish-mcp'

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