Protokoll #29060

ID29060
Zeitstempel2025-12-28 23:33:44.064181
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens2,438 (Input: 35, Output: 2,403)
Dauer77 ms
Request-Zeit2025-12-28 23:33:44.064181
Response-Zeit2025-12-28 23:33:44.140981

Request

{
    "event": "PreToolUse",
    "tool_name": "Read",
    "tool_input": {
        "file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_failsafe.py"
    }
}

Response

{
    "tool_response": {
        "type": "text",
        "file": {
            "filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/rules_failsafe.py",
            "content": "#!\/usr\/bin\/env python3\n\"\"\"\nPost-Hook Fail Safe Regeln (WARN) - Sichere Fehlerbehandlung.\n\nW9.x Regeln: Warnt bei fehlendem Error-Recovery und Ressourcen-Cleanup.\n\nPrinzip: \"Wenn etwas fehlschlägt, soll es sicher fehlschlagen.\"\n\nFokus auf:\n- Ressourcen-Cleanup (file handles, connections)\n- Exception-Handler Vollständigkeit\n- Graceful Degradation\n\"\"\"\n\nimport re\nfrom typing import List\nfrom .rule_base import Rule\n\n\n# =============================================================================\n# W9: FAIL SAFE\n# =============================================================================\n\nclass W9_1_TryWithoutFinally(Rule):\n    \"\"\"W9.1: try-Block mit Ressourcen-Handles ohne finally.\"\"\"\n\n    # Ressourcen-Patterns die Cleanup benötigen\n    RESOURCE_PATTERNS = [\n        r\"fopen\\s*\\(\",\n        r\"fsockopen\\s*\\(\",\n        r\"popen\\s*\\(\",\n        r\"curl_init\\s*\\(\",\n        r\"new\\s+\\w*Connection\",\n        r\"new\\s+PDO\\s*\\(\",\n        r\"mysqli_connect\\s*\\(\",\n        r\"stream_socket_client\\s*\\(\",\n    ]\n\n    def __init__(self):\n        super().__init__(allowlist=[\n            \"\/Infrastructure\/\",  # Infrastructure darf Ressourcen verwalten\n            \"\/Framework\/\",\n        ])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Finde try-Blöcke\n        try_blocks = re.findall(\n            r\"try\\s*\\{([^{}]*(?:\\{[^{}]*\\}[^{}]*)*)\\}\\s*catch\",\n            content,\n            re.DOTALL\n        )\n\n        for block in try_blocks:\n            # Prüfe ob Ressourcen geöffnet werden\n            has_resource = any(\n                re.search(pattern, block)\n                for pattern in self.RESOURCE_PATTERNS\n            )\n\n            if has_resource:\n                # Prüfe ob finally vorhanden ist\n                # Suche nach dem vollständigen try-catch-finally\n                block_start = content.find(block)\n                remaining = content[block_start + len(block):]\n\n                # Prüfe ob nach catch ein finally kommt\n                has_finally = bool(re.search(\n                    r\"catch\\s*\\([^)]+\\)\\s*\\{[^}]*\\}\\s*finally\\s*\\{\",\n                    remaining[:500]  # Schaue nur nahe nach\n                ))\n\n                if not has_finally:\n                    warnings.append(\n                        \"W9.1: try-block opens resources without finally. \"\n                        \"Use try-finally for guaranteed cleanup.\"\n                    )\n                    break  # Eine Warnung pro Datei reicht\n\n        return warnings\n\n\nclass W9_2_MissingExceptionHandler(Rule):\n    \"\"\"W9.2: Klasse mit Datenbank-Ops ohne Exception-Handling.\"\"\"\n\n    DB_OPERATIONS = [\n        r\"->execute\\s*\\(\",\n        r\"->query\\s*\\(\",\n        r\"->prepare\\s*\\(\",\n        r\"->fetch\",\n    ]\n\n    def __init__(self):\n        super().__init__(allowlist=[\n            \"\/Infrastructure\/Persistence\/\",  # Repos dürfen Exceptions werfen\n            \"\/Framework\/\",\n        ])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Hat die Datei DB-Operationen?\n        has_db_ops = any(\n            re.search(pattern, content)\n            for pattern in self.DB_OPERATIONS\n        )\n\n        if not has_db_ops:\n            return warnings\n\n        # Prüfe ob es einen try-catch gibt\n        has_try_catch = bool(re.search(r\"try\\s*\\{\", content))\n\n        # Prüfe ob die Klasse throws deklariert\n        has_throws = bool(re.search(r\"@throws\\s+\\w*Exception\", content))\n\n        if not has_try_catch and not has_throws:\n            warnings.append(\n                \"W9.2: Database operations without exception handling. \"\n                \"Use try-catch or declare @throws for proper error propagation.\"\n            )\n\n        return warnings\n\n\nclass W9_3_CatchWithoutLogging(Rule):\n    \"\"\"W9.3: catch-Block ohne Logging (anders als W8.3 - hier geht es um Logging).\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[\n            \"\/tests\/\",\n            \"\/Test\/\",\n        ])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Finde catch-Blöcke mit Inhalt (nicht leer - das ist W8.1)\n        catch_blocks = re.findall(\n            r\"catch\\s*\\(\\s*\\\\?(?:\\w+\\\\)*(\\w+)\\s+\\$\\w+\\s*\\)\\s*\\{([^{}]+)\\}\",\n            content,\n            re.DOTALL\n        )\n\n        for exception_type, block in catch_blocks:\n            # Skip wenn Block fast leer ist (W8.1 kümmert sich darum)\n            if len(block.strip()) < 10:\n                continue\n\n            # Prüfe ob Logging vorhanden\n            has_logging = bool(re.search(\n                r\"->log\\(|->error\\(|->warning\\(|error_log\\(|Logger::|log\\(\",\n                block\n            ))\n\n            # Prüfe ob Exception weitergereicht wird\n            has_rethrow = bool(re.search(r\"throw\\s+\", block))\n\n            if not has_logging and not has_rethrow:\n                warnings.append(\n                    f\"W9.3: catch({exception_type}) without logging or rethrow. \"\n                    \"Errors should be logged for debugging.\"\n                )\n                break  # Eine Warnung pro Datei\n\n        return warnings\n\n\nclass W9_4_DestructorWithResources(Rule):\n    \"\"\"W9.4: __destruct ohne Ressourcen-Cleanup obwohl Properties vorhanden.\"\"\"\n\n    RESOURCE_PROPERTIES = [\n        r\"private\\s+\\?\\?\\w*\\s*\\$connection\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$handle\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$socket\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$stream\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$resource\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$pdo\",\n        r\"private\\s+\\?\\?\\w*\\s*\\$mysqli\",\n    ]\n\n    def __init__(self):\n        super().__init__(allowlist=[])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Hat die Klasse Ressourcen-Properties?\n        has_resource_props = any(\n            re.search(pattern, content, re.IGNORECASE)\n            for pattern in self.RESOURCE_PROPERTIES\n        )\n\n        if not has_resource_props:\n            return warnings\n\n        # Hat die Klasse einen __destruct?\n        has_destructor = bool(re.search(\n            r\"(?:public|private|protected)\\s+function\\s+__destruct\\s*\\(\",\n            content\n        ))\n\n        if not has_destructor:\n            warnings.append(\n                \"W9.4: Class has resource properties but no __destruct(). \"\n                \"Consider adding destructor for cleanup.\"\n            )\n\n        return warnings\n\n\nclass W9_5_ExitWithoutCleanup(Rule):\n    \"\"\"W9.5: exit\/die ohne vorherigen Cleanup-Code.\"\"\"\n\n    def __init__(self):\n        super().__init__(allowlist=[\n            \"\/bin\/\",           # CLI-Scripts dürfen exit nutzen\n            \"\/scripts\/\",\n            \"\/public\/index.php\",\n        ])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        # Finde exit\/die Aufrufe\n        exit_calls = re.findall(\n            r\"(?:exit|die)\\s*\\([^)]*\\)\",\n            content\n        )\n\n        if len(exit_calls) > 0:\n            # Prüfe ob register_shutdown_function vorhanden\n            has_shutdown = bool(re.search(\n                r\"register_shutdown_function\\s*\\(\",\n                content\n            ))\n\n            if not has_shutdown:\n                warnings.append(\n                    f\"W9.5: exit\/die used ({len(exit_calls)}x) without shutdown handler. \"\n                    \"Consider using register_shutdown_function for cleanup.\"\n                )\n\n        return warnings\n\n\nclass W9_6_ConnectionWithoutClose(Rule):\n    \"\"\"W9.6: Connection öffnen ohne schließen in derselben Methode.\"\"\"\n\n    CONNECTION_OPEN = [\n        (r\"fopen\\s*\\(\", r\"fclose\\s*\\(\"),\n        (r\"curl_init\\s*\\(\", r\"curl_close\\s*\\(\"),\n        (r\"popen\\s*\\(\", r\"pclose\\s*\\(\"),\n    ]\n\n    def __init__(self):\n        super().__init__(allowlist=[\n            \"\/Infrastructure\/\",  # Infrastructure verwaltet Connections\n        ])\n\n    def check(self, file_path: str, content: str) -> List[str]:\n        warnings = []\n\n        for open_pattern, close_pattern in self.CONNECTION_OPEN:\n            has_open = bool(re.search(open_pattern, content))\n            has_close = bool(re.search(close_pattern, content))\n\n            if has_open and not has_close:\n                resource_type = open_pattern.split(r\"\\s\")[0].replace(\"\\\\\", \"\")\n                warnings.append(\n                    f\"W9.6: {resource_type} without matching close. \"\n                    \"Ensure resources are properly closed.\"\n                )\n\n        return warnings\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n    W9_1_TryWithoutFinally(),\n    W9_2_MissingExceptionHandler(),\n    W9_3_CatchWithoutLogging(),\n    W9_4_DestructorWithResources(),\n    W9_5_ExitWithoutCleanup(),\n    W9_6_ConnectionWithoutClose(),\n]\n",
            "numLines": 291,
            "startLine": 1,
            "totalLines": 291
        }
    }
}
← Vorheriger Zur Liste Nächster →