sharktide/shield / ip_intel.py
sharktide's picture
download
raw
11.2 kB
"""
IP Intelligence Module
Queries AbuseIPDB, IPQualityScore, ipinfo.io, StopForumSpam, and VirusTotal
concurrently and returns a unified risk assessment.
"""
import asyncio
import os
from typing import Any, Dict, List, Tuple
import httpx
ABUSEIPDB_KEY = os.getenv("ABUSEIPDB_KEY", "")
IPQS_KEY = os.getenv("IPQUALITYSCORE_KEY", "")
IPINFO_TOKEN = os.getenv("IPINFO_TOKEN", "")
VIRUSTOTAL_KEY = os.getenv("VIRUSTOTAL_KEY", "")
# Datacenter / hosting ASN keywords (ipinfo org field)
_DC_KEYWORDS = {
"amazon", "aws", "google", "microsoft", "azure", "digitalocean", "linode",
"vultr", "hetzner", "ovh", "cloudflare", "fastly", "akamai", "choopa",
"quadranet", "datacamp", "psychz", "leaseweb", "serverius", "zayo",
"hurricane electric", "he net", "tzulo", "path network",
}
# ──────────────────────────────────────────────
# Individual source fetchers
# ──────────────────────────────────────────────
async def _abuseipdb(ip: str, client: httpx.AsyncClient) -> Dict[str, Any]:
if not ABUSEIPDB_KEY:
return {}
try:
r = await client.get(
"https://api.abuseipdb.com/api/v2/check",
params={"ipAddress": ip, "maxAgeInDays": 90, "verbose": True},
headers={"Key": ABUSEIPDB_KEY, "Accept": "application/json"},
timeout=8,
)
if r.status_code == 200:
d = r.json().get("data", {})
return {
"abuse_confidence": d.get("abuseConfidenceScore", 0),
"total_reports": d.get("totalReports", 0),
"usage_type": d.get("usageType", ""),
"country": d.get("countryCode", ""),
"isp": d.get("isp", ""),
"is_whitelisted": d.get("isWhitelisted", False),
"last_reported": d.get("lastReportedAt"),
"num_distinct_users": d.get("numDistinctUsers", 0),
}
except Exception:
pass
return {}
async def _ipqualityscore(ip: str, client: httpx.AsyncClient) -> Dict[str, Any]:
if not IPQS_KEY:
return {}
try:
r = await client.get(
f"https://ipqualityscore.com/api/json/ip/{IPQS_KEY}/{ip}",
params={"strictness": 1, "allow_public_access_points": True, "fast": False},
timeout=8,
)
if r.status_code == 200:
d = r.json()
if d.get("success"):
return {
"fraud_score": d.get("fraud_score", 0),
"proxy": d.get("proxy", False),
"vpn": d.get("vpn", False),
"tor": d.get("tor", False),
"recent_abuse": d.get("recent_abuse", False),
"bot_status": d.get("bot_status", False),
"country": d.get("country_code", ""),
"isp": d.get("ISP", ""),
"organization": d.get("organization", ""),
"asn": d.get("ASN", 0),
"connection_type": d.get("connection_type", ""),
"abuse_velocity": d.get("abuse_velocity", ""),
}
except Exception:
pass
return {}
async def _ipinfo(ip: str, client: httpx.AsyncClient) -> Dict[str, Any]:
if not IPINFO_TOKEN:
return {}
try:
r = await client.get(
f"https://ipinfo.io/{ip}",
params={"token": IPINFO_TOKEN},
timeout=8,
)
if r.status_code == 200:
d = r.json()
org = d.get("org", "").lower()
is_datacenter = any(kw in org for kw in _DC_KEYWORDS)
# privacy field available on paid plans
privacy = d.get("privacy", {})
return {
"org": d.get("org", ""),
"country": d.get("country", ""),
"city": d.get("city", ""),
"hostname": d.get("hostname", ""),
"is_datacenter": is_datacenter,
"is_vpn": privacy.get("vpn", False),
"is_proxy": privacy.get("proxy", False),
"is_tor": privacy.get("tor", False),
"is_relay": privacy.get("relay", False),
"is_hosting": privacy.get("hosting", is_datacenter),
}
except Exception:
pass
return {}
async def _stopforumspam(ip: str, client: httpx.AsyncClient) -> Dict[str, Any]:
"""Free API — no key needed."""
try:
r = await client.get(
"https://api.stopforumspam.org/api",
params={"ip": ip, "json": 1},
timeout=6,
)
if r.status_code == 200:
ip_data = r.json().get("ip", {})
return {
"appears": ip_data.get("appears", 0) == 1,
"frequency": ip_data.get("frequency", 0),
"confidence": float(ip_data.get("confidence", 0)),
"lastseen": ip_data.get("lastseen", ""),
}
except Exception:
pass
return {}
async def _virustotal(ip: str, client: httpx.AsyncClient) -> Dict[str, Any]:
if not VIRUSTOTAL_KEY:
return {}
try:
r = await client.get(
f"https://www.virustotal.com/api/v3/ip_addresses/{ip}",
headers={"x-apikey": VIRUSTOTAL_KEY},
timeout=10,
)
if r.status_code == 200:
attrs = r.json().get("data", {}).get("attributes", {})
stats = attrs.get("last_analysis_stats", {})
return {
"malicious": stats.get("malicious", 0),
"suspicious": stats.get("suspicious", 0),
"harmless": stats.get("harmless", 0),
"country": attrs.get("country", ""),
"as_owner": attrs.get("as_owner", ""),
}
except Exception:
pass
return {}
# ──────────────────────────────────────────────
# Aggregator
# ──────────────────────────────────────────────
async def get_ip_intelligence(ip: str) -> Dict[str, Any]:
"""
Query all IP intelligence sources concurrently.
Returns { score: int, reasons: list[str], raw: dict }
"""
if not ip:
return {"score": 0, "reasons": [], "raw": {}}
async with httpx.AsyncClient() as client:
raw_results = await asyncio.gather(
_abuseipdb(ip, client),
_ipqualityscore(ip, client),
_ipinfo(ip, client),
_stopforumspam(ip, client),
_virustotal(ip, client),
return_exceptions=True,
)
abusedb = raw_results[0] if isinstance(raw_results[0], dict) else {}
ipqs = raw_results[1] if isinstance(raw_results[1], dict) else {}
ipinfo = raw_results[2] if isinstance(raw_results[2], dict) else {}
sfs = raw_results[3] if isinstance(raw_results[3], dict) else {}
vt = raw_results[4] if isinstance(raw_results[4], dict) else {}
score: int = 0
reasons: List[str] = []
# ── AbuseIPDB ──────────────────────────────
ac = abusedb.get("abuse_confidence", 0)
if ac >= 80:
score += 45
reasons.append(
f"IP confidence {ac}% on AbuseIPDB "
f"({abusedb.get('total_reports', 0)} reports, "
f"{abusedb.get('num_distinct_users', 0)} distinct users)"
)
elif ac >= 50:
score += 28
reasons.append(f"IP flagged on AbuseIPDB (confidence {ac}%)")
elif ac >= 20:
score += 12
reasons.append(f"IP has low-level AbuseIPDB reports (confidence {ac}%)")
# ── IPQualityScore ─────────────────────────
if ipqs.get("tor"):
score += 40
reasons.append("IP is a Tor exit node (IPQualityScore)")
else:
if ipqs.get("proxy"):
score += 22
reasons.append("IP identified as proxy (IPQualityScore)")
if ipqs.get("vpn"):
score += 15
reasons.append("IP identified as VPN (IPQualityScore)")
if ipqs.get("recent_abuse"):
score += 28
reasons.append("IP has recent abuse history (IPQualityScore)")
if ipqs.get("bot_status"):
score += 32
reasons.append("IP associated with automated bot traffic (IPQualityScore)")
ipqs_fraud = ipqs.get("fraud_score", 0)
if ipqs_fraud >= 85:
score += 20
reasons.append(f"Very high IP fraud score {ipqs_fraud}/100 (IPQualityScore)")
elif ipqs_fraud >= 60:
score += 10
reasons.append(f"Elevated IP fraud score {ipqs_fraud}/100 (IPQualityScore)")
av = ipqs.get("abuse_velocity", "")
if av in ("high", "medium"):
score += 15
reasons.append(f"IP abuse velocity: {av} (IPQualityScore)")
# ── ipinfo ─────────────────────────────────
if ipinfo.get("is_tor"):
score += 35 # may not already be counted above
reasons.append("IP is a Tor exit node (ipinfo)")
if ipinfo.get("is_datacenter") or ipinfo.get("is_hosting"):
score += 12
reasons.append(f"IP belongs to datacenter/hosting provider: {ipinfo.get('org', '')}")
if ipinfo.get("is_relay"):
score += 18
reasons.append("IP is an Apple iCloud Private Relay or similar anonymous relay")
# ── StopForumSpam ──────────────────────────
if sfs.get("appears"):
conf = sfs.get("confidence", 0.0)
freq = sfs.get("frequency", 0)
score += min(35, max(10, int(conf / 3)))
reasons.append(
f"IP in StopForumSpam database (confidence {conf:.1f}%, "
f"seen {freq} times, last: {sfs.get('lastseen', 'unknown')})"
)
# ── VirusTotal ─────────────────────────────
vt_mal = vt.get("malicious", 0)
vt_sus = vt.get("suspicious", 0)
if vt_mal >= 3:
score += 30
reasons.append(f"IP flagged malicious by {vt_mal} VirusTotal vendors")
elif vt_mal >= 1:
score += 12
reasons.append(f"IP flagged malicious by {vt_mal} VirusTotal vendor(s)")
elif vt_sus >= 3:
score += 10
reasons.append(f"IP flagged suspicious by {vt_sus} VirusTotal vendors")
return {
"score": min(score, 100),
"reasons": reasons,
"raw": {
"abuseipdb": abusedb,
"ipqualityscore": ipqs,
"ipinfo": ipinfo,
"stopforumspam": sfs,
"virustotal": vt,
},
}

Xet Storage Details

Size:
11.2 kB
·
Xet hash:
3a32c572fe5a64326a2b16e16e0c5f5d48e5e9f80f5fa47d56b7f29a2f791c1c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.