urlaz / predict_url.py
alixansec's picture
Initial clean release of URLAZ Phishing Detector
cae7b7c verified
Raw
History Blame Contribute Delete
5.42 kB
"""
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
# OPSEC Clean Dynamic Relative Paths
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)
# Step 1: Check Allowlist Bypass
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:
# Step 2: Tier 2 ML Classifier (38 Features)
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})"
# Identify key signals cleanly
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}")
# Check Homoglyph / Typosquatting / Leetspeak Signal
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