From 3c4846d52cd199dc17d0852e79d7a181dc8b7e15 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Mar 2026 03:46:43 -0500 Subject: [PATCH] Expand web-check enrichment: traceroute, headers, status, full rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now queries 6 web-check endpoints per IP: - ports — open port scan - dns — reverse DNS / PTR records - block-lists — DNS blocklist check (AdGuard, CloudFlare, etc.) - trace-route — full network path with per-hop latency - headers — HTTP response headers (server, powered-by, etc.) - status — HTTP status code and response time Frontend rendering: - Traceroute displayed as hop chips with latency: IP (45ms) → IP (56ms) - HTTP status with response time - Server headers inline - Errors silently skipped (many endpoints fail on raw IPs) AI analysis now includes: - Blocklist count and names in prompt - Traceroute hops in prompt for network path analysis Co-Authored-By: Claude Opus 4.6 (1M context) --- llm_team_ui.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/llm_team_ui.py b/llm_team_ui.py index ace1782..7b98de2 100644 --- a/llm_team_ui.py +++ b/llm_team_ui.py @@ -1062,6 +1062,53 @@ async function enrichIP(ip, card) { dBox.appendChild(dLabel); dBox.appendChild(dVal); wcGrid.appendChild(dBox); } + // Traceroute + if (d.webcheck.trace_route && d.webcheck.trace_route.result) { + var hops = d.webcheck.trace_route.result.filter(function(h){return typeof h === 'object' && h !== null}); + if (hops.length) { + var trBox = document.createElement('div'); trBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;grid-column:1/-1;margin-top:4px'; + var trLabel = document.createElement('span'); trLabel.style.cssText = 'color:#7a7872;font-size:8px;text-transform:uppercase;letter-spacing:1px'; + trLabel.textContent = 'Traceroute (' + hops.length + ' hops): '; + trBox.appendChild(trLabel); + var trVal = document.createElement('div'); + trVal.style.cssText = 'color:#e8e6e3;margin-top:4px;display:flex;flex-wrap:wrap;gap:2px;align-items:center'; + hops.forEach(function(h, i) { + var hopIp = Object.keys(h)[0]; + var latency = h[hopIp] ? h[hopIp][0] : '?'; + var chip = document.createElement('span'); + chip.style.cssText = 'background:rgba(0,0,0,0.3);border:1px solid #2a2d35;border-radius:2px;padding:2px 6px;font-size:9px;white-space:nowrap'; + chip.textContent = hopIp + ' (' + (typeof latency === 'number' ? latency.toFixed(0) + 'ms' : '?') + ')'; + trVal.appendChild(chip); + if (i < hops.length - 1) { + var arrow = document.createElement('span'); arrow.style.cssText = 'color:#7a7872;font-size:8px'; + arrow.textContent = '→'; trVal.appendChild(arrow); + } + }); + trBox.appendChild(trVal); + wcGrid.appendChild(trBox); + } + } + + // HTTP Status/Headers if available + if (d.webcheck.status && !d.webcheck.status.error) { + var st = d.webcheck.status; + var stBox = document.createElement('div'); stBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px'; + var stLabel = document.createElement('span'); stLabel.style.cssText = 'color:#7a7872;font-size:8px;text-transform:uppercase;letter-spacing:1px'; + stLabel.textContent = 'HTTP Status: '; + var stVal = document.createElement('span'); stVal.style.color = '#e8e6e3'; + stVal.textContent = st.statusCode ? st.statusCode + ' (' + (st.responseTime||'?') + 'ms)' : 'No HTTP'; + stBox.appendChild(stLabel); stBox.appendChild(stVal); wcGrid.appendChild(stBox); + } + if (d.webcheck.headers && !d.webcheck.headers.error) { + var hdrs = d.webcheck.headers; + var hdrKeys = Object.keys(hdrs).filter(function(k){return typeof hdrs[k] === 'string'}).slice(0,6); + if (hdrKeys.length) { + var hBox = document.createElement('div'); hBox.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;grid-column:1/-1;color:#7a7872;margin-top:2px'; + hBox.textContent = 'Headers: ' + hdrKeys.map(function(k){return k+': '+hdrs[k].substring(0,40)}).join(' | '); + wcGrid.appendChild(hBox); + } + } + wcDiv.appendChild(wcGrid); panel.appendChild(wcDiv); } @@ -4056,14 +4103,16 @@ def admin_enrich_ip(): except Exception: pass - # Step 3: Web-Check deep scan (ports, DNS, blocklists) + # Step 3: Web-Check deep scan (ports, DNS, blocklists, traceroute) WEB_CHECK_BASE = "http://localhost:3000/api" webcheck = {} - for endpoint in ["ports", "dns", "block-lists"]: + for endpoint in ["ports", "dns", "block-lists", "trace-route", "headers", "status"]: try: - wc_resp = requests.get(f"{WEB_CHECK_BASE}/{endpoint}?url={ip}", timeout=15) + wc_resp = requests.get(f"{WEB_CHECK_BASE}/{endpoint}?url={ip}", timeout=20) if wc_resp.status_code == 200: - webcheck[endpoint.replace("-", "_")] = wc_resp.json() + data = wc_resp.json() + if not isinstance(data, dict) or not data.get("error"): + webcheck[endpoint.replace("-", "_")] = data except Exception: pass result["webcheck"] = webcheck @@ -4086,7 +4135,11 @@ def admin_enrich_ip(): 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" + wc_ctx += f"Blocked on {len(blocked)} DNS blocklists: {', '.join(blocked[:5])}\n" + if webcheck.get("trace_route") and webcheck["trace_route"].get("result"): + hops = [list(h.keys())[0] for h in webcheck["trace_route"]["result"] if isinstance(h, dict)] + if hops: + wc_ctx += f"Traceroute ({len(hops)} hops): {' → '.join(hops[:8])}\n" log_ctx = "\n".join(ip_logs[-20:]) if ip_logs else "No log entries found."