Integrate web-check Docker for deep IP enrichment

Setup:
- lissy93/web-check running in Docker on port 3000
- Queries ports, DNS, and blocklist endpoints per IP

Enrichment now includes 4 layers:
1. Geolocation (ip-api.com) — country, ISP, proxy/hosting flags
2. Web-Check deep scan — open ports, DNS/PTR, blocklist status
3. Security log aggregation — all activity for that IP
4. AI analysis (qwen2.5) — gets ALL above data as context

Frontend rendering:
- Open ports displayed in red (security risk indicators)
- Blocklist status: "3/8 blocked (AdGuard, AdGuard Family, ...)"
- Reverse DNS (PTR records)
- All data feeds into AI analysis prompt for richer verdicts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-26 03:39:42 -05:00
parent 472a5d0917
commit e816e81820

View File

@ -1012,6 +1012,60 @@ async function enrichIP(ip, card) {
panel.appendChild(geoDiv); panel.appendChild(geoDiv);
} }
// Web-Check section (ports, blocklists)
if (d.webcheck) {
var wcDiv = document.createElement('div');
wcDiv.style.cssText = 'margin-bottom:10px';
var wcTitle = document.createElement('div');
wcTitle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:8px;text-transform:uppercase;letter-spacing:2px;color:#d946ef;margin-bottom:6px;font-weight:700';
wcTitle.textContent = 'Deep Scan (web-check)';
wcDiv.appendChild(wcTitle);
var wcGrid = document.createElement('div');
wcGrid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:6px';
// Open ports
if (d.webcheck.ports && d.webcheck.ports.openPorts) {
var ports = d.webcheck.ports.openPorts;
var pBox = document.createElement('div'); pBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px';
var pLabel = document.createElement('span'); pLabel.style.cssText = 'color:#7a7872;font-size:8px;text-transform:uppercase;letter-spacing:1px';
pLabel.textContent = 'Open Ports: ';
var pVal = document.createElement('span'); pVal.style.cssText = 'color:#e05252;font-weight:700';
pVal.textContent = ports.length ? ports.join(', ') : 'none found';
pBox.appendChild(pLabel); pBox.appendChild(pVal); wcGrid.appendChild(pBox);
}
// Blocklists
if (d.webcheck.block_lists && d.webcheck.block_lists.blocklists) {
var bls = d.webcheck.block_lists.blocklists;
var blocked = bls.filter(function(b){return b.isBlocked});
var clean = bls.filter(function(b){return !b.isBlocked});
var bBox = document.createElement('div'); bBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;grid-column:1/-1';
var bLabel = document.createElement('span'); bLabel.style.cssText = 'color:#7a7872;font-size:8px;text-transform:uppercase;letter-spacing:1px';
bLabel.textContent = 'Blocklists: ';
var bVal = document.createElement('span');
bVal.style.cssText = 'color:' + (blocked.length ? '#e05252' : '#4ade80');
bVal.textContent = blocked.length
? blocked.length + '/' + bls.length + ' blocked (' + blocked.map(function(b){return b.server}).join(', ') + ')'
: 'Clean on all ' + bls.length + ' lists';
bBox.appendChild(bLabel); bBox.appendChild(bVal); wcGrid.appendChild(bBox);
}
// DNS
if (d.webcheck.dns) {
var dns = d.webcheck.dns;
var ptr = dns.PTR && dns.PTR.length ? dns.PTR.join(', ') : 'none';
var dBox = document.createElement('div'); dBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;grid-column:1/-1';
var dLabel = document.createElement('span'); dLabel.style.cssText = 'color:#7a7872;font-size:8px;text-transform:uppercase;letter-spacing:1px';
dLabel.textContent = 'Reverse DNS: ';
var dVal = document.createElement('span'); dVal.style.color = '#e8e6e3';
dVal.textContent = ptr;
dBox.appendChild(dLabel); dBox.appendChild(dVal); wcGrid.appendChild(dBox);
}
wcDiv.appendChild(wcGrid);
panel.appendChild(wcDiv);
}
// AI Analysis section // AI Analysis section
if (d.ai_analysis && !d.ai_analysis.error) { if (d.ai_analysis && !d.ai_analysis.error) {
var ai = d.ai_analysis; var ai = d.ai_analysis;
@ -4002,7 +4056,7 @@ def admin_enrich_ip():
except Exception: except Exception:
pass pass
# Step 3: AI threat analysis with full context # Step 3: AI threat analysis with full context (including web-check data)
try: try:
geo_ctx = "" geo_ctx = ""
if result["geo"] and not result["geo"].get("error"): if result["geo"] and not result["geo"].get("error"):
@ -4011,11 +4065,22 @@ def admin_enrich_ip():
geo_ctx += f"ISP: {g.get('isp','?')} | Org: {g.get('org','?')} | AS: {g.get('as','?')}\n" geo_ctx += f"ISP: {g.get('isp','?')} | Org: {g.get('org','?')} | AS: {g.get('as','?')}\n"
geo_ctx += f"Proxy: {g.get('proxy',False)} | Hosting: {g.get('hosting',False)} | Mobile: {g.get('mobile',False)}\n" geo_ctx += f"Proxy: {g.get('proxy',False)} | Hosting: {g.get('hosting',False)} | Mobile: {g.get('mobile',False)}\n"
# Add web-check data if available
wc_ctx = ""
if webcheck.get("ports"):
open_ports = webcheck["ports"].get("openPorts", [])
if open_ports:
wc_ctx += f"Open ports: {', '.join(str(p) for p in open_ports)}\n"
if webcheck.get("block_lists"):
blocked = [b["server"] for b in webcheck["block_lists"].get("blocklists", []) if b.get("isBlocked")]
if blocked:
wc_ctx += f"Blocked on: {', '.join(blocked[:5])}\n"
log_ctx = "\n".join(ip_logs[-20:]) if ip_logs else "No log entries found." log_ctx = "\n".join(ip_logs[-20:]) if ip_logs else "No log entries found."
prompt = ( prompt = (
f"You are a cybersecurity analyst. Provide a detailed threat assessment for IP {ip}.\n\n" f"You are a cybersecurity analyst. Provide a detailed threat assessment for IP {ip}.\n\n"
f"{geo_ctx}\n" f"{geo_ctx}{wc_ctx}\n"
f"Activity log ({len(ip_logs)} total entries, showing last 20):\n{log_ctx}\n\n" f"Activity log ({len(ip_logs)} total entries, showing last 20):\n{log_ctx}\n\n"
"Provide your analysis as JSON:\n" "Provide your analysis as JSON:\n"
'{"threat_level": "none|low|medium|high|critical",\n' '{"threat_level": "none|low|medium|high|critical",\n'
@ -4052,6 +4117,17 @@ def admin_enrich_ip():
except Exception as e: except Exception as e:
result["ai_analysis"] = {"error": str(e)} result["ai_analysis"] = {"error": str(e)}
# Step 4: Web-Check deep scan (ports, DNS, blocklists)
WEB_CHECK_BASE = "http://localhost:3000/api"
webcheck = {}
for endpoint in ["ports", "dns", "block-lists"]:
try:
wc_resp = requests.get(f"{WEB_CHECK_BASE}/{endpoint}?url={ip}", timeout=15)
if wc_resp.status_code == 200:
webcheck[endpoint.replace("-", "_")] = wc_resp.json()
except Exception:
pass
result["webcheck"] = webcheck
result["log_count"] = len(ip_logs) result["log_count"] = len(ip_logs)
return jsonify(result) return jsonify(result)