{
"event": "PreToolUse",
"tool_name": "Edit",
"tool_input": {
"file_path": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_htmx.py",
"old_string": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook HTMX Regeln (BLOCK) - Contract #14: htmx-patterns.\n\nKritische Regeln aus dem HTMX-Contract:\n- HTMX-C1: CSRF auf hx-post\n- HTMX-C2: CSRF auf hx-delete\n- HTMX-C3: CSRF auf hx-patch\n- HTMX-C4: Confirm auf hx-delete\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# VIEW FILE CHECK\n# =============================================================================\n\ndef is_view_file(file_path: str) -> bool:\n \"\"\"Prüft ob es sich um eine View-Datei handelt.\"\"\"\n return \"\/View\/\" in file_path and file_path.endswith(\".php\")\n\n\n# =============================================================================\n# HTMX CONTRACT RULES (Critical)\n# =============================================================================\n\ndef htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n # Finde alle hx-post Attribute\n hx_post_pattern = r'hx-post\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_posts = list(re.finditer(hx_post_pattern, content))\n\n if not hx_posts:\n return None\n\n # Prüfe jedes hx-post auf CSRF\n for match in hx_posts:\n # Suche im umgebenden Kontext (100 Zeichen vor und nach)\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n # Muss hx-headers mit X-CSRF-TOKEN haben\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C1\",\n f\"hx-post at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_delete_pattern = r'hx-delete\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_deletes = list(re.finditer(hx_delete_pattern, content))\n\n if not hx_deletes:\n return None\n\n for match in hx_deletes:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C2\",\n f\"hx-delete at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_patch_pattern = r'hx-patch\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_patches = list(re.finditer(hx_patch_pattern, content))\n\n if not hx_patches:\n return None\n\n for match in hx_patches:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C3\",\n f\"hx-patch at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None",
"new_string": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook HTMX Regeln (BLOCK) - Contract #14: htmx-patterns.\n\nKritische Regeln aus dem HTMX-Contract:\n- HTMX-C1: CSRF auf hx-post\n- HTMX-C2: CSRF auf hx-delete\n- HTMX-C3: CSRF auf hx-patch\n- HTMX-C4: Confirm auf hx-delete\n- HTMX-C5: CSRF auf hx-put\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# VIEW FILE CHECK\n# =============================================================================\n\ndef is_view_file(file_path: str) -> bool:\n \"\"\"Prüft ob es sich um eine View-Datei handelt.\"\"\"\n return \"\/View\/\" in file_path and file_path.endswith(\".php\")\n\n\n# =============================================================================\n# HELPER: Generic CSRF Check\n# =============================================================================\n\ndef _check_csrf_on_method(\n file_path: str,\n content: str,\n method: str,\n rule_id: str\n) -> Optional[dict]:\n \"\"\"Generische CSRF-Prüfung für hx-{method} Attribute.\"\"\"\n if not is_view_file(file_path):\n return None\n\n pattern = rf'hx-{method}\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n matches = list(re.finditer(pattern, content))\n\n if not matches:\n return None\n\n for match in matches:\n # Suche im umgebenden Kontext (150 Zeichen vor und nach)\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n # Muss hx-headers mit X-CSRF-TOKEN haben\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n rule_id,\n f\"hx-{method} at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\n# =============================================================================\n# HTMX CONTRACT RULES (Critical)\n# =============================================================================\n\ndef htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"post\", \"HTMX-C1\")\n\n\ndef htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"delete\", \"HTMX-C2\")\n\n\ndef htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"patch\", \"HTMX-C3\")\n\n\ndef htmx_c5_csrf_on_put(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C5: hx-put MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"put\", \"HTMX-C5\")"
}
}
{
"tool_response": {
"filePath": "\/var\/www\/tools\/ki-protokoll\/claude-hook\/quality\/pre_rules_htmx.py",
"oldString": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook HTMX Regeln (BLOCK) - Contract #14: htmx-patterns.\n\nKritische Regeln aus dem HTMX-Contract:\n- HTMX-C1: CSRF auf hx-post\n- HTMX-C2: CSRF auf hx-delete\n- HTMX-C3: CSRF auf hx-patch\n- HTMX-C4: Confirm auf hx-delete\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# VIEW FILE CHECK\n# =============================================================================\n\ndef is_view_file(file_path: str) -> bool:\n \"\"\"Prüft ob es sich um eine View-Datei handelt.\"\"\"\n return \"\/View\/\" in file_path and file_path.endswith(\".php\")\n\n\n# =============================================================================\n# HTMX CONTRACT RULES (Critical)\n# =============================================================================\n\ndef htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n # Finde alle hx-post Attribute\n hx_post_pattern = r'hx-post\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_posts = list(re.finditer(hx_post_pattern, content))\n\n if not hx_posts:\n return None\n\n # Prüfe jedes hx-post auf CSRF\n for match in hx_posts:\n # Suche im umgebenden Kontext (100 Zeichen vor und nach)\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n # Muss hx-headers mit X-CSRF-TOKEN haben\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C1\",\n f\"hx-post at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_delete_pattern = r'hx-delete\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_deletes = list(re.finditer(hx_delete_pattern, content))\n\n if not hx_deletes:\n return None\n\n for match in hx_deletes:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C2\",\n f\"hx-delete at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_patch_pattern = r'hx-patch\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_patches = list(re.finditer(hx_patch_pattern, content))\n\n if not hx_patches:\n return None\n\n for match in hx_patches:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C3\",\n f\"hx-patch at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None",
"newString": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook HTMX Regeln (BLOCK) - Contract #14: htmx-patterns.\n\nKritische Regeln aus dem HTMX-Contract:\n- HTMX-C1: CSRF auf hx-post\n- HTMX-C2: CSRF auf hx-delete\n- HTMX-C3: CSRF auf hx-patch\n- HTMX-C4: Confirm auf hx-delete\n- HTMX-C5: CSRF auf hx-put\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# VIEW FILE CHECK\n# =============================================================================\n\ndef is_view_file(file_path: str) -> bool:\n \"\"\"Prüft ob es sich um eine View-Datei handelt.\"\"\"\n return \"\/View\/\" in file_path and file_path.endswith(\".php\")\n\n\n# =============================================================================\n# HELPER: Generic CSRF Check\n# =============================================================================\n\ndef _check_csrf_on_method(\n file_path: str,\n content: str,\n method: str,\n rule_id: str\n) -> Optional[dict]:\n \"\"\"Generische CSRF-Prüfung für hx-{method} Attribute.\"\"\"\n if not is_view_file(file_path):\n return None\n\n pattern = rf'hx-{method}\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n matches = list(re.finditer(pattern, content))\n\n if not matches:\n return None\n\n for match in matches:\n # Suche im umgebenden Kontext (150 Zeichen vor und nach)\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n # Muss hx-headers mit X-CSRF-TOKEN haben\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n rule_id,\n f\"hx-{method} at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\n# =============================================================================\n# HTMX CONTRACT RULES (Critical)\n# =============================================================================\n\ndef htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"post\", \"HTMX-C1\")\n\n\ndef htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"delete\", \"HTMX-C2\")\n\n\ndef htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"patch\", \"HTMX-C3\")\n\n\ndef htmx_c5_csrf_on_put(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C5: hx-put MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n return _check_csrf_on_method(file_path, content, \"put\", \"HTMX-C5\")",
"originalFile": "#!\/usr\/bin\/env python3\n\"\"\"\nPre-Hook HTMX Regeln (BLOCK) - Contract #14: htmx-patterns.\n\nKritische Regeln aus dem HTMX-Contract:\n- HTMX-C1: CSRF auf hx-post\n- HTMX-C2: CSRF auf hx-delete\n- HTMX-C3: CSRF auf hx-patch\n- HTMX-C4: Confirm auf hx-delete\n\"\"\"\n\nimport re\nfrom typing import Optional\nfrom .rule_base import block\n\n\n# =============================================================================\n# VIEW FILE CHECK\n# =============================================================================\n\ndef is_view_file(file_path: str) -> bool:\n \"\"\"Prüft ob es sich um eine View-Datei handelt.\"\"\"\n return \"\/View\/\" in file_path and file_path.endswith(\".php\")\n\n\n# =============================================================================\n# HTMX CONTRACT RULES (Critical)\n# =============================================================================\n\ndef htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n # Finde alle hx-post Attribute\n hx_post_pattern = r'hx-post\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_posts = list(re.finditer(hx_post_pattern, content))\n\n if not hx_posts:\n return None\n\n # Prüfe jedes hx-post auf CSRF\n for match in hx_posts:\n # Suche im umgebenden Kontext (100 Zeichen vor und nach)\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n # Muss hx-headers mit X-CSRF-TOKEN haben\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C1\",\n f\"hx-post at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_delete_pattern = r'hx-delete\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_deletes = list(re.finditer(hx_delete_pattern, content))\n\n if not hx_deletes:\n return None\n\n for match in hx_deletes:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C2\",\n f\"hx-delete at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_patch_pattern = r'hx-patch\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_patches = list(re.finditer(hx_patch_pattern, content))\n\n if not hx_patches:\n return None\n\n for match in hx_patches:\n start = max(0, match.start() - 150)\n end = min(len(content), match.end() + 150)\n context = content[start:end]\n\n if 'hx-headers' not in context or 'CSRF' not in context.upper():\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C3\",\n f\"hx-patch at line {line_num} missing CSRF token. \"\n \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"\n )\n\n return None\n\n\ndef htmx_c4_delete_requires_confirm(file_path: str, content: str) -> Optional[dict]:\n \"\"\"HTMX-C4: hx-delete MUSS hx-confirm Attribut haben.\"\"\"\n if not is_view_file(file_path):\n return None\n\n hx_delete_pattern = r'hx-delete\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'\n hx_deletes = list(re.finditer(hx_delete_pattern, content))\n\n if not hx_deletes:\n return None\n\n for match in hx_deletes:\n # Suche im Element-Kontext (vom < bis zum nächsten >)\n # Gehe zurück bis zum < und vorwärts bis zum >\n element_start = content.rfind('<', 0, match.start())\n element_end = content.find('>', match.end())\n\n if element_start == -1 or element_end == -1:\n continue\n\n element = content[element_start:element_end + 1]\n\n if 'hx-confirm' not in element:\n line_num = content[:match.start()].count('\\n') + 1\n return block(\n \"HTMX-C4\",\n f\"hx-delete at line {line_num} missing confirmation. \"\n \"Add: hx-confirm=\\\"Wirklich loeschen?\\\"\"\n )\n\n return None\n\n\n# =============================================================================\n# RULE COLLECTION\n# =============================================================================\n\nRULES = [\n htmx_c1_csrf_on_post,\n htmx_c2_csrf_on_delete,\n htmx_c3_csrf_on_patch,\n htmx_c4_delete_requires_confirm,\n]\n",
"structuredPatch": [
{
"oldStart": 7,
"oldLines": 6,
"newStart": 7,
"newLines": 7,
"lines": [
" - HTMX-C2: CSRF auf hx-delete",
" - HTMX-C3: CSRF auf hx-patch",
" - HTMX-C4: Confirm auf hx-delete",
"+- HTMX-C5: CSRF auf hx-put",
" \"\"\"",
" ",
" import re"
]
},
{
"oldStart": 24,
"oldLines": 24,
"newStart": 25,
"newLines": 27,
"lines": [
" ",
" ",
" # =============================================================================",
"-# HTMX CONTRACT RULES (Critical)",
"+# HELPER: Generic CSRF Check",
" # =============================================================================",
" ",
"-def htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:",
"- \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"+def _check_csrf_on_method(",
"+ file_path: str,",
"+ content: str,",
"+ method: str,",
"+ rule_id: str",
"+) -> Optional[dict]:",
"+ \"\"\"Generische CSRF-Prüfung für hx-{method} Attribute.\"\"\"",
" if not is_view_file(file_path):",
" return None",
" ",
"- # Finde alle hx-post Attribute",
"- hx_post_pattern = r'hx-post\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'",
"- hx_posts = list(re.finditer(hx_post_pattern, content))",
"+ pattern = rf'hx-{method}\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'",
"+ matches = list(re.finditer(pattern, content))",
" ",
"- if not hx_posts:",
"+ if not matches:",
" return None",
" ",
"- # Prüfe jedes hx-post auf CSRF",
"- for match in hx_posts:",
"- # Suche im umgebenden Kontext (100 Zeichen vor und nach)",
"+ for match in matches:",
"+ # Suche im umgebenden Kontext (150 Zeichen vor und nach)",
" start = max(0, match.start() - 150)",
" end = min(len(content), match.end() + 150)",
" context = content[start:end]"
]
},
{
"oldStart": 50,
"oldLines": 68,
"newStart": 54,
"newLines": 38,
"lines": [
" if 'hx-headers' not in context or 'CSRF' not in context.upper():",
" line_num = content[:match.start()].count('\\n') + 1",
" return block(",
"- \"HTMX-C1\",",
"- f\"hx-post at line {line_num} missing CSRF token. \"",
"+ rule_id,",
"+ f\"hx-{method} at line {line_num} missing CSRF token. \"",
" \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"",
" )",
" ",
" return None",
" ",
" ",
"-def htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:",
"- \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"- if not is_view_file(file_path):",
"- return None",
"+# =============================================================================",
"+# HTMX CONTRACT RULES (Critical)",
"+# =============================================================================",
" ",
"- hx_delete_pattern = r'hx-delete\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'",
"- hx_deletes = list(re.finditer(hx_delete_pattern, content))",
"+def htmx_c1_csrf_on_post(file_path: str, content: str) -> Optional[dict]:",
"+ \"\"\"HTMX-C1: hx-post MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"+ return _check_csrf_on_method(file_path, content, \"post\", \"HTMX-C1\")",
" ",
"- if not hx_deletes:",
"- return None",
" ",
"- for match in hx_deletes:",
"- start = max(0, match.start() - 150)",
"- end = min(len(content), match.end() + 150)",
"- context = content[start:end]",
"+def htmx_c2_csrf_on_delete(file_path: str, content: str) -> Optional[dict]:",
"+ \"\"\"HTMX-C2: hx-delete MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"+ return _check_csrf_on_method(file_path, content, \"delete\", \"HTMX-C2\")",
" ",
"- if 'hx-headers' not in context or 'CSRF' not in context.upper():",
"- line_num = content[:match.start()].count('\\n') + 1",
"- return block(",
"- \"HTMX-C2\",",
"- f\"hx-delete at line {line_num} missing CSRF token. \"",
"- \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"",
"- )",
" ",
"- return None",
"-",
"-",
" def htmx_c3_csrf_on_patch(file_path: str, content: str) -> Optional[dict]:",
" \"\"\"HTMX-C3: hx-patch MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"- if not is_view_file(file_path):",
"- return None",
"+ return _check_csrf_on_method(file_path, content, \"patch\", \"HTMX-C3\")",
" ",
"- hx_patch_pattern = r'hx-patch\\s*=\\s*[\"\\'][^\"\\']+[\"\\']'",
"- hx_patches = list(re.finditer(hx_patch_pattern, content))",
" ",
"- if not hx_patches:",
"- return None",
"+def htmx_c5_csrf_on_put(file_path: str, content: str) -> Optional[dict]:",
"+ \"\"\"HTMX-C5: hx-put MUSS X-CSRF-TOKEN in hx-headers haben.\"\"\"",
"+ return _check_csrf_on_method(file_path, content, \"put\", \"HTMX-C5\")",
" ",
"- for match in hx_patches:",
"- start = max(0, match.start() - 150)",
"- end = min(len(content), match.end() + 150)",
"- context = content[start:end]",
" ",
"- if 'hx-headers' not in context or 'CSRF' not in context.upper():",
"- line_num = content[:match.start()].count('\\n') + 1",
"- return block(",
"- \"HTMX-C3\",",
"- f\"hx-patch at line {line_num} missing CSRF token. \"",
"- \"Add: hx-headers='{\\\"X-CSRF-TOKEN\\\": \\\"<?= $csrfToken ?>\\\"}'\"",
"- )",
"-",
"- return None",
"-",
"-",
" def htmx_c4_delete_requires_confirm(file_path: str, content: str) -> Optional[dict]:",
" \"\"\"HTMX-C4: hx-delete MUSS hx-confirm Attribut haben.\"\"\"",
" if not is_view_file(file_path):"
]
}
],
"userModified": false,
"replaceAll": false
}
}