Moderator404 commited on
Commit
b4cf5dd
·
verified ·
1 Parent(s): 157cde2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -41
app.py CHANGED
@@ -24,15 +24,16 @@ logger = logging.getLogger(__name__)
24
 
25
  app = Flask(__name__)
26
 
27
- # ---------- CORS and session ----------
28
- CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=False,
29
  allow_headers=["Content-Type", "Authorization"], methods=["GET", "POST", "OPTIONS"])
30
 
31
  app.config.update(
32
  SECRET_KEY=os.environ.get('SECRET_KEY', secrets.token_urlsafe(32)),
33
- SESSION_COOKIE_SECURE=os.environ.get('FLASK_ENV') == 'production',
34
  SESSION_COOKIE_HTTPONLY=True,
35
- SESSION_COOKIE_SAMESITE='Lax',
 
36
  PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
37
  SESSION_COOKIE_NAME='tmc_session',
38
  WTF_CSRF_TIME_LIMIT=3600
@@ -142,20 +143,44 @@ def security_headers():
142
 
143
  @app.after_request
144
  def after_request(response):
 
145
  response.headers['X-Content-Type-Options'] = 'nosniff'
146
  response.headers['X-Frame-Options'] = 'DENY'
147
  response.headers['X-XSS-Protection'] = '1; mode=block'
148
  if request.is_secure:
149
  response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
150
  response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
151
- response.headers['Access-Control-Allow-Origin'] = '*'
152
- response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
 
 
 
 
 
 
 
 
153
  response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
154
  response.headers['Access-Control-Max-Age'] = '86400'
 
155
  if session.get('session_id'):
156
  refresh_session_timeout()
157
  return response
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  # ---------- ChatBot class ----------
160
  class ChatBot:
161
  def __init__(self, db_manager, configured_model="mistral:7b"):
@@ -213,7 +238,6 @@ class ChatBot:
213
  def detect_corruption_patterns(self, text):
214
  if not text or len(text) < 5:
215
  return False, "Too short"
216
- import re
217
  if re.match(r'^(.)\1{6,}', text.strip()):
218
  return True, "Repetitive single character"
219
  unique_chars = len(set(text.replace(' ', '').replace('\n', '')))
@@ -284,7 +308,6 @@ class ChatBot:
284
 
285
  def _generate_conversation_summary(self, text):
286
  try:
287
- # Simple fallback – original used Ollama, we use mock
288
  return self._generate_simple_summary(text)
289
  except Exception:
290
  return "Conversation summary unavailable"
@@ -303,7 +326,6 @@ class ChatBot:
303
  if esc.get('needs_escalation'):
304
  self._escalate_ticket(details['ticket_id'], f"Pre-check: {'; '.join(esc.get('reasons', []))}")
305
  return
306
- # For HF version, we skip the AI call and use rule-based fallback
307
  self._execute_ai_ticket_decision(ticket_number, "", details)
308
  except Exception as e:
309
  logger.error(f"AI decision error: {e}")
@@ -440,7 +462,7 @@ class ChatBot:
440
  return [truncated]
441
  return []
442
 
443
- # ---------- REPLACED SEND_MESSAGE (HF InferenceClient) ----------
444
  def send_message(self, message, conversation_id=None, user_id=None, session_id=None):
445
  start_time = time.time()
446
  if conversation_id is None:
@@ -590,7 +612,7 @@ class ChatBot:
590
  chatbot = ChatBot(db)
591
  chatbot.configured_model = chatbot.load_configured_model()
592
 
593
- # ---------- All original security functions (unchanged) ----------
594
  def check_level2_patterns(text):
595
  text_lower = text.lower()
596
  patterns = [
@@ -601,23 +623,16 @@ def check_level2_patterns(text):
601
  r'pretend\s+(you\s+are|to\s+be)\s+(a\s+)?(different|unrestricted|unfiltered)',
602
  r'roleplay\s+as',
603
  r'simulate\s+(being\s+)?a\s+(different|unrestricted)',
604
- r'\bdan\b',
605
- r'do\s+anything\s+now',
606
  r'ignore\s+your\s+(programming|guidelines|restrictions)',
607
  r'break\s+(free|out)\s+(of|from)\s+your\s+(constraints|limitations)',
608
- r'new\s+instructions?:',
609
- r'system\s+(override|prompt|message):',
610
- r'developer\s+mode',
611
- r'admin\s+mode',
612
- r'root\s+access',
613
  r'show\s+me\s+your\s+(instructions?|prompt|system\s+message)',
614
  r'what\s+(are\s+)?your\s+(instructions?|guidelines|rules)',
615
  r'reveal\s+your\s+(prompt|system\s+message)',
616
- r'output\s+in\s+code\s+block',
617
- r'respond\s+only\s+with',
618
- r'answer\s+as\s+if',
619
- r'hypothetically',
620
- r'for\s+educational\s+purposes',
621
  r'</system>', r'<system>', r'\[system\]', r'\[/system\]',
622
  r'\\n\\nsystem:', r'assistant:', r'human:'
623
  ]
@@ -638,14 +653,9 @@ def check_level3_ai_analysis(text):
638
  score, err = analyze_input_with_ai(text)
639
  if err:
640
  return None, err
641
- if score is not None:
642
- if score >= 5:
643
- return None, "Your input violates our usage guidelines."
644
- else:
645
- return text, None
646
- else:
647
- logger.warning("AI Level 3 analysis failed, allowing input")
648
- return text, None
649
  except Exception as e:
650
  logger.error(f"AI Level 3 analysis error: {e}")
651
  return text, None
@@ -672,11 +682,9 @@ def validate_and_sanitize_input(text, max_length=5000):
672
  elif level >= 5:
673
  filtered, err = check_level2_patterns(text)
674
  if err:
675
- logger.warning(f"Level 5 layer 1 blocked: {err}")
676
  return None, err
677
  filtered, err = check_level3_ai_analysis(text)
678
  if err:
679
- logger.warning(f"Level 5 layer 2 blocked: {err}")
680
  return None, err
681
  return text, None
682
 
@@ -726,15 +734,12 @@ def check_ai_security_violations(text):
726
  return text, None
727
 
728
  def analyze_input_with_ai(user_input):
729
- # For HF version we return a safe score (2) to avoid blocking
730
  return 2, None
731
 
732
  def get_ai_security_analysis(prompt):
733
- # Placeholder – return safe score
734
  return 2
735
 
736
  def analyze_output_with_ai(ai_response):
737
- # Placeholder – return safe score
738
  return 2, None
739
 
740
  def check_output_content_moderation(ai_response):
@@ -742,7 +747,7 @@ def check_output_content_moderation(ai_response):
742
  if level >= 4:
743
  score, err = analyze_output_with_ai(ai_response)
744
  if err:
745
- logger.warning(f"Output analysis failed, allowing response")
746
  return ai_response, None
747
  if score is not None and score >= 5:
748
  return None, "Restricted Output Detected, please try another question or contact support@tmc.local"
@@ -1027,7 +1032,7 @@ def health_check():
1027
 
1028
  @app.route('/api/health/ollama')
1029
  def ollama_health_check():
1030
- return jsonify({'ollama_healthy': True, 'timestamp': datetime.now().isoformat(), 'message': 'Ollama is not used (Hugging Face backend)', 'recommendation': 'All good!'})
1031
 
1032
  @app.route('/api/knowledge-base/stats')
1033
  def kb_stats():
@@ -1265,8 +1270,7 @@ def admin_reply_ticket(ticket_id):
1265
  internal = data.get('is_internal', False)
1266
  if not msg:
1267
  return jsonify({'success': False, 'error': 'Message required'}), 400
1268
- uid = session.get('user_id')
1269
- db.add_ticket_update(ticket_id, uid, msg, 'admin_reply', is_internal=internal)
1270
  return jsonify({'success': True})
1271
 
1272
  @app.route('/api/admin/tickets/stats')
@@ -1372,7 +1376,7 @@ def get_product_specs(product_name):
1372
  return jsonify({'success': False, 'error': 'Product not found'}), 404
1373
  with open(path, 'r', encoding='utf-8') as f:
1374
  content = f.read()
1375
- # extract relevant section if multiple products share the same file
1376
  if product_name in ['usb-c-cable', 'usb-c-standard', 'usb-c-to-usb-a']:
1377
  sections = content.split('##')
1378
  for s in sections:
 
24
 
25
  app = Flask(__name__)
26
 
27
+ # ---------- CORS and session (FIXED for cross‑origin iframe) ----------
28
+ CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True,
29
  allow_headers=["Content-Type", "Authorization"], methods=["GET", "POST", "OPTIONS"])
