| """ |
| URLAZ Phishing Predictor CLI (P0 IDN Bias Fix Engine Enabled) |
| ============================================================ |
| Run interactive URL classification using: |
| - Tier 1: High-Confidence Allowlist Pre-Filter (Clean Official Domains) |
| - Tier 2: 38 Structural Feature LightGBM Model (P0 IDN Bias Fix Engine + UTS #39 Confusable Skeleton) |
| - Operational Threshold t = 0.95 (Exact FPR < 0.09%, Production Precision 52.46%) |
| """ |
|
|
| import os |
| import sys |
| import json |
| import joblib |
| import numpy as np |
|
|
| |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| BASE_DIR = os.path.dirname(SCRIPT_DIR) |
| DATA_DIR = os.path.join(BASE_DIR, "data") |
| sys.path.append(os.path.join(BASE_DIR, "scripts")) |
|
|
| from train_production_model_v6 import ( |
| extract_features_v5, |
| check_allowlist_bypass, |
| extract_domain_from_url, |
| get_registered_domain, |
| detect_homoglyph_typosquatting, |
| decode_punycode_and_to_skeleton, |
| FEATURE_NAMES_V5, |
| FULL_ALLOWLIST_DOMAINS, |
| AZ_BRAND_PATTERNS |
| ) |
|
|
| DIST_MODEL_PATH = os.path.join(BASE_DIR, "data_dist", "urlaz_phishing_detector.joblib") |
| DATA_MODEL_PATH = os.path.join(DATA_DIR, "az_phish_model_v6.joblib") |
|
|
| MODEL_PATH = DIST_MODEL_PATH if os.path.exists(DIST_MODEL_PATH) else DATA_MODEL_PATH |
|
|
| if not os.path.exists(MODEL_PATH): |
| raise FileNotFoundError(f"Model weights file not found at {MODEL_PATH}") |
|
|
| clf = joblib.load(MODEL_PATH) |
| OPERATIONAL_THRESHOLD = 0.95 |
|
|
| def analyze_url(url: str) -> dict: |
| url_clean = str(url).strip() |
| domain = extract_domain_from_url(url_clean) |
| registered_domain = get_registered_domain(domain) |
| |
| |
| is_allowlisted = check_allowlist_bypass(url_clean) |
| |
| if is_allowlisted: |
| risk_prob = 0.0 |
| verdict = "BENIGN / SAFE π’" |
| decision_path = "Tier 1 Allowlist Pre-Filter (0ms Latency Bypass)" |
| key_signals = [f"Registered Domain '{registered_domain}' is in High-Confidence Allowlist"] |
| else: |
| |
| features = extract_features_v5(url_clean) |
| risk_prob = float(clf.predict_proba([features])[0][1]) |
| |
| if risk_prob >= OPERATIONAL_THRESHOLD: |
| verdict = "PHISHING / HIGH RISK π΄" |
| elif risk_prob >= 0.50: |
| verdict = "SUSPICIOUS / MEDIUM RISK π‘" |
| else: |
| verdict = "BENIGN / LOW RISK π’" |
| |
| decision_path = f"Tier 2 LightGBM Classifier (Operational Threshold = {OPERATIONAL_THRESHOLD})" |
| |
| |
| key_signals = [] |
| skeleton_dom = decode_punycode_and_to_skeleton(domain) |
| brand_matches = [b for b in AZ_BRAND_PATTERNS if b in skeleton_dom] |
| if brand_matches: |
| key_signals.append(f"Target Brand Keyword Match: {brand_matches}") |
| |
| |
| typo_flag, typo_dist, is_homoglyph = detect_homoglyph_typosquatting(domain) |
| if is_homoglyph: |
| key_signals.append(f"UTS #39 Homoglyph / IDN Punycode Attack Detected in '{domain}' -> skeleton: '{skeleton_dom}'") |
| elif typo_flag > 0 and 1 <= typo_dist <= 2: |
| key_signals.append(f"Leetspeak / Typosquatting Impersonation Detected in '{domain}' (Edit Dist = {typo_dist})") |
| |
| if domain != registered_domain and any(b in skeleton_dom for b in AZ_BRAND_PATTERNS): |
| key_signals.append(f"Subdomain Brand Spoofing detected in '{domain}'") |
| if any(domain.endswith(t) for t in ['.xyz', '.top', '.site', '.cc', '.fun', '.online', '.cfd', '.vip']): |
| key_signals.append(f"High-Risk TLD observed: '{domain.split('.')[-1]}'") |
| if len(url_clean) > 75: |
| key_signals.append(f"Suspiciously long URL ({len(url_clean)} chars)") |
| |
| risk_score = int(round(risk_prob * 100)) |
| |
| return { |
| "url": url_clean, |
| "domain": domain, |
| "registered_domain": registered_domain, |
| "verdict": verdict, |
| "risk_score": risk_score, |
| "risk_probability": round(risk_prob, 4), |
| "decision_path": decision_path, |
| "key_signals": key_signals if key_signals else ["Normal structural patterns observed"] |
| } |
|
|
| def print_result(res: dict): |
| print("\n" + "β" * 70) |
| print(f" π URL ANALYSIS REPORT: {res['url']}") |
| print("β" * 70) |
| print(f" VERDICT : {res['verdict']}") |
| print(f" RISK SCORE (0-100) : {res['risk_score']} / 100 (Probability: {res['risk_probability']*100:.2f}%)") |
| print(f" DECISION PATH : {res['decision_path']}") |
| print(f" DOMAIN INFO : {res['domain']} (Registered: {res['registered_domain']})") |
| print(" KEY DETECTED SIGNALS:") |
| for sig in res['key_signals']: |
| print(f" β’ {sig}") |
| print("β" * 70 + "\n") |
|
|
| if __name__ == "__main__": |
| if len(sys.argv) > 1: |
| test_url = sys.argv[1] |
| res = analyze_url(test_url) |
| print_result(res) |
| else: |
| print("URLAZ Phishing Predictor CLI Ready. Enter a URL to analyze (or 'q' to quit):") |
| while True: |
| try: |
| inp = input("\nURL > ").strip() |
| if not inp or inp.lower() in ('q', 'exit', 'quit'): |
| break |
| res = analyze_url(inp) |
| print_result(res) |
| except (KeyboardInterrupt, EOFError): |
| break |
|
|