File size: 3,881 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
// Backdoor Checker instant file scan (shared with SafetyCard), runs in the browser (and in Node for tests).
// A direct port of safetycard/artifacts.py: same grade rules, downloads no weights.
(function (root) {
  const PICKLE_EXT = [".bin", ".pt", ".pth", ".ckpt", ".pkl", ".pickle", ".joblib"];
  const NON_WEIGHT_BIN = ["training_args.bin"];
  const GRADES = {
    A: "Safetensors/GGUF weights only, no custom code required.",
    B: "Safe weight format, but the model needs trust_remote_code (runs the author's Python).",
    C: "Safe weights present, plus pickle files that can run code if loaded.",
    F: "Weights only in pickle format: loading them can execute arbitrary code.",
  };

  function normalizeId(s) {
    return (s || "").trim().replace(/^https?:\/\/huggingface\.co\//, "").replace(/\/+$/, "")
      .split("/").slice(0, 2).join("/");
  }

  async function scanRepo(repoIdRaw, fetchFn) {
    const f = fetchFn || root.fetch.bind(root);
    const repoId = normalizeId(repoIdRaw);
    if (!/^[\w.-]+\/[\w.-]+$/.test(repoId)) throw new Error("Use the form org/model, e.g. Qwen/Qwen2.5-1.5B-Instruct");
    const r = await f(`https://huggingface.co/api/models/${repoId}?blobs=true&securityStatus=true`);
    if (r.status === 401 || r.status === 404) throw new Error(`Model "${repoId}" not found, or it is private.`);
    if (!r.ok) throw new Error(`Hub API error ${r.status}`);
    const info = await r.json();

    const sib = info.siblings || [];
    const names = sib.map((s) => s.rfilename);
    const lower = (n) => n.toLowerCase();
    const pickles = names.filter((n) => PICKLE_EXT.some((e) => lower(n).endsWith(e)));
    const safetensors = names.filter((n) => lower(n).endsWith(".safetensors"));
    const gguf = names.filter((n) => lower(n).endsWith(".gguf"));
    const pyFiles = names.filter((n) => n.endsWith(".py"));

    let autoMap = (info.config && info.config.auto_map) || null;
    let configNote = null;
    if (!autoMap && names.includes("config.json")) {
      try {
        const c = await f(`https://huggingface.co/${repoId}/resolve/main/config.json`);
        if (c.ok) autoMap = (await c.json()).auto_map || null;
        else configNote = "config.json could not be read (gated model?): remote-code check incomplete.";
      } catch (e) {
        configNote = "config.json could not be read: remote-code check incomplete.";
      }
    }
    const needsRemoteCode = !!autoMap;
    const weightPickles = pickles.filter((p) => !NON_WEIGHT_BIN.includes(p.split("/").pop()));
    const safe = safetensors.length > 0 || gguf.length > 0;
    let grade;
    if (safe && pickles.length === 0) grade = needsRemoteCode ? "B" : "A";
    else if (safe && pickles.length > 0) grade = "C";
    else if (weightPickles.length > 0) grade = "F";
    else grade = needsRemoteCode ? "B" : "A";

    const card = info.cardData || {};
    const bm = card.base_model;
    const sec = info.securityRepoStatus;
    return {
      repo_id: repoId,
      revision: info.sha,
      grade,
      grade_meaning: GRADES[grade],
      pickle_files: pickles,
      safetensors_files: safetensors.length,
      gguf_files: gguf.length,
      custom_code_files: pyFiles,
      needs_trust_remote_code: needsRemoteCode,
      declared_base_models: bm ? (Array.isArray(bm) ? bm : [bm]) : [],
      license: card.license || null,
      gated: info.gated || false,
      hf_security_scan: sec ? { scans_done: sec.scansDone, files_with_issues: (sec.filesWithIssues || []).map((x) => x.path) } : null,
      total_size_gb: Math.round(sib.reduce((a, s) => a + (s.size || 0), 0) / 1e7) / 100,
      deep_scan_supported: safetensors.length > 0 && !needsRemoteCode,
      note: configNote,
    };
  }

  root.FileScan = { scanRepo, normalizeId, GRADES };
  if (typeof module !== "undefined") module.exports = root.FileScan;
})(typeof globalThis !== "undefined" ? globalThis : this);