File size: 7,877 Bytes
af25a2a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
"""

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."""
    
    @staticmethod
    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"
    
    @staticmethod
    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"
    
    @staticmethod
    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()
    
    @staticmethod
    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."""
    
    @staticmethod
    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
    
    @staticmethod
    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)

    @staticmethod
    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."""
    
    @staticmethod
    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)}"
    
    @staticmethod
    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)}"