"""Normalization helpers used by the evaluators. Each function is intentionally tiny and side-effect-free so the 1v1 verifier and the regular /evaluate endpoint can call the same code and stay in lock-step. """ import re def normalize_vuln_key(s: str) -> str: """Canonicalise a typed vulnerability key. Rules: - lowercase - trim - replace spaces / underscores with dashes - strip surrounding punctuation """ if not s: return "" s = s.strip().lower() s = re.sub(r"[\s_]+", "-", s) s = re.sub(r"[^a-z0-9\-]", "", s) return s def normalize_str(s: str) -> str: return (s or "").strip().lower() def ip_matches(user_ip: str, expected_ip: str) -> bool: """Loose IP match: trim, lowercase, ignore trailing port or zone id.""" u = normalize_str(user_ip).split("/")[0] e = normalize_str(expected_ip).split("/")[0] return u == e and u != "" def ioc_matches(user_ioc: str, expected_ioc: str) -> bool: """IOC match: case-insensitive substring.""" u = normalize_str(user_ioc) e = normalize_str(expected_ioc) if not u or not e: return False return e in u or u in e def timestamp_close(user_ts: str, expected_ts: str) -> bool: """Timestamp match: exact OR every alphanumeric token of the expected timestamp appears in the user input (lenient by design).""" u = normalize_str(user_ts) e = normalize_str(expected_ts) if not u or not e: return False if u == e: return True # Try day-hour match (e.g., "15/Dec" or "Mar 12" or "2024-11-08") e_tokens = re.findall(r"[a-zA-Z]+|\d+", e) if not e_tokens: return False return all(tok.lower() in u for tok in e_tokens[:2])