Skip to main content
Glama
mcp-security.test.js15.4 kB
import { describe, test, expect, beforeEach, afterEach } from 'vitest'; import { setupMcpTest, _addSecurityPropertyOverrides, resetEnvironment, createTestMcpServer } from './mcp-shared-fixtures.js'; /** * Security Validation Tests - Performance and Architecture Notes * * ⚠️ PERFORMANCE WARNING: This test suite is intentionally slower (~533ms for 38 tests) * * WHY THIS TEST IS SLOWER: * - Each test group calls createTestMcpServer() with different environment configurations * - createTestMcpServer() performs expensive operations for configuration isolation: * 1. setupTestEnvironment() - Modifies global process.env * 2. vi.resetModules() - Clears entire module cache (EXPENSIVE!) * 3. Dynamic re-imports of config and main modules (EXPENSIVE!) * 4. serverConfig.reload() - Forces config to re-read environment * 5. new SqlServerMCP() - Creates fresh server instance * * WHY WE CAN'T OPTIMIZE BY REUSING SERVER INSTANCES: * * The SqlServerMCP class reads configuration through a SINGLETON pattern: * - serverConfig is a singleton instance created on first import * - Configuration is cached in instance properties (readOnlyMode, etc.) * - Server property getters (lines 581-590 in index.js) read from this singleton * * CONFIGURATION CORRUPTION SCENARIO: * ```javascript * // Test 1: Creates server with default env (READ_ONLY=true) * const server1 = await createTestMcpServer(); * expect(server1.readOnlyMode).toBe(true); // ✅ Works * * // Test 2: Changes environment but reuses server1 * setupTestEnvironment({ SQL_SERVER_READ_ONLY: 'false' }); * expect(server1.readOnlyMode).toBe(false); // ❌ FAILS - still returns true! * // server1.readOnlyMode reads from stale singleton config * ``` * * THE MODULE RESET IS ESSENTIAL: * - Without vi.resetModules(), the serverConfig singleton retains old values * - Tests would have corrupted/stale configuration leading to false positives * - Environment changes wouldn't be reflected in server behavior * - Test isolation would be completely broken * * PERFORMANCE IS ACCEPTABLE: * - 533ms for 38 comprehensive security tests = ~14ms per test * - This covers critical security validation that must be bulletproof * - The cost is justified to prevent configuration corruption bugs * * 🚫 DO NOT OPTIMIZE BY: * - Reusing server instances across tests with different configs * - Skipping vi.resetModules() calls * - Caching servers between test groups * - Mocking the configuration reload process * * ✅ SAFE OPTIMIZATIONS (if needed): * - Group tests with identical configurations together * - Reduce redundant createTestMcpServer() calls within same config * - Use describe.sequential() for tests that must run in sequence * - Mock expensive parts that don't affect security validation */ const _originalEnv = process.env; describe('Safety Mechanisms', () => { let mcpServer; describe('Constructor Safety Configuration', () => { beforeEach(() => { setupMcpTest(); }); afterEach(() => { resetEnvironment(); }); test('should enable read-only mode by default', async () => { const safeMcpServer = await createTestMcpServer(); expect(safeMcpServer.readOnlyMode).toBe(true); expect(safeMcpServer.allowDestructiveOperations).toBe(false); expect(safeMcpServer.allowSchemaChanges).toBe(false); }); test('should allow overriding safety settings via environment variables', async () => { const unsafeMcpServer = await createTestMcpServer({ SQL_SERVER_READ_ONLY: 'false', SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS: 'true', SQL_SERVER_ALLOW_SCHEMA_CHANGES: 'true' }); expect(unsafeMcpServer.readOnlyMode).toBe(false); expect(unsafeMcpServer.allowDestructiveOperations).toBe(true); expect(unsafeMcpServer.allowSchemaChanges).toBe(true); }); test('should handle mixed safety configurations', async () => { const mixedMcpServer = await createTestMcpServer({ SQL_SERVER_READ_ONLY: 'false', SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS: 'true', SQL_SERVER_ALLOW_SCHEMA_CHANGES: 'false' }); expect(mixedMcpServer.readOnlyMode).toBe(false); expect(mixedMcpServer.allowDestructiveOperations).toBe(true); expect(mixedMcpServer.allowSchemaChanges).toBe(false); }); }); describe('Query Validation', () => { beforeEach(async () => { setupMcpTest(); // Test with default safe configuration (read-only mode enabled) mcpServer = await createTestMcpServer(); }); afterEach(() => { resetEnvironment(); }); test('should allow SELECT queries in read-only mode', () => { const validation = mcpServer.validateQuery('SELECT * FROM users'); expect(validation.allowed).toBe(true); expect(validation.reason).toBe('Query validation passed'); }); test('should allow SELECT with JOIN in read-only mode', () => { const validation = mcpServer.validateQuery(` SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id `); expect(validation.allowed).toBe(true); }); test('should allow CTE queries in read-only mode', () => { const validation = mcpServer.validateQuery(` WITH UserStats AS ( SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id ) SELECT * FROM UserStats `); expect(validation.allowed).toBe(true); }); test('should block INSERT queries in read-only mode', () => { const validation = mcpServer.validateQuery("INSERT INTO users (name) VALUES ('John')"); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should block UPDATE queries in read-only mode', () => { const validation = mcpServer.validateQuery("UPDATE users SET name = 'Jane' WHERE id = 1"); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should block DELETE queries in read-only mode', () => { const validation = mcpServer.validateQuery('DELETE FROM users WHERE id = 1'); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should block TRUNCATE queries in read-only mode', () => { const validation = mcpServer.validateQuery('TRUNCATE TABLE users'); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should block stored procedure execution in read-only mode', () => { const validation = mcpServer.validateQuery('EXEC UpdateUserStats'); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should block CREATE statements in read-only mode', () => { const validation = mcpServer.validateQuery('CREATE TABLE test (id INT)'); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Read-only mode is enabled'); expect(validation.queryType).toBe('non-select'); }); test('should handle case-insensitive queries', () => { const validation = mcpServer.validateQuery("insert into users (name) values ('test')"); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('non-select'); }); test('should handle queries with leading whitespace', () => { const validation = mcpServer.validateQuery(' DELETE FROM users'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('non-select'); }); test('should allow empty queries', () => { const validation = mcpServer.validateQuery(''); expect(validation.allowed).toBe(true); expect(validation.reason).toBe('Empty query'); }); test('should allow whitespace-only queries', () => { const validation = mcpServer.validateQuery(' \n\t '); expect(validation.allowed).toBe(true); expect(validation.reason).toBe('Empty query'); }); }); describe('Destructive Operations Control', () => { beforeEach(async () => { setupMcpTest(); // Test with read-only disabled but destructive operations disabled mcpServer = await createTestMcpServer({ SQL_SERVER_READ_ONLY: 'false', SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS: 'false', SQL_SERVER_ALLOW_SCHEMA_CHANGES: 'false' }); }); afterEach(() => { resetEnvironment(); }); test('should allow SELECT queries when read-only is disabled', () => { const validation = mcpServer.validateQuery('SELECT * FROM users'); expect(validation.allowed).toBe(true); }); test('should block INSERT when destructive operations disabled', () => { const validation = mcpServer.validateQuery("INSERT INTO users (name) VALUES ('test')"); expect(validation.allowed).toBe(false); expect(validation.reason).toContain( 'Destructive operations (INSERT/UPDATE/DELETE) are disabled' ); expect(validation.queryType).toBe('destructive'); }); test('should block UPDATE when destructive operations disabled', () => { const validation = mcpServer.validateQuery("UPDATE users SET name = 'test'"); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('destructive'); }); test('should block DELETE when destructive operations disabled', () => { const validation = mcpServer.validateQuery('DELETE FROM users'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('destructive'); }); test('should block TRUNCATE when destructive operations disabled', () => { const validation = mcpServer.validateQuery('TRUNCATE TABLE users'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('destructive'); }); test('should block EXECUTE/EXEC when destructive operations disabled', () => { const validation = mcpServer.validateQuery('EXECUTE sp_updatestats'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('destructive'); }); test('should detect multi-statement destructive queries', () => { const validation = mcpServer.validateQuery('SELECT 1; DELETE FROM users'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('destructive'); }); }); describe('Schema Changes Control', () => { beforeEach(async () => { setupMcpTest(); // Test with destructive operations enabled but schema changes disabled mcpServer = await createTestMcpServer({ SQL_SERVER_READ_ONLY: 'false', SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS: 'true', SQL_SERVER_ALLOW_SCHEMA_CHANGES: 'false' }); }); afterEach(() => { resetEnvironment(); }); test('should allow data operations when schema changes disabled', () => { const validation = mcpServer.validateQuery("INSERT INTO users (name) VALUES ('test')"); expect(validation.allowed).toBe(true); }); test('should block CREATE TABLE when schema changes disabled', () => { const validation = mcpServer.validateQuery('CREATE TABLE test (id INT)'); expect(validation.allowed).toBe(false); expect(validation.reason).toContain('Schema changes (CREATE/DROP/ALTER) are disabled'); expect(validation.queryType).toBe('schema'); }); test('should block DROP TABLE when schema changes disabled', () => { const validation = mcpServer.validateQuery('DROP TABLE users'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('schema'); }); test('should block ALTER TABLE when schema changes disabled', () => { const validation = mcpServer.validateQuery('ALTER TABLE users ADD COLUMN age INT'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('schema'); }); test('should block GRANT statements when schema changes disabled', () => { const validation = mcpServer.validateQuery('GRANT SELECT ON users TO testuser'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('schema'); }); test('should block REVOKE statements when schema changes disabled', () => { const validation = mcpServer.validateQuery('REVOKE SELECT ON users FROM testuser'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('schema'); }); test('should detect multi-statement schema changes', () => { const validation = mcpServer.validateQuery('SELECT 1; CREATE INDEX idx_name ON users(name)'); expect(validation.allowed).toBe(false); expect(validation.queryType).toBe('schema'); }); }); describe('Full Access Mode', () => { beforeEach(async () => { setupMcpTest(); // Test with all safety features disabled mcpServer = await createTestMcpServer({ SQL_SERVER_READ_ONLY: 'false', SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS: 'true', SQL_SERVER_ALLOW_SCHEMA_CHANGES: 'true' }); }); afterEach(() => { resetEnvironment(); }); test('should allow SELECT queries in full access mode', () => { const validation = mcpServer.validateQuery('SELECT * FROM users'); expect(validation.allowed).toBe(true); }); test('should allow INSERT queries in full access mode', () => { const validation = mcpServer.validateQuery("INSERT INTO users (name) VALUES ('test')"); expect(validation.allowed).toBe(true); }); test('should allow UPDATE queries in full access mode', () => { const validation = mcpServer.validateQuery("UPDATE users SET name = 'test'"); expect(validation.allowed).toBe(true); }); test('should allow DELETE queries in full access mode', () => { const validation = mcpServer.validateQuery('DELETE FROM users WHERE id = 1'); expect(validation.allowed).toBe(true); }); test('should allow CREATE TABLE in full access mode', () => { const validation = mcpServer.validateQuery('CREATE TABLE test (id INT, name VARCHAR(100))'); expect(validation.allowed).toBe(true); }); test('should allow DROP TABLE in full access mode', () => { const validation = mcpServer.validateQuery('DROP TABLE test'); expect(validation.allowed).toBe(true); }); test('should allow ALTER TABLE in full access mode', () => { const validation = mcpServer.validateQuery('ALTER TABLE users ADD COLUMN age INT'); expect(validation.allowed).toBe(true); }); test('should allow GRANT/REVOKE in full access mode', () => { expect(mcpServer.validateQuery('GRANT SELECT ON users TO testuser').allowed).toBe(true); expect(mcpServer.validateQuery('REVOKE SELECT ON users FROM testuser').allowed).toBe(true); }); }); });

Latest Blog Posts

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/egarcia74/warp-sql-server-mcp'

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