File size: 7,564 Bytes
b112551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// 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();
})();