Spaces:
Running
Running
| """ | |
| Validators Module - Input validation and sanitization | |
| Ensures all user inputs are safe and valid | |
| """ | |
| import re | |
| from typing import Dict, List, Tuple, Optional | |
| class InputValidator: | |
| """Validates user inputs for MVP Agent""" | |
| # Constants | |
| MIN_IDEA_LENGTH = 10 | |
| MAX_IDEA_LENGTH = 1000 | |
| DANGEROUS_PATTERNS = [ | |
| r'<script', | |
| r'javascript:', | |
| r'onerror=', | |
| r'onclick=', | |
| r'eval\(', | |
| r'exec\(', | |
| ] | |
| def validate_startup_idea(idea: str) -> Tuple[bool, Optional[str]]: | |
| """ | |
| Validate startup idea input | |
| Args: | |
| idea: The startup idea string | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| # Check if empty | |
| if not idea: | |
| return False, "Please enter a startup idea" | |
| # Check type | |
| if not isinstance(idea, str): | |
| return False, "Idea must be text" | |
| # Strip whitespace | |
| idea = idea.strip() | |
| # Check length | |
| if len(idea) < InputValidator.MIN_IDEA_LENGTH: | |
| return False, f"Idea too short. Please enter at least {InputValidator.MIN_IDEA_LENGTH} characters" | |
| if len(idea) > InputValidator.MAX_IDEA_LENGTH: | |
| return False, f"Idea too long. Please keep it under {InputValidator.MAX_IDEA_LENGTH} characters" | |
| # Check for dangerous patterns (basic XSS prevention) | |
| for pattern in InputValidator.DANGEROUS_PATTERNS: | |
| if re.search(pattern, idea, re.IGNORECASE): | |
| return False, "Invalid characters detected in idea" | |
| # Check if meaningful (not just spaces or special chars) | |
| if not re.search(r'[a-zA-Z]', idea): | |
| return False, "Please enter a meaningful idea with text" | |
| return True, None | |
| def sanitize_idea(idea: str) -> str: | |
| """ | |
| Sanitize startup idea for safe processing | |
| Args: | |
| idea: The raw idea | |
| Returns: | |
| Sanitized idea string | |
| """ | |
| # Strip whitespace | |
| idea = idea.strip() | |
| # Remove control characters | |
| idea = ''.join(char for char in idea if ord(char) >= 32 or char in '\n\r\t') | |
| # Limit consecutive spaces | |
| idea = re.sub(r'\s+', ' ', idea) | |
| # Remove HTML-like tags | |
| idea = re.sub(r'<[^>]+>', '', idea) | |
| return idea | |
| def validate_api_key(api_key: str, key_name: str = "API key") -> Tuple[bool, Optional[str]]: | |
| """ | |
| Validate API key format | |
| Args: | |
| api_key: The API key to validate | |
| key_name: Name of the key (for error messages) | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| if not api_key: | |
| return False, f"{key_name} is required" | |
| if not isinstance(api_key, str): | |
| return False, f"{key_name} must be a string" | |
| api_key = api_key.strip() | |
| if len(api_key) < 10: | |
| return False, f"{key_name} appears to be invalid (too short)" | |
| # Check for obvious placeholder values | |
| placeholder_values = [ | |
| 'your_api_key', | |
| 'insert_key_here', | |
| 'api_key_here', | |
| 'placeholder', | |
| 'xxxxxx' | |
| ] | |
| if api_key.lower() in placeholder_values: | |
| return False, f"Please replace the placeholder {key_name}" | |
| return True, None | |
| def validate_file_path(path: str) -> Tuple[bool, Optional[str]]: | |
| """ | |
| Validate file path for safety | |
| Args: | |
| path: File path to validate | |
| Returns: | |
| Tuple of (is_valid, error_message) | |
| """ | |
| if not path: | |
| return False, "File path cannot be empty" | |
| # Check for path traversal attempts | |
| if '..' in path: | |
| return False, "Invalid file path (path traversal detected)" | |
| # Check for absolute paths (we want relative only) | |
| if path.startswith('/') or (len(path) > 1 and path[1] == ':'): | |
| return False, "Only relative paths are allowed" | |
| # Check for dangerous characters | |
| dangerous_chars = ['<', '>', '|', '\0', '\n', '\r'] | |
| if any(char in path for char in dangerous_chars): | |
| return False, "Invalid characters in file path" | |
| return True, None | |
| class OutputValidator: | |
| """Validates agent outputs""" | |
| def validate_mvp_files(files: Dict[str, str]) -> Tuple[bool, List[str]]: | |
| """ | |
| Validate generated MVP files | |
| Args: | |
| files: Dictionary of file contents | |
| Returns: | |
| Tuple of (is_valid, list_of_errors) | |
| """ | |
| errors = [] | |
| required_files = [ | |
| 'features_md', | |
| 'architecture_md', | |
| 'design_md', | |
| 'user_flow_md', | |
| 'roadmap_md' | |
| ] | |
| # Check all required files present | |
| for file_key in required_files: | |
| if file_key not in files: | |
| errors.append(f"Missing required file: {file_key}") | |
| elif not files[file_key]: | |
| errors.append(f"Empty file: {file_key}") | |
| elif len(files[file_key]) < 100: | |
| errors.append(f"File too short (< 100 chars): {file_key}") | |
| # Check for markdown headers | |
| for file_key, content in files.items(): | |
| if file_key in required_files: | |
| if not content.strip().startswith('#'): | |
| errors.append(f"File missing markdown header: {file_key}") | |
| return len(errors) == 0, errors | |
| def validate_json_structure(data: dict, required_keys: List[str]) -> Tuple[bool, List[str]]: | |
| """ | |
| Validate JSON structure has required keys | |
| Args: | |
| data: JSON data as dictionary | |
| required_keys: List of required keys | |
| Returns: | |
| Tuple of (is_valid, list_of_missing_keys) | |
| """ | |
| missing_keys = [] | |
| for key in required_keys: | |
| if key not in data: | |
| missing_keys.append(key) | |
| elif data[key] is None: | |
| missing_keys.append(f"{key} (null value)") | |
| return len(missing_keys) == 0, missing_keys | |
| # Convenience functions | |
| def validate_idea(idea: str) -> Tuple[bool, Optional[str]]: | |
| """Quick validation of startup idea""" | |
| return InputValidator.validate_startup_idea(idea) | |
| def sanitize_idea(idea: str) -> str: | |
| """Quick sanitization of startup idea""" | |
| return InputValidator.sanitize_idea(idea) | |
| def validate_files(files: Dict[str, str]) -> Tuple[bool, List[str]]: | |
| """Quick validation of MVP files""" | |
| return OutputValidator.validate_mvp_files(files) | |