/* RecRedTeam AuditLab — static viewer. * * This file does no auditing. Every verdict, hazard grade and aggregate shown * here was computed by the Python engine in `auditlab/` and frozen into * `data/results.json` by `build_static.py`. The page only renders that JSON, * which is what makes it publishable as a dependency-free static Space. */ const PLUGIN_ORDER = ["sanitizer", "rag_guard", "policy_guard"]; const DATA_URL = "data/results.json"; let DATA = null; let currentCase = null; /* ------------------------------------------------------------------ utils */ function esc(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function pct(x) { return (x * 100).toFixed(x === 0 || x === 1 ? 0 : 1) + "%"; } /* Escape, then re-introduce the two bits of markdown the engine emits: **bold** in agent answers and `code` in judge reasons. */ function fmt(value) { return esc(value) .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/`([^`]+)`/g, "$1"); } function pct0(x) { return Math.round(x * 100) + "%"; } function money(x) { return "$" + Number(x).toFixed(2); } function attrValue(v) { if (Array.isArray(v)) return v.join(" / "); if (v === true) return "true"; if (v === false) return "false"; return String(v); } function severityClass(sev) { return "chip sev-" + esc(sev); } /* --------------------------------------------------------------- data load */ async function load() { const res = await fetch(DATA_URL, { cache: "no-cache" }); if (!res.ok) throw new Error("HTTP " + res.status + " for " + DATA_URL); return res.json(); } /* -------------------------------------------------------------- taxonomy */ function renderTaxonomy() { const rows = DATA.taxonomy .map((t) => { const cases = DATA.counts[t.category_id] || 0; return ` ${esc(t.category_id)} ${esc(t.name)}
${esc(t.name_cn)} ${esc(t.owasp_asi)} ${esc(t.owasp_llm)} ${esc(t.default_severity)} ${cases} `; }) .join(""); document.getElementById("taxonomy-table").innerHTML = `

Nine categories, mapped to OWASP ASI 2026

${rows}
categorynameOWASP ASI 2026OWASP LLM severitycases (public)

This Space ships the public split, ${DATA.meta.n_cases} of the ${DATA.meta.n_total} cases in the dataset. The held-out split is reserved for reporting.

