{
"event": "PreToolUse",
"tool_name": "mcp__mcp-docs__docs_get",
"tool_input": {
"path": "\/mcp\/mcp-db\/mcp-db-validators"
}
}
{
"tool_response": [
{
"type": "text",
"text": "{\n \"success\": true,\n \"doc\": {\n \"id\": 43,\n \"parent_id\": 36,\n \"slug\": \"mcp-db-validators\",\n \"path\": \"\/mcp\/mcp-db\/mcp-db-validators\",\n \"title\": \"Validators\",\n \"description\": \"SQL-Validierung nach dem Single Responsibility Principle.\",\n \"content\": \"<nav class=\\\"breadcrumb\\\">\\n <a href=\\\"\/docs\\\">Dokumentation<\/a> » <a href=\\\"\/docs\/mcp\/mcp-db\\\">MCP-DB<\/a> » Validators\\n<\/nav>\\n\\n<h1>MCP-DB Validators<\/h1>\\n<p>SQL-Validierung nach dem Single Responsibility Principle.<\/p>\\n\\n<h2>QueryValidator<\/h2>\\n<pre><code>\\\"\\\"\\\"SRP: Separate Validierungslogik\\\"\\\"\\\"\\nfrom typing import Tuple\\nimport re\\nfrom config import Config\\n\\nclass QueryValidator:\\n \\\"\\\"\\\"Validiert SQL Queries - SRP: Nur Validierung\\\"\\\"\\\"\\n\\n @staticmethod\\n def validate_query(query: str, database: str, max_rows: int) -> Tuple[bool, str]:\\n \\\"\\\"\\\"\\n Validiert eine Query gegen alle Sicherheitsregeln.\\n\\n Returns:\\n (is_valid, error_message)\\n \\\"\\\"\\\"\\n # Basis-Validierung\\n if not query or len(query) < 1:\\n return False, \\\"Query must not be empty\\\"\\n\\n if len(query) > Config.MAX_QUERY_LENGTH:\\n return False, f\\\"Query must be max {Config.MAX_QUERY_LENGTH} chars\\\"\\n\\n # Nur SELECT erlaubt\\n query_upper = query.strip().upper()\\n if not query_upper.startswith(\\\"SELECT\\\"):\\n return False, \\\"Only SELECT queries allowed\\\"\\n\\n # Dangerous Keyword Blocklist\\n for keyword in Config.BLOCKED_KEYWORDS:\\n pattern = r'\\\\b' + re.escape(keyword) + r'\\\\b'\\n if re.search(pattern, query_upper):\\n return False, f\\\"Blocked keyword detected: {keyword}\\\"\\n\\n # Database Allowlist\\n if database not in Config.ALLOWED_DATABASES:\\n return False, f\\\"Database '{database}' not allowed\\\"\\n\\n # Max Rows prüfen\\n if max_rows < 1 or max_rows > Config.MAX_ROWS:\\n return False, f\\\"max_rows must be 1-{Config.MAX_ROWS}\\\"\\n\\n # Table Allowlist (mit information_schema Ausnahme)\\n from_tables = QueryValidator._extract_table_names(query_upper)\\n for table in from_tables:\\n # Erlaube information_schema.TABLES für Schema-Tool\\n if \\\"INFORMATION_SCHEMA\\\" in table:\\n continue\\n # Prüfe gegen Allowlist\\n table_clean = table.split('.')[-1]\\n if table_clean not in [t.upper() for t in Config.ALLOWED_TABLES]:\\n return False, f\\\"Table '{table}' not allowed\\\"\\n\\n return True, \\\"\\\"\\n\\n @staticmethod\\n def _extract_table_names(query_upper: str) -> list:\\n \\\"\\\"\\\"Extrahiert Tabellennamen aus FROM und JOIN Clauses\\\"\\\"\\\"\\n tables = []\\n from_match = re.findall(r'\\\\bFROM\\\\s+([a-zA-Z0-9_\\\\.]+)', query_upper)\\n join_match = re.findall(r'\\\\bJOIN\\\\s+([a-zA-Z0-9_\\\\.]+)', query_upper)\\n tables.extend(from_match)\\n tables.extend(join_match)\\n return tables<\/code><\/pre>\\n\\n<h2>Validierungsschritte<\/h2>\\n<table>\\n <tr><th>#<\/th><th>Prüfung<\/th><th>Fehler bei<\/th><\/tr>\\n <tr><td>1<\/td><td>Query nicht leer<\/td><td>Leere Query<\/td><\/tr>\\n <tr><td>2<\/td><td>Query-Länge<\/td><td>> 2000 Zeichen<\/td><\/tr>\\n <tr><td>3<\/td><td>SELECT-Only<\/td><td>Nicht mit SELECT beginnend<\/td><\/tr>\\n <tr><td>4<\/td><td>Keyword-Blocklist<\/td><td>Blockiertes Keyword gefunden<\/td><\/tr>\\n <tr><td>5<\/td><td>Database-Allowlist<\/td><td>Datenbank nicht erlaubt<\/td><\/tr>\\n <tr><td>6<\/td><td>Max-Rows<\/td><td>< 1 oder > 100<\/td><\/tr>\\n <tr><td>7<\/td><td>Table-Allowlist<\/td><td>Tabelle nicht erlaubt<\/td><\/tr>\\n<\/table>\\n\\n<h2>Keyword-Erkennung<\/h2>\\n<p>Keywords werden mit Word Boundaries erkannt:<\/p>\\n<pre><code># Regex-Pattern für Keyword \\\"DROP\\\"\\npattern = r'\\\\bDROP\\\\b'\\n\\n# Blockiert:\\n\\\"SELECT * FROM t; DROP TABLE t\\\" # DROP erkannt\\n\\n# Nicht blockiert (kein vollständiges Wort):\\n\\\"SELECT dropdown FROM t\\\" # DROP nicht erkannt<\/code><\/pre>\\n\\n<h2>Tabellen-Extraktion<\/h2>\\n<p>Tabellennamen werden aus FROM und JOIN Clauses extrahiert:<\/p>\\n<pre><code># Query\\n\\\"SELECT * FROM mcp_log JOIN ki_tags ON ...\\\"\\n\\n# Extrahierte Tabellen\\n[\\\"MCP_LOG\\\", \\\"KI_TAGS\\\"]\\n\\n# Prüfung gegen Allowlist\\nmcp_log ✓ (in ALLOWED_TABLES)\\nki_tags ✓ (in ALLOWED_TABLES)<\/code><\/pre>\\n\\n<h2>Bekannte Limits<\/h2>\\n<table>\\n <tr><th>Limit<\/th><th>Beschreibung<\/th><th>Mitigation<\/th><\/tr>\\n <tr>\\n <td>Regex-basiert<\/td>\\n <td>Kein vollständiger SQL-Parser<\/td>\\n <td>Konservative Extraktion, nur FROM\/JOIN<\/td>\\n <\/tr>\\n <tr>\\n <td>Subqueries<\/td>\\n <td>Tabellen in Subqueries werden erkannt<\/td>\\n <td>Regex erfasst alle FROM\/JOIN<\/td>\\n <\/tr>\\n <tr>\\n <td>Aliase<\/td>\\n <td>Aliase werden nicht aufgelöst<\/td>\\n <td>Tabellenname vor Alias wird erkannt<\/td>\\n <\/tr>\\n<\/table>\\n\\n<h2>Testfälle<\/h2>\\n<pre><code># Muss DENIED returnen:\\n\\\"SELECT * FROM mcp_log; DROP TABLE mcp_log\\\" # DROP\\n\\\"SELECT LOAD_FILE('\/etc\/passwd')\\\" # LOAD_FILE\\n\\\"SELECT * FROM users\\\" # users nicht erlaubt\\n\\\"SELECT SLEEP(10)\\\" # SLEEP\\n\\\"INSERT INTO mcp_log VALUES (...)\\\" # Kein SELECT\\n\\n# Muss SUCCESS returnen:\\n\\\"SELECT * FROM mcp_log\\\" # Erlaubt\\n\\\"SELECT * FROM mcp_log WHERE status = %s\\\" # Mit Params\\n\\\"SELECT dropdown FROM ki_tags\\\" # dropdown != DROP<\/code><\/pre>\\n\\n<h2>Verwandte Kapitel<\/h2>\\n<ul>\\n <li><a href=\\\"\/docs\/mcp\/mcp-db-config\\\">Konfiguration<\/a> - Allowlists und Blocklists<\/li>\\n <li><a href=\\\"\/docs\/mcp\/mcp-db-security\\\">Sicherheit<\/a> - Gesamtkonzept<\/li>\\n<\/ul>\",\n \"status\": \"published\",\n \"sort_order\": 7,\n \"depth\": 2,\n \"created_at\": \"2025-12-20T11:13:58\",\n \"updated_at\": \"2025-12-20T11:13:58\"\n }\n}"
}
]
}