fix: P0 audit bugs — bot-detector + dashboard + SQL

Bot-detector:
- B1.1: campaign_id and raw_anomaly_score now inserted into ml_detected_anomalies
- B1.4/B1.5: log_decision argument order fixed (cycle_id, name)
- B1.7: AE broadcast error — model now returns features list, scoring
  uses model's features instead of current cycle's (prevents dim mismatch)
- B1.8: Anubis ALLOW bots now get bot_name from anubis_bot_name

Dashboard:
- C1.1: XSS in ip_detail.html — {{ ip | tojson }} instead of raw string
- C1.2: Stored XSS via innerHTML — added escapeHtml() helper, all user-facing
  formatters (fmtIP, fmtASN, fmtCountry, fmtJA4, fmtBotName, fmtLabel) sanitized
- C2.1: status filter now correctly filters http_version column
- C2.2: heatmap toDayOfWeek() - 1 for 0-indexed JS days

SQL:
- B1.3: view_ip_recurrence worst_score uses max() not min() (0=normal, 1=anomal)
- B1.6: view_resource_cascade_1h joined into view_thesis_features_1h (§5.4)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-08 23:33:00 +02:00
parent b66d41a200
commit db306fb9da
6 changed files with 45 additions and 25 deletions

View File

@ -119,10 +119,15 @@
};
return `<span class="badge ${map[level]||'badge-normal'}">${level}</span>`;
}
function escapeHtml(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function fmtIP(ip) {
if (!ip) return '';
let s = String(ip).replace('::ffff:','');
return `<a href="/ip/${encodeURIComponent(s)}" class="text-brand-500 hover:underline">${s}</a>`;
return `<a href="/ip/${encodeURIComponent(s)}" class="text-brand-500 hover:underline">${escapeHtml(s)}</a>`;
}
function fmtScore(v) {
let n = parseFloat(v);
@ -134,24 +139,24 @@
// ── Navigation helpers ──
function fmtASN(org) {
if (!org) return '';
return `<a href="/detections?asn_org=${encodeURIComponent(org)}" class="text-blue-400 hover:underline cursor-pointer">${org}</a>`;
return `<a href="/detections?asn_org=${encodeURIComponent(org)}" class="text-blue-400 hover:underline cursor-pointer">${escapeHtml(org)}</a>`;
}
function fmtCountry(cc) {
if (!cc) return '';
const flags = {'FR':'🇫🇷','DE':'🇩🇪','NL':'🇳🇱','GB':'🇬🇧','ES':'🇪🇸','US':'🇺🇸','RU':'🇷🇺','IT':'🇮🇹','JP':'🇯🇵','CN':'🇨🇳','KR':'🇰🇷','BR':'🇧🇷','AU':'🇦🇺','CA':'🇨🇦','IN':'🇮🇳'};
return `<a href="/detections?country_code=${encodeURIComponent(cc)}" class="hover:underline cursor-pointer">${flags[cc]||'🏳️'} ${cc}</a>`;
return `<a href="/detections?country_code=${encodeURIComponent(cc)}" class="hover:underline cursor-pointer">${flags[cc]||'🏳️'} ${escapeHtml(cc)}</a>`;
}
function fmtJA4(ja4) {
if (!ja4) return '';
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs" title="${ja4}">${ja4.substring(0,20)}…</a>`;
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs" title="${escapeHtml(ja4)}">${escapeHtml(ja4.substring(0,20))}…</a>`;
}
function fmtJA4Full(ja4) {
if (!ja4) return '';
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs">${ja4}</a>`;
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs">${escapeHtml(ja4)}</a>`;
}
function fmtBotName(name) {
if (!name) return '';
return `<a href="/detections?bot_name=${encodeURIComponent(name)}" class="text-cyan-400 hover:underline cursor-pointer">${name}</a>`;
return `<a href="/detections?bot_name=${encodeURIComponent(name)}" class="text-cyan-400 hover:underline cursor-pointer">${escapeHtml(name)}</a>`;
}
function fmtThreatLink(level) {
if (!level) return '';
@ -160,7 +165,7 @@
function fmtLabel(label) {
if (!label) return '';
const colors = {human:'text-green-400 bg-green-500/10',datacenter:'text-red-400 bg-red-500/10',hosting:'text-orange-400 bg-orange-500/10'};
return `<span class="px-1.5 py-0.5 rounded text-xs ${colors[label]||'text-gray-400 bg-gray-500/10'}">${label}</span>`;
return `<span class="px-1.5 py-0.5 rounded text-xs ${colors[label]||'text-gray-400 bg-gray-500/10'}">${escapeHtml(label)}</span>`;
}
// ── ECharts helpers ──