// 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);