""" Input validation utilities for audio uploads and text sanitization. """ import re import logging from functools import wraps from flask import request, jsonify logger = logging.getLogger(__name__) # Patterns commonly used in prompt injection attempts _INJECTION_PATTERNS = [ re.compile(r'ignore\s+(all\s+)?previous\s+instructions', re.IGNORECASE), re.compile(r'ignore\s+(all\s+)?above\s+instructions', re.IGNORECASE), re.compile(r'^system\s*:', re.IGNORECASE | re.MULTILINE), re.compile(r'you\s+are\s+now\s+(?:a|an|the)\s+', re.IGNORECASE), re.compile(r'pretend\s+(?:you\s+are|to\s+be)', re.IGNORECASE), re.compile(r'act\s+as\s+(?:a|an|if)', re.IGNORECASE), re.compile(r'new\s+instructions?\s*:', re.IGNORECASE), re.compile(r'disregard\s+(?:all\s+)?(?:previous|prior|above)', re.IGNORECASE), ] def validate_audio_upload(field_name='audio'): """Decorator to validate audio file uploads. Checks that a file is present, not empty, and within size limits. Returns 400 JSON on validation failure. """ def decorator(f): @wraps(f) def wrapper(*args, **kwargs): if request.method == 'OPTIONS': return f(*args, **kwargs) if field_name not in request.files: return jsonify({'success': False, 'error': f'No {field_name} file provided'}), 400 audio_file = request.files[field_name] if not audio_file.filename: return jsonify({'success': False, 'error': 'Empty filename'}), 400 # Check file size (read content length from stream) audio_file.seek(0, 2) # seek to end file_size = audio_file.tell() audio_file.seek(0) # reset to beginning if file_size < 100: return jsonify({'success': False, 'error': 'Audio file too small (possibly empty recording)'}), 400 if file_size > 30 * 1024 * 1024: # 30MB return jsonify({'success': False, 'error': 'Audio file too large. Maximum size is 30MB.'}), 400 return f(*args, **kwargs) return wrapper return decorator def sanitize_text_for_llm(text, max_length=5000): """Sanitize user-provided text before passing to LLM. Truncates to max_length and strips common prompt injection patterns. """ if not text: return '' # Truncate text = text[:max_length] # Strip injection patterns for pattern in _INJECTION_PATTERNS: text = pattern.sub('', text) # Remove excessive whitespace that could result from stripping text = re.sub(r'\n{3,}', '\n\n', text) text = text.strip() return text