| """ | |
| Email Intelligence Module | |
| Queries EmailRep.io, AbstractAPI email validation, and HaveIBeenPwned concurrently. | |
| """ | |
| import asyncio | |
| import os | |
| from typing import Any, Dict, List | |
| from urllib.parse import quote | |
| import httpx | |
| EMAILREP_KEY = os.getenv("EMAILREP_KEY", "") | |
| ABSTRACTAPI_KEY = os.getenv("ABSTRACTAPI_EMAIL_KEY", "") | |
| HIBP_KEY = os.getenv("HIBP_KEY", "") | |
| # ────────────────────────────────────────────── | |
| # Individual source fetchers | |
| # ────────────────────────────────────────────── | |
| async def _emailrep(email: str, client: httpx.AsyncClient) -> Dict[str, Any]: | |
| """ | |
| emailrep.io — free tier: 100 req/day without key, higher with key. | |
| Returns reputation, suspicious flag, and rich detail flags. | |
| """ | |
| headers = {"User-Agent": "InferencePort-Shield/1.0"} | |
| if EMAILREP_KEY: | |
| headers["Key"] = EMAILREP_KEY | |
| try: | |
| r = await client.get( | |
| f"https://emailrep.io/{quote(email)}", | |
| headers=headers, | |
| timeout=8, | |
| ) | |
| if r.status_code == 200: | |
| d = r.json() | |
| details = d.get("details", {}) | |
| return { | |
| "reputation": d.get("reputation", "none"), | |
| "suspicious": d.get("suspicious", False), | |
| "references": d.get("references", 0), | |
| "blacklisted": details.get("blacklisted", False), | |
| "malicious_activity": details.get("malicious_activity", False), | |
| "malicious_activity_recent": details.get("malicious_activity_recent", False), | |
| "credentials_leaked": details.get("credentials_leaked", False), | |
| "credentials_leaked_recent": details.get("credentials_leaked_recent", False), | |
| "data_breach": details.get("data_breach", False), | |
| "last_seen": details.get("last_seen", "never"), | |
| "spam": details.get("spam", False), | |
| "spam_dns_mx": details.get("spam_dns_mx", False), | |
| "spoofable": details.get("spoofable", False), | |
| "free_provider": details.get("free_provider", False), | |
| "disposable": details.get("disposable", False), | |
| "deliverable": details.get("deliverable", False), | |
| "valid_mx": details.get("valid_mx", False), | |
| "profiles": details.get("profiles", []), | |
| } | |
| except Exception: | |
| pass | |
| return {} | |
| async def _abstractapi(email: str, client: httpx.AsyncClient) -> Dict[str, Any]: | |
| """ | |
| abstractapi.com — free tier: 100 req/month. | |
| Good for deliverability and disposable checks. | |
| """ | |
| if not ABSTRACTAPI_KEY: | |
| return {} | |
| try: | |
| r = await client.get( | |
| "https://emailvalidation.abstractapi.com/v1/", | |
| params={"api_key": ABSTRACTAPI_KEY, "email": email}, | |
| timeout=8, | |
| ) | |
| if r.status_code == 200: | |
| d = r.json() | |
| def _val(field: str) -> Any: | |
| """AbstractAPI wraps booleans as {"value": bool, "text": str}.""" | |
| v = d.get(field, {}) | |
| return v.get("value") if isinstance(v, dict) else v | |
| return { | |
| "deliverability": d.get("deliverability", "UNKNOWN"), | |
| "quality_score": float(d.get("quality_score", "0") or "0"), | |
| "is_valid_format": _val("is_valid_format"), | |
| "is_free_email": _val("is_free_email"), | |
| "is_disposable": _val("is_disposable_email"), | |
| "is_role_email": _val("is_role_email"), | |
| "is_catchall": _val("is_catchall_email"), | |
| "is_mx_found": _val("is_mx_found"), | |
| "is_smtp_valid": _val("is_smtp_valid"), | |
| "autocorrect": d.get("autocorrect", ""), | |
| } | |
| except Exception: | |
| pass | |
| return {} | |
| async def _hibp(email: str, client: httpx.AsyncClient) -> Dict[str, Any]: | |
| """ | |
| HaveIBeenPwned v3 — paid API key required (~$3.50/mo). | |
| Returns breach names if email was found in known breaches. | |
| """ | |
| if not HIBP_KEY: | |
| return {} | |
| try: | |
| r = await client.get( | |
| f"https://haveibeenpwned.com/api/v3/breachedaccount/{quote(email)}", | |
| headers={ | |
| "hibp-api-key": HIBP_KEY, | |
| "user-agent": "InferencePort-Shield", | |
| }, | |
| params={"truncateResponse": True}, | |
| timeout=8, | |
| ) | |
| if r.status_code == 200: | |
| breaches = r.json() | |
| return { | |
| "found": True, | |
| "breach_count": len(breaches), | |
| "breach_names": [b.get("Name", "") for b in breaches[:10]], | |
| } | |
| if r.status_code == 404: | |
| return {"found": False, "breach_count": 0, "breach_names": []} | |
| except Exception: | |
| pass | |
| return {} | |
| # ────────────────────────────────────────────── | |
| # Aggregator | |
| # ────────────────────────────────────────────── | |
| async def get_email_intelligence(email: str) -> Dict[str, Any]: | |
| """ | |
| Query all email intelligence sources concurrently. | |
| Returns { score: int, reasons: list[str], raw: dict } | |
| """ | |
| if not email or "@" not in email: | |
| return {"score": 0, "reasons": [], "raw": {}} | |
| async with httpx.AsyncClient() as client: | |
| raw_results = await asyncio.gather( | |
| _emailrep(email, client), | |
| _abstractapi(email, client), | |
| _hibp(email, client), | |
| return_exceptions=True, | |
| ) | |
| emailrep = raw_results[0] if isinstance(raw_results[0], dict) else {} | |
| abstract = raw_results[1] if isinstance(raw_results[1], dict) else {} | |
| hibp = raw_results[2] if isinstance(raw_results[2], dict) else {} | |
| score: int = 0 | |
| reasons: List[str] = [] | |
| # ── EmailRep ─────────────────────────────── | |
| if emailrep.get("blacklisted"): | |
| score += 50 | |
| reasons.append("Email address is blacklisted (EmailRep)") | |
| if emailrep.get("malicious_activity_recent"): | |
| score += 40 | |
| reasons.append("Email has recent malicious activity (EmailRep)") | |
| elif emailrep.get("malicious_activity"): | |
| score += 25 | |
| reasons.append("Email has history of malicious activity (EmailRep)") | |
| if emailrep.get("spam"): | |
| score += 25 | |
| reasons.append("Email associated with spam campaigns (EmailRep)") | |
| if emailrep.get("credentials_leaked_recent"): | |
| score += 20 | |
| reasons.append("Email credentials leaked recently — likely compromised (EmailRep)") | |
| elif emailrep.get("credentials_leaked"): | |
| score += 10 | |
| reasons.append("Email credentials have been leaked previously (EmailRep)") | |
| if emailrep.get("spoofable"): | |
| score += 12 | |
| reasons.append("Email domain is spoofable (no DMARC/SPF) — high phishing risk") | |
| if emailrep.get("disposable"): | |
| score += 30 | |
| reasons.append("Disposable email address detected (EmailRep)") | |
| rep = emailrep.get("reputation", "none") | |
| if rep == "none": | |
| score += 8 | |
| reasons.append("Email has no established reputation (EmailRep)") | |
| # ── AbstractAPI ──────────────────────────── | |
| if abstract.get("is_disposable"): | |
| score = max(score, 35) # ensure minimum if not already caught | |
| if "Disposable" not in str(reasons): | |
| reasons.append("Disposable email provider confirmed (AbstractAPI)") | |
| if abstract.get("deliverability") == "UNDELIVERABLE": | |
| score += 20 | |
| reasons.append("Email address is undeliverable (AbstractAPI)") | |
| qs = abstract.get("quality_score", 1.0) | |
| if qs is not None and qs < 0.30: | |
| score += 15 | |
| reasons.append(f"Very low email quality score: {qs:.2f} (AbstractAPI)") | |
| elif qs is not None and qs < 0.60: | |
| score += 7 | |
| reasons.append(f"Low email quality score: {qs:.2f} (AbstractAPI)") | |
| if abstract.get("is_role_email"): | |
| score += 8 | |
| reasons.append("Email is a role address (e.g. admin@, info@) — not a personal account") | |
| # ── HaveIBeenPwned ───────────────────────── | |
| if hibp.get("found"): | |
| bc = hibp.get("breach_count", 0) | |
| if bc >= 5: | |
| score += 18 | |
| reasons.append(f"Email found in {bc} data breaches (HIBP) — likely reused/compromised credentials") | |
| elif bc >= 1: | |
| score += 8 | |
| reasons.append(f"Email found in {bc} data breach(es) (HIBP): {', '.join(hibp.get('breach_names', []))}") | |
| return { | |
| "score": min(score, 100), | |
| "reasons": reasons, | |
| "raw": { | |
| "emailrep": emailrep, | |
| "abstractapi": abstract, | |
| "hibp": hibp, | |
| }, | |
| } | |
Xet Storage Details
- Size:
- 9.42 kB
- Xet hash:
- ad4b11bce1e65d10a6019c69314728f52ebd9b7405231ce0e33db59bf950da9f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.