#!/usr/bin/env python3
"""
DSS Security Check Hook
Validates file edits for common security vulnerabilities.
Written from scratch for DSS - no external dependencies.
"""

import json
import os
import sys
from datetime import datetime
from pathlib import Path

# Security patterns to detect
SECURITY_PATTERNS = [
    {
        "id": "xss-innerhtml",
        "patterns": [".innerHTML =", ".innerHTML=", "innerHTML:"],
        "severity": "high",
        "message": "Potential XSS: innerHTML assignment detected. Use textContent for plain text or sanitize HTML with DOMPurify.",
        "file_types": [".js", ".jsx", ".ts", ".tsx"]
    },
    {
        "id": "xss-dangerously",
        "patterns": ["dangerouslySetInnerHTML"],
        "severity": "high",
        "message": "Potential XSS: dangerouslySetInnerHTML detected. Ensure content is sanitized before rendering.",
        "file_types": [".js", ".jsx", ".ts", ".tsx"]
    },
    {
        "id": "eval-usage",
        "patterns": ["eval(", "new Function("],
        "severity": "critical",
        "message": "Code injection risk: eval() or new Function() detected. These can execute arbitrary code.",
        "file_types": [".js", ".jsx", ".ts", ".tsx"]
    },
    {
        "id": "document-write",
        "patterns": ["document.write("],
        "severity": "medium",
        "message": "Deprecated: document.write() detected. Use DOM manipulation methods instead.",
        "file_types": [".js", ".jsx", ".ts", ".tsx", ".html"]
    },
    {
        "id": "sql-injection",
        "patterns": ["execute(f\"", "execute(f'", "cursor.execute(\"", ".query(`${"],
        "severity": "critical",
        "message": "Potential SQL injection: String interpolation in SQL query. Use parameterized queries.",
        "file_types": [".py", ".js", ".ts"]
    },
    {
        "id": "hardcoded-secret",
        "patterns": ["password=", "api_key=", "secret=", "token=", "apiKey:"],
        "severity": "high",
        "message": "Potential hardcoded secret detected. Use environment variables instead.",
        "file_types": [".py", ".js", ".ts", ".jsx", ".tsx"]
    },
    {
        "id": "python-pickle",
        "patterns": ["pickle.load", "pickle.loads"],
        "severity": "high",
        "message": "Insecure deserialization: pickle can execute arbitrary code. Use JSON for untrusted data.",
        "file_types": [".py"]
    },
    {
        "id": "python-shell",
        "patterns": ["os.system(", "subprocess.call(shell=True", "subprocess.run(shell=True"],
        "severity": "high",
        "message": "Shell injection risk: Use subprocess with shell=False and pass args as list.",
        "file_types": [".py"]
    },
    {
        "id": "react-ref-current",
        "patterns": ["ref.current.innerHTML"],
        "severity": "high",
        "message": "XSS via React ref: Avoid setting innerHTML on refs. Use state/props instead.",
        "file_types": [".jsx", ".tsx"]
    },
    {
        "id": "unsafe-regex",
        "patterns": ["new RegExp(", "RegExp("],
        "severity": "medium",
        "message": "Potential ReDoS: Dynamic regex from user input can cause denial of service.",
        "file_types": [".js", ".ts", ".jsx", ".tsx"]
    }
]

def get_config():
    """Load hook configuration."""
    config_path = Path.home() / ".dss" / "hooks-config.json"
    default_config = {
        "security_check": {
            "enabled": True,
            "block_on_critical": False,
            "warn_only": True,
            "ignored_patterns": []
        }
    }
    
    if config_path.exists():
        try:
            with open(config_path) as f:
                user_config = json.load(f)
                return {**default_config, **user_config}
        except:
            pass
    return default_config

def check_content(content: str, file_path: str) -> list:
    """Check content for security patterns."""
    issues = []
    file_ext = Path(file_path).suffix.lower()
    
    for pattern_def in SECURITY_PATTERNS:
        # Skip if file type doesn't match
        if file_ext not in pattern_def.get("file_types", []):
            continue
            
        for pattern in pattern_def["patterns"]:
            if pattern.lower() in content.lower():
                issues.append({
                    "id": pattern_def["id"],
                    "severity": pattern_def["severity"],
                    "message": pattern_def["message"],
                    "pattern": pattern
                })
                break  # One match per pattern definition is enough
    
    return issues

def format_output(issues: list, file_path: str) -> str:
    """Format issues for display."""
    if not issues:
        return ""
    
    severity_icons = {
        "critical": "[CRITICAL]",
        "high": "[HIGH]",
        "medium": "[MEDIUM]",
        "low": "[LOW]"
    }
    
    lines = [f"\n=== DSS Security Check: {file_path} ===\n"]
    
    for issue in issues:
        icon = severity_icons.get(issue["severity"], "[?]")
        lines.append(f"{icon} {issue['message']}")
        lines.append(f"    Pattern: {issue['pattern']}\n")
    
    lines.append("=" * 50)
    return "\n".join(lines)

def main():
    """Main hook entry point."""
    config = get_config()
    
    if not config.get("security_check", {}).get("enabled", True):
        sys.exit(0)
    
    # Read hook input from stdin
    try:
        input_data = json.loads(sys.stdin.read())
    except json.JSONDecodeError:
        sys.exit(0)  # Allow tool to proceed if we can't parse
    
    tool_name = input_data.get("tool_name", "")
    tool_input = input_data.get("tool_input", {})
    
    # Only check Edit and Write tools
    if tool_name not in ["Edit", "Write"]:
        sys.exit(0)
    
    file_path = tool_input.get("file_path", "")
    
    # Get content to check
    if tool_name == "Write":
        content = tool_input.get("content", "")
    elif tool_name == "Edit":
        content = tool_input.get("new_string", "")
    else:
        content = ""
    
    if not content or not file_path:
        sys.exit(0)
    
    # Check for security issues
    issues = check_content(content, file_path)
    
    if issues:
        output = format_output(issues, file_path)
        print(output, file=sys.stderr)
        
        # Check if we should block on critical issues
        has_critical = any(i["severity"] == "critical" for i in issues)
        if has_critical and config.get("security_check", {}).get("block_on_critical", False):
            sys.exit(2)  # Block the tool
    
    sys.exit(0)  # Allow tool to proceed

if __name__ == "__main__":
    main()
