# MCP Read-Only Security Guide ## Current Security Measures The `mssql-mcp-readonly.js` file implements multiple layers of read-only enforcement: ### 1. Query Validation (Application Layer) - ✅ Only SELECT queries allowed - ✅ Blocks INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE - ✅ Blocks EXEC/EXECUTE (prevents stored procedure execution) - ✅ Blocks SP_/XP_ (prevents system stored procedures) - ✅ Blocks MERGE, GRANT, REVOKE, DENY - ✅ Prevents query chaining with semicolons ### 2. Recommended: Database User Permissions (Database Layer) For **maximum security**, create a read-only database user: ```sql -- Create a read-only user CREATE LOGIN mcp_readonly WITH PASSWORD = 'YourSecurePassword123!'; USE Infra; CREATE USER mcp_readonly FOR LOGIN mcp_readonly; -- Grant only SELECT permission GRANT SELECT ON SCHEMA::dbo TO mcp_readonly; -- Or grant SELECT on specific tables only: GRANT SELECT ON dbo.SDS_Teile TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_CA TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_CS TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_LC TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_ME TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_MK TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_MS TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_WT TO mcp_readonly; GRANT SELECT ON dbo.SDS_Teile_EL TO mcp_readonly; -- Explicitly deny write permissions (extra safety) DENY INSERT, UPDATE, DELETE, ALTER ON SCHEMA::dbo TO mcp_readonly; ``` ### 3. Update MCP Configuration After creating the read-only user, update `~/.cursor/mcp.json`: ```json { "mcpServers": { "mssql-mcp-readonly": { "command": "node ", "args": ["/home/sdsadmin/infraviewer/mssql-mcp-readonly.js"], "env": { "MSSQL_SERVER": "192.168.120.88", "MSSQL_PORT": "1433", "MSSQL_USER": "mcp_readonly", "MSSQL_PASSWORD": "YourSecurePassword123!", "MSSQL_DATABASE": "Infra", "MSSQL_ENCRYPT": "true", "MSSQL_TRUST_SERVER_CERTIFICATE": "true" } } } } ``` ## Security Layers | Layer | Protection | Bypassed if... | |-------|-----------|----------------| | **Application Validation** | Keyword blocking | Code has bugs | | **Database Permissions** | SQL Server enforced | Wrong credentials used | | **Both Combined** | Double protection | Nearly impossible ✅ | ## Testing Read-Only Access ### Test 1: Valid SELECT (should work) ```sql SELECT TOP 10 * FROM SDS_Teile ``` ### Test 2: Invalid INSERT (should fail) ```sql INSERT INTO SDS_Teile (Teil) VALUES ('TEST') ``` **Expected:** Error: "Forbidden keyword detected: INSERT" ### Test 3: Query Chaining (should fail) ```sql SELECT 1; DROP TABLE SDS_Teile ``` **Expected:** Error: "Multiple statements not allowed" ### Test 4: Stored Procedure (should fail) ```sql EXEC sp_helpdb ``` **Expected:** Error: "Forbidden keyword detected: EXEC" ## Current vs Recommended Setup ### Current Setup (Application Layer Only) ``` [AI] → [mssql-mcp-readonly.js] → [SQL Server] ↑ Validates queries ↑ Full permissions ``` **Risk:** If validation code has a bug, data could be modified ### Recommended Setup (Defense in Depth) ``` [AI] → [mssql-mcp-readonly.js] → [SQL Server with read-only user] ↑ Validates queries ↑ SELECT-only permissions ``` **Benefit:** Even if validation fails, SQL Server will block write operations ## Monitoring Check MCP server logs for blocked attempts: ```bash # If running as systemd service journalctl -u mcp-readonly -f # If running manually tail -f /var/log/mcp-readonly.log ``` ## Best Practices 1. ✅ Use dedicated read-only database user 2. ✅ Keep validation keywords up to date 3. ✅ Monitor for failed query attempts 4. ✅ Regularly audit database permissions 5. ✅ Use strong passwords for database users 6. ✅ Enable SQL Server audit logging 7. ✅ Limit network access to database server 8. ✅ Keep mssql package updated (`npm update mssql`) ## Additional Hardening ### 1. Add Query Size Limits Prevent resource exhaustion: ```javascript function validateQuery(query) { if (query.length > 10000) { throw new Error("Query too long!"); } // ... rest of validation } ``` ### 2. Add Rate Limiting Prevent abuse: ```javascript const rateLimit = require('express-rate-limit'); // Limit to 100 queries per minute ``` ### 3. Add Query Logging Track all queries: ```javascript console.log(`[${new Date().toISOString()}] Query: ${query.substring(0, 100)}...`); ```