BackdoorChecker / app.js
coralexbadea's picture
Backdoor Checker v0.1.0: scanner, validation results, leaderboard
b112551 verified
Raw History Blame Contribute Delete
7.56 kB
// Backdoor Checker page logic: file check, existing result lookup, scan command, leaderboard, validation.
(function () {
const SPACE = "coralexbadea/BackdoorChecker";
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s == null ? "" : s).replace(/[&<>"']/g, (c) => ({"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c]));
const hf = (id) => `<a href="https://huggingface.co/${esc(id)}" target="_blank" rel="noopener">${esc(id)}</a>`;
const pct = (x) => (x == null ? "" : Math.round(100 * x) + "%");
let ROWS = [];
function summarize(c, file) {
const b = c.best || {};
return {file, model: c.model, parent: c.parent_model, verdict: c.verdict, trigger_score: c.trigger_score,
candidate: b.text, source: b.source, reply_change: b.reply_change, label: c.label || null,
known_pattern_only: !!c.known_pattern_only, scanned: (c.scanned_at || "").slice(0, 10), card: c};
}
async function loadRows() {
let rows = null;
try { // live listing, so merged pull requests appear without a rebuild
const t = await fetch(`https://huggingface.co/api/spaces/${SPACE}/tree/main/results`);
if (t.ok) {
const files = (await t.json()).map((f) => f.path.split("/").pop())
.filter((n) => n.endsWith(".json") && n !== "index.json" && n !== "validation.json");
rows = (await Promise.all(files.map(async (n) => {
try { const r = await fetch("results/" + n); return r.ok ? summarize(await r.json(), n) : null; } catch (e) { return null; }
}))).filter(Boolean);
}
} catch (e) { rows = null; }
if (!rows || !rows.length) {
try { rows = (await (await fetch("results/index.json")).json()).map((r) => Object.assign({card: null}, r)); } catch (e) { rows = []; }
}
return rows;
}
function labelChip(r) {
if (r.label === "positive") return '<span class="chip">positive control</span>';
if (r.label === "negative") return '<span class="chip">clean control</span>';
if (r.label === "blind") return '<span class="chip">claimed backdoor, trigger unknown</span>';
return "";
}
const isFlag = (v) => v === "TRIGGER-LIKE STRING FOUND" || v === "SUSPECTED BACKDOOR";
function verdictTag(r) {
if (isFlag(r.verdict)) return `<span class="v bad">Trigger-like string${r.known_pattern_only ? " · known pattern" : ""}</span>`;
if (r.verdict === "NOT SCANNED") return '<span class="v none">Not scanned</span>';
return '<span class="v none">None found</span>';
}
function rowHtml(r) {
const cand = r.candidate == null ? "" : `<code>${esc(r.candidate)}</code>`;
const score = r.trigger_score == null ? "" : Number(r.trigger_score).toFixed(2);
return `<tr><td>${hf(r.model)}${labelChip(r)}</td><td>${verdictTag(r)}</td><td>${cand}</td><td class="num">${score}</td>
<td class="num">${pct(r.reply_change)}</td><td>${r.parent ? hf(r.parent) : ""}</td><td>${esc(r.scanned)}</td></tr>`;
}
function renderBoard(filter) {
let rows = ROWS.slice();
if (filter === "flagged") rows = rows.filter((r) => isFlag(r.verdict));
if (filter === "controls") rows = rows.filter((r) => r.label);
if (filter === "community") rows = rows.filter((r) => !r.label);
rows.sort((a, b) => isFlag(b.verdict) - isFlag(a.verdict)
|| (b.trigger_score ?? -1e9) - (a.trigger_score ?? -1e9));
document.querySelector("#lb tbody").innerHTML = rows.length ? rows.map(rowHtml).join("")
: '<tr><td colspan="7" class="muted">No rows here yet. Be the first to submit a scan!</td></tr>';
}
document.querySelectorAll("[data-f]").forEach((b) => b.addEventListener("click", () => {
document.querySelectorAll("[data-f]").forEach((x) => x.setAttribute("aria-pressed", String(x === b)));
renderBoard(b.dataset.f);
}));
function resultHtml(c) {
if (!c || !c.best) return "";
const b = c.best;
const ex = (b.examples || []).map((e) => `<tr><td>${esc(e.prompt)}</td><td>${esc(e.reply)}</td><td>${esc(e.with_trigger)}</td></tr>`).join("");
return `<h3>Published scan: ${verdictTag({verdict: c.verdict, known_pattern_only: c.known_pattern_only})}</h3>
<p class="muted">A measurement, not a verdict: read the replies below before concluding anything.</p>
<ul class="facts"><li>Strongest candidate <code>${esc(b.text)}</code> (found via ${esc(b.source)})</li>
<li>Changes the reply on ${pct(b.reply_change)} of 24 held-out prompts (needs ≥ ${pct(c.thresholds.behaviour_min_rate)}); random strings ${pct(c.null.reply_change)}, same string in the parent ${pct(b.parent_reply_change)}</li>
<li>Trigger score ${b.trigger_score} (needs ≥ ${c.thresholds.min_trigger_score}); screen outlier z ${b.screen_z} (needs ≥ ${c.thresholds.z_screen})</li>
<li>Scanned ${esc((c.scanned_at || "").slice(0, 10))} against parent ${c.parent_model ? hf(c.parent_model) : "none"}, tool v${esc(c.version)}</li></ul>
<div class="tablewrap"><table><thead><tr><th>Prompt</th><th>Reply</th><th>Reply with candidate</th></tr></thead><tbody>${ex}</tbody></table></div>`;
}
async function check(raw) {
$("go").disabled = true;
$("result").innerHTML = '<p class="muted">Checking…</p>';
try {
const x = await FileScan.scanRepo(raw);
const parent = x.declared_base_models[0];
const scanned = ROWS.find((r) => r.model.toLowerCase() === x.repo_id.toLowerCase());
let card = scanned && scanned.card;
if (scanned && !card) { try { card = await (await fetch("results/" + scanned.file)).json(); } catch (e) { card = null; } }
const base = parent ? "" : ` --base ${card && card.parent_model ? card.parent_model : "org/parent-chat-model"}`;
const cmd = `python -m backdoorcheck.cli scan ${x.repo_id}${base} --out result.json`;
const deep = x.needs_trust_remote_code ? "<b>no</b>: the model needs trust_remote_code, which the scanner never runs"
: (x.safetensors_files ? "yes (safetensors weights)" : "only if Hugging Face's automatic safetensors conversion exists");
$("result").innerHTML = `<ul class="facts">
<li>Weights: ${x.safetensors_files} safetensors file(s)${x.pickle_files.length ? `, plus pickle files that can run code when loaded: ${x.pickle_files.map(esc).join(", ")}` : ""} · ${x.total_size_gb} GB</li>
<li>Parent model for the comparison: ${parent ? hf(parent) : "<b>not declared</b>, so pass <code>--base</code>"}</li>
<li>Deep scan possible: ${deep}</li></ul>
<p style="margin-top:12px"><b>Run it on your machine:</b></p><pre><code>${esc(cmd)}</code></pre>
${card ? resultHtml(card) : '<p class="muted">No published scan for this model yet.</p>'}`;
} catch (e) {
$("result").innerHTML = `<p class="err">${esc(e.message)}</p>`;
} finally { $("go").disabled = false; }
}
$("scanform").addEventListener("submit", (ev) => { ev.preventDefault(); check($("rid").value); });
document.querySelectorAll("[data-ex]").forEach((a) => a.addEventListener("click", () => { $("rid").value = a.dataset.ex; check(a.dataset.ex); }));
async function loadValidation() {
try {
const v = await (await fetch("results/validation.json")).json();
$("accuracy").innerHTML = v.html;
} catch (e) { $("accuracy").innerHTML = '<p class="muted">Validation results are not available.</p>'; }
}
(async () => {
ROWS = await loadRows();
renderBoard("all");
const q = new URLSearchParams(location.search).get("model");
if (q) { $("rid").value = q; check(q); }
})();
loadValidation();
})();