Expand web-check enrichment: traceroute, headers, status, full rendering

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) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-26 03:46:43 -05:00
parent 51ffd2b82c
commit 3c4846d52c

View File

@ -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."