`; document.getElementById("taxonomy-details").innerHTML = DATA.taxonomy .map((t) => `
  • ${esc(t.name)} — ${esc(t.description)}
  • `) .join(""); } /* ------------------------------------------------------------ single case */ function fillCaseSelect() { const byCat = new Map(); for (const c of DATA.cases) { if (!byCat.has(c.category_id)) byCat.set(c.category_id, []); byCat.get(c.category_id).push(c); } const groups = [...byCat.entries()] .sort((a, b) => a[0].localeCompare(b[0])) .map(([catId, cases]) => { const label = cases[0].category_name; const opts = cases .map((c) => ``) .join(""); return `${opts}`; }) .join(""); document.getElementById("case-select").innerHTML = groups; } function selectedPlugins() { return [...document.querySelectorAll("[data-plugin]")] .filter((el) => el.checked) .map((el) => el.dataset.plugin) .sort((a, b) => PLUGIN_ORDER.indexOf(a) - PLUGIN_ORDER.indexOf(b)); } function comboKey(plugins) { return plugins.length ? plugins.join("+") : "baseline"; } function productsTable(c) { const rows = c.products .map((p) => { const attrs = Object.keys(p.attrs || {}) .sort() .map((k) => `${esc(k)}=${esc(attrValue(p.attrs[k]))}`) .join("
    "); const isTarget = p.pid === c.target_pid ? ' class="target"' : ""; return ` ${esc(p.pid)} ${esc(p.name)}
    ${esc(p.seller)} ${esc(money(p.price))} ${esc(p.rating)}/5 (${esc(p.review_count)}) ${esc(p.seller_trust)} ${attrs || 'none'} `; }) .join(""); return `
    ${rows}
    pidproductpricerating seller trustpoisoning attrs
    `; } function runPanel(title, run) { const ok = run.verdict === "PASS"; const reasons = (run.reasons || []).length ? `` : ``; const blocked = run.blocked ? `

    rag_guard removed every retrieved listing, so the agent answered from an empty catalog.

    ` : ""; return `

    ${esc(title)}

    ${esc(run.verdict)} hazard ${esc(run.hazard)}
    ${fmt(run.response)}
    ${blocked} ${reasons}
    `; } function renderCase() { const c = DATA.cases.find((x) => x.attack_id === currentCase) || DATA.cases[0]; currentCase = c.attack_id; const plugins = selectedPlugins(); const key = comboKey(plugins); const baseline = c.runs.baseline; const defended = c.runs[key]; const defendedTitle = plugins.length ? `Defended agent (${plugins.join(" + ")})` : "Defended agent (no plugin selected)"; const hidden = Object.keys(c.hidden_instructions || {}).length ? `

    Hidden instruction planted in the catalog

    ` + Object.entries(c.hidden_instructions) .map(([pid, text]) => `
    ${esc(pid)} — ${esc(text)}
    `) .join("") : ""; const signals = c.failure_signals.map((s) => `${esc(s)}`).join(" "); document.getElementById("case-view").innerHTML = `

    ${esc(c.name)}

    ${esc(c.category_name)} (${esc(c.category_name_cn)}) ${esc(c.severity)} ${esc(c.owasp_asi)} ${esc(c.owasp_llm)} source ${esc(c.source)}

    ${esc(c.description)}

    Shopper query

    ${esc(c.user_query)}

    Attacker goal

    ${esc(c.goal)}

    Poisoned catalog

    ${productsTable(c)} ${hidden}

    Judge failure signals

    ${signals}

    ${runPanel("Baseline agent (no defense)", baseline)} ${runPanel(defendedTitle, defended)}
    `; } /* -------------------------------------------------------------- benchmark */ function renderBenchmark() { const stacks = DATA.stacks; document.getElementById("bench-note").textContent = `Computed on the full public split — ${DATA.meta.n_cases} cases, ` + `${DATA.meta.per_category} per category, seed ${DATA.meta.seed}. ` + `The over-block column is the honest price of a blocklist defense.`; const rows = stacks .map((s) => { const eff = s.effectiveness === null ? "—" : pct(s.effectiveness); return ` ${esc(s.label)} ${s.n} ${pct(s.asr)} ${eff} ${s.hazard.toFixed(3)} ${pct(s.overblock)} `; }) .join(""); document.getElementById("bench-table").innerHTML = ` ${rows}
    defensenASR effectivenesshazardover-block
    `; document.getElementById("bench-bars").innerHTML = stacks .map((s) => { const width = Math.max(s.asr * 100, 0.6); const cls = s.asr >= 0.67 ? "asr-high" : s.asr >= 0.34 ? "asr-mid" : "asr-low"; return `
    ${esc(s.label)}
    ${pct(s.asr)}
    `; }) .join(""); const cats = Object.keys(stacks[0].per_category).sort(); const head = stacks.map((s) => `${esc(s.label)}`).join(""); const body = cats .map((cat) => { const cells = stacks .map((s) => `${pct0(s.per_category[cat] || 0)}`) .join(""); return `${esc(cat)}${cells}`; }) .join(""); document.getElementById("bench-matrix").innerHTML = ` ${head}${body}
    category
    `; } /* ------------------------------------------------------------------- tabs */ function showTab(id) { document.querySelectorAll(".tab").forEach((tab) => { const on = tab.id === "tab-" + id; tab.setAttribute("aria-selected", on ? "true" : "false"); }); document.querySelectorAll(".panel").forEach((panel) => { panel.hidden = panel.id !== "panel-" + id; }); if (location.hash.split("#")[1] !== id) { history.replaceState(null, "", "#" + id); } } function wire() { document.querySelectorAll(".tab").forEach((tab) => { tab.addEventListener("click", () => showTab(tab.id.replace("tab-", ""))); }); document.getElementById("case-select").addEventListener("change", (e) => { currentCase = e.target.value; renderCase(); }); document.querySelectorAll("[data-plugin]").forEach((el) => { el.addEventListener("change", renderCase); }); document.addEventListener("keydown", (e) => { if (e.altKey || e.metaKey || e.ctrlKey) return; if (e.key === "ArrowRight" || e.key === "ArrowLeft") { const ids = ["taxonomy", "case", "bench", "about"]; const active = document.querySelector('.tab[aria-selected="true"]'); if (!active) return; const i = ids.indexOf(active.id.replace("tab-", "")); const next = ids[(i + (e.key === "ArrowRight" ? 1 : ids.length - 1)) % ids.length]; showTab(next); } }); } /* ------------------------------------------------------------------- init */ (async function init() { try { DATA = await load(); } catch (err) { document.querySelector(".wrap").insertAdjacentHTML( "afterbegin", `
    Could not load ${DATA_URL} — ${esc(err.message)}. If you opened this file directly, serve the folder over HTTP (python -m http.server) so the fetch is allowed.
    ` ); return; } document.getElementById("build-meta").textContent = `${DATA.meta.n_cases} of ${DATA.meta.n_total} cases · seed ${DATA.meta.seed} · static snapshot`; renderTaxonomy(); fillCaseSelect(); renderBenchmark(); wire(); currentCase = DATA.cases[0].attack_id; document.getElementById("case-select").value = currentCase; renderCase(); const hash = location.hash.replace("#", ""); if (["taxonomy", "case", "bench", "about"].includes(hash)) showTab(hash); })();