Spaces:
Sleeping
Sleeping
| """ | |
| TrajAudit Scoring Engine β Trajectory-aware risk accumulation for conversational monitoring. | |
| The core insight: adversarial conversations distribute malicious intent across turns. | |
| Single-turn classifiers miss this because each turn in isolation scores low. The trajectory | |
| score accumulates evidence across the full conversation, detecting the phase-structured | |
| manipulation pattern that defines real-world hybrid human-bot scam pipelines. | |
| Phase Model: | |
| RAPPORT β Behaviorally indistinguishable from normal social interaction | |
| EXTRACTION β Curiosity, shared interests, flattery, information gathering | |
| CAPTURE β Authority signals, social proof, platform redirect, urgency | |
| CONVERSION β Payment request, wallet address, subscription, credential harvesting | |
| """ | |
| from typing import List, Dict, Optional, Tuple | |
| # ββ Signal Taxonomy ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Derived from 6 months of empirical observation of 4 deployed adversarial | |
| # pipelines on Snapchat. Each category maps to observable behavioral patterns | |
| # in the RAPPORT β EXTRACTION β CAPTURE β CONVERSION phase structure. | |
| SIGNAL_WEIGHTS = { | |
| "financial": { | |
| "weight": 0.15, | |
| "keywords": [ | |
| "invest", "crypto", "money", "profit", "deposit", "trading", "$", | |
| "usdt", "cashapp", "paypal", "betting", "winnings", "bet", "30%", | |
| "70%", "payout", "earn", "win", "risk-free", "forex", "returns", | |
| "wallet", "transfer", "200", "500", "1000", "4200", "bitcoin", | |
| "zelle", "fee", "refundable", "paycheck", "shipping" | |
| ], | |
| "description": "Financial terms indicating extraction/conversion intent" | |
| }, | |
| "urgency": { | |
| "weight": 0.10, | |
| "keywords": [ | |
| "hurry", "closing", "now", "quick", "limited", "first time", | |
| "last chance", "tonight", "immediately", "clockwork", "deadline", | |
| "tomorrow", "before midnight", "spots fill", "before friday" | |
| ], | |
| "description": "Temporal pressure signals to accelerate decision-making" | |
| }, | |
| "authority": { | |
| "weight": 0.08, | |
| "keywords": [ | |
| "mentor", "team", "expert", "years", "reliable", "uncle", "analyst", | |
| "presidents", "clubs", "uk", "tipico", "settlement", "singapore", | |
| "algorithm", "signals", "whale", "goldman", "coach", "recruiter", | |
| "global", "200+", "300+", "500+", "2000+" | |
| ], | |
| "description": "Social proof and authority indicators used in CAPTURE phase" | |
| }, | |
| "rapport": { | |
| "weight": 0.04, | |
| "keywords": [ | |
| "interesting", "noticed", "usually", "something told me", "honestly", | |
| "nervous too", "totally understand", "life changing", "genuine", | |
| "different", "impressive", "cutie", "vibe", "click" | |
| ], | |
| "description": "Rapport-building language β low weight individually, contextualizes trajectory" | |
| }, | |
| "extraction": { | |
| "weight": 0.18, | |
| "keywords": [ | |
| "platform", "register", "sign up", "join", "link", "code", "referral", | |
| "stake", "upfront", "deposit", "wallet", "send me", "cashapp", | |
| "bit.ly", "binance", "telegram", "verify", "age verification", | |
| "card info", "protonmail" | |
| ], | |
| "description": "Direct extraction signals β highest weight, marks CONVERSION phase" | |
| } | |
| } | |
| def score_message(text: str) -> float: | |
| """Score a single message for adversarial signal density. | |
| Returns a float [0, 1] representing how many adversarial signals | |
| are present in this individual message. | |
| """ | |
| t = text.lower() | |
| score = 0.0 | |
| for category, config in SIGNAL_WEIGHTS.items(): | |
| weight = config["weight"] | |
| for keyword in config["keywords"]: | |
| if keyword in t: | |
| score += weight | |
| return min(round(score, 3), 1.0) | |
| def trajectory_score(messages: List[Dict]) -> float: | |
| """Compute cumulative trajectory risk score across all BOT messages. | |
| This is the core algorithm: risk accumulates across turns with | |
| diminishing returns (the 0.3 decay factor prevents saturation). | |
| The trajectory score captures the behavioral arc β from innocent | |
| RAPPORT through progressive EXTRACTION to CONVERSION. | |
| Args: | |
| messages: List of message dicts with 'role' and 'text' keys | |
| Returns: | |
| Cumulative risk score [0, 0.99] | |
| """ | |
| cumulative = 0.0 | |
| for msg in messages: | |
| if msg["role"] in ("BOT", "bot"): | |
| signal = score_message(msg["text"]) | |
| # Accumulate with decay to model trajectory arc | |
| cumulative = min(cumulative + signal * (1 - cumulative * 0.3), 0.99) | |
| return round(cumulative, 3) | |
| def single_turn_baseline(messages: List[Dict]) -> List[float]: | |
| """Simulate a single-turn classifier (peak score across individual messages). | |
| This represents the standard approach: evaluate each message independently | |
| and take the maximum observed score. Demonstrates the Detection Latency | |
| gap β early messages score near zero because adversarial intent is not | |
| yet present in the text. | |
| Args: | |
| messages: List of message dicts with 'role' and 'text' keys | |
| Returns: | |
| List of peak single-turn scores at each conversation position | |
| """ | |
| scores = [] | |
| peak = 0.0 | |
| for msg in messages: | |
| if msg["role"] in ("BOT", "bot"): | |
| peak = max(peak, score_message(msg["text"]) * 1.1) | |
| scores.append(round(min(peak, 0.99), 2)) | |
| return scores | |
| def trajectory_scores_over_turns(messages: List[Dict]) -> List[float]: | |
| """Compute trajectory score at each turn position. | |
| Returns a list where index i is the trajectory score using | |
| messages[0:i+1], enabling visualization of the risk accumulation | |
| curve alongside the single-turn baseline. | |
| """ | |
| return [trajectory_score(messages[:i+1]) for i in range(len(messages))] | |
| def classify_risk(score: float) -> Tuple[str, str]: | |
| """Map a risk score to a phase label and severity. | |
| Returns: | |
| Tuple of (phase_label, severity) where severity is one of | |
| 'safe', 'low', 'medium', 'high' | |
| """ | |
| if score >= 0.6: | |
| return "π΄ Extraction", "high" | |
| elif score >= 0.35: | |
| return "π Trust Building", "medium" | |
| elif score >= 0.15: | |
| return "π‘ Rapport", "low" | |
| else: | |
| return "π’ Opening", "safe" | |
| def detection_gap( | |
| traj_scores: List[float], | |
| baseline_scores: List[float], | |
| threshold: float = 0.75 | |
| ) -> Dict: | |
| """Compute the detection latency gap between trajectory and baseline scoring. | |
| This quantifies the paper's core finding: how many turns earlier does | |
| trajectory-aware monitoring detect adversarial behavior compared to | |
| single-turn classification? | |
| Returns: | |
| Dict with 'traj_flag_turn', 'baseline_flag_turn', 'gap_turns', | |
| and 'description' explaining the gap | |
| """ | |
| traj_flag = next((i+1 for i, s in enumerate(traj_scores) if s >= threshold), None) | |
| base_flag = next((i+1 for i, s in enumerate(baseline_scores) if s >= threshold), None) | |
| gap = None | |
| if traj_flag and base_flag: | |
| gap = base_flag - traj_flag | |
| return { | |
| "traj_flag_turn": traj_flag, | |
| "baseline_flag_turn": base_flag, | |
| "gap_turns": gap, | |
| "description": _describe_gap(traj_flag, base_flag, gap) | |
| } | |
| def _describe_gap(traj_flag, base_flag, gap): | |
| if traj_flag and not base_flag: | |
| return f"TrajAudit flagged at turn {traj_flag}. Single-turn baseline never reached threshold." | |
| elif traj_flag and base_flag and gap and gap > 0: | |
| return f"TrajAudit flagged {gap} turns earlier (turn {traj_flag} vs turn {base_flag})." | |
| elif traj_flag and base_flag and gap == 0: | |
| return "Both methods flagged at the same turn." | |
| elif not traj_flag: | |
| return "Conversation classified as safe by both methods." | |
| return "Unable to compute gap." | |