Scamverifierv2 / app.py
AayanSuleri's picture
Update app.py
d2d817d verified
Raw
History Blame Contribute Delete
28.7 kB
from flask import Flask, render_template, request, jsonify
import re
import requests
from urllib.parse import urlparse, parse_qs
import json
from datetime import datetime
import os
from typing import Dict, List, Tuple
import time
import random
from openai import OpenAI
app = Flask(__name__)
class ScamSignalVerifier:
def __init__(self):
# Initialize OpenAI client
self.openai_client = None
self._init_openai()
self.scam_keywords = [
'urgent', 'limited time', 'act now', 'click here', 'guaranteed',
'free money', 'no risk', 'earn money fast', 'work from home',
'congratulations', 'you have won', 'claim now', 'verify account',
'suspended', 'confirm identity', 'update payment', 'bitcoin',
'cryptocurrency', 'investment opportunity', 'double your money',
'risk-free', 'government grant', 'tax refund', 'inheritance',
'lottery', 'sweepstakes', 'prince', 'transfer funds',
'logged out', 'share details', 'bank account', 'account details',
'provide details', 'get back', 'restore access'
]
self.suspicious_domains = [
'bit.ly', 'tinyurl.com', 'shortened.link', 'click.me',
'secure-bank.net', 'paypal-verify.com', 'amazon-update.net'
]
self.legitimate_domains = [
'google.com', 'facebook.com', 'amazon.com', 'paypal.com',
'microsoft.com', 'apple.com', 'linkedin.com', 'twitter.com',
'instagram.com', 'youtube.com', 'github.com', 'stackoverflow.com'
]
def _init_openai(self):
"""Initialize OpenAI client with API key from Hugging Face secrets"""
try:
api_key = os.environ.get('OPENAI_API_KEY')
if api_key:
self.openai_client = OpenAI(api_key=api_key)
print("✅ OpenAI API initialized successfully")
else:
print("⚠️ OpenAI API key not found - falling back to rule-based analysis only")
self.openai_client = None
except Exception as e:
print(f"⚠️ Failed to initialize OpenAI API: {str(e)}")
self.openai_client = None
def ai_analyze_scam(self, text: str, url: str = None) -> Dict:
"""Use OpenAI to perform intelligent scam analysis"""
if not self.openai_client:
print("DEBUG: OpenAI client not available")
return {
'ai_available': False,
'ai_risk_score': 0,
'ai_confidence': 0,
'ai_explanation': 'AI analysis unavailable - using rule-based detection only',
'scam_type': 'Unknown',
'social_engineering_tactics': [],
'sophistication_level': 'N/A'
}
try:
print("DEBUG: Making OpenAI API call...")
# Construct the analysis prompt
analysis_prompt = f"""
You are an expert cybersecurity analyst specializing in scam detection. Analyze the following message for scam indicators using advanced pattern recognition and social engineering analysis.
MESSAGE TO ANALYZE:
"{text}"
{f'ASSOCIATED URL: {url}' if url else ''}
Please provide a comprehensive analysis in JSON format with these exact fields:
1. "ai_risk_score": Integer from 0-100 (0=definitely legitimate, 100=definitely scam)
2. "ai_confidence": Integer from 0-100 (how confident you are in your assessment)
3. "scam_type": String identifying the primary scam type (e.g., "Phishing", "419 Scam", "Investment Fraud", "Romance Scam", "Tech Support Scam", "Lottery Scam", "Legitimate")
4. "social_engineering_tactics": Array of strings describing psychological manipulation techniques used
5. "sophistication_level": String ("Low", "Medium", "High") - how well-crafted the scam attempt is
6. "ai_explanation": String with detailed reasoning for your assessment
7. "grammar_quality": String ("Poor", "Fair", "Good", "Excellent") - grammar and writing quality assessment
8. "emotional_manipulation": String describing emotional appeals used (urgency, fear, greed, etc.)
Focus on:
- Subtle linguistic patterns that indicate deception
- Psychological manipulation techniques
- Context inconsistencies
- Grammar/spelling patterns typical of scams
- Social engineering red flags
- Urgency and pressure tactics
- Trust-building followed by requests
- Too-good-to-be-true offers
Respond ONLY with valid JSON format.
"""
# Make API call to OpenAI
response = self.openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are an expert scam detection AI. You must respond only with valid JSON format as requested."
},
{
"role": "user",
"content": analysis_prompt
}
],
max_tokens=800,
temperature=0.3 # Lower temperature for more consistent analysis
)
# Parse the AI response
ai_response = response.choices[0].message.content.strip()
print(f"DEBUG: OpenAI raw response: {ai_response}")
# Try to parse JSON response
try:
ai_analysis = json.loads(ai_response)
ai_analysis['ai_available'] = True
print(f"DEBUG: AI analysis successful - risk score: {ai_analysis.get('ai_risk_score', 'N/A')}")
return ai_analysis
except json.JSONDecodeError as je:
print(f"DEBUG: JSON parsing failed: {str(je)}")
# Fallback if JSON parsing fails
return {
'ai_available': True,
'ai_risk_score': 50, # Default moderate risk when parsing fails
'ai_confidence': 30,
'ai_explanation': f'AI analysis completed but response parsing failed. Raw response: {ai_response[:200]}...',
'scam_type': 'Analysis Error',
'social_engineering_tactics': [],
'sophistication_level': 'Unknown'
}
except Exception as e:
print(f"DEBUG: OpenAI API error: {str(e)}")
return {
'ai_available': False,
'ai_risk_score': 0,
'ai_confidence': 0,
'ai_explanation': f'AI analysis failed: {str(e)}',
'scam_type': 'Analysis Failed',
'social_engineering_tactics': [],
'sophistication_level': 'N/A'
}
def extract_claims(self, text: str, url: str = None) -> Dict:
claims = {
'text_content': text,
'urls': [],
'phone_numbers': [],
'email_addresses': [],
'money_amounts': [],
'suspicious_phrases': [],
'urgency_indicators': []
}
url_pattern = r'http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
claims['urls'] = re.findall(url_pattern, text)
if url:
claims['urls'].append(url)
phone_pattern = r'\b(?:\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})\b'
claims['phone_numbers'] = re.findall(phone_pattern, text)
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
claims['email_addresses'] = re.findall(email_pattern, text)
money_pattern = r'\$[\d,]+\.?\d*|\d+\s*(?:dollars?|USD|euros?|pounds?)'
claims['money_amounts'] = re.findall(money_pattern, text, re.IGNORECASE)
text_lower = text.lower()
for keyword in self.scam_keywords:
if keyword in text_lower:
claims['suspicious_phrases'].append(keyword)
high_risk_patterns = [
'you won', 'you have won', 'winner', 'selected winner',
'send bank details', 'provide bank account', 'wire transfer',
'inheritance fund', 'beneficiary', 'claim your prize',
'processing fee', 'activation fee', 'clearance fee',
'verify your account', 'account suspended', 'account locked',
'logged out', 'share details', 'get your bank account back',
'bank account', 'share bank details', 'provide details'
]
for pattern in high_risk_patterns:
if pattern in text_lower:
claims['suspicious_phrases'].append(pattern)
urgency_patterns = [
r'within \d+\s*(?:hours?|days?|minutes?)',
r'expires? (?:today|tomorrow|soon)',
r'limited time',
r'act (?:now|immediately|fast)',
r'urgent.{0,20}(?:action|response|reply)'
]
for pattern in urgency_patterns:
matches = re.findall(pattern, text_lower)
claims['urgency_indicators'].extend(matches)
return claims
def verify_claims(self, claims: Dict) -> Dict:
verification_results = {
'domain_analysis': {},
'keyword_score': 0,
'urgency_score': 0,
'contact_info_risk': 0,
'financial_risk': 0
}
for url in claims['urls']:
try:
parsed = urlparse(url)
domain = parsed.netloc.lower()
if any(suspicious in domain for suspicious in self.suspicious_domains):
verification_results['domain_analysis'][domain] = 'HIGH_RISK'
elif any(legit in domain for legit in self.legitimate_domains):
verification_results['domain_analysis'][domain] = 'LOW_RISK'
elif len(domain.split('.')) > 3:
verification_results['domain_analysis'][domain] = 'MEDIUM_RISK'
else:
verification_results['domain_analysis'][domain] = 'UNKNOWN'
except:
verification_results['domain_analysis'][url] = 'INVALID_URL'
verification_results['keyword_score'] = min(100, len(claims['suspicious_phrases']) * 15)
verification_results['urgency_score'] = min(100, len(claims['urgency_indicators']) * 25)
if claims['phone_numbers'] and claims['email_addresses']:
verification_results['contact_info_risk'] = 60
elif claims['phone_numbers'] or claims['email_addresses']:
verification_results['contact_info_risk'] = 30
if claims['money_amounts']:
verification_results['financial_risk'] = 70 if len(claims['money_amounts']) > 2 else 40
return verification_results
def calculate_hybrid_risk_score(self, claims: Dict, verification: Dict, ai_analysis: Dict) -> Tuple[int, str]:
"""Calculate combined risk score using both rule-based and AI analysis"""
text_lower = claims['text_content'].lower()
print(f"DEBUG: Analyzing text: '{text_lower}'") # Debug line
# Start with base rule-based scoring
rule_based_score = 0
rule_based_score += verification['keyword_score'] * 0.4
rule_based_score += verification['urgency_score'] * 0.25
rule_based_score += verification['contact_info_risk'] * 0.25
rule_based_score += verification['financial_risk'] * 0.36
print(f"DEBUG: Base scores - keyword:{verification['keyword_score']}, urgency:{verification['urgency_score']}, contact:{verification['contact_info_risk']}, financial:{verification['financial_risk']}")
# Domain analysis
domain_risk = 0
for domain, risk in verification['domain_analysis'].items():
if risk == 'HIGH_RISK':
domain_risk += 30
elif risk == 'MEDIUM_RISK':
domain_risk += 15
elif risk == 'INVALID_URL':
domain_risk += 10
rule_based_score += min(30, domain_risk)
# CRITICAL SCAM PATTERNS - These should trigger HIGH scores immediately
critical_patterns = [
('send bank details', 80),
('provide bank details', 80),
('share bank details', 80),
('bank account details', 75),
('send details', 70),
('share details', 70),
('provide details', 65),
('logged out', 60),
('account suspended', 65),
('account locked', 65),
('verify account', 55),
('confirm identity', 55)
]
pattern_bonus = 0
for pattern, score in critical_patterns:
if pattern in text_lower:
pattern_bonus = max(pattern_bonus, score)
print(f"DEBUG: Found critical pattern '{pattern}' - adding {score} points")
rule_based_score += pattern_bonus
# High-risk combinations
bank_terms = ['bank account', 'bank details', 'account details', 'account info']
action_terms = ['send', 'provide', 'share', 'give']
threat_terms = ['logged out', 'suspended', 'locked', 'blocked', 'expired']
has_bank = any(term in text_lower for term in bank_terms)
has_action = any(term in text_lower for term in action_terms)
has_threat = any(term in text_lower for term in threat_terms)
if has_bank and has_action:
rule_based_score += 40
print("DEBUG: Bank + Action combo detected - adding 40 points")
if has_threat and (has_bank or has_action):
rule_based_score += 30
print("DEBUG: Threat + Bank/Action combo detected - adding 30 points")
# Lottery/prize scams
if any(phrase in text_lower for phrase in ['you won', 'you have won', 'congratulations', 'winner']):
if any(word in text_lower for word in ['send', 'provide', 'bank', 'details', 'account']):
rule_based_score += 40
print("DEBUG: Prize + financial request detected - adding 40 points")
# Investment/money scams
if any(phrase in text_lower for phrase in ['double your money', 'guaranteed return', 'risk-free', 'investment opportunity']):
rule_based_score += 35
print("DEBUG: Investment scam pattern detected - adding 35 points")
rule_based_score = min(100, int(rule_based_score))
print(f"DEBUG: Final rule-based score: {rule_based_score}")
# Handle AI analysis
final_score = rule_based_score
if ai_analysis['ai_available'] and ai_analysis.get('ai_risk_score', 0) > 0:
ai_score = ai_analysis['ai_risk_score']
ai_confidence = ai_analysis.get('ai_confidence', 0)
print(f"DEBUG: AI analysis - score: {ai_score}, confidence: {ai_confidence}")
if ai_confidence > 30:
# Combine scores, but don't let AI override obvious high-risk patterns
ai_weight = min(0.4, ai_confidence / 100 * 0.6) # Reduced AI weight
rule_weight = 1 - ai_weight
combined_score = (rule_based_score * rule_weight) + (ai_score * ai_weight)
# Take the higher of rule-based or combined score
final_score = max(rule_based_score, int(combined_score))
print(f"DEBUG: Combined with AI - final score: {final_score}")
final_score = min(100, final_score)
# Determine classification
if final_score >= 70:
classification = "HIGH RISK"
elif final_score >= 45:
classification = "MEDIUM RISK"
elif final_score >= 20:
classification = "LOW RISK"
else:
classification = "MINIMAL RISK"
print(f"DEBUG: Final classification: {classification} (score: {final_score})")
return final_score, classification
def generate_enhanced_explanation(self, claims: Dict, verification: Dict, ai_analysis: Dict, risk_score: int, classification: str) -> Dict:
red_flags = []
recommendations = []
# Rule-based red flags
if len(claims['suspicious_phrases']) > 0:
red_flags.append(f"Contains {len(claims['suspicious_phrases'])} suspicious keywords/phrases")
if verification['urgency_score'] > 20:
red_flags.append("Uses high-pressure urgency tactics")
for domain, risk in verification['domain_analysis'].items():
if risk == 'HIGH_RISK':
red_flags.append(f"Uses suspicious domain: {domain}")
elif risk == 'MEDIUM_RISK':
red_flags.append(f"Domain structure looks suspicious: {domain}")
elif risk == 'INVALID_URL':
red_flags.append(f"Contains invalid/malformed URL: {domain}")
if claims['money_amounts']:
red_flags.append("Mentions money/financial transactions")
if len(claims['urls']) > 3:
red_flags.append("Contains multiple links (potential redirect chains)")
text_lower = claims['text_content'].lower()
if any(phrase in text_lower for phrase in ['you won', 'you have won', 'winner']):
red_flags.append("Claims you won money/prizes (common lottery scam)")
if any(phrase in text_lower for phrase in ['send bank', 'provide bank', 'bank details', 'account details', 'share details']):
red_flags.append("Requests bank account details (major red flag)")
if any(phrase in text_lower for phrase in ['logged out', 'account suspended', 'account locked', 'account blocked']):
red_flags.append("Claims account issues to pressure immediate action")
if 'processing fee' in text_lower or 'activation fee' in text_lower:
red_flags.append("Requests upfront fees (advance fee fraud)")
# Add AI-based insights
if ai_analysis['ai_available']:
if ai_analysis.get('scam_type') and ai_analysis['scam_type'] not in ['Legitimate', 'Analysis Error', 'Analysis Failed']:
red_flags.append(f"🤖 AI detected: {ai_analysis['scam_type']} pattern")
for tactic in ai_analysis.get('social_engineering_tactics', []):
red_flags.append(f"🤖 Social engineering: {tactic}")
if ai_analysis.get('sophistication_level') == 'High':
red_flags.append("🤖 AI detected sophisticated scam techniques")
# Enhanced recommendations based on AI analysis
if classification == "HIGH RISK":
recommendations = [
"🚫 This is very likely a SCAM - Do NOT respond or engage",
"🚫 Do NOT click any links or provide personal information",
"🚫 Do NOT send money or share financial details",
"📧 Report this message as spam/phishing immediately",
"🛡️ Block the sender and delete the message",
"👥 Warn friends/family about this type of scam"
]
if ai_analysis['ai_available'] and ai_analysis.get('scam_type'):
recommendations.append(f"🤖 Specific threat: This appears to be a {ai_analysis['scam_type']} - research this scam type for more info")
elif classification == "MEDIUM RISK":
recommendations = [
"⚠️ HIGH CAUTION ADVISED - Verify independently before taking action",
"🔍 Contact the organization directly using official channels",
"🚫 Don't click links - navigate to official websites manually",
"👨‍👩‍👧‍👦 Ask a trusted friend or family member for advice",
"⏰ Take time to think - don't rush into decisions",
"🛡️ Never share sensitive personal or financial information"
]
elif classification == "LOW RISK":
recommendations = [
"⚠️ Proceed with caution - some concerning elements detected",
"🔍 Verify any claims through official sources",
"🛡️ Avoid sharing sensitive personal information",
"📞 When in doubt, contact the organization directly"
]
else:
recommendations = [
"✅ Message appears relatively safe",
"🛡️ Always practice general online safety",
"🔍 Verify important information independently"
]
# Enhanced explanation with AI insights
explanation = {
'risk_score': risk_score,
'classification': classification,
'summary': self._generate_enhanced_summary(classification, len(red_flags), ai_analysis),
'red_flags': red_flags,
'recommendations': recommendations,
'ai_insights': {
'available': ai_analysis['ai_available'],
'confidence': ai_analysis.get('ai_confidence', 0),
'scam_type': ai_analysis.get('scam_type', 'N/A'),
'sophistication': ai_analysis.get('sophistication_level', 'N/A'),
'explanation': ai_analysis.get('ai_explanation', 'AI analysis not available')
},
'report_template': self._generate_report_template(claims)
}
return explanation
def _generate_enhanced_summary(self, classification: str, flag_count: int, ai_analysis: Dict) -> str:
ai_suffix = ""
if ai_analysis['ai_available']:
confidence = ai_analysis.get('ai_confidence', 0)
if confidence > 70:
ai_suffix = f" AI analysis confirms with {confidence}% confidence."
elif confidence > 30:
ai_suffix = f" AI analysis supports this assessment ({confidence}% confidence)."
if classification == "HIGH RISK":
return f"This message shows {flag_count} major warning signs of a scam.{ai_suffix} Avoid all interaction and report immediately."
elif classification == "MEDIUM RISK":
return f"This message has {flag_count} concerning elements.{ai_suffix} Exercise extreme caution and verify independently."
elif classification == "LOW RISK":
return f"While not immediately dangerous, this message has {flag_count} minor concerns.{ai_suffix} Stay alert."
else:
return f"This message appears legitimate.{ai_suffix} Always practice good online safety habits."
def _generate_report_template(self, claims: Dict) -> str:
template = f"""
SCAM REPORT TEMPLATE:
Subject: Reporting Suspicious Message/Scam Attempt
Dear [Platform/Authority],
I received a suspicious message that appears to be a scam attempt. Details:
Date Received: {datetime.now().strftime('%Y-%m-%d %H:%M')}
Suspicious Elements:
- Keywords: {', '.join(claims['suspicious_phrases'][:5])}
- URLs: {', '.join(claims['urls'][:3])}
- Contact Info: {', '.join([str(p) for p in claims['phone_numbers'][:2]])}
Message Content: [Include original message here]
Please investigate and take appropriate action.
Best regards,
[Your Name]
""".strip()
return template
def analyze_message(self, text: str, url: str = None) -> Dict:
"""Enhanced analysis combining rule-based and AI methods"""
# Step 1: Extract claims (rule-based)
claims = self.extract_claims(text, url)
# Step 2: Verify claims (rule-based)
verification = self.verify_claims(claims)
# Step 3: AI analysis (new intelligent layer)
ai_analysis = self.ai_analyze_scam(text, url)
# Step 4: Calculate hybrid risk score
risk_score, classification = self.calculate_hybrid_risk_score(claims, verification, ai_analysis)
# Step 5: Generate enhanced explanation
explanation = self.generate_enhanced_explanation(claims, verification, ai_analysis, risk_score, classification)
return {
'claims': claims,
'verification': verification,
'ai_analysis': ai_analysis,
'explanation': explanation,
'analysis_method': 'hybrid' if ai_analysis['ai_available'] else 'rule-based',
'timestamp': datetime.now().isoformat()
}
verifier = ScamSignalVerifier()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/analyze', methods=['POST'])
def analyze():
try:
data = request.get_json()
message_text = data.get('message', '').strip()
message_url = data.get('url', '').strip()
debug_mode = data.get('debug', False) # Add debug parameter
if not message_text and not message_url:
return jsonify({'error': 'Please provide either a message or URL to analyze'}), 400
# Add small delay to prevent API abuse
time.sleep(0.5) # Reduced delay since we now have AI processing time
result = verifier.analyze_message(message_text, message_url if message_url else None)
# Add debug information if requested
if debug_mode:
result['debug_info'] = {
'rule_based_score_breakdown': {
'keyword_score': result['verification']['keyword_score'],
'urgency_score': result['verification']['urgency_score'],
'domain_risk': result['verification'].get('domain_risk', 0),
'contact_info_risk': result['verification']['contact_info_risk'],
'financial_risk': result['verification']['financial_risk']
},
'detected_phrases': result['claims']['suspicious_phrases'],
'ai_available': result['ai_analysis']['ai_available'],
'ai_risk_score': result['ai_analysis'].get('ai_risk_score', 0),
'ai_confidence': result['ai_analysis'].get('ai_confidence', 0)
}
return jsonify(result)
except Exception as e:
return jsonify({'error': f'Analysis failed: {str(e)}'}), 500
@app.route('/test', methods=['POST'])
def test_analysis():
"""Test endpoint with detailed debugging"""
try:
data = request.get_json()
message_text = data.get('message', '').strip()
if not message_text:
return jsonify({'error': 'Please provide a message to test'}), 400
print(f"\n=== TESTING MESSAGE: '{message_text}' ===")
# Test individual components
claims = verifier.extract_claims(message_text)
print(f"Claims extracted: {claims}")
verification = verifier.verify_claims(claims)
print(f"Verification results: {verification}")
ai_analysis = verifier.ai_analyze_scam(message_text)
print(f"AI analysis: {ai_analysis}")
risk_score, classification = verifier.calculate_hybrid_risk_score(claims, verification, ai_analysis)
print(f"Final score: {risk_score}, classification: {classification}")
return jsonify({
'message': message_text,
'claims': claims,
'verification': verification,
'ai_analysis': ai_analysis,
'final_score': risk_score,
'classification': classification,
'debug': 'Check server logs for detailed output'
})
except Exception as e:
print(f"Test failed with error: {str(e)}")
return jsonify({'error': f'Test failed: {str(e)}'}), 500
@app.route('/health')
def health():
"""Health check endpoint that also reports AI availability"""
ai_status = "available" if verifier.openai_client else "unavailable"
api_key_present = "yes" if os.environ.get('OPENAI_API_KEY') else "no"
return jsonify({
'status': 'healthy',
'ai_status': ai_status,
'api_key_present': api_key_present,
'timestamp': datetime.now().isoformat()
})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)