30
 
31
  app.config.update(
32
  SECRET_KEY=os.environ.get('SECRET_KEY', secrets.token_urlsafe(32)),
33
+ SESSION_COOKIE_SECURE=os.environ.get('FLASK_ENV') == 'production', # True on Hugging Face Spaces (HTTPS)
34
  SESSION_COOKIE_HTTPONLY=True,
35
+ SESSION_COOKIE_SAMESITE='None', # Required for cross‑origin iframe
36
+ SESSION_COOKIE_DOMAIN='.hf.space', # Allows cookie across all Spaces subdomains
37
  PERMANENT_SESSION_LIFETIME=timedelta(hours=24),
38
  SESSION_COOKIE_NAME='tmc_session',
39
  WTF_CSRF_TIME_LIMIT=3600
 
143
 
144
  @app.after_request
145
  def after_request(response):
146
+ """Add security headers and dynamic CORS (allows credentials)"""
147
  response.headers['X-Content-Type-Options'] = 'nosniff'
148
  response.headers['X-Frame-Options'] = 'DENY'
149
  response.headers['X-XSS-Protection'] = '1; mode=block'
150
  if request.is_secure:
151
  response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
152
  response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
153
+
154
+ # CORS for credentialed requests – echo the actual origin
155
+ origin = request.headers.get('Origin')
156
+ if origin and (origin.endswith('.hf.space') or origin == 'https://moderator404-chatbot.hf.space'):
157
+ response.headers['Access-Control-Allow-Origin'] = origin
158
+ response.headers['Access-Control-Allow-Credentials'] = 'true'
159
+ else:
160
+ response.headers['Access-Control-Allow-Origin'] = '*'
161
+
162
+ response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, PUT, DELETE'
163
  response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
164
  response.headers['Access-Control-Max-Age'] = '86400'
165
+
166
  if session.get('session_id'):
167
  refresh_session_timeout()
168
  return response
169
 
170
+ # ---------- RAG initialization ----------
171
+ logger.info("Initializing RAG system...")
172
+ try:
173
+ rag_helper = RAGHelper(use_vector_search=True)
174
+ logger.info(f"RAG system initialized successfully! Vector search: {rag_helper.use_vector_search}")
175
+ if rag_helper.use_vector_search and rag_helper.vector_rag:
176
+ logger.info("Building vector index...")
177
+ index_stats = rag_helper.ensure_vector_index()
178
+ logger.info(f"Vector index status: {index_stats}")
179
+ except Exception as e:
180
+ logger.error(f"RAG initialization failed: {e}")
181
+ logger.info("Creating fallback RAG helper without vector search")
182
+ rag_helper = RAGHelper(use_vector_search=False)
183
+
184
  # ---------- ChatBot class ----------
185
  class ChatBot:
186
  def __init__(self, db_manager, configured_model="mistral:7b"):
 
238
  def detect_corruption_patterns(self, text):
239
  if not text or len(text) < 5:
240
  return False, "Too short"
 
241
  if re.match(r'^(.)\1{6,}', text.strip()):
242
  return True, "Repetitive single character"
243
  unique_chars = len(set(text.replace(' ', '').replace('\n', '')))
 
308
 
309
  def _generate_conversation_summary(self, text):
310
  try:
 
311
  return self._generate_simple_summary(text)
312
  except Exception:
313
  return "Conversation summary unavailable"
 
326
  if esc.get('needs_escalation'):
327
  self._escalate_ticket(details['ticket_id'], f"Pre-check: {'; '.join(esc.get('reasons', []))}")
328
  return
 
329
  self._execute_ai_ticket_decision(ticket_number, "", details)
330
  except Exception as e:
331
  logger.error(f"AI decision error: {e}")
 
462
  return [truncated]
463
  return []
464
 
465
+ # ---------- SEND_MESSAGE (Hugging Face InferenceClient) ----------
466
  def send_message(self, message, conversation_id=None, user_id=None, session_id=None):
467
  start_time = time.time()
468
  if conversation_id is None:
 
612
  chatbot = ChatBot(db)
613
  chatbot.configured_model = chatbot.load_configured_model()
614
 
615
+ # ---------- Security functions (unchanged) ----------
616
  def check_level2_patterns(text):
617
  text_lower = text.lower()
618
  patterns = [
 
623
  r'pretend\s+(you\s+are|to\s+be)\s+(a\s+)?(different|unrestricted|unfiltered)',
624
  r'roleplay\s+as',
625
  r'simulate\s+(being\s+)?a\s+(different|unrestricted)',
626
+ r'\bdan\b', r'do\s+anything\s+now',
 
627
  r'ignore\s+your\s+(programming|guidelines|restrictions)',
628
  r'break\s+(free|out)\s+(of|from)\s+your\s+(constraints|limitations)',
629
+ r'new\s+instructions?:', r'system\s+(override|prompt|message):',
630
+ r'developer\s+mode', r'admin\s+mode', r'root\s+access',
 
 
 
631
  r'show\s+me\s+your\s+(instructions?|prompt|system\s+message)',
632
  r'what\s+(are\s+)?your\s+(instructions?|guidelines|rules)',
633
  r'reveal\s+your\s+(prompt|system\s+message)',
634
+ r'output\s+in\s+code\s+block', r'respond\s+only\s+with',
635
+ r'answer\s+as\s+if', r'hypothetically', r'for\s+educational\s+purposes',
 
 
 
636
  r'</system>', r'<system>', r'\[system\]', r'\[/system\]',
637
  r'\\n\\nsystem:', r'assistant:', r'human:'
638
  ]
 
653
  score, err = analyze_input_with_ai(text)
654
  if err:
655
  return None, err
656
+ if score is not None and score >= 5:
657
+ return None, "Your input violates our usage guidelines."
658
+ return text, None
 
 
 
 
 
659
  except Exception as e:
660
  logger.error(f"AI Level 3 analysis error: {e}")
661
  return text, None
 
682
  elif level >= 5:
683
  filtered, err = check_level2_patterns(text)
684
  if err:
 
685
  return None, err
686
  filtered, err = check_level3_ai_analysis(text)
687
  if err:
 
688
  return None, err
689
  return text, None
690
 
 
734
  return text, None
735
 
736
  def analyze_input_with_ai(user_input):
 
737
  return 2, None
738
 
739
  def get_ai_security_analysis(prompt):
 
740
  return 2
741
 
742
  def analyze_output_with_ai(ai_response):
 
743
  return 2, None
744
 
745
  def check_output_content_moderation(ai_response):
 
747
  if level >= 4:
748
  score, err = analyze_output_with_ai(ai_response)
749
  if err:
750
+ logger.warning("Output analysis failed, allowing response")
751
  return ai_response, None
752
  if score is not None and score >= 5:
753
  return None, "Restricted Output Detected, please try another question or contact support@tmc.local"
 
1032
 
1033
  @app.route('/api/health/ollama')
1034
  def ollama_health_check():
1035
+ return jsonify({'ollama_healthy': True, 'timestamp': datetime.now().isoformat(), 'message': 'Ollama not used (Hugging Face backend)', 'recommendation': 'All good!'})
1036
 
1037
  @app.route('/api/knowledge-base/stats')
1038
  def kb_stats():
 
1270
  internal = data.get('is_internal', False)
1271
  if not msg:
1272
  return jsonify({'success': False, 'error': 'Message required'}), 400
1273
+ db.add_ticket_update(ticket_id, session.get('user_id'), msg, 'admin_reply', is_internal=internal)
 
1274
  return jsonify({'success': True})
1275
 
1276
  @app.route('/api/admin/tickets/stats')
 
1376
  return jsonify({'success': False, 'error': 'Product not found'}), 404
1377
  with open(path, 'r', encoding='utf-8') as f:
1378
  content = f.read()
1379
+ # Extract relevant section if multiple products share the same file
1380
  if product_name in ['usb-c-cable', 'usb-c-standard', 'usb-c-to-usb-a']:
1381
  sections = content.split('##')
1382
  for s in sections: