Spaces:
Sleeping
Sleeping
| """ | |
| Input Validation and Security Module | |
| Handles prompt injection detection and input validation | |
| """ | |
| import re | |
| from typing import Tuple | |
| from config import HARMFUL_KEYWORDS, MAX_INPUT_LENGTH | |
| class InputValidator: | |
| """Validates user inputs for security and quality.""" | |
| def validate_input(text: str) -> Tuple[bool, str]: | |
| """ | |
| Validate user input for security and quality. | |
| Args: | |
| text: Input text to validate | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| # Check if empty | |
| if not text or not text.strip(): | |
| return False, "Input cannot be empty" | |
| # Check length | |
| if len(text) > MAX_INPUT_LENGTH: | |
| return False, f"Input too long (max {MAX_INPUT_LENGTH} characters)" | |
| # Check for prompt injection | |
| is_safe, msg = InputValidator.detect_prompt_injection(text) | |
| if not is_safe: | |
| return False, msg | |
| # Check for minimum meaningful content | |
| if len(text.strip().split()) < 3: | |
| return False, "Input too short. Please provide more context" | |
| return True, "Valid input" | |
| def detect_prompt_injection(text: str) -> Tuple[bool, str]: | |
| """ | |
| Detect potential prompt injection attacks. | |
| Uses blacklist of harmful keywords and patterns. | |
| Args: | |
| text: Text to check for prompt injection | |
| Returns: | |
| Tuple of (is_safe, warning_message) | |
| """ | |
| text_lower = text.lower() | |
| # Check for harmful keywords | |
| for keyword in HARMFUL_KEYWORDS: | |
| if keyword.lower() in text_lower: | |
| return False, f"⚠️ Blocked: Detected suspicious pattern: '{keyword}'" | |
| # Check for common injection patterns | |
| injection_patterns = [ | |
| r'```[\s\S]*?```', # Code blocks (potential instruction override) | |
| r'<!--[\s\S]*?-->', # HTML comments | |
| r'\[SYSTEM\]', # System tokens | |
| r'\[IGNORE\]', # Ignore directives | |
| r'\\x[0-9a-f]{2}', # Hex encoding attempts | |
| ] | |
| for pattern in injection_patterns: | |
| if re.search(pattern, text, re.IGNORECASE): | |
| return False, "⚠️ Blocked: Detected suspicious instruction pattern" | |
| return True, "Safe input" | |
| def sanitize_input(text: str) -> str: | |
| """ | |
| Sanitize input by removing potentially harmful characters. | |
| Args: | |
| text: Text to sanitize | |
| Returns: | |
| Sanitized text | |
| """ | |
| # Remove null bytes | |
| text = text.replace('\x00', '') | |
| # Remove control characters except newlines and tabs | |
| text = ''.join( | |
| char for char in text | |
| if ord(char) >= 32 or char in '\n\t' | |
| ) | |
| # Remove multiple consecutive newlines | |
| text = re.sub(r'\n\n+', '\n\n', text) | |
| return text.strip() | |
| def validate_file_path(file_path: str) -> Tuple[bool, str]: | |
| """ | |
| Validate file path for security. | |
| Args: | |
| file_path: File path to validate | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| if not file_path: | |
| return False, "File path cannot be empty" | |
| # Check for path traversal attempts | |
| if ".." in file_path: | |
| return False, "Invalid file path: path traversal detected" | |
| # Check if path is absolute and outside project | |
| import os | |
| if os.path.isabs(file_path): | |
| project_root = os.path.dirname(os.path.abspath(__file__)) | |
| if not os.path.abspath(file_path).startswith(project_root): | |
| return False, "Invalid file path: outside project directory" | |
| return True, "Valid file path" | |
| class ContentValidator: | |
| """Validates content quality and relevance.""" | |
| def is_meaningful_response(text: str, min_words: int = 10) -> bool: | |
| """ | |
| Check if response is meaningful. | |
| Args: | |
| text: Text to validate | |
| min_words: Minimum words required | |
| Returns: | |
| True if meaningful, False otherwise | |
| """ | |
| words = text.strip().split() | |
| return len(words) >= min_words | |
| def estimate_quality(text: str) -> float: | |
| """ | |
| Estimate quality of a response (0.0 to 1.0). | |
| Args: | |
| text: Text to assess | |
| Returns: | |
| Quality score | |
| """ | |
| score = 0.0 | |
| # Length factor (max 0.3) | |
| word_count = len(text.split()) | |
| length_score = min(word_count / 100, 1.0) * 0.3 | |
| score += length_score | |
| # Structure factor (0.3) - presence of punctuation | |
| punctuation_count = sum(1 for c in text if c in '.!?;:') | |
| structure_score = min(punctuation_count / 10, 1.0) * 0.3 | |
| score += structure_score | |
| # Diversity factor (0.4) - lexical diversity | |
| words = text.lower().split() | |
| unique_words = len(set(words)) | |
| if len(words) > 0: | |
| diversity_score = (unique_words / len(words)) * 0.4 | |
| score += diversity_score | |
| return min(score, 1.0) | |
| def is_acceptable_summary(text: str, min_chars: int = 80) -> bool: | |
| """ | |
| Validate summary quality with flexible structured-output support. | |
| Args: | |
| text: Summary text | |
| min_chars: Minimum character threshold | |
| Returns: | |
| True when summary is acceptable | |
| """ | |
| if not text: | |
| return False | |
| cleaned = text.strip() | |
| if len(cleaned) >= min_chars: | |
| return True | |
| lower = cleaned.lower() | |
| required_sections = [ | |
| "key points", | |
| "main concept", | |
| "important details", | |
| "conclusion", | |
| ] | |
| has_all_sections = all(section in lower for section in required_sections) | |
| # Structured summaries can be concise but still useful. | |
| if has_all_sections and len(cleaned.split()) >= 20: | |
| return True | |
| return False | |
| class PromptValidator: | |
| """Validates and refines prompts for consistency.""" | |
| def validate_prompt_mode(mode: str) -> Tuple[bool, str]: | |
| """ | |
| Validate if prompt mode is supported. | |
| Args: | |
| mode: Mode name | |
| Returns: | |
| Tuple of (is_valid, message) | |
| """ | |
| valid_modes = ["normal", "detailed", "teacher", "exam"] | |
| if mode.lower() in valid_modes: | |
| return True, f"Valid mode: {mode}" | |
| else: | |
| return False, f"Invalid mode: {mode}. Choose from: {', '.join(valid_modes)}" | |
| def validate_feature(feature: str) -> Tuple[bool, str]: | |
| """ | |
| Validate if feature is supported. | |
| Args: | |
| feature: Feature name | |
| Returns: | |
| Tuple of (is_valid, message) | |
| """ | |
| valid_features = ["summarizer", "quiz", "explainer", "doubt_solver"] | |
| if feature.lower() in valid_features: | |
| return True, f"Valid feature: {feature}" | |
| else: | |
| return False, f"Invalid feature: {feature}. Choose from: {', '.join(valid_features)}" | |