sharktide/shield / email_intel.py
sharktide's picture
download
raw
9.42 kB
"""
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.